Skip to content

Commit e13b591

Browse files
harshithpabbatiacao
authored andcommitted
feat: add support for i18n (#1526)
1 parent 8d85bd2 commit e13b591

File tree

26 files changed

+290
-43
lines changed

26 files changed

+290
-43
lines changed

examples/monaco-graphql-webpack/tsconfig.json

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,7 @@
99
"strictPropertyInitialization": false,
1010
"types": ["node", "jest"],
1111
"typeRoots": ["../../node_modules/@types", "node_modules/@types"],
12-
"lib": ["dom"],
13-
"module": "umd"
12+
"lib": ["dom"]
1413
},
1514
"references": [{ "path": "../../packages/monaco-graphql" }],
1615
"include": ["src"],

packages/graphiql/package.json

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,10 +46,13 @@
4646
"codemirror": "^5.52.2",
4747
"copy-to-clipboard": "^3.2.0",
4848
"entities": "^2.0.0",
49-
"markdown-it": "^10.0.0",
50-
"monaco-graphql": "^2.3.4-alpha.4",
5149
"graphql-languageservice": "^2.4.0-alpha.7",
50+
"i18next": "^19.4.4",
51+
"i18next-browser-languagedetector": "^4.1.1",
52+
"markdown-it": "^10.0.0",
5253
"monaco-editor": "^0.20.0",
54+
"monaco-graphql": "^2.3.4-alpha.4",
55+
"react-i18next": "^11.4.0",
5356
"regenerator-runtime": "^0.13.5",
5457
"theme-ui": "^0.3.1",
5558
"@theme-ui/core": "^0.4.0-alpha.1"

packages/graphiql/resources/build.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ fi
1010

1111
babel src --ignore __tests__ --out-dir dist/
1212
ESM=true babel src --ignore __tests__ --out-dir esm/
13+
cp -rf src/locales dist/
1314
echo "Bundling graphiql.js..."
1415
browserify -g browserify-shim -s GraphiQL dist/index.js > graphiql.js
1516
echo "Bundling graphiql.min.js..."

packages/graphiql/src/components/ExecuteButton.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import React, { MouseEventHandler, useState } from 'react';
88
import { OperationDefinitionNode } from 'graphql';
99
import { useSessionContext } from '../api/providers/GraphiQLSessionProvider';
1010
import useQueryFacts from '../api/hooks/useQueryFacts';
11+
import { useTranslation } from 'react-i18next';
1112

1213
/**
1314
* ExecuteButton
@@ -30,6 +31,7 @@ export function ExecuteButton(props: ExecuteButtonProps) {
3031
const session = useSessionContext();
3132
const operations = queryFacts?.operations ?? [];
3233
const hasOptions = operations && operations.length > 1;
34+
const { t } = useTranslation('Toolbar');
3335

3436
let options: JSX.Element | null = null;
3537
if (hasOptions && optionsOpen) {
@@ -115,7 +117,7 @@ export function ExecuteButton(props: ExecuteButtonProps) {
115117
className="execute-button"
116118
onMouseDown={onMouseDown}
117119
onClick={onClick}
118-
title="Execute Query (Ctrl-Enter)">
120+
title={t('Execute Query (Ctrl-Enter)')}>
119121
<svg width="34" height="34">
120122
{pathJSX}
121123
</svg>

packages/graphiql/src/components/GraphiQL.tsx

Lines changed: 23 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,12 @@ import {
3333
SessionContext,
3434
} from '../api/providers/GraphiQLSessionProvider';
3535
import { getFetcher } from '../api/common';
36+
3637
import { Unsubscribable, Fetcher, ReactNodeLike } from '../types';
3738
import { Provider, useThemeLayout } from './common/themes/provider';
3839
import Tabs from './common/Toolbar/Tabs';
40+
import { I18nextProvider } from 'react-i18next';
41+
import i18n from '../i18n';
3942

4043
const DEFAULT_DOC_EXPLORER_WIDTH = 350;
4144

@@ -117,26 +120,28 @@ export type GraphiQLState = {
117120
*/
118121
export const GraphiQL: React.FC<GraphiQLProps> = props => {
119122
if (!props.fetcher && !props.uri) {
120-
throw Error('fetcher or uri property are required');
123+
throw Error(i18n.t('Errors:Fetcher or uri property are required'));
121124
}
122125
const fetcher = getFetcher(props);
123126
return (
124-
<EditorsProvider>
125-
<SchemaProvider
126-
fetcher={fetcher}
127-
config={{ uri: props.uri, ...props.schemaConfig }}>
128-
<SessionProvider fetcher={fetcher} sessionId={0}>
129-
<GraphiQLInternals
130-
{...{
131-
formatResult,
132-
formatError,
133-
...props,
134-
}}>
135-
{props.children}
136-
</GraphiQLInternals>
137-
</SessionProvider>
138-
</SchemaProvider>
139-
</EditorsProvider>
127+
<I18nextProvider i18n={i18n}>
128+
<EditorsProvider>
129+
<SchemaProvider
130+
fetcher={fetcher}
131+
config={{ uri: props.uri, ...props.schemaConfig }}>
132+
<SessionProvider fetcher={fetcher} sessionId={0}>
133+
<GraphiQLInternals
134+
{...{
135+
formatResult,
136+
formatError,
137+
...props,
138+
}}>
139+
{props.children}
140+
</GraphiQLInternals>
141+
</SessionProvider>
142+
</SchemaProvider>
143+
</EditorsProvider>
144+
</I18nextProvider>
140145
);
141146
};
142147

