Skip to content

Commit

Permalink
docs: add react-native tutorial (#243)
Browse files Browse the repository at this point in the history
* wip

wip 2

add image

fix typo

use withI18n

add demo link

* remove unnecessary change

* add contrib
  • Loading branch information
vonovak authored and tricoder42 committed Jul 22, 2018
1 parent 85eb87a commit f0ebd7d
Show file tree
Hide file tree
Showing 8 changed files with 232 additions and 3 deletions.
9 changes: 9 additions & 0 deletions .all-contributorsrc
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,15 @@
"contributions": [
"code"
]
},
{
"login": "vonovak",
"name": "Vojtech Novak",
"avatar_url": "https://avatars1.githubusercontent.com/u/1566403?v=4",
"profile": "https://github.com/vonovak",
"contributions": [
"doc"
]
}
],
"repoType": "github"
Expand Down
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ yarn run add -- <yourGitHubName> <type>

# Examples:
# yarn run add -- tricoder42 code
# yarn run add -- tricoder42 code,docs # type may be also comma separated value
# yarn run add -- tricoder42 code,doc # type may be also comma separated value
```

See the [key](https://github.com/jfmengels/all-contributors-cli#addupdate-contributors) for all types of contribution.
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ Seamless internationalization in Javascript and React
[![CircleCI][Badge-CI]][CI]
[![Code Coverage][Badge-Coverage]][Coverage]
[![PRs Welcome][Badge-PRWelcome]][PRWelcome]
[![All Contributors](https://img.shields.io/badge/all_contributors-12-orange.svg?style=flat-square)](#contributors)
[![All Contributors](https://img.shields.io/badge/all_contributors-13-orange.svg?style=flat-square)](#contributors)
[![MIT License][Badge-License]][LICENSE]

[![Watch on GitHub][Badge-Watch]][Watch]
Expand Down Expand Up @@ -257,7 +257,7 @@ Please read the [contributing guide](CONTRIBUTING.md) to start contributing! Tha
<!-- prettier-ignore -->
| [<img src="https://avatars1.githubusercontent.com/u/827862?v=3" width="100px;"/><br /><sub><b>Tomáš Ehrlich</b></sub>](http://www.tomasehrlich.cz)<br />[💻](https://github.com/lingui/js-lingui/commits?author=tricoder42 "Code") [📖](https://github.com/lingui/js-lingui/commits?author=tricoder42 "Documentation") [💡](#example-tricoder42 "Examples") [✅](#tutorial-tricoder42 "Tutorials") | [<img src="https://avatars1.githubusercontent.com/u/3697116?v=3" width="100px;"/><br /><sub><b>Josef Hornych</b></sub>](https://github.com/Peping)<br />[📖](https://github.com/lingui/js-lingui/commits?author=Peping "Documentation") [🐛](https://github.com/lingui/js-lingui/issues?q=author%3APeping "Bug reports") | [<img src="https://avatars2.githubusercontent.com/u/307006?v=3" width="100px;"/><br /><sub><b>Christian Kaps</b></sub>](https://www.silhouette.rocks)<br />[🐛](https://github.com/lingui/js-lingui/issues?q=author%3Aakkie "Bug reports") | [<img src="https://avatars0.githubusercontent.com/u/2085291?v=3" width="100px;"/><br /><sub><b>brunesto</b></sub>](https://github.com/brunesto)<br />[💻](https://github.com/lingui/js-lingui/commits?author=brunesto "Code") [🐛](https://github.com/lingui/js-lingui/issues?q=author%3Abrunesto "Bug reports") | [<img src="https://avatars0.githubusercontent.com/u/614768?v=3" width="100px;"/><br /><sub><b>David Furlong</b></sub>](https://davidfurlong.github.io/)<br />[💬](#question-davidfurlong "Answering Questions") | [<img src="https://avatars2.githubusercontent.com/u/1416801?v=4" width="100px;"/><br /><sub><b>Thibaut</b></sub>](http://thibaut.re)<br />[🐛](https://github.com/lingui/js-lingui/issues?q=author%3AthibautRe "Bug reports") [📖](https://github.com/lingui/js-lingui/commits?author=thibautRe "Documentation") | [<img src="https://avatars1.githubusercontent.com/u/2965120?v=4" width="100px;"/><br /><sub><b>Sebastian Sobociński</b></sub>](https://github.com/hiddenboox)<br />[💻](https://github.com/lingui/js-lingui/commits?author=hiddenboox "Code") |
| :---: | :---: | :---: | :---: | :---: | :---: | :---: |
| [<img src="https://avatars2.githubusercontent.com/u/296106?v=4" width="100px;"/><br /><sub><b>Matt Labrum</b></sub>](https://github.com/mlabrum)<br />[📖](https://github.com/lingui/js-lingui/commits?author=mlabrum "Documentation") | [<img src="https://avatars1.githubusercontent.com/u/1098399?v=4" width="100px;"/><br /><sub><b>Vincent Ricard</b></sub>](https://github.com/ghostd)<br />[📖](https://github.com/lingui/js-lingui/commits?author=ghostd "Documentation") | [<img src="https://avatars2.githubusercontent.com/u/2378900?v=4" width="100px;"/><br /><sub><b>Adam Gruber</b></sub>](https://github.com/adamgruber)<br />[💻](https://github.com/lingui/js-lingui/commits?author=adamgruber "Code") | [<img src="https://avatars1.githubusercontent.com/u/1268629?v=4" width="100px;"/><br /><sub><b>Spencer Mefford</b></sub>](https://github.com/spencermefford)<br />[💻](https://github.com/lingui/js-lingui/commits?author=spencermefford "Code") | [<img src="https://avatars2.githubusercontent.com/u/1448788?v=4" width="100px;"/><br /><sub><b>Jeow Li Huan</b></sub>](https://github.com/huan086)<br />[💻](https://github.com/lingui/js-lingui/commits?author=huan086 "Code") |
| [<img src="https://avatars2.githubusercontent.com/u/296106?v=4" width="100px;"/><br /><sub><b>Matt Labrum</b></sub>](https://github.com/mlabrum)<br />[📖](https://github.com/lingui/js-lingui/commits?author=mlabrum "Documentation") | [<img src="https://avatars1.githubusercontent.com/u/1098399?v=4" width="100px;"/><br /><sub><b>Vincent Ricard</b></sub>](https://github.com/ghostd)<br />[📖](https://github.com/lingui/js-lingui/commits?author=ghostd "Documentation") | [<img src="https://avatars2.githubusercontent.com/u/2378900?v=4" width="100px;"/><br /><sub><b>Adam Gruber</b></sub>](https://github.com/adamgruber)<br />[💻](https://github.com/lingui/js-lingui/commits?author=adamgruber "Code") | [<img src="https://avatars1.githubusercontent.com/u/1268629?v=4" width="100px;"/><br /><sub><b>Spencer Mefford</b></sub>](https://github.com/spencermefford)<br />[💻](https://github.com/lingui/js-lingui/commits?author=spencermefford "Code") | [<img src="https://avatars2.githubusercontent.com/u/1448788?v=4" width="100px;"/><br /><sub><b>Jeow Li Huan</b></sub>](https://github.com/huan086)<br />[💻](https://github.com/lingui/js-lingui/commits?author=huan086 "Code") | [<img src="https://avatars1.githubusercontent.com/u/1566403?v=4" width="100px;"/><br /><sub><b>Vojtech Novak</b></sub>](https://github.com/vonovak)<br />[📖](https://github.com/lingui/js-lingui/commits?author=vonovak "Documentation") |
<!-- ALL-CONTRIBUTORS-LIST:END -->
This project follows the [all-contributors][all-contributors] specification.
Expand Down
1 change: 1 addition & 0 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ Key features
:caption: Tutorials

React <tutorials/react>
React Native <tutorials/react-native>
JavaScript <tutorials/javascript>
CLI <tutorials/cli>

Expand Down
2 changes: 2 additions & 0 deletions docs/tutorials/javascript.rst
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
.. _js-tutorial-label:

**************************************************
Tutorial - Internationalization of JavaScript apps
**************************************************
Expand Down
213 changes: 213 additions & 0 deletions docs/tutorials/react-native.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,213 @@
****************************************************
Tutorial - Internationalization of React Native apps
****************************************************

Through 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 :ref:`React <react-tutorial-label>`, 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.

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>`_.

