-
Notifications
You must be signed in to change notification settings - Fork 15
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
Recipe: Migrating from i18n-js to react-i18next #177
Changes from 8 commits
ad3dea3
033f973
1507a48
d99afe7
e89b369
6f564f5
de842db
a1147ec
a30e408
1d7cd37
383eea9
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,164 @@ | ||||||||||||||||||||||||||||||||||
--- | ||||||||||||||||||||||||||||||||||
title: Migrating from i18n-js to react-i18next | ||||||||||||||||||||||||||||||||||
description: How to migrate from i18n-js to react-i18next | ||||||||||||||||||||||||||||||||||
tags: | ||||||||||||||||||||||||||||||||||
- i18n | ||||||||||||||||||||||||||||||||||
last_update: | ||||||||||||||||||||||||||||||||||
author: Felipe Peña | ||||||||||||||||||||||||||||||||||
publish_date: 2024-09-25 | ||||||||||||||||||||||||||||||||||
--- | ||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||
# Migrating from i18n-js to react-i18next within an Ignite project | ||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||
## Overview | ||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||
In this guide, we will be going through the steps required to migrate your Ignite generated project from using `i18n-js` to the `react-i18next` library. It is meant specifically for projects generated with version 9 and below. | ||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||
`react-i18next` will be included in Ignite's version 10. If you're using an earlier version, this guide provides the necessary steps to successfully complete the migration. | ||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||
Finally, the steps outlined in this guide, are based on the changes outlined in the following two PRs: | ||||||||||||||||||||||||||||||||||
* [Swap out i18n-js for react-18next](https://github.com/infinitered/ignite/pull/2770) | ||||||||||||||||||||||||||||||||||
* [Fix language switching and update date-fns to v4](https://github.com/infinitered/ignite/pull/2778) | ||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||
## Step 1: Manage dependencies | ||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||
Remove the `i18n-js` package and its types: | ||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||
```bash | ||||||||||||||||||||||||||||||||||
yarn remove i18n-js | ||||||||||||||||||||||||||||||||||
yarn remove @types/i18n-js | ||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. These can be combined into one command |
||||||||||||||||||||||||||||||||||
``` | ||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||
Add the two new dependencies: | ||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||
```bash | ||||||||||||||||||||||||||||||||||
yarn add react-i18nex i18next | ||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||||||||||||||||||
``` | ||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||
## Step 2: Set up initialization logic in app.tsx | ||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||
To ensure that `react-i18next` finishes initializing before your app proceeds, we recommend adding state and logic to the `app.tsx` entry file: | ||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||
frankcalise marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||||||||||||||||||
1. Create a state variable, `isI18nInitialized`, to track initialization status. | ||||||||||||||||||||||||||||||||||
2. Use the `useEffect` hook to set the state when initialization completes. | ||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||
```js | ||||||||||||||||||||||||||||||||||
const [isI18nInitialized, setIsI18nInitialized] = useState(false); | ||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||
useEffect(() => { | ||||||||||||||||||||||||||||||||||
initI18n().then(() => setIsI18nInitialized(true)); | ||||||||||||||||||||||||||||||||||
}, []); | ||||||||||||||||||||||||||||||||||
``` | ||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||
Additionally, consider including the new state variable in the rendering condition for the app. | ||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||
```js | ||||||||||||||||||||||||||||||||||
// error-line-start | ||||||||||||||||||||||||||||||||||
if (!rehydrated || !isNavigationStateRestored || (!areFontsLoaded && !fontLoadError)) { | ||||||||||||||||||||||||||||||||||
// error-line-end | ||||||||||||||||||||||||||||||||||
// success-line-start | ||||||||||||||||||||||||||||||||||
if (!rehydrated || !isNavigationStateRestored || !isI18nInitialized || (!areFontsLoaded && !fontLoadError)) { | ||||||||||||||||||||||||||||||||||
// success-line-end | ||||||||||||||||||||||||||||||||||
return null | ||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||
``` | ||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||
This ensures that your app will wait until `react-i18next` is fully initialized before continuing, preventing any issues with missing translations. | ||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||
## Step 3: Update the i18n initialization method | ||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||
Next, update your i18n initialization to use `react-i18next`, which also includes RTL (right-to-left) language support and handles locale selection. In a Ingnite generated project, this is located in `app/i18n/i18n.ts`. | ||||||||||||||||||||||||||||||||||
frankcalise marked this conversation as resolved.
Show resolved
Hide resolved
frankcalise marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||
```js | ||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||
import * as i18next from "i18next" | ||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||
frankcalise marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||||||||||||||||||
const initI18n = async () => { | ||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||||||||||||||||||
await i18n.use(initReactI18next).init({ | ||||||||||||||||||||||||||||||||||
resources, | ||||||||||||||||||||||||||||||||||
frankcalise marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||||||||||||||||||
lng: pickSupportedLocale()?.languageTag || fallbackLocale, | ||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||||||||||||||||||||||||||||||||||
fallbackLng: fallbackLocale, | ||||||||||||||||||||||||||||||||||
interpolation: { escapeValue: false }, | ||||||||||||||||||||||||||||||||||
}); | ||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||
const locale = pickSupportedLocale(); | ||||||||||||||||||||||||||||||||||
if (locale?.textDirection === 'rtl') { | ||||||||||||||||||||||||||||||||||
I18nManager.allowRTL(true); | ||||||||||||||||||||||||||||||||||
isRTL = true; | ||||||||||||||||||||||||||||||||||
} else { | ||||||||||||||||||||||||||||||||||
I18nManager.allowRTL(false); | ||||||||||||||||||||||||||||||||||
isRTL = false; | ||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||
return i18n; | ||||||||||||||||||||||||||||||||||
}; | ||||||||||||||||||||||||||||||||||
``` | ||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||
This ensures that supported locales are chosen based on the device’s settings, and RTL is correctly applied when necessary. For more on detail on these changes, check the [this PR](https://github.com/infinitered/ignite/pull/2770). | ||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||
## Step 4: Add intl-pluralrules for react-i18next and JSON v4 | ||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||
To support pluralization and `react-i18next`'s JSON v4 format, you’ll need to add the `intl-pluralrules` package: | ||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||
```bash | ||||||||||||||||||||||||||||||||||
yarn add intl-pluralrules | ||||||||||||||||||||||||||||||||||
``` | ||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||
Make sure to import this package into your i18n configuration file (`app/i18n/i18n.ts`): | ||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||
```js | ||||||||||||||||||||||||||||||||||
import 'intl-pluralrules'; | ||||||||||||||||||||||||||||||||||
``` | ||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||
## Step 5: Update the translate function | ||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||
The next step is to replace your old translate function with the one provided by `react-i18next`. This is located in `app/i18n/translate.ts`: | ||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||
```js | ||||||||||||||||||||||||||||||||||
// error-line | ||||||||||||||||||||||||||||||||||
import { TranslateOptions } from "i18n-js" | ||||||||||||||||||||||||||||||||||
// success-line | ||||||||||||||||||||||||||||||||||
import { TOptions } from "i18next" | ||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add |
||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||
// error-line-start | ||||||||||||||||||||||||||||||||||
export function translate(key: TxKeyPath, options?: TranslateOptions): string { | ||||||||||||||||||||||||||||||||||
return i18n.t(key, options) | ||||||||||||||||||||||||||||||||||
// error-line-end | ||||||||||||||||||||||||||||||||||
// success-line-start | ||||||||||||||||||||||||||||||||||
export function translate(key, options) { | ||||||||||||||||||||||||||||||||||
return i18n.isInitialized ? i18n.t(key, options) : key; | ||||||||||||||||||||||||||||||||||
// success-line-end | ||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||||||||||||||||||
``` | ||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||
## Step 6: Update translation keys from dots (.) to colons (:) | ||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||
`react-i18next` uses different types of separators for translation keys. Colons (:) are used for first-level translations within an object, while dots (.) are used for nested translations. As a result, you’ll need to update all translation keys in your app accordingly. For example: | ||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In doing this dot notation to colon notation, you will receive errors on any
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So after showing this change, I'd probably show an example of changing some props as you did with the translate function below (but we generally don't use that one as much in the boilerplate). Maybe an example from the useHeader(
{
rightTx: "common:logOut",
onRightPress: logout,
},
[logout],
) return (
<View style={$container}>
<View style={$topContainer}>
<Image style={$welcomeLogo} source={welcomeLogo} resizeMode="contain" />
<Text
testID="welcome-heading"
style={$welcomeHeading}
tx="welcomeScreen:readyForLaunch"
preset="heading"
/>
<Text tx="welcomeScreen:exciting" preset="subheading" />
<Image style={$welcomeFace} source={welcomeFace} resizeMode="contain" />
</View>
<View style={[$bottomContainer, $bottomContainerInsets]}>
<Text tx="welcomeScreen:postscript" size="md" />
<Button
testID="next-screen-button"
preset="reversed"
tx="welcomeScreen:letsGo"
onPress={goNext}
/>
</View>
</View>
)
}) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I never looked into this, but I was wondering why we kept types that were defined when using the previous library. I would've expected we'd be able to use types defined by the new library. |
||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||
```js | ||||||||||||||||||||||||||||||||||
translate("common.ok") | ||||||||||||||||||||||||||||||||||
``` | ||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||
Should be changed to: | ||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||
```js | ||||||||||||||||||||||||||||||||||
translate("common:ok") | ||||||||||||||||||||||||||||||||||
``` | ||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||
--- | ||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||
Lastly, update the usage of `i18n`s `locale` method to `language` instead. For example in `app/utils/formatDate.ts`: | ||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||
```js | ||||||||||||||||||||||||||||||||||
// error-line | ||||||||||||||||||||||||||||||||||
const locale = i18n.locale.split("-")[0] | ||||||||||||||||||||||||||||||||||
// success-line | ||||||||||||||||||||||||||||||||||
const locale = i18n.language.split("-")[0] | ||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
also needs import of |
||||||||||||||||||||||||||||||||||
``` | ||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||
For detailed code changes, including initialization updates, translation function updates, and testing, refer to the following PRs on the Ignite Github repo: | ||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||
* [Swap out i18n-js for react-18next](https://github.com/infinitered/ignite/pull/2770) | ||||||||||||||||||||||||||||||||||
* [Fix language switching and update date-fns to v4](https://github.com/infinitered/ignite/pull/2778) | ||||||||||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||
By following this guide, you will be able to seamlessly transition your React Native app from `i18n-js` to `react-i18next`, ensuring improved localization features and support for modern internationalization practices. Let us know if you have any questions! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.