Skip to content

Commit

Permalink
feat: react native docs
Browse files Browse the repository at this point in the history
  • Loading branch information
vonovak committed Mar 12, 2023
1 parent 32c7f91 commit 3bf4396
Showing 1 changed file with 58 additions and 62 deletions.
120 changes: 58 additions & 62 deletions website/docs/tutorials/react-native.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,30 @@
# Internationalization of React Native apps

In this tutorial, we'll learn how to add internationalization to an existing application in React Native. The React Native tutorial is largely similar to the one for [React](/docs/tutorials/react.md), and we highly recommend you check out that tutorial first because it covers installation, setup and other topics. Here we will cover parts that are relevant for React Native and hopefully answer all questions you may have.
In this tutorial, we'll learn how to add internationalization to an existing application in React Native. Before going further, please follow the [setup guide](tutorials/setup-react.md) for installation and setup instructions.

The React Native tutorial is similar to the one for [React](/docs/tutorials/react.md) but here we will cover parts that are relevant for React Native and hopefully answer all questions you may have.

:::tip Hint
If you're looking for a working solution, check out the [sources available here](https://github.com/vonovak/js-lingui-demo) and the [demo app on Expo](https://exp.host/@vonovak/js-lingui-demo).
:::

:::caution Note
The latest version of `@lingui/react` working out-of-the-box for React Native on Android is 2.2. Newer versions depend on the [Intl object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl) which is not available on the JavaScript Core that is used on Android by default. See the [JSC build scripts for Android](https://github.com/react-community/jsc-android-buildscripts) for possible solution or use the [Intl polyfill](https://github.com/andyearnshaw/Intl.js/).
This tutorial assumes you use Lingui >=4.0 and React Native >=0.70 or Expo 47, with the Hermes JavaScript Engine.

`@lingui/core` depends on several apis exposed by the [Intl object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl). Support of the Intl object can vary across React Native and OS versions.
If some Intl feature is not supported by your runtime, you can [polyfill it](https://formatjs.io/docs/polyfills).

See [here](https://github.com/facebook/hermes/issues/23) for details about Intl support in the Hermes engine.
:::

If you're looking for a working solution, check out the [demo on Expo](https://exp.host/@vonovak/js-lingui-demo). The source code is [available here](https://github.com/vonovak/js-lingui-demo).
## Polyfilling Intl apis

React Native does not support all Intl features out of the box and we need to polyfill [`Intl.Locale`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Locale) using [`@formatjs/intl-locale`](https://formatjs.io/docs/polyfills/intl-locale/) and [`Intl.PluralRules`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/PluralRules) using [`@formatjs/intl-pluralrules`](https://formatjs.io/docs/polyfills/intl-pluralrules).

Follow their installation instuctions and then import the polyfills at the top of your application entry file.

## Let's Start

## Example component

We're going to translate the following app:

Expand Down Expand Up @@ -60,120 +76,100 @@ const Inbox = ({ messages, markAsRead, username }) => {

As you can see, it's a simple mailbox application with only one screen.

## Introducing internationalization
## Internationalization in React

Not surprisingly, this part isn't too different from the [React tutorial](/docs/tutorials/react.md).

:::caution Note
Make sure to update `metro.config.js` resolvers with `mjs` extension to avoid [this problem](https://github.com/eemeli/make-plural/issues/15):

``` js title="metro.config.js"
resolver: {
sourceExts: ['js', 'ts', 'tsx', 'mjs'],
},
```
:::

Let's use the [`Trans`](/docs/ref/macro.md#trans) macro first. Don't forget that we need to wrap our root component with the [`I18nProvider`](/docs/ref/react.md#i18nprovider) so we can set the active locale and load catalogs:

Let's translate the screen heading:
First we need to wrap our app with [`I18nProvider`](/docs/ref/react.md#i18nprovider) and then we can use the [`Trans`](/docs/ref/macro.md#trans) macro to translate the screen heading:

```jsx
import { I18nProvider } from '@lingui/react'
import { Trans } from '@lingui/macro'
import { i18n } from "@lingui/core"
import { Text } from "react-native";

i18n.load('en', messages);
i18n.activate('en');

<I18nProvider i18n={i18n}>
<I18nProvider i18n={i18n} defaultComponent={Text}>
<YourRootComponent someProp="someValue" />
</I18nProvider>

// later on somewhere deep in the React component tree:
// later on somewhere in the React element tree:
<Text style={styles.heading}><Trans>Message Inbox</Trans></Text>
```

This was easy. Now, the next step is to translate the `title` prop of the `<Button>` component. But wait a sec, the button expects to receive a `string`, so we cannot use the [`Trans`](/docs/ref/macro.md#trans) macro here! Also notice that the `Alert.alert` call requires a string as well.

Luckily, there is a simple solution: the `I18n` is a render prop component which gives us an `i18n` prop that we can use like this: ``i18n._(t`this will be translated`)`` and the result of such a call is a string. Let's see how to do this!

:::tip Hint
The `i18n` object is covered in greater detail in the [JavaScript tutorial](/docs/tutorials/javascript.md).
We're importing the default `i18n` object from `@lingui/core`. The `i18n` object is covered in greater detail in the [JavaScript tutorial](/docs/tutorials/javascript.md).
:::

Under the hood, [`I18nProvider`](/docs/ref/react.md#i18nprovider) creates an instance of the `i18n` object automatically and passes it to [`Trans`](/docs/ref/react.md#trans) components through React Context. The [`Trans`](/docs/ref/react.md#trans) components then use the instance to get the translations from it. If we cannot use the [`Trans`](/docs/ref/react.md#trans) component, we can use the `I18n` render prop component to get hold of the `i18n` object ourselves and get the translations from it.
This was easy. Now, let's translate the `title` prop in the `<Button title="mark messages as read" />` element. In this case, `Button` expects to receive a `string`, so we cannot use the [`Trans`](/docs/ref/macro.md#trans) macro here!

The solution is to use the `t` macro together with the `i18n` object which we can obtain from the `useLingui` hook. We use the two like this to get a translated string:

```ts
const { i18n } = useLingui()
...
<Button title={t(i18n)`this will be translated and rerendered with locale updates`}/>
```

Under the hood, [`I18nProvider`](/docs/ref/react.md#i18nprovider) takes an instance of the `i18n` object and passes it to [`Trans`](/docs/ref/react.md#trans) components through React Context. `I18nProvider` will also update the Context value (which rerenders components using the provided Context) when locale or message catalogs are updated.

The [`Trans`](/docs/ref/react.md#trans) components use the `I18n` instance to get the translations from it. If we cannot use the [`Trans`](/docs/ref/react.md#trans) component, we can use the `useLingui` hook to get hold of the `i18n` instance ourselves and get the translations from there.

So, we need to do two things: first, we need to setup the [`I18nProvider`](/docs/ref/react.md#i18nprovider) and then we can use the `I18n` render prop component, as shown in the following simplified example:
The interplay of `I18nProvider` and `useLingui` is shown in the following simplified example:

``` jsx
import { I18nProvider } from '@lingui/react'
import { t, Trans } from '@lingui/macro'
import { i18n } from "@lingui/core";

<I18nProvider locale="en">
<I18nProvider i18n={i18n}>
<YourRootComponent someProp="someValue" />
</I18nProvider>

const Inbox = (({ markAsRead }) => {
const { i18n } = useLingui()
return (
<View>
<View>
<Text style={styles.heading}>
<Trans>Message Inbox</Trans>
</Text>
<Trans>See all unread messages or</Trans>
{/* you can also use the withI18n HOC */}
<I18n>
{({ i18n }) => (
<Button onPress={markAsRead} title={i18n._(t`mark messages as read`)} />
)}
</I18n>
<Button onPress={markAsRead} title={t(i18n)`mark messages as read`}/>
</View>
);
});

// later on somewhere deep in the React component tree:
<Inbox markAsRead={this.showAlert} />
```

:::caution Note
There are several ways to render translations: You may use the [`Trans`](/docs/ref/react.md#trans) component, the [`withI18n`](/docs/ref/react.md#withi18n) HOC or the `I18n` component that provides a render prop. The important thing about all of these approaches is that when you change the active locale (through the `locale` prop passed to [`I18nProvider`](/docs/ref/react.md#i18nprovider)), all the components that show translated text will re-render, making sure the UI shows the correct translations. All of these approaches are equivalent in their result.
There are several ways to render translations: You may use the [`Trans`](/docs/ref/react.md#trans) component or the [`useLingui`](/docs/ref/react.md#withi18n) hook together with the `t` macro. When you change the active locale or load new messages, all the components that consume the Context provided by `I18nProvider` will re-render, making sure the UI shows the correct translations.
:::

## Internationalization Outside of React Components
## Internationalization outside of React

Until now, we have covered the [`Trans`](/docs/ref/react.md#trans) macro and the `I18n` render prop component. Using them will make sure our components are always in sync with the currently active locale.
Until now, we have covered the [`Trans`](/docs/ref/react.md#trans) macro and the `useLingui` hook. Using them will make sure our components are always in sync with the currently active locale and message catalog.

However, often you'll need to show localized strings outside of React, for example when you want to show a toast from some business logic code. In that case you'll also need access to the `i18n` object, but you don't want to pass it around from some component's props. At this point, we need to turn our attention to the `@lingui/core` package, namely the [`setupI18n`](/docs/ref/core.md#setupi18n) method which returns an `i18n` object.
However, often you'll need to show localized strings outside of React, for example when you want to show an Alert from some business logic code.

```jsx
import { setupI18n } from '@lingui/core';

// this file is generated by the cli
import enMessages from './locale/en/messages.js';
In that case you'll also need access to the `i18n` object, but you don't want to pass it around from some React component.
Lingui uses a default i18n object instance that you can import as follows:

// import this constant as get translations from it outside of React
export const i18n = setupI18n({
locale: 'en',
catalogs: {
en: enMessages,
},
});
```
import { i18n } from '@lingui/core';
```

As explained before, [`I18nProvider`](/docs/ref/react.md#i18nprovider) creates an instance of the `i18n` object automatically and passes it to [`Trans`](/docs/ref/react.md#trans) components through React Context. Since we created the `i18n` instance by ourselves, we need to pass it to the [`I18nProvider`](/docs/ref/react.md#i18nprovider) as a prop. This way we tell it not to create a new instance but use the one we provide, like this:
This instance is the source of truth for the active locale and the `t` macro implicitly uses this instance, unless you pass custom instance like this `` t(i18n)`some message` ``.

```jsx
<I18nProvider i18n={i18n} locale="en">
<YourRootComponent someProp="someValue" />
</I18nProvider>
```
What that means is that in non-react code you can just call `` t`this will be translated` `` which will render the translation for currently active locale.

Now we're ready to show correctly translated strings anywhere in our app! Just import the `i18n` object into your non-react code and use it, for example like this: `` i18n._(t`this will be translated`) ``.
## Changing the active locale

The last remaining piece of the puzzle is changing the active locale. The `i18n` object exposes two methods for that: [`i18n.load(catalogs)`](/docs/ref/core.md#i18n.load(catalogs)) and [`i18n.activate(locale)`](/docs/ref/core.md#i18n.activate). Just call the two methods, pass the changed `i18n` object and the new active locale to the [`I18nProvider`](/docs/ref/react.md#i18nprovider) and `js-lingui` takes care of the rest. It all becomes clear when you take a look at the [final code](https://github.com/vonovak/js-lingui-demo/blob/master/App.js).
The last remaining piece of the puzzle is changing the active locale. The `i18n` object exposes two methods for that: [`i18n.load(catalogs)`](/docs/ref/core.md#i18n.load(catalogs)) and [`i18n.activate(locale)`](/docs/ref/core.md#i18n.activate). Call the two methods one after another and the [`I18nProvider`](/docs/ref/react.md#i18nprovider) will pick up the change and re-render the translations. It all becomes clear when you take a look at the [final code](https://github.com/vonovak/js-lingui-demo/blob/master/App.js).

## Rendering of Translations
## Rendering of translations

As described in the [reference](/docs/ref/react.md#rendering-translations), by default, translation components render translation as a text without a wrapping tag. In React Native though, all text must be wrapped in the `Text` component. This means we would need to use the [`Trans`](/docs/ref/react.md#trans) component like this:

Expand All @@ -189,7 +185,7 @@ You'll surely agree the `Text` component looks a little redundant. That's why th

Alternatively, you may override the default locally on the i18n components, using the `render` prop. This is also documented in the [reference](/docs/ref/react.md#rendering-translations).

## Nesting Components
## Nesting components

It is worth mentioning that the [`Trans`](/docs/ref/react.md#trans) macro and `Text` component may be nested, for example to achieve the effect shown in the picture. This is thanks to how React Native [handles nested text](https://facebook.github.io/react-native/docs/text#nested-text).

Expand Down

0 comments on commit 3bf4396

Please sign in to comment.