Let's Start
===========

We're going to translate the following app:

.. code-block:: jsx
import React from 'react';
import { StyleSheet, Text, View, Alert, SafeAreaView, Button } from 'react-native';
export default class App extends React.Component {
render() {
return (
<Inbox
username="John"
markAsRead={this.showAlert}
messages={[]}
/>
);
}
showAlert = () => {
Alert.alert('', 'Do you want to set all your messages as read?');
};
}
const Inbox = ({ messages, markAsRead, username }) => {
const messagesCount = messages.length;
return (
<SafeAreaView style={styles.container}>
<View style={styles.container2}>
<Text style={styles.heading}>Message Inbox</Text>
<Text>
See all unread messages or
</Text>
<Button onPress={markAsRead} title="mark messages as read" />
<Text>
{messagesCount === 1
? `There's {messagesCount} message in your inbox.`
: `There're ${messagesCount} messages in your inbox.`}
</Text>
</View>
<Text>{username}.</Text>
</SafeAreaView>
);
};
As you can see, it's a simple mailbox application with only one screen.

Introducing internationalization
================================

Not surprisingly, this part isn't too different from the :ref:`React tutorial <react-tutorial-label>`.

Let's use the :component:`Trans` component first. Don't forget that we need to wrap our root component with the :component:`I18nProvider` for :component:`Trans` to work correctly.

Let's translate the screen heading:

.. code-block:: jsx
import { I18nProvider, Trans } from '@lingui/react';
<I18nProvider language="en">
<YourRootComponent someProp="someValue" />
</I18nProvider>
// later on somewhere deep in the React component 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 :component:`Button` component. But wait a sec, the button expects to receive a ``string``, so we cannot use the :component:`Trans` component here! Also notice that the ``Alert.alert`` call requires a string as well.

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


