Skip to content
This repository was archived by the owner on Jan 10, 2024. It is now read-only.

Commit 23dc246

Browse files
Merge branch 'feature/context-api-updates' into release/1.0.0
2 parents 5e4cbf1 + d441e51 commit 23dc246

23 files changed

+7555
-6753
lines changed

CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@
22

33
## v1.0.0 (2019-07-15)
44

5-
-
5+
- Implemented TextdomainContext context that adheres the current version of Context APIs.
6+
- Reworked Poedit example project to use new API.
67

78
## v0.4.0 (2017-08-17)
89

README.md

Lines changed: 196 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -2,21 +2,97 @@
22

33
[![npm version](https://badge.fury.io/js/react-gettext.svg)](https://badge.fury.io/js/react-gettext) [![Build Status](https://travis-ci.org/eugene-manuilov/react-gettext.svg?branch=master)](https://travis-ci.org/eugene-manuilov/react-gettext)
44

5-
Tiny React library for implementing gettext localization in your application. It provides HOC function to enhance your application by exposing gettext functions in the context scope.
5+
A tiny React library that helps to implement internalization in your application using gettext functions. It uses [React Context API](https://reactjs.org/docs/context.html) to expose gettext functions to children components.
66

77
## Instalation
88

9-
React Gettext requires **React 15.0 or later**. You can add this package using following commands:
9+
> **Note:** This library requires **React 16.3 or later**
1010
1111
```
12-
npm install react-gettext --save
12+
npm i react react-gettext
1313
```
1414

15+
## Usage
16+
17+
To use this library in your application, you need to do a few simple steps:
18+
19+
1. Prepare translations and define plural form functions.
20+
1. Add `TextdomainContext.Provider` provider to the root of your application.
21+
1. Updated your components to use context functions, provided by `TextdomainContext`, to translate text messages.
22+
23+
Let's take a closer look at each step. First of all, you to create translation catalogs and prepare plural form functions for every language that you are going to use. Each language needs one catalog and one plural form function.
24+
25+
The translation catalog is an object that contains key/value pairs where keys are original singular messages and values are translations. If you have a message that can have plural forms, the value for it should be an array with translations where each translation corresponds to appropriate plural form. Finally, if you want to use a context with your messages, then it should be prepended to the message itself and separated by using `\u0004` (end of transition) character. Here is an example:
26+
27+
```javascript
28+
{
29+
"Hello world!": "¡Hola Mundo!", // regular message
30+
"article": ["artículo", "artículos"], // plural version
31+
"Logo link\u0004Homepage": "Página principal", // single message with "Logo link" contex
32+
"Search results count\u0004article": ["artículo", "artículos"], // plural version with "Search results count" context
33+
}
1534
```
16-
yarn add react-gettext
35+
36+
The plural form function is a function that determines the number of a plural form that should be used for a particular translation. For English, the plural form is `n != 1 ? 1 : 0`, that means to use a translation with `0` index when `n == 1`, and a translation with `1` index in all other cases. Slavic and arabic languages have more than 2 plural forms and their functions are more complicated. Translate Toolkit has a [list of plural forms expressions for many languages](http://docs.translatehouse.org/projects/localization-guide/en/latest/l10n/pluralforms.html?id=l10n/pluralforms) that you can use in your project. An example of a plural form function can be the following:
37+
38+
```javascript
39+
function getPluralForm(n) {
40+
return n != 1 ? 1 : 0;
41+
}
1742
```
1843

19-
## Usage
44+
The next step is to pass translations and plural form function for the current language to the `buildTextdomain` function. It will create APIs that need to be passed to the `TextdomainContext.Provider` provider that you need to add to the root of your project:
45+
46+
```javascript
47+
import React, { Component } from 'react';
48+
import { TextdomainContext, buildTextdomain } from 'react-gettext';
49+
50+
class MyApp extends Component {
51+
52+
constructor(props) {
53+
super(props);
54+
this.state = { textdomain: buildTextdomain(...) };
55+
}
56+
57+
render() {
58+
return (
59+
<div>
60+
<TextdomainContext.Provider value={this.state.textdomain}>
61+
<ComponentA />
62+
...
63+
</TextdomainContext>
64+
</div>
65+
);
66+
}
67+
68+
}
69+
```
70+
71+
> **Note:** Please, pay attention that you need to avoid passing the results of `buildTextdomain` function directly into `TextdomainContext.Provider`'s value to escape unintentional renders in consumers when a provider’s parent re-renders.
72+
73+
Finally, the last step is to update your descendant components to consume these context APIs. Import `TexdomainContext` in the child component and assign it to the component `contextType` static properly. It will expose gettext APIs to that component via `this.context` field:
74+
75+
76+
```javascript
77+
import React, { Component } from 'react';
78+
import { TextdomainContext } from 'react-gettext';
79+
80+
class ComponentA extends Component {
81+
82+
render() {
83+
const { gettext, ngettext, xgettext, nxgettext } = this.context;
84+
85+
return (
86+
<div>...</div>
87+
);
88+
}
89+
90+
}
91+
92+
ComponentA.contextType = TextdomainContext;
93+
```
94+
95+
## An example
2096

2197
Let's assume you have following React application:
2298

@@ -56,115 +132,83 @@ export default class Header extends Component {
56132
}
57133
```
58134

59-
To make it translatable you need to update your `app.js` file to use HOC function and export higher-order component:
135+
To make it translatable, you need to update your `app.js` file to use TextdomainContext provider and build textdomain using messages list and plural form function:
60136

61137
```diff
62138
// app.js
63139
import React, { Component } from 'react';
64-
+ import withGettext from 'react-gettext';
140+
+ import { TextdomainContext, buildTextdomain } from 'react-gettext';
65141
import Header from './Header';
66142
import Footer from './Footer';
67143

68-
- export default class App extends Component {
69-
+ class App extends Component {
70-
...
71-
}
144+
export default class App extends Component {
145+
146+
+ constructor(props) {
147+
+ super(props);
148+
+ this.state = {
149+
+ textdomain: buildTextdomain(
150+
+ {
151+
+ 'Welcome to my application!': 'Bienvenido a mi aplicación!',
152+
+ // ...
153+
+ },
154+
+ n => n != 1
155+
+ ),
156+
+ };
157+
+ }
72158

73-
+ export default withGettext({...}, 'n != 1')(App);
159+
render() {
160+
return (
161+
<div id="app">
162+
+ <TextdomainContext.Provider value={this.state.textdomain}>
163+
<Header />
164+
...
165+
<Footer />
166+
+ </TextdomainContext.Provider>
167+
</div>
168+
);
169+
}
170+
}
74171
```
75172

76173
After doing it you can start using `gettext`, `ngettext`, `xgettext` and `nxgettext` functions in your descending components:
77174

78175
```diff
79176
// Header.js
80-
- import React, { Component } from 'react';
81-
+ import React, { Component } from 'react';
82-
+ import PropTypes from 'prop-types';
177+
import React, { Component } from 'react';
178+
+ import { TextdomainContext } from 'react-gettext';
83179

84180
export default class Header extends Component {
85181

86182
render() {
183+
+ const { gettext } = this.context;
87184
return (
88185
- <h1>Welcome to my application!</h1>
89-
+ <h1>{this.context.gettext('Welcome to my application!')}</h1>
186+
+ <h1>{gettext('Welcome to my application!')}</h1>
90187
);
91188
}
92189

93190
}
94191

95-
+ Header.contextTypes = {
96-
+ gettext: PropTypes.func.isRequired,
97-
+ ngettext: PropTypes.func.isRequired,
98-
+ xgettext: PropTypes.func.isRequired,
99-
+ nxgettext: PropTypes.func.isRequired,
100-
+ };
192+
+ Header.contextType = TextdomainContext;
101193
```
102194

103-
See an [example](https://github.com/eugene-manuilov/react-gettext/tree/master/examples) application to get better understanding how to use it.
195+
Check a [sample](https://github.com/eugene-manuilov/react-gettext/tree/master/examples/poedit) application to see how it works.
104196

105197
## Documentation
106198

107-
### withGettext(translations, pluralForms, options)
108-
109-
Higher-order function which is exported by default from `react-gettext` package. It accepts two arguments and returns function to create higher-order component.
110-
111-
- **translations**: a hash object or a function which returns hash object where keys are original messages and values are translated messages.
112-
- **pluralForms**: a string to calculate plural form (used by [Gettext PO](http://docs.translatehouse.org/projects/localization-guide/en/latest/l10n/pluralforms.html?id=l10n/pluralforms)) or a function which accepts a number and calculates a plural form number. Pay attentions that plural forms are zero-based what means to get 1st plural form it should return 0, to get 2nd - 1, and so on.
113-
- **options**: a hash object with options. Currently supports following options:
114-
- **withRef**: an optional boolean flag that determines whether or not to set `ref` property to a wrapped component what will allow you to get wrapped component instance by calling `getWrappedComponent()` function of the HOC. By default: `FALSE`.
115-
116-
Example:
117-
118-
```javascript
119-
const translations = {
120-
'Some text': 'Some translated text',
121-
...
122-
};
199+
### buildTextdomain(translations, pluralForm)
123200

124-
const pluralForms = '(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2)'; // 3 plural forms for Russian, Belarusian, Bosnian, Croatian, Serbian, Ukrainian, etc.
201+
Builds gettext APIs for TextdomainContext provider that will work with provided translations.
125202

126-
const HOC = withGettext(translations, pluralForms)(App);
127-
```
203+
- **translations**: an object with translated messages.
204+
- **pluralForm**: a function that determines a plural form or a stringular reresentation of it.
128205

129206
```javascript
130-
function getTranslations() {
131-
return {
132-
'Some text': 'Some translated text',
133-
...
134-
};
135-
}
136-
137-
function getPluralForms(n) {
138-
return n > 1 ? 1 : 0;
139-
}
140-
141-
const HOC = withGettext(getTranslations, getPluralForms)(App);
207+
const api = buildTextdomain( { ... }, n => n == 1 ? 0 : 1 );
208+
// or
209+
const api = buildTextdomain( { ... }, 'n == 1 ? 0 : 1' );
142210
```
143211

144-
As an alternative you can pass translations and plural form as properties to higher-order-component, like this:
145-
146-
```javascript
147-
function getTranslations() {
148-
return {
149-
'Some text': 'Some translated text',
150-
...
151-
};
152-
}
153-
154-
function getPluralForms(n) {
155-
return n > 1 ? 1 : 0;
156-
}
157-
158-
const HOC = withGettext()(App);
159-
160-
...
161-
162-
ReactDOM.render(<HOC translations={getTranslations} plural={getPluralForms}>...</HOC>, ...);
163-
```
164-
165-
One more alternative is to not create HOC, but use Textdomain component directly. You can import it using `import { Textdomain } from 'react-gettext'` and use it as a regular component which will provide context functions to translate your messages. Just don't forget to pass `translations` and `plural` props to this component when you render it.
166-
167-
168212
### gettext(message)
169213

170214
The function to translate a string. Accepts original message and returns translation if it exists, otherwise original message.
@@ -223,11 +267,81 @@ Example:
223267
this.context.nxgettext('day ago', 'days ago', numberOfDays, 'Article publish date');
224268
```
225269

270+
## Legacy API
271+
272+
The initial version of this library had been created when React used the legacy version of Context APIs, thus it played a keystone role in the main approach of how to use this library at that time. However, in the late March of 2018, React 16.3 was released and that API became deprecated, so do the main approach used in this library.
273+
274+
The proper way to use this library is described in the [Usage](#usage) section, this section contains legacy API that will be removed in next versions of the library. We don't encourage you to use it in a new project.
275+
276+
### withGettext(translations, pluralForms, options)
277+
278+
Higher-order function which is exported by default from `react-gettext` package. It accepts two arguments and returns function to create higher-order component.
279+
280+
- **translations**: a hash object or a function which returns hash object where keys are original messages and values are translated messages.
281+
- **pluralForms**: a string to calculate plural form (used by [Gettext PO](http://docs.translatehouse.org/projects/localization-guide/en/latest/l10n/pluralforms.html?id=l10n/pluralforms)) or a function which accepts a number and calculates a plural form number. Pay attentions that plural forms are zero-based what means to get 1st plural form it should return 0, to get 2nd - 1, and so on.
282+
- **options**: a hash object with options. Currently supports following options:
283+
- **withRef**: an optional boolean flag that determines whether or not to set `ref` property to a wrapped component what will allow you to get wrapped component instance by calling `getWrappedComponent()` function of the HOC. By default: `FALSE`.
284+
285+
Example:
286+
287+
```javascript
288+
const translations = {
289+
'Some text': 'Some translated text',
290+
...
291+
};
292+
293+
const pluralForms = '(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2)'; // 3 plural forms for Russian, Belarusian, Bosnian, Croatian, Serbian, Ukrainian, etc.
294+
295+
const HOC = withGettext(translations, pluralForms)(App);
296+
```
297+
298+
```javascript
299+
function getTranslations() {
300+
return {
301+
'Some text': 'Some translated text',
302+
...
303+
};
304+
}
305+
306+
function getPluralForms(n) {
307+
return n > 1 ? 1 : 0;
308+
}
309+
310+
const HOC = withGettext(getTranslations, getPluralForms)(App);
311+
```
312+
313+
As an alternative you can pass translations and plural form as properties to higher-order-component, like this:
314+
315+
```javascript
316+
function getTranslations() {
317+
return {
318+
'Some text': 'Some translated text',
319+
...
320+
};
321+
}
322+
323+
function getPluralForms(n) {
324+
return n > 1 ? 1 : 0;
325+
}
326+
327+
const HOC = withGettext()(App);
328+
329+
...
330+
331+
ReactDOM.render(<HOC translations={getTranslations} plural={getPluralForms}>...</HOC>, ...);
332+
```
333+
334+
One more alternative is to not create HOC, but use Textdomain component directly. You can import it using `import { Textdomain } from 'react-gettext'` and use it as a regular component which will provide context functions to translate your messages. Just don't forget to pass `translations` and `plural` props to this component when you render it.
335+
226336
## Poedit
227337

228-
If you use Poedit app to translate your messages, then you can use `gettext;ngettext:1,2;xgettext:1,2c;nxgettext:1,2,4c` as keywords list to properly parse and extract strings from your javascript files.
338+
If you want to use Poedit application to translate your messages, then use the following keywords to properly extract static copy from your javascript files:
229339

230-
Here is an example of a **POT** file which you can start with:
340+
```
341+
gettext;ngettext:1,2;xgettext:1,2c;nxgettext:1,2,4c
342+
```
343+
344+
Here is an example of a **POT** file that you can use as a starting point:
231345

232346
```
233347
msgid ""
@@ -246,7 +360,8 @@ msgstr ""
246360
"X-Poedit-SourceCharset: UTF-8\n"
247361
```
248362

249-
If you prefer using npm script, please add this to your package.json file. Make sure correct `project path` and `output` path is set.
363+
If you prefer using npm scripts, then you can add the following command to your `package.json` file to extract static copy and generate POT file using CLI commands. Make sure, you have correct `project` and `output` paths.
364+
250365
```
251366
"gettext:extract": "find /path/to/project -name \"*.js\" | xargs xgettext --from-code=UTF-8 --language=JavaScript --keyword=gettext --keyword=ngettext:1,2 --keyword=xgettext:1,2c --keyword=nxgettext:1,2,4c --output=/path/to/project/projectname.pot --sort-by-file --package-name=\"My Project Name\" --package-version=\"1.0.0\""
252367
```

__tests__/hoc.js

Lines changed: 1 addition & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { Component } from 'react';
22
import PropTypes from 'prop-types';
33
import faker from 'faker';
44

5-
import withGettext, { Textdomain } from '../lib/index';
5+
import withGettext from '../lib';
66

77
describe('Higher-order-component', () => {
88
class baseComponent extends Component {
@@ -33,17 +33,4 @@ describe('Higher-order-component', () => {
3333
nxgettext: PropTypes.func,
3434
});
3535
});
36-
37-
test('properly calculates plural form', () => {
38-
const instance = new Textdomain({plural});
39-
expect(instance.getPluralForm(1)).toBe(0);
40-
expect(instance.getPluralForm(2)).toBe(1);
41-
expect(instance.getPluralForm(5)).toBe(2);
42-
});
4336
});
44-
45-
describe('Textdomain', () => {
46-
test('named export', () => {
47-
expect(typeof Textdomain).toBe('function');
48-
});
49-
});

0 commit comments

Comments
 (0)