@@ -172,7 +177,7 @@ class GraphiQLInternals extends React.Component<
172177
graphiqlContainer: Maybe<HTMLDivElement>;
173178
resultComponent: Maybe<typeof ResultViewer>;
174179
variableEditorComponent: Maybe<typeof VariableEditor>;
175-
_queryHistory: Maybe<QueryHistory>;
180+
_queryHistory: Maybe<typeof QueryHistory>;
176181
editorBarComponent: Maybe<HTMLDivElement>;
177182
queryEditorComponent: Maybe<typeof QueryEditor>;
178183
resultViewerElement: Maybe<HTMLElement>;

packages/graphiql/src/components/HistoryQuery.tsx

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
import React from 'react';
99
import { QueryStoreItem } from '../utility/QueryStore';
10+
import { WithTranslation, withTranslation } from 'react-i18next';
1011

1112
export type HandleEditLabelFn = (
1213
query?: string,
@@ -38,9 +39,10 @@ export type HistoryQueryProps = {
3839
handleToggleFavorite: HandleToggleFavoriteFn;
3940
operationName?: string;
4041
onSelect: HandleSelectQueryFn;
41-
} & QueryStoreItem;
42+
} & QueryStoreItem &
43+
WithTranslation;
4244

43-
export default class HistoryQuery extends React.Component<
45+
class HistoryQuerySource extends React.Component<
4446
HistoryQueryProps,
4547
{ editable: boolean }
4648
> {
@@ -54,6 +56,7 @@ export default class HistoryQuery extends React.Component<
5456
}
5557

5658
render() {
59+
const { t } = this.props;
5760
const displayName =
5861
this.props.label ||
5962
this.props.operationName ||
@@ -73,7 +76,7 @@ export default class HistoryQuery extends React.Component<
7376
}}
7477
onBlur={this.handleFieldBlur.bind(this)}
7578
onKeyDown={this.handleFieldKeyDown.bind(this)}
76-
placeholder="Type a label"
79+
placeholder={t('Type a label')}
7780
/>
7881
) : (
7982
<button
@@ -84,13 +87,15 @@ export default class HistoryQuery extends React.Component<
8487
)}
8588
<button
8689
onClick={this.handleEditClick.bind(this)}
87-
aria-label="Edit label">
90+
aria-label={t('Edit label')}>
8891
{'\u270e'}
8992
</button>
9093
<button
9194
className={this.props.favorite ? 'favorited' : undefined}
9295
onClick={this.handleStarClick.bind(this)}
93-
aria-label={this.props.favorite ? 'Remove favorite' : 'Add favorite'}>
96+
aria-label={
97+
this.props.favorite ? t('Remove favorite') : t('Add favorite')
98+
}>
9499
{starIcon}
95100
</button>
96101
</li>
@@ -152,3 +157,6 @@ export default class HistoryQuery extends React.Component<
152157
});
153158
}
154159
}
160+
161+
const HistoryQuery = withTranslation('Toolbar')(HistoryQuerySource);
162+
export default HistoryQuery;

packages/graphiql/src/components/QueryHistory.tsx

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import HistoryQuery, {
1414
HandleSelectQueryFn,
1515
} from './HistoryQuery';
1616
import StorageAPI from '../utility/StorageAPI';
17+
import { WithTranslation, withTranslation } from 'react-i18next';
1718

