Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[RFR] i18nProvider - initial locale #3755

Merged
merged 4 commits into from
Oct 1, 2019
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 11 additions & 3 deletions UPGRADE.md
Original file line number Diff line number Diff line change
Expand Up @@ -1085,7 +1085,7 @@ If you had custom reducer or sagas based on these actions, they will no longer w

## i18nProvider Signature Changed

The i18nProvider, that react-admin uses for translating UI and content, must now be an object exposing two methods: `trasnlate` and `changeLocale`.
The i18nProvider, that react-admin uses for translating UI and content, must now be an object exposing three methods: `translate`, `changeLocale` and `getLocale`.

```jsx
// react-admin 2.x
Expand All @@ -1100,12 +1100,19 @@ const i18nProvider = {
return new Promise((resolve, reject) => {
// load new messages and update the translate function
})
},
getLocale: () => {
return new Promise((resolve, reject) => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the type shows that this function is sync

// load new messages and update the translate function
})
}
}
```

But don't worry: react-admin v3 contains a module called `ra-i18n-polyglot`, that is a wrapper around your old `i18nProvider` to make it compatible with the new provider signature:

Besides, the `Admin` component does not accept a `locale` prop anymore as it is the `i18nProvider` provider responsibility:

```diff
import React from 'react';
import { Admin, Resource } from 'react-admin';
Expand All @@ -1118,10 +1125,11 @@ const messages = {
en: englishMessages,
};
-const i18nProvider = locale => messages[locale];
+const i18nProvider = polyglotI18nProvider(locale => messages[locale]);
+const i18nProvider = polyglotI18nProvider(locale => messages[locale], 'fr);

const App = () => (
<Admin locale="en" i18nProvider={i18nProvider}>
- <Admin locale="fr i18nProvider={i18nProvider}>
+ <Admin i18nProvider={i18nProvider}>
...
</Admin>
);
Expand Down
2 changes: 1 addition & 1 deletion docs/Admin.md
Original file line number Diff line number Diff line change
Expand Up @@ -544,7 +544,7 @@ const App = () => (

## Internationalization

The `locale` and `i18nProvider` props let you translate the GUI. The [Translation Documentation](./Translation.md) details this process.
The `i18nProvider` props let you translate the GUI. The [Translation Documentation](./Translation.md) details this process.

## Declaring resources at runtime

Expand Down
27 changes: 18 additions & 9 deletions docs/Translation.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ Just like for data fetching and authentication, react-admin relies on a simple o
const i18nProvider = {
translate: (key, options) => string,
changeLocale: locale => Promise,
getLocale: () => string,
}
```

Expand Down Expand Up @@ -86,12 +87,16 @@ const frenchMessages = {
};
let messages = englishMessages;

let locale = 'en';

const i18nProvider = {
translate: key => lodashGet(messages, key),
changeLocale: newLocale => {
messages = (newLocale === 'fr') ? frenchMessages : englishMessages;
locale = newLocale;
return Promise.resolve();
}
},
getLocale: () => locale;
};
```

Expand Down Expand Up @@ -126,7 +131,8 @@ const frenchMessages = {
};

const i18nProvider = polyglotI18nProvider(locale =>
locale === 'fr' ? frenchMessages : englishMessages
locale === 'fr' ? frenchMessages : englishMessages,
'en' // Default locale
);
```

Expand All @@ -140,10 +146,10 @@ import { Admin, Resource } from 'react-admin';
import polyglotI18nProvider from 'ra-i18n-polyglot';
import frenchMessages from 'ra-language-french';

const i18nProvider = polyglotI18nProvider(() => frenchMessages);
const i18nProvider = polyglotI18nProvider(() => frenchMessages, 'fr');

const App = () => (
<Admin locale="fr" i18nProvider={i18nProvider}>
<Admin i18nProvider={i18nProvider}>
...
</Admin>
);
Expand Down Expand Up @@ -293,18 +299,18 @@ const i18nProvider = polyglotI18nProvider(locale => {
if (locale === 'fr') {
return import('../i18n/fr.js').then(messages => messages.default);
}
});
}, 'en');

const App = () => (
<Admin locale="en" i18nProvider={i18nProvider}>
<Admin i18nProvider={i18nProvider}>
...
</Admin>
);
```

## Using The Browser Locale

React-admin provides a helper function named `resolveBrowserLocale()`, which detects the user's browser locale. To use it, simply pass the function as `locale` prop.
React-admin provides a helper function named `resolveBrowserLocale()`, which detects the user's browser locale. To use it, simply pass the function as the `initialLocale` argument of `polyglotI18nProvider`.

```jsx
import React from 'react';
Expand All @@ -321,10 +327,13 @@ const messages = {
fr: frenchMessages,
en: englishMessages,
};
const i18nProvider = polyglotI18nProvider(locale => messages[locale] ? messages[locale] : messages.en);
const i18nProvider = polyglotI18nProvider(
locale => messages[locale] ? messages[locale] : messages.en,
resolveBrowserLocale()
);

