Tutorial | API reference
lingui-react
React Components for I18n using ICU message format
lingui-react
provides React components for writing texts using ICU message format
Install
npm install lingui-react
yarn add lingui-react
:warning: It's possible to use this library without babel-preset-lingui-react
.
However, it's highly encouraged to use this preset. All examples below assume
that it's installed.
Quickstart
This is an introductory tutorial. For API reference, scroll below.
lingui-react
provides several components which represent ICU MessageFormat in
React. It's useful to understand this format, but certainly not required.
Please make sure that you have babel-preset-lingui-react
installed and added
to your babel config.
:bulb: All components are exported from the top of lingui-react
package.
1. Setup I18nProvider
Translations are loaded from message catalogs for given language. The top
component which adds messages and language to React component tree is
I18nProvider:
import React from 'react'
import { render } from 'react-dom'
import { I18nProvider } from 'lingui-react'
import messages from './locales/en.json'
import App from './App'
render(
<I18nProvider language="en" messages={{ en: messages }}>
<App />
</I18nProvider>,
document.getElementById("app")
)
:hammer_and_wrench: Under the hood this component adds messages and language to
context. All lingui-react
components access messages from this context, so
it's not necessary to pass them in props manually.
:bulb: Active language can be managed for example in redux
store. In that case,
add I18nProvider in main component (e.g. App
) and read
language
from redux
store. Loading of messages dynamically can be achieved
in several ways depending on bundler (e.g. webpack) and it's not covered in this
tutorial.
2. Wrap messages in i18n components
The simplest i18n component is Trans. It's used for simple, singular
translations. It supports both variables and inline components.
Suppose we have a Header
component, which displays the name of currently
logged in user:
import React from 'react'
const Header = ({ username }) => (
<div>
<h1>Documentation</h1>
<div className="UserProfile">
Welcome, <Link to="/profile">{username}</Link>
</div>
</div>
)
If we want to translate this component, just wrap all messages in
Trans component:
import React from 'react'
import { Trans } from 'lingui-react'
const Header = ({ username }) => (
<div>
<h1><Trans>Documentation</Trans></h1>
<div className="UserProfile">
<Trans>Welcome, <Link to="/profile">{username}</Link></Trans>
</div>
</div>
)
This process is completely seamless and doesn't require any other changes
to Header
component.
:warning: Only simple variables are supported inside Trans component.
It's not possible to use constants, function calls or object properties.
Any of following examples won't work:
const messages = [
"Hello",
"World"
]
<Trans>{messages.length}</Trans>
<Trans>{isEmpty(messages)}</Trans>
<Trans>The answer is {42}</Trans>
:hammer_and_wrench: How the extracted messages are going to look like
Extraction of messages into external message catalogs isn't covered in this
tutorial because it's a responsibility of lingui-cli
package. However, now it's
probably the best and only time to mention, how extracted messages are going
to look like.
Given the example above, we'll get two messages:
export default {
"Documentation": "",
"Welcome, <0>{username}</0>": ""
}
Anything wrapped inside Trans component is considered as
a source message.
Names of variables are preserved. This is the reason why only simple variables
are allowed - variable names are used as placeholders.
Inline components are replaced with dummy <0>
, <1>
, etc. tags. Using
anonymous tags has several advantages:
- Both built-in (e.g:
span
) and custom (e.g: Link
) components are supported - Change of props doesn't require update of translations (e.g. when class name is changed)
3. Translations of attributes
Sometimes it's necessary to translate element attributes, like link title
or
aria-label
. Trans is a React component, but in this case we need
just a text.
It's possible to access low-level i18n API using WithI18n
high-order component.
All we need to do is wrap our component and then
we can use lingui-i18n
API from i18n
prop:
import React from 'react'
import { Trans, WithI18n } from 'lingui-react'
const LogoutIcon = WithI18n()(
({ i18n }) => <Icon name="turn-off" aria-label={i18n.t`Log out`}/>
)
:warning: Watch the empty braces after WithI18n
. WithI18n
expects options
as a first argument and return configured HOC.
i18n.t
returns plain text. It's the same API which can be used outside React
context and it's described in lingui-i18n
4. Plurals, categories, and formats
ICU MessageFormat is very powerful and extensible format. It supports plurals,
categories and date/number formats.
Plurals
Plural component selects the appropriate plural form based on given
value
. For example in English, there are only two plural forms: singular and
plural. If we want to translate message with plural support, we need to define
both:
import React from 'react'
import { Trans } from 'lingui-react'
const Notifications = ({ newMessageCount }) => {
return (
<div>
<Plural
value={newMessageCount}
one="# message"
other="# messages"
/>
</div>
)
}
In English, Plural will render 1 message
, but 3 messages
depending on
value
. It means that one
prop expresses singular while other
is for plural.
Plural forms for other than source language are defined in message catalogs.
If we use English as a source language, we'll always use only one
and other
props in Plural.
Just to clarify, the message above is extracted as:
{newMessageCount, plural, one {# message} other {# messages}}
In Czech, it would be translated as:
{newMessageCount, plural, one {# zpráva} few {# zprávy} other {# zpráv}}
This is different compared to gettext
, which maps singular/plural to
language specific plural forms. In ICU message format, we always have only
one message with all plural forms per language.
:hammer_and_wrench: All possible plural forms are zero
, one
, two
, few
,
many
, other
.
:bulb: Plural forms for all languages are listed at CLDR page -
Language Plural Rules.
Select
Some languages have different forms based on gender or level of politeness.
Such cases are handled using Select component, which works like
a switch
statement:
import React from 'react'
import { Trans } from 'lingui-react'
const Book = ({ gender }) => {
return (
<div>
<Select
value={gender}
male="His book"
female="Her book"
other="Their book"
/>
</div>
)
}
There's no magic here. Select uses the form based on value
.
other
prop defines default form.
DateFormat and NumberFormat
Localization isn't just about text, but also about different number and date
formats. NumberFormat is used for formatting numbers
(decimals, percents, currencies), while DateFormat is for
dates:
import React from 'react'
import { DateFormat, NumberFormat } from 'lingui-react'
const Invoice = ({ invoiceNumber, datePaid, amount }) => {
return (
<div>
<h1><Trans>Invoice {invoiceNumber}</Trans></h1>
<Trans>
Amount <NumberFormat
value={amount}
format={{ style: 'currency', currency: 'EUR' }}
/> paid on <DateFormat value={datePaid} />
</Trans>
</div>
)
}
6. What's next
Once the app is internationalized, it's ready to be localized. Localization
process includes extracting messages into message catalog, translating message
catalogs and importing them in the application.
lingui-cli
provides extract
command, which creates new or updates existing
message catalogs.
Finally, after the message catalog is translated, it can be loaded back to the
application. I18nProvider expects a dictionary of messages,
where the key is a message in source language and the value is a message in target langauge.
Other useful links:
API
Component | Example MessageFormat |
---|
Trans | My name is {name} |
Categories | |
Plural | {count, plural, one {# book} other {# books}} |
Select | {gender, select, male {His} female {Her} other {Their}} |
SelectOrdinal | {count, selectordinal, one {#st} two {#nd} few {#rd} other {#th}} |
Formats | |
DateFormat | {value, date, short} |
NumberFormat | {value, number, percent} |
General concepts
Rendering in components
All i18n components support two optional props:
Prop name | Type | Description |
---|
className | string | Class name to be added to <span> element |
render | React.Element | React.Class |
Setting className
is enough in most cases. If we need to replace <span>
with a custom element, we can pass it to render
prop.
When render
is React.Element, it is cloned with translation
as children:
<Trans render={<h1 />}>Heading</Trans>
Using React.Component (or stateless component) in render
prop is useful to
get more control over the rendering of translation. Component passed to render
receive translation/value in translation
prop.
<Trans render={({ translation }) => <Icon label={translation} />}>
Sign in
</Trans>
Variables inside components
:warning: Only simple variables are supported inside Trans component
and as a value
prop in other i18n components.
It's not possible to use constants, function calls or object properties.
Any of following examples won't work:
const messages = [
"Hello",
"World"
]
<Trans>{messages.length}</Trans>
<Trans>{isEmpty(messages)}</Trans>
<Trans>The answer is {42}</Trans>
<DateFormat value={new Date()}
Trans
This is the main component for translation. It supports variables and components
inside messages. It's also possible to override generated message ID using id
prop.
Prop name | Type | Description |
---|
id | string | Override auto-generated message ID |
Examples:
<Trans>Hello World</Trans>
<Trans id="msg.hello">Hello World</Trans>
const name = "Fred"
<Trans>My name is {name}</Trans>
<Trans>See the <Link to="/more">description</Link> below.</Trans>
Plural
MessageFormat: {arg, plural, ...forms}
Prop name | Type | Description |
---|
value | number | Override auto-generated message ID |
offset | number | Offsets plural forms but doesn't affect exact matches |
zero | string | Form for empty value |
one | string | Singular form |
two | string | Dual form |
few | string | Paucal form |
many | string | Plural form |
other | string | (required) general plural form |
_0 , _1 , ... | string | Exact match form, correspond to =N rule |
See Language Plural Rules
overview.
Based on language, the value
is mapped to specific plural form. If such plural
form isn't defined, the other
form is used. #
character inside message is used
as a placeholder for value
:
const count = 42
<Plural
value={count}
one="# book"
other="# books"
/>
It's also possible to use exact matches. This is common used in combination with
offset
prop. offset
doesn't affect value
for exact matches, only plural forms:
const count = 42
<Plural
value={count}
offset={1}
_0="Nobody arrived"
_1="Only you arrived"
one="You and # other guest"
other="You and # other guests"
/>
Select
MessageFormat: {arg, select, ...forms}
Prop name | Type | Description |
---|
value | number | Override auto-generated message ID |
other | number | (required) Default, catch-all form |
This component selects appropriate form based on content of value
. It
behaves like an switch
statement. other
prop is used when no prop matches
value
:
<Select
value={gender}
male="His book"
female="Her book"
other="Their books"
/>
SelectOrdinal
MessageFormat: {arg, selectordinal, ...forms}
Prop name | Type | Description |
---|
value | number | Override auto-generated message ID |
offset | number | Offsets plural forms but doesn't affect exact matches |
zero | string | Form for empty value |
one | string | Singular form |
two | string | Dual form |
few | string | Paucal form |
many | string | Plural form |
other | string | (required) general plural form |
_0 , _1 , ... | string | Exact match form, correspond to =N rule |
See Language Plural Rules
overview.
This component is equivalent to Plural. The only difference is that it
uses ordinal plural forms, instead of cardinal ones.
<SelectOrdinal
value={count}
one="#st"
two="#nd"
few="#rd"
other="#th"
/>
DateFormat
MessageFormat: {arg, date, format}
Prop name | Type | Description |
---|
value | number | Date to be formatted |
format | string or Object | Date format passed as options to Intl.DateTimeFormat |
This component is a wrapper around Intl.DateTimeFormat.
const now = new Date()
<DateFormat value={now} />
const now = new Date()
<DateFormat value={now} format={{
year: "numberic",
month: "long",
day: "numeric"
}}/>
NumberFormat
MessageFormat: {arg, number, format}
Prop name | Type | Description |
---|
value | number | Number to be formatted |
format | string or Object | Number format passed as options to Intl.NumberFormat |
This component is a wrapper around Intl.NumberFormat.
const num = 0.42
<NumberFormat value={num} />
const amount = 3.14
<NumberFormat value={amount} format={{
style: "currency",
currency: 'EUR',
minimumFractionDigits: 2
}}/>
Providers
Message catalogs and active language are passed to the context in
I18nProvider. However, context should never be accessed
directly. WithI18n hoc passes i18n prop to wrapped component
and shadows all implementation details:
I18nProvider
Prop name | Type | Description |
---|
language | string | Active language |
messages | object | Message catalog |
messages
must be in format: { [language]: messageCatalog }
. This component
should live above all i18n components. The good place is top-level application
component. However, if the language
is stored in the redux
store, this
component should be inserted below react-redux/Provider
.
import React from 'react'
import { Provider, connect } from 'react-redux'
import { I18nProvider } from 'lingui-react'
const App = connect(state => ({ language: state.language }))(
({ language} ) => {
const messages = require(`locales/${language}.json`)
return (
<I18nProvider language={language} messages={{ [language]: messages }}>
// the rest of app
</I18nProvider>,
)
}
)
WithI18n
WithI18n([ options ])
Options | Description |
---|
withRef | Returns reference to wrapped instance in getWrappedInstance |
This HOC injects i18n
prop to wrapped component. It's useful when wrapped
component needs to access lingui-i18n
API for plain text translations.
import React from 'react'
import { Trans, WithI18n } from 'lingui-react'
const LogoutIcon = WithI18n()(
({ i18n }) => <Icon name="turn-off" aria-label={i18n.t`Log out`}/>
)
License
MIT