1819
const MAX_QUERY_SIZE = 100000;
1920
const MAX_HISTORY_LENGTH = 20;
@@ -60,13 +61,13 @@ type QueryHistoryProps = {
6061
queryID?: number;
6162
onSelectQuery: HandleSelectQueryFn;
6263
storage: StorageAPI;
63-
};
64+
} & WithTranslation;
6465

6566
type QueryHistoryState = {
6667
queries: Array<QueryStoreItem>;
6768
};
6869

69-
export class QueryHistory extends React.Component<
70+
export class QueryHistorySource extends React.Component<
7071
QueryHistoryProps,
7172
QueryHistoryState
7273
> {
@@ -89,6 +90,7 @@ export class QueryHistory extends React.Component<
8990
}
9091

9192
render() {
93+
const { t } = this.props;
9294
const queries = this.state.queries.slice().reverse();
9395
const queryNodes = queries.map((query, i) => {
9496
return (
@@ -102,9 +104,9 @@ export class QueryHistory extends React.Component<
102104
);
103105
});
104106
return (
105-
<section aria-label="History">
107+
<section aria-label={t('History')}>
106108
<div className="history-title-bar">
107-
<div className="history-title">{'History'}</div>
109+
<div className="history-title">{t('History')}</div>
108110
<div className="doc-explorer-rhs">{this.props.children}</div>
109111
</div>
110112
<ul className="history-contents">{queryNodes}</ul>
@@ -183,3 +185,5 @@ export class QueryHistory extends React.Component<
183185
});
184186
};
185187
}
188+
189+
export const QueryHistory = withTranslation('Toolbar')(QueryHistorySource);

packages/graphiql/src/i18n.ts

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
import i18n from 'i18next';
2+
import LanguageDetector from 'i18next-browser-languagedetector';
3+
import { initReactI18next } from 'react-i18next';
4+
5+
import enTranslations from './locales/en/translation.json';
6+
import enDocExplorer from './locales/en/DocExplorer.json';
7+
import enToolbar from './locales/en/Toolbar.json';
8+
import enEditor from './locales/en/Editor.json';
9+
import enErrors from './locales/en/Errors.json';
10+
import ruTranslations from './locales/ru/translation.json';
11+
import ruDocExplorer from './locales/ru/DocExplorer.json';
12+
import ruToolbar from './locales/ru/Toolbar.json';
13+
import ruEditor from './locales/ru/Editor.json';
14+
import ruErrors from './locales/ru/Errors.json';
15+
16+
const resources = {
17+
en: {
18+
translations: enTranslations,
19+
DocExplorer: enDocExplorer,
20+
Toolbar: enToolbar,
21+
Editor: enEditor,
22+
Errors: enErrors,
23+
},
24+
ru: {
25+
translations: ruTranslations,
26+
DocExplorer: ruDocExplorer,
27+
Toolbar: ruToolbar,
28+
Editor: ruEditor,
29+
Errors: ruErrors,
30+
},
31+
};
32+
33+
i18n
34+
.use(LanguageDetector)
35+
.use(initReactI18next)
36+
.init({
37+
// Language detector options
38+
detection: {
39+
// order and from where user language should be detected
40+
order: [
41+
'querystring',
42+
'localStorage',
43+
'navigator',
44+
'htmlTag',
45+
'path',
46+
'subdomain',
47+
],
48+
49+
// keys or params to lookup language from
50+
lookupQuerystring: 'lng',
51+
lookupCookie: 'i18next',
52+
lookupLocalStorage: 'i18nextLng',
53+
lookupFromPathIndex: 0,
54+
lookupFromSubdomainIndex: 0,
55+
56+
// cache user language on
57+
caches: ['localStorage'],
58+
excludeCacheFor: ['cimode'], // languages to not persist (cookie, localStorage)
59+
60+
// optional expire and domain for set cookie
61+
cookieMinutes: 10,
62+
cookieDomain: window.location.hostname,
63+
64+
// optional htmlTag with lang attribute, the default is:
65+
htmlTag: document.documentElement,
66+
},
67+
68+
// we init with resources
69+
resources,
70+
fallbackLng: {
71+
'en-US': ['en'],
72+
default: ['en'],
73+
},
74+
whitelist: ['en', 'ru'],
75+
// // have a common namespace used around the full app
76+
// ns: ['translations'],
77+
defaultNS: 'translation',
78+
load: 'currentOnly',
79+
preload: ['en', 'ru'],
80+
keySeparator: '.', // we use content as keys
81+
nsSeparator: ':',
82+
interpolation: {
83+
escapeValue: false, // not needed for react!!
84+
},
85+
react: {
86+
wait: true,
87+
},
88+
});
89+
90+
export default i18n;

