Skip to content

Commit 1fd126a

Browse files
authored
Merge pull request #9314 from marmelab/i18next
Introduce ra-i18n-i18next
2 parents b676c2f + 1071f8c commit 1fd126a

13 files changed

+1032
-0
lines changed

docs/Translation.md

+64
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,70 @@ const App = () => (
130130

131131
Check [the translation setup documentation](./TranslationSetup.md) for details about `ra-i18n-polyglot` and how to configure it.
132132

133+
## `ra-i18n-i18next`
134+
135+
React-admin also provides a package called `ra-i18n-i18next` that leverages [the i18next library](https://www.i18next.com/) to build an `i18nProvider` based on a dictionary of translations.
136+
137+
You might prefer this package over `ra-i18n-polyglot` when:
138+
- you already use i18next services such as [locize](https://locize.com/)
139+
- you want more control on how you organize translations, leveraging [multiple files and namespaces](https://www.i18next.com/principles/namespaces)
140+
- you want more control on how you [load translations](https://www.i18next.com/how-to/add-or-load-translations)
141+
- you want to use features not available in Polyglot such as:
142+
- [advanced formatting](https://www.i18next.com/translation-function/formatting);
143+
- [nested translations](https://www.i18next.com/translation-function/nesting)
144+
- [context](https://www.i18next.com/translation-function/context)
145+
146+
```tsx
147+
// in src/i18nProvider.js
148+
import i18n from 'i18next';
149+
import { useI18nextProvider, convertRaTranslationsToI18next } from 'ra-i18n-i18next';
150+
import en from 'ra-language-english';
151+
import fr from 'ra-language-french';
152+
153+
const i18nInstance = i18n.use(
154+
resourcesToBackend(language => {
155+
if (language === 'fr') {
156+
return import(
157+
`ra-language-french`
158+
).then(({ default: messages }) =>
159+
convertRaTranslationsToI18next(messages)
160+
);
161+
}
162+
return import(`ra-language-english`).then(({ default: messages }) =>
163+
convertRaTranslationsToI18next(messages)
164+
);
165+
})
166+
);
167+
168+
export const useMyI18nProvider = () => useI18nextProvider({
169+
i18nInstance,
170+
availableLocales: [
171+
{ locale: 'en', name: 'English' },
172+
{ locale: 'fr', name: 'French' },
173+
],
174+
});
175+
176+
// in src/App.tsx
177+
import { Admin } from 'react-admin';
178+
import { useMyI18nProvider } from './i18nProvider';
179+
180+
const App = () => {
181+
const i18nProvider = useMyI18nProvider();
182+
if (!i18nProvider) return null;
183+
184+
return (
185+
<Admin
186+
i18nProvider={i18nProvider}
187+
dataProvider={dataProvider}
188+
>
189+
...
190+
</Admin>
191+
);
192+
};
193+
```
194+
195+
Check [the ra-i18n-i18next documentation](https://github.com/marmelab/react-admin/tree/master/packages/ra-i18n-i18next) for details.
196+
133197
## Translation Files
134198

135199
`ra-i18n-polyglot` relies on JSON objects for translations. This means that the only thing required to add support for a new language is a JSON file.

packages/ra-i18n-i18next/README.md

+228
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,228 @@
1+
# i18next i18n provider for react-admin
2+
3+
i18next i18n provider for [react-admin](https://github.com/marmelab/react-admin), the frontend framework for building admin applications on top of REST/GraphQL services. It relies on [i18next](https://www.i18next.com/).
4+
5+
You might prefer this package over `ra-i18n-polyglot` when:
6+
- you already use i18next services such as [locize](https://locize.com/)
7+
- you want more control on how you organize translations, leveraging [multiple files and namespaces](https://www.i18next.com/principles/namespaces)
8+
- you want more control on how you [load translations](https://www.i18next.com/how-to/add-or-load-translations)
9+
- you want to use features not available in Polyglot such as:
10+
- [advanced formatting](https://www.i18next.com/translation-function/formatting);
11+
- [nested translations](https://www.i18next.com/translation-function/nesting)
12+
- [context](https://www.i18next.com/translation-function/context)
13+
14+
## Installation
15+
16+
```sh
17+
npm install --save ra-i18n-i18next
18+
```
19+
20+
## Usage
21+
22+
```tsx
23+
import { Admin } from 'react-admin';
24+
import { useI18nextProvider, convertRaMessagesToI18next } from 'ra-i18n-i18next';
25+
import englishMessages from 'ra-language-english';
26+
27+
const App = () => {
28+
const i18nProvider = useI18nextProvider({
29+
options: {
30+
resources: {
31+
translations: convertRaMessagesToI18next(englishMessages)
32+
}
33+
}
34+
});
35+
if (!i18nProvider) return (<div>Loading...</div>);
36+
37+
return (
38+
<Admin i18nProvider={i18nProvider}>
39+
...
40+
</Admin>
41+
);
42+
};
43+
```
44+
45+
## API
46+
47+
### `useI18nextProvider` hook
48+
49+
A hook that returns an i18nProvider for react-admin applications, based on i18next.
50+
51+
You can provide your own i18next instance but don't initialize it, the hook will do it for you with the options you may provide. Besides, this hook already adds the `initReactI18next` plugin to i18next.
52+
53+
#### Usage
54+
55+
```tsx
56+
import { Admin } from 'react-admin';
57+
import { useI18nextProvider, convertRaMessagesToI18next } from 'ra-i18n-i18next';
58+
import englishMessages from 'ra-language-english';
59+
60+
const App = () => {
61+
const i18nProvider = useI18nextProvider({
62+
options: {
63+
resources: {
64+
translations: convertRaMessagesToI18next(englishMessages)
65+
}
66+
}
67+
});
68+
if (!i18nProvider) return (<div>Loading...</div>);
69+
70+
return (
71+
<Admin i18nProvider={i18nProvider}>
72+
...
73+
</Admin>
74+
);
75+
};
76+
```
77+
78+
#### Parameters
79+
80+
| Parameter | Required | Type | Default | Description |
81+
| -------------------- | -------- | ----------- | ------- | ---------------------------------------------------------------- |
82+
| `i18nextInstance` | Optional | I18n | | Your own i18next instance. If not provided, one will be created. |
83+
| `options` | Optional | InitOptions | | The options passed to the i18next init function |
84+
| `availableLocales` | Optional | Locale[] | | An array describing the available locales. Used to automatically include the locale selector menu in the default react-admin AppBar |
85+
86+
##### `i18nextInstance`
87+
88+
This parameter lets you pass your own instance of i18next, allowing you to customize its plugins such as the backends.
89+
90+
```tsx
91+
import { Admin } from 'react-admin';
92+
import { useI18nextProvider } from 'ra-i18n-i18next';
93+
import i18n from 'i18next';
94+
import Backend from 'i18next-http-backend';
95+
import LanguageDetector from 'i18next-browser-languagedetector';
96+
97+
const App = () => {
98+
const i18nextInstance = i18n
99+
.use(Backend)
100+
.use(LanguageDetector);
101+
102+
const i18nProvider = useI18nextProvider({
103+
i18nextInstance
104+
});
105+
106+
if (!i18nProvider) return (<div>Loading...</div>);
107+
108+
return (
109+
<Admin i18nProvider={i18nProvider}>
110+
...
111+
</Admin>
112+
);
113+
};
114+
```
115+
116+
##### `options`
117+
118+
This parameter lets you pass your own options for the i18n `init` function.
119+
120+
Please refer to [the i18next documentation](https://www.i18next.com/overview/configuration-options) for details.
121+
122+
```tsx
123+
import { Admin } from 'react-admin';
124+
import { useI18nextProvider } from 'ra-i18n-i18next';
125+
import i18n from 'i18next';
126+
127+
const App = () => {
128+
const i18nProvider = useI18nextProvider({
129+
options: {
130+
debug: true,
131+
}
132+
});
133+
134+
if (!i18nProvider) return (<div>Loading...</div>);
135+
136+
return (
137+
<Admin i18nProvider={i18nProvider}>
138+
...
139+
</Admin>
140+
);
141+
};
142+
```
143+
144+
#### `availableLocales`
145+
146+
This parameter lets you provide the list of available locales for your application. This is used by the default react-admin AppBar to detect whether to display a locale selector.
147+
148+
```tsx
149+
import { Admin } from 'react-admin';
150+
import { useI18nextProvider, convertRaTranslationsToI18next } from 'ra-i18n-i18next';
151+
import i18n from 'i18next';
152+
import resourcesToBackend from 'i18next-resources-to-backend';
153+
154+
const App = () => {
155+
const i18nextInstance = i18n.use(
156+
// Here we use a Backend provided by i18next that allows us to load
157+
// the translations however we want.
158+
// See https://www.i18next.com/how-to/add-or-load-translations#lazy-load-in-memory-translations
159+
resourcesToBackend(language => {
160+
if (language === 'fr') {
161+
// Load the ra-language-french package and convert its translations in i18next format
162+
return import(
163+
`ra-language-french`
164+
).then(({ default: messages }) =>
165+
convertRaTranslationsToI18next(messages)
166+
);
167+
}
168+
// Load the ra-language-english package and convert its translations in i18next format
169+
return import(`ra-language-english`).then(({ default: messages }) =>
170+
convertRaTranslationsToI18next(messages)
171+
);
172+
})
173+
);
174+
175+
const i18nProvider = useI18nextProvider({
176+
i18nextInstance,
177+
availableLocales: [
178+
{ locale: 'en', name: 'English' },
179+
{ locale: 'fr', name: 'French' },
180+
],
181+
});
182+
183+
if (!i18nProvider) return (<div>Loading...</div>);
184+
185+
return (
186+
<Admin i18nProvider={i18nProvider}>
187+
...
188+
</Admin>
189+
);
190+
};
191+
```
192+
193+
### `convertRaMessagesToI18next` function
194+
195+
A function that takes translations from a standard react-admin language package and converts them to i18next format.
196+
It transforms the following:
197+
- interpolations wrappers from `%{foo}` to `{{foo}}` unless a prefix and/or a suffix are provided
198+
- pluralization messages from a single key containing text like `"key": "foo |||| bar"` to multiple keys `"foo_one": "foo"` and `"foo_other": "bar"`
199+
200+
#### Usage
201+
202+
```ts
203+
import englishMessages from 'ra-language-english';
204+
import { convertRaMessagesToI18next } from 'ra-i18n-18next';
205+
206+
const messages = convertRaMessagesToI18next(englishMessages);
207+
```
208+
209+
#### Parameters
210+
211+
| Parameter | Required | Type | Default | Description |
212+
| -------------------- | -------- | ----------- | ------- | ---------------------------------------------------------------- |
213+
| `raMessages` | Required | object | | An object containing standard react-admin translations such as provided by ra-language-english |
214+
| `options` | Optional | object | | An object providing custom interpolation suffix and/or suffix |
215+
216+
##### `options`
217+
218+
If you provided interpolation options to your i18next instance, you should provide them when calling this function:
219+
220+
```ts
221+
import englishMessages from 'ra-language-english';
222+
import { convertRaMessagesToI18next } from 'ra-i18n-18next';
223+
224+
const messages = convertRaMessagesToI18next(englishMessages, {
225+
prefix: '#{',
226+
suffix: '}#',
227+
});
228+
```

packages/ra-i18n-i18next/package.json

+39
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
{
2+
"name": "ra-i18n-i18next",
3+
"version": "4.13.4",
4+
"description": "i18next i18n provider for react-admin",
5+
"main": "dist/cjs/index.js",
6+
"module": "dist/esm/index.js",
7+
"types": "dist/cjs/index.d.ts",
8+
"sideEffects": false,
9+
"files": [
10+
"*.md",
11+
"dist",
12+
"src"
13+
],
14+
"authors": [
15+
"François Zaninotto"
16+
],
17+
"repository": "marmelab/react-admin",
18+
"homepage": "https://github.com/marmelab/react-admin#readme",
19+
"bugs": "https://github.com/marmelab/react-admin/issues",
20+
"license": "MIT",
21+
"scripts": {
22+
"build": "yarn run build-cjs && yarn run build-esm",
23+
"build-cjs": "rimraf ./dist/cjs && tsc --outDir dist/cjs",
24+
"build-esm": "rimraf ./dist/esm && tsc --outDir dist/esm --module es2015",
25+
"watch": "tsc --outDir dist/esm --module es2015 --watch"
26+
},
27+
"dependencies": {
28+
"i18next": "^23.5.1",
29+
"ra-core": "^4.13.4",
30+
"react-i18next": "^13.2.2"
31+
},
32+
"devDependencies": {
33+
"cross-env": "^5.2.0",
34+
"i18next-resources-to-backend": "^1.1.4",
35+
"rimraf": "^3.0.2",
36+
"typescript": "^5.1.3"
37+
},
38+
"gitHead": "e936ff2c3f887d2e98ef136cf3b3f3d254725fc4"
39+
}

0 commit comments

Comments
 (0)