Skip to content

Commit 4777c3f

Browse files
committed
Add string templates & alias
1 parent 8cb4fe7 commit 4777c3f

25 files changed

+787
-270
lines changed

.eslintrc

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@
2222
],
2323
"react/prefer-stateless-function": 0,
2424
"react/prop-types": 0,
25-
"no-plusplus": 0
25+
"no-plusplus": 0,
26+
"default-case": 0,
27+
"react/jsx-one-expression-per-line": 0
2628
}
2729
}

README.md

Lines changed: 109 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -3,20 +3,25 @@
33
[![npm](https://img.shields.io/npm/v/react-localized.svg)](https://www.npmjs.com/package/react-localized)
44
[![npm](https://img.shields.io/npm/v/preact-localized.svg)](https://www.npmjs.com/package/preact-localized)
55

6-
Internationalization for React and Preact components.
6+
Complete internationalization tool for React and Preact components.
77

88
Features:
9-
- based on `gettext` format, uses translation `.po` files
109

11-
- translation strings can be extracted automatically from source files
10+
- based on [gettext](https://www.gnu.org/software/gettext/manual/gettext.html) format, uses translation `.po` files
11+
12+
- translation strings for `.po` files can be extracted automatically from project source files
1213

1314
- plural forms are supported
1415

16+
- gettext function aliases are supported
17+
18+
- JavaScript string templates are supported
19+
1520
- locale data is extendable, for example by adding date formats, currencies, etc.
1621

1722
- locale data can be separated from the main bundle by using dynamic import
1823

19-
### Component example
24+
### Basic component example
2025

2126
```js
2227
import { useLocales } from 'react-localized' // or from 'preact-localized'
@@ -25,15 +30,21 @@ export default () => {
2530
const { gettext } = useLocales()
2631
return (
2732
<>
28-
{ gettext('Hello, world!') } // Привет, мир!
33+
{ gettext('Hello, World!') } // Привет, Мир!
2934
</>
3035
)
3136
}
3237
```
3338

39+
### Complex example
40+
41+
[Live demo](http://fakundo.github.io/react-localized/)
42+
|
43+
[Source](https://github.com/fakundo/react-localized/tree/master/examples)
44+
3445
### Installation and usage
3546

36-
#### 1. Use existing `.po` files with translation or generate them from your project source files
47+
#### 1. Use existing `.po` files with translation messages or generate them from your project source files
3748

3849
To generate `.po` files you can use the extractor CLI tool. Extractor searches your project source files for functions like `gettext`, `ngettext`, etc. Extractor also can update existing `.po` files without erasing existing translations in those files.
3950

@@ -47,18 +58,20 @@ react-localized-extractor [options]
4758
Options:
4859
--version Show version number [boolean]
4960
--help Show help [boolean]
50-
--locales, -l List of desired locales [array] [required]
61+
--locales, -l List of desired locales (comma separated) [string] [required]
5162
--source, -s Source files pattern
52-
[string] [default: "./src/**/*.@(js|ts|jsx|tsx)"]
53-
--output, -o Output path [string] [default: "./locales"]
54-
--save-pot Should save .pot file [boolean] [default: false]
63+
[string] [default: "./src/**/*.@(js|ts|jsx|tsx)"]
64+
--output, -o Output .po files directory [string] [default: "./locales"]
65+
--alias, -a Function alias [string]
66+
--save-pot Should create catalog .pot file in output directory
67+
[boolean] [default: false]
5568
```
5669

5770
```console
58-
react-localized-extractor -l ru -s ./src/**/*.js -o ./locales
71+
react-localized-extractor -l ru
5972
```
6073

61-
#### 2. Add / edit translation in `.po` files
74+
#### 2. Modify `.po` files to add / edit translation
6275

6376
Use your favourite editor for `.po` files. I recommend you to use `Poedit`.
6477

@@ -91,11 +104,11 @@ npm i react-localized
91104
npm i preact-localized
92105
```
93106

94-
#### 5. Create locale data
107+
#### 5. Create data object for each locale
95108

96-
Use the second argument for extra data such as date formats, currencies, and so on.
109+
Use the `createLocale` function from the package. The first argument is for translation messages taken from `.po` file. The second argument is for extra data such as date formats, currencies, and so on.
97110

98-
Example of the file `ru.js`
111+
Example of the file `ru.js` for russian locale
99112

100113
```js
101114
import { createLocale } from 'react-localized' // or from 'preact-localized'
@@ -106,7 +119,7 @@ const extra = { ... }
106119
export default createLocale(messages, extra)
107120
```
108121

109-
##### 6. Render provider
122+
#### 6. Render provider component
110123

111124
```js
112125
import { LocalizedProvider } from 'react-localized' // or from 'preact-localized'
@@ -119,7 +132,10 @@ const ru = () => import('ru.js').then(data => data.default) // separated from th
119132
const locales = { fr, de, ru }
120133

121134
export default () => (
122-
<LocalizedProvider locales={locales} selected="fr">
135+
<LocalizedProvider
136+
locales={locales}
137+
selected="fr"
138+
>
123139
{ ({ localeReady }) => (
124140
localeReady
125141
? 'render children'
@@ -138,10 +154,10 @@ export default () => {
138154
const { gettext, ngettext, ...extra } = useLocales()
139155
return (
140156
<>
141-
{ gettext('Hello, world!') } // Привет, мир!
142-
{ ngettext('%s apple', '%s apples', 1, 1) } // 1 яблоко
143-
{ ngettext('%s apple', '%s apples', 2, 2) } // 2 яблока
144-
{ ngettext('%s apple', '%s apples', 10, 10) } // 10 яблок
157+
{ gettext('Hello, World!') } // Привет, Мир!
158+
{ ngettext('apple', 'apples', 1) } // яблоко
159+
{ ngettext('apple', 'apples', 2) } // яблока
160+
{ ngettext('apple', 'apples', 5) } // яблок
145161
</>
146162
)
147163
}
@@ -154,10 +170,9 @@ import { withLocales } from 'react-localized' // or from 'preact-localized'
154170

155171
class LocalizedComponent extends Component {
156172
render() {
157-
const { gettext, pgettext, formatDate, formats } = this.props // 'formatDate' and 'formats' are extra data passed to the 'createLocale'
173+
const { pgettext, formatDate, formats } = this.props // 'formatDate' and 'formats' are extra data passed to the 'createLocale'
158174
return (
159175
<>
160-
{ gettext('My name is %s', 'John') } // Мое имя John
161176
{ pgettext('Context', 'Text with context') } // Текст с контекстом
162177
{ formatDate(new Date(), formats.date) } // 1 января 2020
163178
</>
@@ -167,28 +182,91 @@ class LocalizedComponent extends Component {
167182

168183
export default withLocales()(LocalizedComponent)
169184
```
185+
### Using string templates
170186

171-
### See example
187+
```js
188+
import { useLocales } from 'react-localized' // or from 'preact-localized'
172189

173-
[Demo](http://fakundo.github.io/react-localized/)
174-
|
175-
[Source](https://github.com/fakundo/react-localized/tree/master/examples)
190+
export default () => {
191+
const { gettext, ngettext, pgettext, npgettext } = useLocales()
192+
const name = 'Anna'
193+
const i = 2
194+
return (
195+
<>
196+
{ gettext`My name is ${name}` } // Мое имя Anna
197+
{ ngettext`${i} apple``${i} apples`(i) } // 2 яблока
198+
{ pgettext('Ctx')`My name is ${name}` }
199+
{ npgettext('Ctx')`${i} apple``${i} apples`(i) }
200+
</>
201+
)
202+
}
203+
```
204+
205+
### Using function aliases
206+
207+
Use `LocalizedProvider` `alias` prop to define function aliases.
208+
209+
Example of alias for `gettext` only
210+
211+
```js
212+
<LocalizedProvider alias="__" />
213+
```
214+
215+
Example of aliases for `gettext` and `ngettext`
216+
217+
```js
218+
<LocalizedProvider alias={{ gettext: '__', ngettext: '__n' }} />
219+
```
220+
221+
Example of alias usage
222+
223+
```js
224+
import { useLocales } from 'react-localized' // or from 'preact-localized'
225+
226+
export default () => {
227+
const { __, __n } = useLocales()
228+
const name = 'Anna'
229+
return (
230+
<>
231+
{ __('Hello, World!') }
232+
{ __`My name is ${name}` }
233+
{ __n`apple``apples`(5) }
234+
</>
235+
)
236+
}
237+
```
238+
239+
Also configure extractor.
240+
241+
Example of alias for `gettext` only
242+
243+
```console
244+
react-localized-extractor -l ru -a __
245+
```
246+
247+
Example of aliases for `gettext` and `ngettext`
248+
249+
```console
250+
react-localized-extractor -l ru -a.gettext __ -a.ngettext __n
251+
```
176252

177253
### API
178254

179255
#### LocalizedProvider props
180256

181257
- `locales` - defined locales
182258
- `selected` - selected locale (default `en`)
259+
- `alias` - function aliases (string or object)
183260
- `children({ localeReady })`
184261

185262
#### Data returned by hook / props passed to the child of the HOC
186263

187264
- `locale`
188-
- `gettext(input, ...injections)`
189-
- `ngettext(singular, plural, n, ...injections)`
190-
- `pgettext(context, input, ...injections)`
191-
- `npgettext(context, singular, plural, n, ...injections)`
265+
- `gettext(text)`
266+
- `ngettext(text, textPlural, n)`
267+
- `pgettext(context, text)`
268+
- `npgettext(context, text, textPlural, n)`
269+
- `...aliases` - defined function aliases
192270
- `...extra` - extra data passed to the `createLocale`
193271

194272
### License

examples/locales/en.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
import format from 'date-fns/format'
1+
import formatDate from 'date-fns/format'
22
import { createLocale } from 'react-localized'
33

44
export default createLocale(null, {
5-
formatDate: format,
5+
formatDate,
66
formats: {
77
date: 'MMMM d, yyyy',
88
},

examples/locales/index.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1-
import englishData from './en'
1+
import en from './en'
2+
// import ru from './ru'
23

34
export default {
4-
en: englishData,
5+
en,
6+
// ru,
57
ru: () => import('./ru').then((data) => data.default),
68
}

examples/locales/ru.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
import format from 'date-fns/format'
1+
import formatDate from 'date-fns/format'
22
import locale from 'date-fns/locale/ru'
33
import { createLocale } from 'react-localized'
44
import messages from './ru.po'
55

66
export default createLocale(messages, {
7-
formatDate: (date, formatStr) => format(date, formatStr, { locale }),
7+
formatDate: (date, formatStr) => formatDate(date, formatStr, { locale }),
88
formats: {
99
date: 'dd MMMM yyyy',
1010
},

examples/locales/ru.po

Lines changed: 44 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -16,32 +16,56 @@ msgstr ""
1616
"X-Poedit-SourceCharset: UTF-8\n"
1717
"X-Poedit-SearchPath-0: .\n"
1818

19-
#: ../../examples/src/DecoratedClassComponent.js:10
20-
#: ../../examples/src/DecoratedFunctionalComponent.js:7
21-
#: ../../examples/src/HookedComponent.js:9
22-
msgid "%s apple"
23-
msgid_plural "%s apples"
24-
msgstr[0] "%s яблоко"
25-
msgstr[1] "%s яблока"
26-
msgstr[2] "%s яблок"
19+
#: ../../examples/src/AliasUsingComponent.js:13
20+
#: ../../examples/src/TemplateUsingComponent.js:13
21+
msgid "${} apple"
22+
msgid_plural "${} apples"
23+
msgstr[0] "${} яблоко"
24+
msgstr[1] "${} яблока"
25+
msgstr[2] "${} яблок"
2726

27+
#: ../../examples/src/DecoratedFunctionalComponent.js:12
28+
#: ../../examples/src/DecoratedFunctionalComponent.js:16
2829
#: ../../examples/src/DecoratedFunctionalComponent.js:8
29-
msgid "%s table"
30-
msgid_plural "%s tables"
31-
msgstr[0] "%s стол"
32-
msgstr[1] "%s стола"
33-
msgstr[2] "%s столов"
30+
msgid "apple"
31+
msgid_plural "apples"
32+
msgstr[0] "яблоко"
33+
msgstr[1] "яблока"
34+
msgstr[2] "яблок"
3435

3536
#: ../../examples/src/HookedComponent.js:8
36-
msgid "Hello, world!"
37-
msgstr "Привет, мир!"
37+
msgid "Hello, World!"
38+
msgstr "Привет, Мир!"
39+
40+
#: ../../examples/src/AliasUsingComponent.js:11
41+
#: ../../examples/src/TemplateUsingComponent.js:11
42+
msgid "My name is ${}"
43+
msgstr "Мое имя ${}"
44+
45+
#: ../../examples/src/AliasUsingComponent.js:14
46+
#: ../../examples/src/TemplateUsingComponent.js:14
47+
msgctxt "Context"
48+
msgid "${} table"
49+
msgid_plural "${} tables"
50+
msgstr[0] "${} стол"
51+
msgstr[1] "${} стола"
52+
msgstr[2] "${} столов"
3853

39-
#: ../../examples/src/DecoratedClassComponent.js:9
40-
#: ../../examples/src/DecoratedFunctionalComponent.js:6
41-
msgid "My name is %s"
42-
msgstr "Мое имя %s"
54+
#: ../../examples/src/DecoratedClassComponent.js:13
55+
msgctxt "Context"
56+
msgid "table"
57+
msgid_plural "tables"
58+
msgstr[0] "стол"
59+
msgstr[1] "стола"
60+
msgstr[2] "столов"
4361

44-
#: ../../examples/src/HookedComponent.js:10
62+
#: ../../examples/src/DecoratedClassComponent.js:10
4563
msgctxt "Context"
4664
msgid "Text with context"
4765
msgstr "Текст с контекстом"
66+
67+
#: ../../examples/src/AliasUsingComponent.js:12
68+
#: ../../examples/src/TemplateUsingComponent.js:12
69+
msgctxt "Context"
70+
msgid "Text with context ${}"
71+
msgstr "Текст с контекстом ${}"

examples/src/AliasUsingComponent.js

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import React from 'react'
2+
import { useLocales } from 'react-localized'
3+
4+
export default () => {
5+
const { __, __p, __n, __np } = useLocales()
6+
const name = 'Anna'
7+
const i = 2
8+
const j = 12
9+
return (
10+
<>
11+
<p>{__`My name is ${name}`}</p>
12+
<p>{__p('Context')`Text with context ${name}`}</p>
13+
<p>{__n`${i} apple``${i} apples`(i)}</p>
14+
<p>{__np('Context')`${j} table``${j} tables`(j)}</p>
15+
</>
16+
)
17+
}

0 commit comments

Comments
 (0)