packages/graphiql/src/index.ts

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,9 @@
44
* This source code is licensed under the MIT license found in the
55
* LICENSE file in the root directory of this source tree.
66
*/
7-
// eslint-disable-next-line spaced-comment
8-
/// <reference path='../../../node_modules/monaco-editor/monaco.d.ts'/>
9-
// eslint-disable-next-line spaced-comment
10-
/// <reference path='../../../packages/monaco-graphql/src/typings/monaco.d.ts'/>
117

128
import { GraphiQL } from './components/GraphiQL';
9+
import './i18n';
1310

1411
export * from './api';
1512
export * from './components/common';
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
{
2+
"Docs": "Docs",
3+
"Search {{name}}": "Search {{name}}...",
4+
"Schema": "Schema",
5+
"root types": "root types",
6+
"Documentation Explorer": "Documentation Explorer",
7+
"No Schema Available": "No Schema Available",
8+
"A GraphQL schema provides a root type for each kind of operation": "A GraphQL schema provides a root type for each kind of operation.",
9+
"query": "query",
10+
"mutation": "mutation",
11+
"subscription": "subscription",
12+
"Go back to {{value}}": "Go back to {{value}}",
13+
"Close History": "Close History",
14+
"Open Documentation Explorer": "Open Documentation Explorer",
15+
"Close Documentation Explorer": "Close Documentation Explorer"
16+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"Welcome to GraphiQL": "# Welcome to GraphiQL\\r\\n#\\r\\n# GraphiQL is an in-browser tool for writing, validating, and\\r\\n# testing GraphQL queries.\\r\\n#\\r\\n# Type queries into this side of the screen, and you will see intelligent\\r\\n# typeaheads aware of the current GraphQL type schema and live syntax and\\r\\n# validation errors highlighted within the text.\\r\\n#\\r\\n# GraphQL queries typically start with a \\\"{\\\" character. Lines that starts\\r\\n# with a # are ignored.\\r\\n#\\r\\n# An example GraphQL query might look like:\\r\\n#\\r\\n# {\\r\\n# field(arg: \\\"value\\\") {\\r\\n# subField\\r\\n# }\\r\\n# }\\r\\n#\\r\\n# Keyboard shortcuts:\\r\\n#\\r\\n# Prettify Query: Shift-Ctrl-P (or press the prettify button above)\\r\\n#\\r\\n# Merge Query: Shift-Ctrl-M (or press the merge button above)\\r\\n#\\r\\n# Run Query: Ctrl-Enter (or press the play button above)\\r\\n#\\r\\n# Auto Complete: Ctrl-Space (or just start typing)\\r\\n#",
3+
"Automatically added leaf fields": "Automatically added leaf fields",
4+
"Query Variables": "Query Variables",
5+
"Query Editor": "Query Editor"
6+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"Fetcher or uri property are required": "fetcher or uri property are required",
3+
"Fetcher did not return a Promise for introspection": "Fetcher did not return a Promise for introspection.",
4+
"Variables are invalid JSON": "Variables are invalid JSON.",
5+
"Variables are not a JSON object": "Variables are not a JSON object.",
6+
"no value resolved": "no value resolved"
7+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
{
2+
"Execute Query (Ctrl-Enter)": "Execute Query (Ctrl-Enter)",
3+
"Prettify": "Prettify",
4+
"Prettify Query (Shift-Ctrl-P)": "Prettify Query (Shift-Ctrl-P)",
5+
"Merge": "Merge",
6+
"Merge Query (Shift-Ctrl-M)": "Merge Query (Shift-Ctrl-M)",
7+
"Copy": "Copy",
8+
"Copy Query (Shift-Ctrl-C)": "Copy Query (Shift-Ctrl-C)",
9+
"History": "History",
10+
"Show History": "Show History",
11+
"Editor Commands": "Editor Commands",
12+
"Edit label": "Edit label",
13+
"Add favorite": "Add favorite",
14+
"Remove favorite": "Remove favorite",
15+
"Type a label": "Type a label",
16+
"Docs": "Docs"
17+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{}

0 commit comments

Comments
 (0)