├── .babelrc.js
├── .github
└── workflows
│ └── node.js.yml
├── .gitignore
├── .prettierignore
├── .prettierrc.json
├── LICENSE
├── README.md
├── __test__
├── API.spec.js
├── Base.spec.js
├── I18n.spec.js
├── Localize.spec.js
├── Translate.spec.js
└── index.spec.js
├── eslint.config.mjs
├── example
├── example.js
└── run.js
├── package.json
├── src
├── components
│ ├── Base.js
│ ├── I18n.js
│ ├── Localize.js
│ └── Translate.js
├── index.js
└── lib
│ ├── localize.js
│ ├── settings.js
│ ├── translate.js
│ └── utils.js
├── tsconfig.types.json
├── types
├── index.d.ts
├── react-i18nify-tests.tsx
└── tsconfig.json
└── yarn.lock
/.babelrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: ['@babel/preset-react', '@babel/preset-env'],
3 | env: {
4 | es: {
5 | presets: ['@babel/preset-react', ['@babel/preset-env', { modules: false }]],
6 | },
7 | cjs: {
8 | presets: ['@babel/preset-react', ['@babel/preset-env', {}]],
9 | },
10 | },
11 | };
12 |
--------------------------------------------------------------------------------
/.github/workflows/node.js.yml:
--------------------------------------------------------------------------------
1 | # This workflow will do a clean install of node dependencies, cache/restore them, build the source code and run tests across different versions of node
2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions
3 |
4 | name: Node.js CI
5 |
6 | on:
7 | push:
8 | branches: [master]
9 | pull_request:
10 | branches: [master]
11 |
12 | jobs:
13 | build:
14 | runs-on: ubuntu-latest
15 |
16 | strategy:
17 | matrix:
18 | node-version: [22]
19 |
20 | steps:
21 | - uses: actions/checkout@v4
22 | - name: Use Node.js ${{ matrix.node-version }}
23 | uses: actions/setup-node@v4
24 | with:
25 | node-version: ${{ matrix.node-version }}
26 | - run: yarn --frozen-lockfile
27 | - run: yarn build
28 | - run: yarn prettier:check
29 | - run: yarn lint
30 | - run: yarn typecheck
31 | - run: yarn test
32 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /node_modules
2 | /npm-debug.log
3 | /build
4 | /es
5 | /cjs
6 |
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | cjs
3 | es
4 |
--------------------------------------------------------------------------------
/.prettierrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "tabWidth": 2,
3 | "semi": true,
4 | "singleQuote": true,
5 | "printWidth": 200,
6 | "useTabs": false,
7 | "trailingComma": "all",
8 | "bracketSpacing": true,
9 | "arrowParens": "always"
10 | }
11 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 Sealninja
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # React I18nify
2 |
3 | Simple i18n translation and localization components and helpers for React.
4 |
5 | [](https://npmjs.org/package/react-i18nify)
6 | [](https://npmjs.org/package/react-i18nify)
7 |
8 | A working example of this package can be found [here at RunKit](https://runkit.com/npm/react-i18nify).
9 |
10 | ## Migration guide
11 |
12 | ### Upgrading to v6
13 |
14 | `react-i18nify` v6 uses `dayjs` for date localization instead of `date-fns`, to make `react-i18nify` smaller and simpler to use. Migrating to this version requires the following changes to your project:
15 |
16 | - Replace locale imports. E.g., `import nl from 'date-fns/locale/nl';` needs to be replaced with `import 'dayjs/locale/nl';`
17 | - Remove calls to `addLocale` and `addLocales`, these are not needed anymore.
18 | - Update date formatting strings. For example, `MM-dd-yyyy` is now `MM-DD-YYYY`. See for more information the [day.js documentation](https://day.js.org/docs/en/display/format).
19 |
20 | The v5 documentation can still be found [here](https://github.com/sealninja/react-i18nify/blob/v5/README.md).
21 |
22 | ## Installation
23 |
24 | Install by using npm:
25 |
26 | ```
27 | npm i react-i18nify
28 | ```
29 |
30 | ## Getting started
31 |
32 | Start by setting the translations and locale to be used:
33 |
34 | ```javascript
35 | import { setTranslations, setLocale } from 'react-i18nify';
36 |
37 | setTranslations({
38 | en: {
39 | application: {
40 | title: 'Awesome app with i18n!',
41 | hello: 'Hello, %{name}!'
42 | },
43 | date: {
44 | long: 'MMMM do, yyyy'
45 | },
46 | export: 'Export %{count} items',
47 | export_0: 'Nothing to export',
48 | export_1: 'Export %{count} item',
49 | two_lines:
Line 1
Line 2
50 | },
51 | nl: {
52 | application: {
53 | title: 'Toffe app met i18n!',
54 | hello: 'Hallo, %{name}!'
55 | },
56 | date: {
57 | long: 'd MMMM yyyy'
58 | },
59 | export: 'Exporteer %{count} dingen',
60 | export_0: 'Niks te exporteren',
61 | export_1: 'Exporteer %{count} ding',
62 | two_lines:
Regel 1
Regel 2
63 | }
64 | });
65 |
66 | setLocale('nl');
67 | ```
68 |
69 | Now you're all set up to unleash the power of `react-i18nify`!
70 |
71 | ## Components
72 |
73 | The easiest way to translate or localize in your React application is by using the `Translate` and `Localize` components:
74 |
75 | ```javascript
76 | import { Translate, Localize } from 'react-i18nify';
77 |
78 |
79 | // => Toffe app met i18n!
80 |
81 | // => Hallo, Aad!
82 |
83 | // => Exporteer 1 ding
84 |
85 | // => Exporteer 2 dingen
86 |
87 | // =>
Regel 1
Regel 2
88 |
89 |
90 | // => 7 april 2016
91 |
92 | // => 3 september 2015
93 |
94 | // => 7 jaar geleden
95 |
96 | // => € 3,33
97 | ```
98 |
99 | ## Helpers
100 |
101 | If for some reason, you cannot use the components, you can use the `translate` and `localize` helpers instead:
102 |
103 | ```javascript
104 | import { translate, localize } from 'react-i18nify';
105 |
106 | translate('application.title');
107 | // => Toffe app met i18n!
108 | translate('application.hello', { name: 'Aad' });
109 | // => Hallo, Aad!'
110 | translate('export', { count: 0 });
111 | // => Niks te exporteren
112 | translate('application.unknown_translation');
113 | // => unknown_translation
114 | translate('application', { name: 'Aad' });
115 | // => {hello: 'Hallo, Aad!', title: 'Toffe app met i18n!'}
116 |
117 | localize(1385856000000, { dateFormat: 'date.long' });
118 | // => 1 december 2013
119 | localize(Math.PI, { maximumFractionDigits: 2 });
120 | // => 3,14
121 | localize('huh', { dateFormat: 'date.long' });
122 | // => null
123 | ```
124 |
125 | If you want these helpers to be re-rendered automatically when the locale or translations change, you have to wrap them in a `
` component using its `render` prop:
126 |
127 | ```javascript
128 | import { I18n, translate } from 'react-i18nify';
129 |
130 | } />;
131 | ```
132 |
133 | ## Date localization
134 |
135 | `react-i18nify` uses [day.js](https://github.com/iamkun/dayjs/) internally to handle date localization. To reduce the base bundle size, `day.js` localizations are not loaded by default. If you need date localization, you can manually import them. For a list of available locales, refer to the [day.js list of locales](https://github.com/iamkun/dayjs/tree/dev/src/locale).
136 |
137 | ```javascript
138 | import 'dayjs/locale/en';
139 | import 'dayjs/locale/nl';
140 | import 'dayjs/locale/it';
141 | ```
142 |
143 | ## API Reference
144 |
145 | ### ``
146 |
147 | React translate component, with the following props:
148 |
149 | - `value` (string)
150 |
151 | The translation key to translate.
152 |
153 | - Other props
154 |
155 | All other provided props will be used as replacements for the translation.
156 |
157 | ### ``
158 |
159 | React localize component, with the following props:
160 |
161 | - `value` (number|string|object)
162 |
163 | The number or date to localize.
164 |
165 | - `dateFormat` (string)
166 |
167 | The translation key for providing the format string. Only needed for localizing dates.
168 | For the full list of formatting tokens which can be used in the format string, see the [day.js documentation](https://day.js.org/docs/en/display/format).
169 |
170 | - `parseFormat` (string)
171 |
172 | An optional formatting string for parsing the value when localizing dates.
173 | For the full list of formatting tokens which can be used in the parsing string, see the [day.js documentation](https://day.js.org/docs/en/parse/string-format).
174 |
175 | - `options` (object)
176 |
177 | When localizing numbers, the localize component supports all options as provided by the Javascript built-in `Intl.NumberFormat` object.
178 | For the full list of options, see https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/NumberFormat.
179 |
180 | ### ``
181 |
182 | React I18n wrapper component, with the following prop:
183 |
184 | - `render` (func)
185 |
186 | The return value of the provide function will be rendered and automatically re-render when the locale or translations change.
187 |
188 | ### `setLocale(locale, rerenderComponents = true)`
189 |
190 | The used locale can be set with this function. By default, changing the locale will re-render all components.
191 | This behavior can be prevented by providing `false` as a second argument.
192 |
193 | ### `getLocale()`
194 |
195 | Get the currently used locale.
196 |
197 | ### `setTranslations(translations, rerenderComponents = true)`
198 |
199 | The used translations can be set with this function. By default, changing the translations will re-render all components.
200 | This behavior can be prevented by providing `false` as a second argument.
201 |
202 | ### `getTranslations()`
203 |
204 | Get the currently used translations.
205 |
206 | ### `setLocaleGetter(fn)`
207 |
208 | Alternatively to using `setLocale`, you can provide a callback to return the locale with `setLocaleGetter`:
209 |
210 | ```javascript
211 | import { setLocaleGetter } from 'react-i18nify';
212 |
213 | const localeFunction = () => 'nl';
214 |
215 | setLocaleGetter(localeFunction);
216 | ```
217 |
218 | ### `setTranslationsGetter(fn)`
219 |
220 | Alternatively to using `setTranslations`, you can provide a callback to return the translations with `setTranslationsGetter`:
221 |
222 | ```javascript
223 | import { setTranslationsGetter } from 'react-i18nify';
224 |
225 | const translationsFunction = () => ({
226 | en: { ... },
227 | nl: { ... }
228 | });
229 |
230 | setTranslationsGetter(translationsFunction);
231 | ```
232 |
233 | ### `setHandleMissingTranslation(fn)`
234 |
235 | By default, when a translation is missing, the translation key will be returned in a slightly formatted way,
236 | as can be seen in the `translate('application.unknown_translation');` example above.
237 | You can however overwrite this behavior by setting a function to handle missing translations.
238 |
239 | ```javascript
240 | import { setHandleMissingTranslation, translate } from 'react-i18nify';
241 |
242 | setHandleMissingTranslation((key, replacements, options, err) => `Missing translation: ${key}`);
243 |
244 | translate('application.unknown_translation');
245 | // => Missing translation: application.unknown_translation
246 | ```
247 |
248 | ### `setHandleFailedLocalization(fn)`
249 |
250 | By default, when a localization failed, `null` will be returned,
251 | as can be seen in the `localize('huh', { dateFormat: 'date.long' });` example above.
252 | You can however overwrite this behavior by setting a function to handle failed localizations.
253 |
254 | ```javascript
255 | import { setHandleFailedLocalization, localize } from 'react-i18nify';
256 |
257 | setHandleFailedLocalization((value, options, err) => `Failed localization: ${value}`);
258 |
259 | localize('huh', { dateFormat: 'date.long' });
260 | // => Failed localization: huh
261 | ```
262 |
263 | ### `translate(key, replacements = {})`
264 |
265 | Helper function to translate a `key`, given an optional set of `replacements`. See the above Helpers section for examples.
266 |
267 | ### `localize(value, options)`
268 |
269 | Helper function to localize a `value`, given a set of `options`. See the above Helpers section for examples.
270 |
271 | For localizing dates, the `day.js` library is used.
272 | A `dateFormat` option can be used for providing a translation key with the format string.
273 | For the full list of formatting tokens which can be used in the format string, see the [day.js documentation](https://day.js.org/docs/en/display/format).
274 | Moreover, `parseFormat` option can be used for providing a formatting string for parsing the value.
275 | For the full list of formatting tokens which can be used in the parsing string, see the [day.js documentation](https://day.js.org/docs/en/parse/string-format).
276 |
277 | For number formatting, the localize helper supports all options as provided by the Javascript built-in `Intl.NumberFormat` object.
278 | For the full list of options, see https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/NumberFormat.
279 |
280 | ### `t(key, replacements = {})`
281 |
282 | Alias for `translate`.
283 |
284 | ### `l(value, options)`
285 |
286 | Alias for `localize`.
287 |
288 | ### `forceComponentsUpdate()`
289 |
290 | This function can be called to force a re-render of all I18n components.
291 |
292 | ## Example application with SSR
293 |
294 | An example application with server-side rendering using features of `react-i18nify` can be found at https://github.com/sealninja/react-ssr-example.
295 |
296 | ## License
297 |
298 | MIT
299 |
--------------------------------------------------------------------------------
/__test__/API.spec.js:
--------------------------------------------------------------------------------
1 | /* global describe, test, expect, beforeEach */
2 |
3 | import dayjs from 'dayjs';
4 | import 'dayjs/locale/nl';
5 | import 'dayjs/locale/it';
6 | import 'dayjs/locale/zh';
7 | import 'dayjs/locale/en';
8 | import 'dayjs/locale/en-gb';
9 | import utc from 'dayjs/plugin/utc';
10 | import timezonePlugin from 'dayjs/plugin/timezone';
11 | import { getLocale, getTranslations, setLocale, setTranslations, setLocaleGetter, setTranslationsGetter, setHandleMissingTranslation, translate, localize, t, l } from '../src';
12 |
13 | dayjs.extend(utc);
14 | dayjs.extend(timezonePlugin);
15 |
16 | describe('API', () => {
17 | describe('setLocale', () => {
18 | setLocale('zh');
19 | const result = getLocale();
20 | expect(result).toEqual('zh');
21 | });
22 |
23 | describe('setTranslations', () => {
24 | setTranslations({
25 | en: {
26 | hello: 'Hello, %{name}!',
27 | },
28 | });
29 | const result = getTranslations();
30 | expect(result).toEqual({
31 | en: {
32 | hello: 'Hello, %{name}!',
33 | },
34 | });
35 | });
36 |
37 | describe('setLocaleGetter', () => {
38 | setLocaleGetter(() => 'zh');
39 | const result = getLocale();
40 | expect(result).toEqual('zh');
41 | });
42 |
43 | describe('setLocaleGetter', () => {
44 | setTranslationsGetter(() => ({
45 | en: {
46 | hello: 'Hello, %{name}!',
47 | },
48 | }));
49 | const result = getTranslations();
50 | expect(result).toEqual({
51 | en: {
52 | hello: 'Hello, %{name}!',
53 | },
54 | });
55 | });
56 |
57 | describe('setHandleMissingTranslation', () => {
58 | setHandleMissingTranslation((key) => `Missing translation: ${key}`);
59 | const result = t('application.unknown_translation');
60 | expect(result).toEqual('Missing translation: application.unknown_translation');
61 | });
62 |
63 | describe('translate', () => {
64 | beforeEach(() => {
65 | setTranslations({
66 | en: {
67 | application: {
68 | hello: 'Hello, %{name}!',
69 | empty: '',
70 | },
71 | },
72 | nl: {
73 | application: {
74 | hello: 'Hallo, %{name}!',
75 | empty: '',
76 | },
77 | },
78 | });
79 | setLocale('en');
80 | });
81 |
82 | [translate, t].forEach((translateFunction) => {
83 | test('should support fallback locale', () => {
84 | setLocale('nl-argh');
85 | const result1 = translateFunction('application.hello', { name: 'Aad' });
86 | expect(result1).toEqual('Hallo, Aad!');
87 | });
88 |
89 | test('should handle dynamic placeholder', () => {
90 | const result1 = translateFunction('application.hello', { name: 'Aad' });
91 | expect(result1).toEqual('Hello, Aad!');
92 |
93 | const result2 = translateFunction('application.hello', { name: 'Piet' });
94 | expect(result2).toEqual('Hello, Piet!');
95 | });
96 |
97 | test('should handle nested dynamic placeholder', () => {
98 | const result1 = translateFunction('application', { name: 'Aad' });
99 | expect(result1).toEqual({ hello: 'Hello, Aad!', empty: '' });
100 |
101 | const result2 = translateFunction('application', { name: 'Piet' });
102 | expect(result2).toEqual({ hello: 'Hello, Piet!', empty: '' });
103 | });
104 |
105 | test('should handle empty translation', () => {
106 | const result1 = translateFunction('application.empty');
107 | expect(result1).toEqual('');
108 | });
109 |
110 | test('should support providing locale', () => {
111 | const result1 = translateFunction('application.hello', { name: 'Aad' }, { locale: 'nl' });
112 | expect(result1).toEqual('Hallo, Aad!');
113 | });
114 | });
115 | });
116 |
117 | describe('localize', () => {
118 | beforeEach(() => {
119 | setTranslations({
120 | en: {
121 | dates: {
122 | short: 'MM-DD-YYYY',
123 | long: 'MMMM Do, YYYY',
124 | },
125 | },
126 | nl: {
127 | dates: {
128 | long: 'D MMMM YYYY',
129 | },
130 | },
131 | });
132 | setLocale('en');
133 | });
134 |
135 | [localize, l].forEach((localizeFunction) => {
136 | test('should return null when locale invalid', () => {
137 | setLocale('argh');
138 | const result = localizeFunction(1517774664107, { dateFormat: 'dates.long' });
139 | expect(result).toEqual(null);
140 | });
141 |
142 | test('should return null when locale not loaded', () => {
143 | setLocale('fr');
144 | const result = localizeFunction('2014-30-12', { parseFormat: 'YYYY-DD-MM', dateFormat: 'dates.short' });
145 | expect(result).toEqual(null);
146 | });
147 |
148 | test('should return null when localization failed', () => {
149 | const result = localizeFunction('huh', { parseFormat: 'YYYY-DD-MM', dateFormat: 'dates.short' });
150 | expect(result).toEqual(null);
151 | });
152 |
153 | test('should support parseFormat', () => {
154 | const result = localizeFunction('2014-30-12', { parseFormat: 'YYYY-DD-MM', dateFormat: 'dates.short' });
155 | expect(result).toEqual('12-30-2014');
156 | });
157 |
158 | test('should support providing locale', () => {
159 | const result = localizeFunction(1517774664107, { locale: 'nl', dateFormat: 'dates.long' });
160 | expect(result).toEqual('4 februari 2018');
161 | });
162 |
163 | test('should support distance to now', () => {
164 | const result = localizeFunction(new Date(new Date().setFullYear(new Date().getFullYear() - 3)).getTime(), { locale: 'nl', dateFormat: 'distance-to-now' });
165 | expect(result).toEqual('3 jaar geleden');
166 | });
167 |
168 | test('should support distance to now in days', () => {
169 | const result = localizeFunction(new Date(new Date().setHours(new Date().getHours() - 30)).getTime(), { locale: 'nl', dateFormat: 'distance-to-now' });
170 | expect(result).toEqual('een dag geleden');
171 | });
172 |
173 | test('should support dayjs with custom timezone', () => {
174 | const result = localizeFunction(dayjs.utc('2022-07-01T03:00:00.000Z').tz('America/Chihuahua'), { locale: 'nl', dateFormat: 'DD MMM YYYY, HH:mm Z' });
175 | expect(result).toEqual('30 jun 2022, 21:00 -06:00');
176 | });
177 |
178 | test('should return date when locale can fall back', () => {
179 | setLocale('nl-be');
180 | const result = localizeFunction(1517774664107, { dateFormat: 'LL' });
181 | expect(result).toEqual('4 februari 2018');
182 | });
183 |
184 | test('should return date when provided locale can fall back', () => {
185 | const result = localizeFunction(1517774664107, { locale: 'zh-tw', dateFormat: 'LL' });
186 | expect(result).toEqual('2018年2月4日');
187 | });
188 |
189 | test('should return date for regional locale with region uppercase', () => {
190 | setLocale('en-GB');
191 | const result = localizeFunction(1517774664107, { dateFormat: 'LL' });
192 | expect(result).toEqual('4 February 2018');
193 | });
194 |
195 | test('should return date for regional locale with region lowercase', () => {
196 | setLocale('en-gb');
197 | const result = localizeFunction(1517774664107, { dateFormat: 'LL' });
198 | expect(result).toEqual('4 February 2018');
199 | });
200 | });
201 | });
202 | });
203 |
--------------------------------------------------------------------------------
/__test__/Base.spec.js:
--------------------------------------------------------------------------------
1 | /* global describe, test, expect */
2 |
3 | import Base from '../src/components/Base';
4 |
5 | describe('Base.jsx', () => {
6 | test('should export component', () => {
7 | expect(Base).toBeDefined();
8 | });
9 | });
10 |
--------------------------------------------------------------------------------
/__test__/I18n.spec.js:
--------------------------------------------------------------------------------
1 | /* global describe, test, expect, beforeAll, beforeEach */
2 |
3 | import React from 'react';
4 | import { renderToString } from 'react-dom/server';
5 | import { setLocale, setTranslations, t, I18n } from '../src';
6 |
7 | describe('I18n.jsx', () => {
8 | beforeAll(() => {
9 | setTranslations({
10 | en: {
11 | application: {
12 | title: 'Awesome app with i18n!',
13 | },
14 | },
15 | nl: {
16 | application: {
17 | title: 'Toffe app met i18n!',
18 | },
19 | },
20 | });
21 | });
22 |
23 | describe(' component', () => {
24 | beforeEach(() => {
25 | setLocale('en');
26 | });
27 |
28 | test('should handle locale switching for attributes', () => {
29 | const component = } />;
30 |
31 | expect(renderToString(component)).toEqual('');
32 | setLocale('nl');
33 | expect(renderToString(component)).toEqual('');
34 | });
35 |
36 | test('should handle locale switching for children', () => {
37 | const component = {t('application.title')}} />;
38 |
39 | expect(renderToString(component)).toEqual('Awesome app with i18n!');
40 | setLocale('nl');
41 | expect(renderToString(component)).toEqual('Toffe app met i18n!');
42 | });
43 | });
44 | });
45 |
--------------------------------------------------------------------------------
/__test__/Localize.spec.js:
--------------------------------------------------------------------------------
1 | /* global describe, test, expect, beforeAll, beforeEach */
2 |
3 | import React from 'react';
4 | import 'dayjs/locale/nl';
5 | import 'dayjs/locale/en';
6 | import { renderToString } from 'react-dom/server';
7 | import { setLocale, setTranslations, Localize } from '../src';
8 |
9 | describe('Localize.jsx', () => {
10 | beforeAll(() => {
11 | setTranslations({
12 | en: {
13 | date: 'MMMM Do, YYYY',
14 | },
15 | nl: {
16 | date: 'D MMMM YYYY',
17 | },
18 | });
19 | });
20 |
21 | describe(' component', () => {
22 | beforeEach(() => {
23 | setLocale('en');
24 | });
25 | test('should handle date localization', () => {
26 | const component = ;
27 | expect(renderToString(component)).toMatch('July 4th, 2016');
28 | });
29 |
30 | test('should handle NL date localization', () => {
31 | setLocale('nl');
32 | const component = ;
33 | expect(renderToString(component)).toMatch('4 juli 2016');
34 | });
35 |
36 | test('should handle locale switching', () => {
37 | const component = ;
38 | expect(renderToString(component)).toMatch('July 4th, 2016');
39 | setLocale('nl');
40 | expect(renderToString(component)).toMatch('4 juli 2016');
41 | });
42 |
43 | test('should handle date localization with parseFormat', () => {
44 | const component = ;
45 | expect(renderToString(component)).toMatch('July 4th, 2016');
46 | });
47 |
48 | test('should handle number localization', () => {
49 | const component = (
50 |
59 | );
60 | expect(renderToString(component)).toMatch('$3.33');
61 | });
62 | });
63 | });
64 |
--------------------------------------------------------------------------------
/__test__/Translate.spec.js:
--------------------------------------------------------------------------------
1 | /* global describe, test, expect, beforeAll, beforeEach */
2 |
3 | import React from 'react';
4 | import { renderToString } from 'react-dom/server';
5 | import { setLocale, setTranslations, Translate } from '../src';
6 |
7 | describe('Translate.jsx', () => {
8 | beforeAll(() => {
9 | setTranslations({
10 | en: {
11 | application: {
12 | title: 'Awesome app with i18n!',
13 | hello: 'Hello, %{name}!',
14 | },
15 | export: 'Export %{count} items',
16 | export_0: 'Nothing to export',
17 | export_1: 'Export %{count} item',
18 | },
19 | nl: {
20 | application: {
21 | title: 'Toffe app met i18n!',
22 | },
23 | },
24 | });
25 | });
26 |
27 | describe(' component', () => {
28 | beforeEach(() => {
29 | setLocale('en');
30 | });
31 |
32 | test('should handle translation', () => {
33 | const component = ;
34 | expect(renderToString(component)).toMatch('Awesome app with i18n!');
35 | });
36 |
37 | test('should handle NL translation', () => {
38 | setLocale('nl');
39 | const component = ;
40 | expect(renderToString(component)).toMatch('Toffe app met i18n!');
41 | });
42 |
43 | test('should handle locale switching', () => {
44 | const component = ;
45 | expect(renderToString(component)).toMatch('Awesome app with i18n!');
46 | setLocale('nl');
47 | expect(renderToString(component)).toMatch('Toffe app met i18n!');
48 | });
49 |
50 | test('should handle dynamic placeholder', () => {
51 | const component = ;
52 | expect(renderToString(component)).toMatch('Hello, Aad!');
53 | });
54 |
55 | test('should handle pluralization', () => {
56 | expect(renderToString()).toMatch('Nothing to export');
57 | expect(renderToString()).toMatch('Export 1 item');
58 | expect(renderToString()).toMatch('Export 4 items');
59 | });
60 | });
61 | });
62 |
--------------------------------------------------------------------------------
/__test__/index.spec.js:
--------------------------------------------------------------------------------
1 | /* global describe, test, expect */
2 |
3 | import {
4 | getLocale,
5 | setLocale,
6 | setLocaleGetter,
7 | getTranslations,
8 | setTranslations,
9 | setTranslationsGetter,
10 | setHandleMissingTranslation,
11 | setHandleFailedLocalization,
12 | translate,
13 | localize,
14 | t,
15 | l,
16 | forceComponentsUpdate,
17 | Translate,
18 | Localize,
19 | I18n,
20 | } from '../src';
21 |
22 | describe('index.js', () => {
23 | const exportedFunctions = [
24 | getLocale,
25 | setLocale,
26 | setLocaleGetter,
27 | getTranslations,
28 | setTranslations,
29 | setTranslationsGetter,
30 | setHandleMissingTranslation,
31 | setHandleMissingTranslation,
32 | setHandleFailedLocalization,
33 | translate,
34 | localize,
35 | t,
36 | l,
37 | forceComponentsUpdate,
38 | Translate,
39 | Localize,
40 | I18n,
41 | ];
42 |
43 | exportedFunctions.forEach((exportedFunction) => {
44 | test(`should export ${exportedFunction.name} function`, () => {
45 | expect(exportedFunction).toBeDefined();
46 | });
47 | });
48 | });
49 |
--------------------------------------------------------------------------------
/eslint.config.mjs:
--------------------------------------------------------------------------------
1 | import prettier from 'eslint-plugin-prettier';
2 | import babelParser from '@babel/eslint-parser';
3 | import path from 'node:path';
4 | import { fileURLToPath } from 'node:url';
5 | import js from '@eslint/js';
6 | import { FlatCompat } from '@eslint/eslintrc';
7 | import { fixupPluginRules, fixupConfigRules } from '@eslint/compat';
8 | import importPlugin from 'eslint-plugin-import';
9 |
10 | const __filename = fileURLToPath(import.meta.url);
11 | const __dirname = path.dirname(__filename);
12 | const compat = new FlatCompat({
13 | baseDirectory: __dirname,
14 | recommendedConfig: js.configs.recommended,
15 | allConfig: js.configs.all,
16 | });
17 |
18 | export default [
19 | ...fixupConfigRules(compat.extends('airbnb', 'airbnb/hooks')),
20 | ...compat.extends('prettier', 'plugin:prettier/recommended'),
21 | {
22 | plugins: {
23 | prettier,
24 | import: fixupPluginRules(importPlugin),
25 | },
26 |
27 | languageOptions: {
28 | globals: {
29 | fetch: 'readonly',
30 | JSX: true,
31 | },
32 |
33 | parser: babelParser,
34 | },
35 |
36 | settings: {
37 | 'import/resolver': {
38 | node: {},
39 | exports: {},
40 | },
41 | },
42 |
43 | rules: {
44 | 'react/jsx-filename-extension': 'off',
45 | 'no-underscore-dangle': 'off',
46 | 'react/forbid-prop-types': 'off',
47 | 'react/jsx-fragments': 'off',
48 | 'import/no-named-as-default': 'off',
49 | 'import/no-named-as-default-member': 'off',
50 | },
51 | },
52 | ];
53 |
--------------------------------------------------------------------------------
/example/example.js:
--------------------------------------------------------------------------------
1 | const React = require('react');
2 | const ReactDOMServer = require('react-dom/server');
3 | require('dayjs/locale/nl');
4 | require('dayjs/locale/en');
5 |
6 | let ReactI18nfiy = null;
7 |
8 | try {
9 | ReactI18nfiy = require('react-i18nify');
10 | } catch (e) {
11 | ReactI18nfiy = require('../cjs/index.js');
12 | }
13 |
14 | const { setTranslations, setLocale, setHandleMissingTranslation, setHandleFailedLocalization, translate, localize, Translate, Localize, I18n } = ReactI18nfiy;
15 |
16 | setTranslations({
17 | en: {
18 | application: {
19 | title: 'Awesome app with i18n!',
20 | hello: 'Hello, %{name}!',
21 | },
22 | date: {
23 | long: 'MMMM do, yyyy',
24 | },
25 | export: 'Export %{count} items',
26 | export_0: 'Nothing to export',
27 | export_1: 'Export %{count} item',
28 | },
29 | nl: {
30 | application: {
31 | title: 'Toffe app met i18n!',
32 | hello: 'Hallo, %{name}!',
33 | },
34 | date: {
35 | long: 'd MMMM yyyy',
36 | },
37 | export: 'Exporteer %{count} dingen',
38 | export_0: 'Niks te exporteren',
39 | export_1: 'Exporteer %{count} ding',
40 | },
41 | });
42 |
43 | setLocale('nl');
44 |
45 | console.log(translate('application.title'));
46 | console.log(translate('application.hello', { name: 'Aad' }));
47 | console.log(translate('export', { count: 0 }));
48 | console.log(translate('application', { name: 'Aad' }));
49 |
50 | console.log(localize(1385856000000, { dateFormat: 'date.long' }));
51 | console.log(localize(Math.PI, { maximumFractionDigits: 2 }));
52 | console.log(localize('huh', { dateFormat: 'date.long' }));
53 |
54 | setHandleMissingTranslation((key, replacements, options, err) => `Missing translation: ${key}`);
55 |
56 | console.log(translate('application.unknown_translation'));
57 |
58 | setHandleFailedLocalization((value, options, err) => `Failed localization: ${value}`);
59 |
60 | console.log(localize('huh', { dateFormat: 'date.long' }));
61 |
62 | function AwesomeComponent() {
63 | return (
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 | -
73 |
74 |
75 | -
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
95 |
96 |
97 |
98 |
99 | } />
100 |
101 | );
102 | }
103 |
104 | console.log(ReactDOMServer.renderToString());
105 |
--------------------------------------------------------------------------------
/example/run.js:
--------------------------------------------------------------------------------
1 | require('@babel/register');
2 | require('./example');
3 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-i18nify",
3 | "version": "6.2.0",
4 | "description": "Simple i18n translation and localization components and helpers for React.",
5 | "main": "./cjs/index.js",
6 | "module": "./es/index.js",
7 | "types": "./types/index.d.ts",
8 | "scripts": {
9 | "test": "jest --verbose",
10 | "test:watch": "npm test -- --watch",
11 | "lint": "eslint src/**/*.js __test__/**/*.js",
12 | "lint:fix": "npm run lint -- --fix",
13 | "typecheck": "tsc --project tsconfig.types.json --noEmit",
14 | "build": "rimraf cjs es && npx browserslist --update-db && NODE_ENV=cjs babel src -d cjs && NODE_ENV=es babel src -d es",
15 | "prepare": "npm run build",
16 | "prettier": "prettier --write --loglevel error \"**/*.+(js|jsx|json|yml|yaml|css|ts|tsx|md|mdx|html)\"",
17 | "prettier:check": "prettier --check \"**/*.+(js|jsx|json|yml|yaml|css|ts|tsx|md|mdx|html)\""
18 | },
19 | "files": [
20 | "cjs",
21 | "es",
22 | "src",
23 | "example",
24 | "types/index.d.ts"
25 | ],
26 | "repository": {
27 | "type": "git",
28 | "url": "git+https://github.com/sealninja/react-i18nify.git"
29 | },
30 | "author": "Sealninja",
31 | "license": "MIT",
32 | "bugs": {
33 | "url": "https://github.com/sealninja/react-i18nify/issues"
34 | },
35 | "homepage": "https://sealninja.com",
36 | "keywords": [
37 | "react",
38 | "i18n",
39 | "translation",
40 | "localization",
41 | "components",
42 | "helpers",
43 | "javascript",
44 | "flux",
45 | "redux"
46 | ],
47 | "runkitExampleFilename": "example/example.js",
48 | "browserslist": "> 0.5%, last 2 versions, not op_mini all, not dead",
49 | "peerDependencies": {
50 | "dayjs": "^1.11.6",
51 | "react": "^16.8.0 || ^17.x || ^18.x || ^19.x"
52 | },
53 | "dependencies": {
54 | "prop-types": "15.8.1"
55 | },
56 | "devDependencies": {
57 | "@babel/cli": "7.27.1",
58 | "@babel/core": "7.27.4",
59 | "@babel/eslint-parser": "7.27.5",
60 | "@babel/preset-env": "7.27.1",
61 | "@babel/preset-react": "7.27.1",
62 | "@babel/register": "7.27.1",
63 | "@eslint/compat": "1.2.9",
64 | "@eslint/eslintrc": "3.3.1",
65 | "@eslint/js": "9.28.0",
66 | "@types/react": "19.1.6",
67 | "dayjs": "1.11.13",
68 | "eslint": "9.28.0",
69 | "eslint-config-airbnb": "19.0.4",
70 | "eslint-config-prettier": "10.1.5",
71 | "eslint-plugin-import": "2.31.0",
72 | "eslint-plugin-jsx-a11y": "6.10.2",
73 | "eslint-plugin-prettier": "5.4.1",
74 | "eslint-plugin-react": "7.37.5",
75 | "eslint-plugin-react-hooks": "5.2.0",
76 | "jest": "29.7.0",
77 | "prettier": "3.5.3",
78 | "react": "19.1.0",
79 | "react-dom": "19.1.0",
80 | "rimraf": "6.0.1",
81 | "typescript": "5.8.3"
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/src/components/Base.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export default class Base extends React.Component {
4 | static instances = [];
5 |
6 | static rerenderAll() {
7 | Base.instances.forEach((instance) => instance.forceUpdate());
8 | }
9 |
10 | componentDidMount() {
11 | Base.instances.push(this);
12 | }
13 |
14 | componentWillUnmount() {
15 | Base.instances.splice(Base.instances.indexOf(this), 1);
16 | }
17 | }
18 |
19 | export const forceComponentsUpdate = () => {
20 | Base.rerenderAll();
21 | };
22 |
--------------------------------------------------------------------------------
/src/components/I18n.js:
--------------------------------------------------------------------------------
1 | import PropTypes from 'prop-types';
2 | import BaseComponent from './Base';
3 |
4 | class I18n extends BaseComponent {
5 | render = () => this.props.render();
6 | }
7 |
8 | I18n.propTypes = {
9 | render: PropTypes.func.isRequired,
10 | };
11 |
12 | export default I18n;
13 |
--------------------------------------------------------------------------------
/src/components/Localize.js:
--------------------------------------------------------------------------------
1 | import PropTypes from 'prop-types';
2 | import localize from '../lib/localize';
3 | import BaseComponent from './Base';
4 |
5 | class Localize extends BaseComponent {
6 | render() {
7 | const { value, dateFormat, parseFormat, options = {} } = this.props;
8 | const localization = localize(value, { ...options, dateFormat, parseFormat });
9 |
10 | return localization;
11 | }
12 | }
13 |
14 | Localize.propTypes = {
15 | value: PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.object]).isRequired,
16 | dateFormat: PropTypes.string,
17 | parseFormat: PropTypes.string,
18 | options: PropTypes.object,
19 | };
20 |
21 | export default Localize;
22 |
--------------------------------------------------------------------------------
/src/components/Translate.js:
--------------------------------------------------------------------------------
1 | import PropTypes from 'prop-types';
2 | import translate from '../lib/translate';
3 | import BaseComponent from './Base';
4 |
5 | class Translate extends BaseComponent {
6 | render() {
7 | const { value, locale, ...otherProps } = this.props;
8 | const translation = translate(value, otherProps, { locale });
9 |
10 | return translation;
11 | }
12 | }
13 |
14 | Translate.propTypes = {
15 | value: PropTypes.string.isRequired,
16 | locale: PropTypes.string,
17 | };
18 |
19 | export default Translate;
20 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | export { forceComponentsUpdate } from './components/Base.js';
2 | export { default as Translate } from './components/Translate.js';
3 | export { default as Localize } from './components/Localize.js';
4 | export { default as I18n } from './components/I18n.js';
5 | export { getLocale, setLocale, setLocaleGetter, getTranslations, setTranslations, setTranslationsGetter, setHandleMissingTranslation, setHandleFailedLocalization } from './lib/settings';
6 | export { default as translate } from './lib/translate';
7 | export { default as t } from './lib/translate';
8 | export { default as localize } from './lib/localize';
9 | export { default as l } from './lib/localize';
10 | export { replace as translateReplace } from './lib/utils';
11 |
--------------------------------------------------------------------------------
/src/lib/localize.js:
--------------------------------------------------------------------------------
1 | import dayjs from 'dayjs';
2 | import customParseFormat from 'dayjs/plugin/customParseFormat';
3 | import advancedFormat from 'dayjs/plugin/advancedFormat';
4 | import localizedFormat from 'dayjs/plugin/localizedFormat';
5 | import relativeTime from 'dayjs/plugin/relativeTime';
6 |
7 | import { getLocale, handleFailedLocalization } from './settings';
8 | import translate from './translate';
9 |
10 | dayjs.extend(customParseFormat);
11 | dayjs.extend(advancedFormat);
12 | dayjs.extend(localizedFormat);
13 | dayjs.extend(relativeTime);
14 |
15 | export default (value, options = {}) => {
16 | const locale = options.locale || getLocale();
17 |
18 | if (options.dateFormat) {
19 | try {
20 | let dayJsLocale = locale.toLowerCase();
21 | if (dayJsLocale === 'no') dayJsLocale = 'nb'; // Bokmål as default Norwegian
22 |
23 | const parsedDate = (options.parseFormat ? dayjs(value, translate(options.parseFormat, {}, { locale, returnKeyOnError: true }), dayJsLocale) : dayjs(value)).locale(dayJsLocale);
24 | if (!dayJsLocale.startsWith(parsedDate.locale())) throw new Error('Invalid locale');
25 |
26 | if (!parsedDate.isValid()) throw new Error('Invalid date');
27 |
28 | if (options.dateFormat === 'distance-to-now') {
29 | return parsedDate.fromNow();
30 | }
31 | return parsedDate.format(translate(options.dateFormat, {}, { locale, returnKeyOnError: true }));
32 | } catch (err) {
33 | return handleFailedLocalization(value, options, err);
34 | }
35 | }
36 | if (typeof value === 'number') {
37 | try {
38 | let intlLocale = locale;
39 | if (intlLocale.toLowerCase() === 'ar') intlLocale = 'ar-EG'; // work-around for Chrome
40 | return new Intl.NumberFormat(intlLocale, options).format(value);
41 | } catch (err) {
42 | return handleFailedLocalization(value, options, err);
43 | }
44 | }
45 | return value;
46 | };
47 |
--------------------------------------------------------------------------------
/src/lib/settings.js:
--------------------------------------------------------------------------------
1 | /* eslint no-console: "off" */
2 |
3 | import BaseComponent from '../components/Base';
4 |
5 | const settings = {
6 | localeKey: 'en',
7 | translationsObject: {},
8 | getTranslations: null,
9 | getLocale: null,
10 | handleMissingTranslation: (text) => text.split('.').pop(),
11 | handleFailedLocalization: () => null,
12 | };
13 |
14 | export const getLocale = () => (settings.getLocale ? settings.getLocale() : settings.localeKey);
15 |
16 | export const setLocale = (locale, rerenderComponents = true) => {
17 | settings.localeKey = locale;
18 | settings.getLocale = null;
19 | if (rerenderComponents) {
20 | BaseComponent.rerenderAll();
21 | }
22 | };
23 |
24 | export const handleMissingTranslation = (...args) => settings.handleMissingTranslation(...args);
25 | export const handleFailedLocalization = (...args) => settings.handleFailedLocalization(...args);
26 |
27 | export const getTranslations = () => (settings.getTranslations ? settings.getTranslations() : settings.translationsObject);
28 |
29 | export const setTranslations = (translations, rerenderComponents = true) => {
30 | settings.translationsObject = translations;
31 | settings.getTranslations = null;
32 | if (rerenderComponents) {
33 | BaseComponent.rerenderAll();
34 | }
35 | };
36 |
37 | export const setLocaleGetter = (fn) => {
38 | if (typeof fn !== 'function') {
39 | throw new Error('Locale getter must be a function');
40 | }
41 | settings.getLocale = fn;
42 | };
43 |
44 | export const setTranslationsGetter = (fn) => {
45 | if (typeof fn !== 'function') {
46 | throw new Error('Translations getter must be a function');
47 | }
48 | settings.getTranslations = fn;
49 | };
50 |
51 | export const setHandleMissingTranslation = (fn) => {
52 | if (typeof fn !== 'function') {
53 | throw new Error('Handle missing translation must be a function');
54 | }
55 | settings.handleMissingTranslation = fn;
56 | };
57 |
58 | export const setHandleFailedLocalization = (fn) => {
59 | if (typeof fn !== 'function') {
60 | throw new Error('Handle failed localization must be a function');
61 | }
62 | settings.handleFailedLocalization = fn;
63 | };
64 |
--------------------------------------------------------------------------------
/src/lib/translate.js:
--------------------------------------------------------------------------------
1 | import { fetchTranslation, replace } from './utils';
2 | import { getLocale, getTranslations, handleMissingTranslation } from './settings';
3 |
4 | export default (key, replacements = {}, options = {}) => {
5 | const locale = options.locale || getLocale();
6 | let translation = '';
7 | try {
8 | const translationLocale = getTranslations()[locale] ? locale : locale.split('-')[0];
9 | translation = fetchTranslation(getTranslations(), `${translationLocale}.${key}`, replacements.count);
10 | } catch (err) {
11 | if (options.returnNullOnError) return null;
12 | if (options.returnKeyOnError) return key;
13 | return handleMissingTranslation(key, replacements, options, err);
14 | }
15 | return replace(translation, replacements);
16 | };
17 |
--------------------------------------------------------------------------------
/src/lib/utils.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export const replace = (translation, replacements) => {
4 | if (typeof translation === 'string') {
5 | let result = translation;
6 | Object.keys(replacements).forEach((replacement) => {
7 | result = result.split(`%{${replacement}}`).join(replacements[replacement] ?? '');
8 | });
9 | return result;
10 | }
11 | if (React.isValidElement(translation)) {
12 | return translation;
13 | }
14 | if (typeof translation === 'object') {
15 | const result = {};
16 | Object.keys(translation).forEach((translationKey) => {
17 | result[translationKey] = replace(translation[translationKey], replacements);
18 | });
19 | return result;
20 | }
21 | return null;
22 | };
23 |
24 | export const fetchTranslation = (translations, key, count = null) => {
25 | const _index = key.indexOf('.');
26 | if (typeof translations === 'undefined') {
27 | throw new Error('not found');
28 | }
29 | if (_index > -1) {
30 | return fetchTranslation(translations[key.substring(0, _index)], key.substr(_index + 1), count);
31 | }
32 | if (count !== null) {
33 | if (translations[`${key}_${count}`]) {
34 | // when key = 'items_3' if count is 3
35 | return translations[`${key}_${count}`];
36 | }
37 | if (count !== 1 && translations[`${key}_plural`]) {
38 | // when count is not simply singular, return _plural
39 | return translations[`${key}_plural`];
40 | }
41 | }
42 | if (translations[key] != null) {
43 | return translations[key];
44 | }
45 | throw new Error('not found');
46 | };
47 |
--------------------------------------------------------------------------------
/tsconfig.types.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "strict": true,
4 | "noEmit": true,
5 | "skipLibCheck": false,
6 | "forceConsistentCasingInFileNames": true,
7 | "types": ["react"],
8 | "moduleResolution": "bundler",
9 | "lib": ["dom", "esnext"],
10 | "target": "ESNext"
11 | },
12 | "include": ["types/**/*.d.ts"]
13 | }
14 |
--------------------------------------------------------------------------------
/types/index.d.ts:
--------------------------------------------------------------------------------
1 | // TypeScript Version: 3.7
2 | import React = require('react');
3 |
4 | export class I18n extends React.Component {}
5 |
6 | // Localization
7 | export type LocaleGetter = () => string;
8 | export function getLocale(): string | undefined;
9 | export function setLocale(locale: string, rerenderComponents?: boolean): void;
10 | export function setLocaleGetter(fn: LocaleGetter): void;
11 |
12 | export interface LocalizeDateOptions {
13 | locale?: string;
14 | parseFormat?: string;
15 | dateFormat?: string;
16 | }
17 | export type LocalizeNumberOptions = Intl.NumberFormatOptions;
18 | export function localize(value: string | number, options?: LocalizeDateOptions): string;
19 | export function localize(value: number, options?: LocalizeNumberOptions): string;
20 | export function l(value: string | number, options?: LocalizeDateOptions): string;
21 | export function l(value: number, options?: LocalizeNumberOptions): string;
22 |
23 | export type LocalizeDateProps = {
24 | value: string | number;
25 | } & LocalizeDateOptions;
26 | export type LocalizeNumberProps = {
27 | value: number;
28 | } & LocalizeNumberOptions;
29 | export class Localize extends React.Component {}
30 |
31 | // Translations
32 | export type Translations = Record;
33 | // The `count` key has some special behavior, so we need to support number. See src/lib/utils.js#L36
34 | // The value gets piped into `Array.join`, so the number will get coerced to a string. Should be ok.
35 | export type Replacements = Record;
36 |
37 | export type TranslationsGetter = () => Translations;
38 | export function getTranslations(): Translations | undefined;
39 | export function setTranslations(transations: Translations, rerenderComponents?: boolean): void;
40 | export function setTranslationsGetter(fn: TranslationsGetter): void;
41 |
42 | export type ReplacementsGetter = (key: string, replacements: Replacements) => string;
43 | export function setHandleMissingTranslation(fn: ReplacementsGetter): void;
44 |
45 | export interface TranslateOptions {
46 | locale?: string;
47 | returnNullOnError?: boolean;
48 | returnKeyOnError?: boolean;
49 | }
50 |
51 | export function translate(key: string, replacements?: Replacements, options?: TranslateOptions): string;
52 | export function t(key: string, replacements?: Replacements, options?: TranslateOptions): string;
53 |
54 | export type TranslateProps = {
55 | value: string;
56 | } & Replacements;
57 | export class Translate extends React.Component {}
58 |
59 | // Utility
60 | export function forceComponentsUpdate(): void;
61 |
--------------------------------------------------------------------------------
/types/react-i18nify-tests.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {
3 | I18n,
4 | getLocale,
5 | setLocale,
6 | setLocaleGetter,
7 | localize,
8 | l,
9 | Localize,
10 | getTranslations,
11 | setTranslations,
12 | setTranslationsGetter,
13 | setHandleMissingTranslation,
14 | Replacements,
15 | translate,
16 | t,
17 | Translate,
18 | } from 'react-i18nify';
19 |
20 | ;
21 |
22 | getLocale(); // $ExpectType string | undefined
23 |
24 | setLocale('en'); // $ExpectType void
25 | setLocale('en', true); // $ExpectType void
26 | setLocale(123); // $ExpectError
27 | setLocale('en', 123); // $ExpectError
28 |
29 | setLocaleGetter(() => 'en'); // $ExpectType void
30 | setLocaleGetter(() => 123); // $ExpectError
31 |
32 | localize('11-11-2019'); // $ExpectType string
33 | localize('11-11-2019', { dateFormat: 'mm/dd/YYYY', parseFormat: 'mm-dd-YYYY' }); // $ExpectType string
34 | localize(1234567890, { dateFormat: 'mm/dd/YYYY', parseFormat: 'mm-dd-YYYY' }); // $ExpectType string
35 | localize('11-11-2019', { maximumFractionDigits: 2 }); // $ExpectError
36 |
37 | localize(1234); // $ExpectType string
38 | localize(1234, { maximumFractionDigits: 2 }); // $ExpectType string
39 |
40 | localize(true); // $ExpectError
41 |
42 | l('11-11-2019'); // $ExpectType string
43 | l('11-11-2019', { dateFormat: 'mm/dd/YYYY', parseFormat: 'mm-dd-YYYY' }); // $ExpectType string
44 | l(1234567890, { dateFormat: 'mm/dd/YYYY', parseFormat: 'mm-dd-YYYY' }); // $ExpectType string
45 | l('11-11-2019', { maximumFractionDigits: 2 }); // $ExpectError
46 |
47 | l(1234); // $ExpectType string
48 | l(1234, { maximumFractionDigits: 2 }); // $ExpectType string
49 |
50 | l(true); // $ExpectError
51 |
52 | ; // $ExpectError
53 | ;
54 | ;
55 | ;
56 | ;
57 |
58 | // getTranslations(); // $ExpectType Translations | undefined
59 |
60 | setTranslations({ foo: 'bar', baz: { foo: 'bar' } }); // $ExpectType void
61 | setTranslations({ foo: 'bar' }, true); // $ExpectType void
62 | setTranslations('asdf'); // $ExpectError
63 | setTranslations({ foo: 'bar' }, 'asdf'); // $ExpectError
64 |
65 | setTranslationsGetter(() => ({ foo: 'bar' })); // $ExpectType void
66 | setTranslationsGetter(() => 'asdf'); // $ExpectError
67 |
68 | setHandleMissingTranslation(() => 'asdf'); // $ExpectType void
69 | setHandleMissingTranslation((key: string) => 'asdf'); // $ExpectType void
70 | // $ExpectType void
71 | setHandleMissingTranslation((key: string, replacements: Replacements) => 'asdf');
72 |
73 | translate('foo.bar'); // $ExpectType string
74 | translate('foo.bar', { asdf: 'baz' }); // $ExpectType string
75 | translate('foo.bar', { count: 1234 }); // $ExpectType string
76 | translate('foo.bar', { asdf: true }); // $ExpectError
77 | t('foo.bar'); // $ExpectType string
78 | t('foo.bar', { asdf: 'baz' }); // $ExpectType string
79 | t('foo.bar', { count: 1234 }); // $ExpectType string
80 | t('foo.bar', { asdf: true }); // $ExpectError
81 |
82 | ; // $ExpectError
83 | ;
84 | ;
85 | ;
86 | ; // $ExpectError
87 |
--------------------------------------------------------------------------------
/types/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "module": "commonjs",
4 | "lib": ["es6"],
5 | "esModuleInterop": true,
6 | "noImplicitAny": true,
7 | "noImplicitThis": true,
8 | "strictNullChecks": true,
9 | "strictFunctionTypes": true,
10 | "noEmit": true,
11 | "baseUrl": ".",
12 | "jsx": "preserve",
13 | "paths": { "react-i18nify": ["."] }
14 | }
15 | }
16 |
--------------------------------------------------------------------------------