const App = () => (
<Admin locale={resolveBrowserLocale()} i18nProvider={i18nProvider}>
<Admin i18nProvider={i18nProvider}>
...
</Admin>
);
Expand Down
10 changes: 6 additions & 4 deletions packages/ra-core/src/CoreAdmin.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,11 @@ const CoreAdmin: FunctionComponent<AdminProps> = ({
'You are using deprecated prop "appLayout", it was replaced by "layout", see https://github.com/marmelab/react-admin/issues/2918'
);
}
if (locale) {
console.warn(
'You are using deprecated prop "locale". You must now pass the initial locale to your i18nProvider'
);
}
if (loginPage === true && process.env.NODE_ENV !== 'production') {
console.warn(
'You passed true to the loginPage prop. You must either pass false to disable it or a component class to customize it'
Expand All @@ -105,10 +110,7 @@ const CoreAdmin: FunctionComponent<AdminProps> = ({

return (
<DataProviderContext.Provider value={finalDataProvider}>
<TranslationProvider
locale={locale}
i18nProvider={i18nProvider}
>
<TranslationProvider i18nProvider={i18nProvider}>
<ConnectedRouter history={history}>
<Switch>
{loginPage !== false && loginPage !== true ? (
Expand Down
1 change: 1 addition & 0 deletions packages/ra-core/src/i18n/TestTranslationProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export default ({ translate, messages, children }: any) => (
: options._
: translate,
changeLocale: () => Promise.resolve(),
getLocale: () => 'en',
},
}}
>
Expand Down
1 change: 1 addition & 0 deletions packages/ra-core/src/i18n/TranslationContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ const TranslationContext = createContext<TranslationContextProps>({
i18nProvider: {
translate: x => x,
changeLocale: () => Promise.resolve(),
getLocale: () => 'en',
},
});

Expand Down
6 changes: 3 additions & 3 deletions packages/ra-core/src/i18n/TranslationProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,17 +22,17 @@ interface Props {
* @example
* const MyApp = () => (
* <Provider store={store}>
* <TranslationProvider locale="fr" i18nProvider={i18nProvider}>
* <TranslationProvider i18nProvider={i18nProvider}>
* <!-- Child components go here -->
* </TranslationProvider>
* </Provider>
* );
*/
const TranslationProvider: FunctionComponent<Props> = props => {
const { i18nProvider, children, locale = 'en' } = props;
const { i18nProvider, children } = props;

const [state, setState] = useSafeSetState({
locale,
locale: i18nProvider ? i18nProvider.getLocale() : 'en',
i18nProvider,
});

Expand Down
3 changes: 2 additions & 1 deletion packages/ra-core/src/i18n/useTranslate.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ describe('useTranslate', () => {
i18nProvider: {
translate: () => 'hallo',
changeLocale: () => Promise.resolve(),
getLocale: () => 'de',
},
setLocale: () => Promise.resolve(),
}}
Expand All @@ -42,10 +43,10 @@ describe('useTranslate', () => {
it('should use the i18n provider when using TranslationProvider', () => {
const { queryAllByText } = renderWithRedux(
<TranslationProvider
locale="fr"
i18nProvider={{
translate: () => 'bonjour',
changeLocale: () => Promise.resolve(),
getLocale: () => 'fr',
}}
>
<Component />
Expand Down
1 change: 1 addition & 0 deletions packages/ra-core/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ export type Translate = (key: string, options?: any) => string;
export type I18nProvider = {
translate: Translate;
changeLocale: (locale: string, options?: any) => Promise<void>;
getLocale: () => string;
[key: string]: any;
};

Expand Down
11 changes: 7 additions & 4 deletions packages/ra-i18n-polyglot/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,33 +24,36 @@ export default (
initialLocale: string = 'en',
polyglotOptions: any = {}
): I18nProvider => {
let locale = initialLocale;
const messages = getMessages(initialLocale);
if (messages instanceof Promise) {
throw new Error(
`The i18nProvider returned a Promise for the messages of the default locale (${initialLocale}). Please update your i18nProvider to return the messages of the default locale in a synchronous way.`
);
}
const polyglot = new Polyglot({
locale: initialLocale,
locale,
phrases: { '': '', ...messages },
...polyglotOptions,
});
let translate = polyglot.t.bind(polyglot);

return {
translate: (key: string, options: any = {}) => translate(key, options),
changeLocale: (locale: string) =>
changeLocale: (newLocale: string) =>
new Promise(resolve =>
// so we systematically return a Promise for the messages
// i18nProvider may return a Promise for language changes,
resolve(getMessages(locale as string))
resolve(getMessages(newLocale as string))
).then(messages => {
locale = newLocale;
const newPolyglot = new Polyglot({
locale,
locale: newLocale,
phrases: { '': '', ...messages },
...polyglotOptions,
});
translate = newPolyglot.t.bind(newPolyglot);
}),
getLocale: () => locale,
};
};