.. hint::

The ``i18n`` object is covered in greater detail in the :ref:`JavaScript tutorial <js-tutorial-label>`.


Under the hood, :component:`I18nProvider` creates an instance of the ``i18n`` object automatically and passes it to :component:`Trans` components through React Context. The :component:`Trans` components then use the instance to get the translations from it. If we cannot use the :component:`Trans` component, we can use the ``withI18n`` HOC to get hold of the ``i18n`` object ourselves and get the translations from it. So, we need to do two things: first, we need to setup the :component:`I18nProvider` and then we can use the ``withI18n`` HOC, as shown in the following simplified example:

.. code-block:: jsx
import { I18nProvider, withI18n } from '@lingui/react';
<I18nProvider language="en">
<YourRootComponent someProp="someValue" />
</I18nProvider>
const Inbox = withI18n()(({ markAsRead, i18n }) => {
return (
<View>
<View>
<Text style={styles.heading}>
<Trans>Message Inbox</Trans>
</Text>
<Trans>See all unread messages or</Trans>
<Button onPress={markAsRead} title={i18n.t`mark messages as read`} />
</View>
);
});
// later on somewhere deep in the React component tree:
<Inbox markAsRead={this.showAlert} />
.. note::

The important thing about both the :component:`Trans` (and the other provided components) and ``withI18n`` HOC is that when you change the active language (through the ``language`` prop passed to :component:`I18nProvider`), all the components that show translated text will re-render, making sure the UI shows the correct translations. The two approaches are equivalent in their result.


i18n Outside of React Components
================================

Until now, we have covered the :component:`Trans` component and the ``withI18n`` HOC. Using them will make sure our components are always in sync with the currently active language.

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`` method which returns an ``i18n`` object.

.. code-block:: jsx
import { setupI18n } from '@lingui/core';
// this file is generated by the cli
import enMessages from './locale/en/messages.js';
// import this constant as get translations from it outside of React
export const i18n = setupI18n({
language: 'en',
catalogs: {
en: enMessages,
},
});
As explained before, :component:`I18nProvider` creates an instance of the ``i18n`` object automatically and passes it to :component:`Trans` components through React Context. Since we created the ``i18n`` instance by ourselves, we need to pass it to the :component:`I18nProvider` as a prop. This way we tell it not to create a new instance but use the one we provide, like this:

.. code-block:: jsx
<I18nProvider i18n={18n} language="en">
<YourRootComponent someProp="someValue" />
</I18nProvider>
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```.

The last remaining piece of the puzzle is changing the active language. The ``i18n`` object exposes two methods for that: ``i18n.load(catalogs)`` and ``i18n.activate(language)``. Just call the two methods, pass the changed ``i18n`` object and the new active language to the :component:`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>`_.


Rendering of Translations
=========================

As described in the :ref:`reference <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 :component:`Text` component. This means we would need to use the :component:`Trans` component like this:

.. code-block:: jsx
<Text><Trans>Message Inbox</Trans></Text>
You'll surely agree the :component:`Text` component looks a little redundant. That's why the :component:`I18nProvider` component accepts a ``defaultRender`` prop. Just supply the :component:`Text` component as the ``defaultRender`` prop and the previous example can be simplified to:

.. code-block:: jsx
<Trans>Message Inbox</Trans>
Alternatively, you may override the default locally on the i18n components, using the ``render`` prop. This is also documented in the :ref:`reference <rendering-translations>`.


Nesting Components
==================

It is worth mentioning that the :component:`Trans` and :component:`Text` components 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>`_.

.. image:: rn-component-nesting.png

This can be achieved by the following code:

.. code-block:: jsx
<Trans>
<Text style={{ fontSize: 20 }}>
<Text>Concert of </Text>
<Text style={{ color: 'green' }}>Green Day</Text>
<Text style={{ fontWeight: 'bold' }}> tonight!</Text>
</Text>
</Trans>
The extracted string for translation will look like this:

``"<0><1>Concert of </1><2>Green Day</2><3> tonight!</3></0>"``


The important point here is that the sentence isn't broken into pieces but remains together - that will allow the translator to deliver a quality result.

Further reading
===============

- `@lingui/react reference documentation <../ref/lingui-react.html>`_
- `@lingui/cli reference documentation <../ref/lingui-cli.html>`_
- `Pluralization Guide <../guides/plurals.html>`_
4 changes: 4 additions & 0 deletions docs/tutorials/react.rst
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
.. _react-tutorial-label:

*********************************************
Tutorial - Internationalization of React apps
*********************************************
Expand Down Expand Up @@ -163,6 +165,8 @@ component:

.. code-block:: jsx
import { Trans } from '@lingui/react';
<h1><Trans>Message Inbox</Trans></h1>
Yes, that's it. Let's move on to another paragraph. This paragraph has some
Expand Down
Binary file added docs/tutorials/rn-component-nesting.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit f0ebd7d

Please sign in to comment.