Painless localising of React applications with translations, plurals, numbers, dates and times. 🌍
Internationalisation is not fun, so it should be easy. 😌
npm install --save react-globe react-intl
First off, you have to wrap your application in the Provider component. This does the bootstrapping of the translations and ensures that all translated components are synchronized with the specified language.
import GlobeProvider from 'react-globe'
// Define which language the application uses. You can wrap this
// provider in a stateful component, that sets the "lang" prop or
// use something like redux (see below) for state management. If this
// value changes, the whole application rerenders on the fly
let lang = 'en'
// Define your translation messages here as well as
// import the needed localeData for the language
import deLocaleData from 'react-intl/locale-data/de'
const translations = {
de: {
localeData: deLocaleData,
messages: {
'Message will be translated': 'Nachricht wird übersetzt',
// ...
}
}
}
// (Optional) Specify the debug mode. If set to "true",
// missing keys in translated languages will be highlighted
// with square brackets: [[This text is missing]]
const debug = true
// Wrap your application
React.render(
<GlobeProvider
lang={lang}
translations={translations}
debug={debug}>
// router / application container / ...
</GlobeProvider>,
document.getElementById('root')
)
Since it is very cumbersome to maintain a list of messages yourself or convert them from po files manually, you can import po files directly via webpack. First you need to install the loader:
npm install --save i18next-po-loader
Then you need to include the loader in your webpack config:
config.module.loaders = config.module.loaders
.concat([{test: /\.po$/, loaders: ['i18next-po-loader']}])
Now you can just import the messages from po files, which will automatically convert them into the format that is expected:
import deMessages from './translations/de.po'
import deLocaleData from 'react-intl/locale-data/de'
const translations = {
de: {messages: deMessages, localeData: deLocaleData}
}
Note: If you wish to import your messages manually, make sure to follow the i18next message format, especially for the naming of plural keys in different languages.
import {connect} from 'react-redux'
import GlobeProvider from 'react-globe'
import deMessages from './translations/de.po'
import deLocaleData from 'react-intl/locale-data/de'
// Load the translation data
const translations = {
de: {messages: deMessages, localeData: deLocaleData}
}
// Use a redux key to set the language state
const mapStateToProps = (state) => ({
lang: state.settings.language,
translations: translations,
debug: true
})
export default connect(mapStateToProps)(GlobeProvider)
This wraps the GlobeProvider in a container, which you can use like this:
render(
<Provider store={store}>
<WrappedGlobeProvider>
<Routes history={history} />
</WrappedGlobeProvider>
</Provider>,
document.getElementById('root')
)
To ensure that all translated components are in sync and don't get rerendered unnecessary, you have to wrap all your translated components in a higher order component.
import {translate} from 'react-globe'
class MyComponent extends React.Component {
// ...
}
export default translate(MyComponent)
Note: This will make your component a pure component, which means you have to ensure that it renders the same result given the same state and props.
Note: You can turn off the "pure rendering" part of the translate wrapper off by passing
falseas the second argument. This will re-translate all of your messages whenever the component containing the translated component updates - only use this when necessary!
import {t} from 'react-globe'
t('Message will be translated')
// -> 'Message will be translated' for 'en'
// -> 'Nachricht wird übersetzt' for 'de'
<span>{t('Message in a React component will be translated')}</span>
You can interpolate any values that can be converted to a string in the translated messages.
import {t} from 'react-globe'
t('This {{value}} will be interpolated. This one too: {{num}}',
{value: 'text', num: 42})
// -> 'This text will be interpolated. This one too: 42'
import {t} from 'react-globe'
const user = <strong>Bob</strong>
t('Hello {{user}}! How are you?', {react: true, user: user})
// -> <span>Hello <strong>Bob</strong>! How are you?</span>
Note: This will return a
<span>element, whereas the other methods just return a string. You can see the full list of options you can pass as a second argument here.
You can translate a plural with an optional message if the count is zero. This will use the correct pluralisation rules for the selected language.
import {tPlural} from 'react-globe'
tPlural({
zero: 'This is an optional string is the count is zero',
one: 'The count is one!',
many: 'The count is {{count}}!'
}, {
count: 1
})
Note: You can also interpolate values and React components in the plural method, by setting the options like described above.
Note: The formatters return React components, not strings, so if you want to use them inside translated strings, make sure you set the option to interpolate a React component.
import {tNumber} from 'react-globe'
tNumber(12308.32490, optionalOptions)
The options follow the specification for date formatting from Intl.DateTimeFormat.
import {tDate} from 'react-globe'
tDate(new Date(), optionalOptions)
The options follow the specification for number formatting from Intl.NumberFormat.
import {tDate} from 'react-globe'
tDate(new Date(), {relative: true})
// -> 'now' / '10 seconds ago' / 'in 2 years'
To extract the messages from your source code you can install react-globe-cli:
npm install --save-dev react-globe-cli
# Go through all the files and extract the messages into the output file
react-globe-cli --files='./src/**/*.js' --output='templates.pot'
Note: Make sure you are running this on unminified source code, since it requires the
tandtPluralfunction calls to keep their names. It should be fine with parsing es2015 and jsx code, but if you are using bleeding edge (e.g. thestage-0babel plugin) you will have to pre-compile that.
When setting the debug flag in the provider missing messages will be highlighted with brackets around them [[Some missing string]]. If you want to highlight them more obviously, you can use the following code as a bookmarklet show all missing translation messages with a orange background:
javascript:for(var elements=document.getElementsByTagName("*"),i=0;i!=elements.length;i++){var element=elements[i];element.innerHTML.match(/^\[\[.*\]\]$/)&&(element.style+=";background-color:orange !important")}
It feels laggy when I have a lot of components.
localStorage.debug = 'react-globe:*'My component doesn't update anymore.
Make sure that the component wrapped with the higher order component is a pure component or turn the pure rendering off by passing false as the second argument to translate.
This module allows to be easily mocked via the Jest moduleNameMapper configuration. This way, it always just returns the English message string for all methods and does not require the Provider setup, allowing for isolated testing of components.
"jest": {
"moduleNameMapper": {
"^react-globe$": "react-globe/mock"
}
}
MIT