@oneflowab/pomes
v0.2.2
Published
A simple and powerful package to translate your react applications.
Downloads
40
Maintainers
Readme
🍎 Pomes
pomes is a simple yet powerful package to translate your react applications using react-redux.
Installation
npm i @oneflowab/pomes --save
or
yarn add @oneflowab/pomes --save
Features
- Translate literals.
- Pluralize literals.
- Designed for react-redux.
- Compatible with Immutable.js.
- Export translations to POT files (make your translations with Poedit).
- Import translations from .PO files to translations.js object (for use in your project).
- Add comments for translators.
Requirements
- node >= 6.0.0
Overview
pomes offers your app the t()
function to translate literals.
The t()
function is available in the components of your app via React context. To achieve this you need to wrap your app into the <I18n />
component from pomes that provides for the context. Furthermore, for all components that want to use the t()
function you need to define contextTypes
, e.g.:
// import ...
import PropTypes from 'prop-types'
class MyComponent extends React.Component {
render() {
return <div>{this.context.t("Hello World!")}</div>
}
}
MyComponent.contextTypes = {
t: PropTypes.func
}
If contextTypes
is not defined, then context will be an empty object.
The t()
function takes up to three arguments t(textKey [, params, comments])
, where textKey
is either the string to be translated or --- for pluralization --- an object as defined below.
For setting the language in the redux store pomes offers an action creator setLanguage
.
To manage the translations in your React app, pomes supports two choices:
- load all your translations into a one big JS object
- load your translations into a slice of your redux store
For the latter pomes provides an action function creator setTranslations
. As setTranslations
is an action function creator you need to add redux-thunk to your middleware for it to work.
pomes supports your store in plain JavaScript structures, but also if it is managed by help of immutable.js.
Finally, pomes offers scripts to generate a translations object from po files that can be managed in Poedit.
Usage
The package provides a parent component to encapsulate your application as well as helpers functions to translate your project.
// import ...
import I18n from "pomes"
// with Immutable.js:
import I18n from "pomes/immutable"
import {translations} from "./translations"
class Root extends React.Component {
render() {
return (
<Provider store={store}>
<I18n translations={translations}>
<App />
</I18n>
</Provider>
)
}
}
Where translations
is a dictionary similar to this:
export const translations = {
"es": {
"Translate this text": "Traduce este texto",
"Hello {n}!": "Hola {n}!",
}
}
You can also set the initial language with the initialLang prop:
<I18n translations={translations} initialLang="es">
<div>
<h1>My Project</h1>
{this.props.children}
</div>
</I18n>
If you have partial translations, this means that you don't have your translations at 100% and you want to show untranslated literals in an other language, you can use the fallbackLang
prop.
<I18n translations={translations} initialLang="de" fallbackLang="en">
<div>
<h1>My Project</h1>
{this.props.children}
</div>
</I18n>
In this case, if you want to show this translations:
<div>{this.context.t('_hello_')}</div>
And this isn't in "de" language, it will show in "en".
Redux Reducer
The language state is managed in a slice of the store named i18nState
. Therefore, you have to add the i18nState reducer in your combineReducers
.
import {otherreducers} from "./Yourproject"
import {i18nState} from "pomes"
// with Immutable.js:
import {i18nState} from "pomes/immutable"
const appReducer = combineReducers({
otherreducers,
i18nState
})
The current language is contained in the lang
key of i18nState
.
The i18nState
is initially defined as
const defaultI18nState = {
lang: 'en',
translations: {},
forceRefresh: false
}
// immutablejs
const defaultI18nState = new Map({
lang: 'en',
translations: {},
forceRefresh: false
})
When you map your state to props with connect you can also access the lang
attribute in your components:
export default connect(state => ({
lang: state.i18nState.lang,
}))(Home)
// with Immutable.js:
export default connect(state => ({
lang: state.getIn(['i18nState', 'lang']),
}))(Home)
Translate literals
You can access the functions of <I18n />
using your component's context. For example:
Home.contextTypes = {
t: PropTypes.func.isRequired
}
...you will then be able to use the t()
method in your component.
render() {
return (
<div>
<strong>Your current language, is: {this.props.lang}</strong><br/>
{this.context.t("Translate this text")}<br/>
{this.context.t("Hello {n}!", {n: "World"})}<br/><br/>
<button onClick={this.changeLanguage.bind(this)}>Change Language</button>
</div>
)
}
You can also use the t()
function to change date formats
export const translations = {
"de": {
"YYYY-MM-DD": "DD.MM.YYYY"
}
}
render() {
let today = moment()
return (
<div>
{today.format(this.context.t("YYYY-MM-DD"))}
</div>
)
}
Add comments for translators.
render() {
return (
<div>
{this.context.t("Translate this text", {},
"This is a comment for translators.")}
{this.context.t("Hello {n}!", {n: "Cesc"},
"This is another comment.")}
</div>
)
}
Here's how Poedit will show the comments:
HTML Object as parameter
const user = {name: 'World'}
const name = <span>{user.name}</span>
return <div dangerouslySetInnerHTML={{ __html: context.t('Hello {name}', {name: name}) }}/>
Result:
Hello <span>Cesc</span>
Notice that for security reasons we can't print html code directly, which is why we need to use the "dangerouslySetInnerHTML" method for that.
Stateless components
Example:
const Foo = ({}, context) => <h1>{context.t("Hello World")}</h1>
Pluralize
To use plurals in your translations.
<div>{this.context.t(['una noche', '{n} noches', 'n'], {n: 1})}</div>
Pass an array instead of a string as first parameter. The first element is a singular term, the second is the plural form and the last one is an object used to set the quantity.
After extracting the translations to a POT file and opening it with Poedit you will see the following:
Also the translations
object allows to set an options node. There you can set a plurals form rule and a plurals number. For example:
export const translations = {
"es": {
"Translate this text": "Traduce este texto",
"Hello {n}!": "Hola {n}!",
},
"options": {
"plural_rule": "n > 1",
"plural_number": "2",
}
}
When the translations are generated from po import file, this node is created automatically.
Note: Versions >=1.5.10 allow to use all existing pluralization rules: http://docs.translatehouse.org/projects/localization-guide/en/latest/l10n/pluralforms.html
Change language
Use the setLanguage action.
import {setLanguage} from "pomes"
componentWillMount() {
this.props.dispatch(setLanguage("es"))
}
If you work with combined languages like "es-ES"
, "en-GB"
, but your translations object doesn't include those properties...
export const translations = {
"es": {
...
},
"en": {
...
}
}
...pomes will fallback on the closest property. In this case, "es"
or "en"
.
Extract/Import scripts
pomes includes a script to extract your translation strings to a .pot template which you can use in Poedit, and another to import strings from po files to a translation.js
.
Add the scripts in your package.json for this purpose:
"scripts": {
"extract": "i18n_extract",
"import": "i18n_import"
}
You can then run the following commands in your terminal:
npm run extract
npm run import
Extract texts and build template.pot
npm run extract
By default, this script will search for all literals inside your src folder with a regular expression and build a locales/template.pot file. This file can then be used in Poedit to build en.po, es.po, etc. files.
If you want to set other source folder, you can use the --source
switch.
"scripts": {
"extract": "i18n_extract --source=mysourcefolder",
"import": "i18n_import"
}
Or if you want to export your locales to a different folder...
"scripts": {
"extract": "i18n_extract --source=mysourcefolder --locales=mylocalesfolder",
"import": "i18n_import"
}
By default this command find in all .js and .jsx file extensions, but you can customize it with fexts parameter. Check out this example:
"scripts": {
"extract": "i18n_extract --fexts=js,jsx,coffee",
"import": "i18n_import"
}
The default regular expression will search all occurrences of this.context.t
string, but you can also supply your own custom pattern, as in the following example:
export default function App({aProp, bProp}, {t: translate}) {
return <div>{translate('Hello world!')}</div>;
}
You will then need to set the --pattern
flag in package.json:
"scripts": {
"extract": "i18n_extract --pattern=translate",
"import": "i18n_import"
}
Import .po files
When your translators are done translating your terms, you can import your po files running the import script:
npm run import
This script read all po files inside your locales folder, extract all translations and build a src/translations.js that you can then use in your project.
Your .po files must define header language, check mininal format for more information.
You can also set another locales folder:
"scripts": {
"extract": "i18n_extract --source=mysource --locales=mylocales",
"import": "i18n_import --locales=mylocales"
}
Or, save translation.js to a different location:
"scripts": {
"extract": "i18n_extract --source=mysource --locales=mylocales",
"import": "i18n_import --locales=mylocales --translations=myfolder"
}
You can also change the encoding for your extraction from PO (default is iso-8859-1)
"scripts": {
"extract": "i18n_extract --source=mysource --locales=mylocales",
"import": "i18n_import --encoding=utf-8"
}
Async translations
When applications grow, translations tend to bigger as well, adding a lot to the overall size of the js bundle.
You can set an empty translations object to the <I18n/>
component and set the useReducer
prop to true to use the store as the source of strings. For example:
<Provider store={this.store}>
<I18n translations={{}} useReducer={true}>
<MainApp/>
</I18n>
</Provider>
Then you can use the setTranslations
action.
import {setTranslations} from 'pomes'
api.get('...').then(r => this.props.dispatch(setTranslations(r.translations)))
You can pass a second parameter to the action to set the language. Depending on your response's structure, it could look like this:
api.get('...').then(r => this.props.dispatch(setTranslations(r.translations, 'en')))
Since version 1.5.1 is possible pass a dictionary as a second param with some options. This allows us set more functionalities to method.
- preserveExisting (bool): If is true, the translations received does merge with existing translations.
- language (string): Language code
Some examples:
setTranslations(newTranslations, {preserveExisting: true})
setTranslations({'Hello': 'Hallo'}, {language: 'de'})
InitialState
Sometimes language is set initially by the redux store creation, or in an isomorphic way. In this case, you can set the initialized
prop to stop the I18n
provider from dispatching an action.
HOC
If you want to isolate the use of context from your components, you can import the Localize Hoc to provide the translate function as a prop to your component. For example:
import { localize } from 'pomes'
class SomeComponent extends Component {
render() {
return this.props.t('hello world')
}
}
export default localize()(SomeComponent)
You can also change the name of the provided prop:
import { localize } from 'pomes'
class SomeComponent extends Component {
render() {
return this.props.translate('hello world')
}
}
export default localize('translate')(SomeComponent)
Please, if you like my package, don't forget to rate it. Click on the "star"!