@wix/fed-cli-i18next 1.6.38
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +300 -0
- package/dist/create-async-messages-loader.d.ts +15 -0
- package/dist/create-async-messages-loader.d.ts.map +1 -0
- package/dist/create-async-messages-loader.js +2 -0
- package/dist/create-async-messages-loader.js.map +1 -0
- package/dist/i18n.d.ts +49 -0
- package/dist/i18n.d.ts.map +1 -0
- package/dist/i18n.js +2 -0
- package/dist/i18n.js.map +1 -0
- package/dist/icu.d.ts +65 -0
- package/dist/icu.d.ts.map +1 -0
- package/dist/icu.js +2 -0
- package/dist/icu.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +1 -0
- package/dist/relative-time-formatting.d.ts +9 -0
- package/dist/relative-time-formatting.d.ts.map +1 -0
- package/dist/relative-time-formatting.js +2 -0
- package/dist/relative-time-formatting.js.map +1 -0
- package/package.json +66 -0
package/README.md
ADDED
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
# fed-cli-i18next
|
|
2
|
+
|
|
3
|
+
This config encapsulates i18next initialization so it works out of the box with react apps.
|
|
4
|
+
It also solves versioning issues between i18next and react-i18next by exposing compatible versions.
|
|
5
|
+
|
|
6
|
+
Wrapper supports ICU messages format.
|
|
7
|
+
|
|
8
|
+
## Install
|
|
9
|
+
|
|
10
|
+
`yarn add @wix/fed-cli-i18next`
|
|
11
|
+
|
|
12
|
+
## Usage
|
|
13
|
+
|
|
14
|
+
### Loading translations async
|
|
15
|
+
|
|
16
|
+
```js
|
|
17
|
+
import { initI18n, withTranslation, I18nextProvider, useTranslation } from '@wix/fed-cli-i18next';
|
|
18
|
+
|
|
19
|
+
const App = withTranslation()(({ t }) => <div>{t('foo', { baz: 'hello world' })}</div>);
|
|
20
|
+
|
|
21
|
+
// or using hooks
|
|
22
|
+
const App = () => {
|
|
23
|
+
const [t] = useTranslation();
|
|
24
|
+
return <div>{t('foo', { baz: 'hello world' })}</div>;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const i18n = initI18n({
|
|
28
|
+
disableAutoInit: true,
|
|
29
|
+
locale: 'en',
|
|
30
|
+
asyncMessagesLoader: (locale) => import(`./path/to/locale/messages_${locale}.json`),
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
i18n.init(); // can also be awaited
|
|
34
|
+
|
|
35
|
+
ReactDOM.render(
|
|
36
|
+
<I18nextProvider i18n={i18n}>
|
|
37
|
+
<App />
|
|
38
|
+
</I18nextProvider>,
|
|
39
|
+
container,
|
|
40
|
+
);
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### Passing translations directly
|
|
44
|
+
|
|
45
|
+
Should be used when:
|
|
46
|
+
|
|
47
|
+
1. Your server already renders the message files with your initial HTML.;
|
|
48
|
+
2. You want to bundle all of the translation files.;
|
|
49
|
+
|
|
50
|
+
Example project with this setup can be found at [full stack project](https://github.com/wix/yoshi/tree/master/packages/create-yoshi-app/templates/fullstack/javascript).
|
|
51
|
+
|
|
52
|
+
```js
|
|
53
|
+
import { initI18n, withTranslation, I18nextProvider } from '@wix/fed-cli-i18next';
|
|
54
|
+
|
|
55
|
+
const App = withTranslation()(({ t }) => <div>{t('foo', { baz: 'hello world' })}</div>);
|
|
56
|
+
|
|
57
|
+
const i18n = initI18n({
|
|
58
|
+
disableAutoInit: true,
|
|
59
|
+
locale: 'en',
|
|
60
|
+
messages: { foo: 'Bar {baz}' },
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
i18n.init(); // can also be awaited
|
|
64
|
+
|
|
65
|
+
ReactDOM.render(
|
|
66
|
+
<I18nextProvider i18n={i18n}>
|
|
67
|
+
<App />
|
|
68
|
+
</I18nextProvider>,
|
|
69
|
+
container,
|
|
70
|
+
);
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## API
|
|
74
|
+
|
|
75
|
+
### `initI18n({ locale, messages?, asyncMessagesLoader? })`
|
|
76
|
+
|
|
77
|
+
Configures i18next instance with ICU message support and return the instance itself.
|
|
78
|
+
Messages can be passed directly in `messages` param or loaded async with `asyncMessagesLoader`.
|
|
79
|
+
|
|
80
|
+
Parameters:
|
|
81
|
+
|
|
82
|
+
| Name | Type | Description |
|
|
83
|
+
| --------------------: | :-------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
84
|
+
| `locale` | String **(required)** | locale of your messages, i.e. 'en'; |
|
|
85
|
+
| `messages` | Object | translations for the locale in format of '{key: 'message'}', usually it's the content of your 'messages_xx.json' |
|
|
86
|
+
| `asyncMessagesLoader` | Function | function that receives locale and return promise that resolves to messages. |
|
|
87
|
+
| `disableAutoInit` | Boolean | disables automatic fetching of i18n assets. |
|
|
88
|
+
| `useSuspense` | Boolean | when waiting for translation files, `i18next-react` triggers [Suspense](https://reactjs.org/docs/concurrent-mode-suspense.html). in case you want to use Suspense, set this to `true` and wrap your translated components [like in this example](https://react.i18next.com/latest/using-with-hooks#using-the-withtranslation-hoc). default: `false`. This also triggers suspense if polyfill is loading. |
|
|
89
|
+
| `wait` | Boolean | Prevent rendering until react translation files and polyfills will be loaded. Useful if useSuspense is disabled. Make sure to pass it only for client-side rendering. |
|
|
90
|
+
| `parseErrorHandler` | Function | Will be run when parser throws an error. Can return any string, which can be used as a fallback, in case of broken translation. If omitted, the default swallows the error and returns the unsubstituted string |
|
|
91
|
+
| `icuOptions` | Object | pass options to i18next [ICU plugin](https://github.com/i18next/i18next-icu). Allows to set custom format styles for the supported formatters [link](https://github.com/formatjs/intl-messageformat#user-defined-formats) |
|
|
92
|
+
|
|
93
|
+
**[withTranslation](https://react.i18next.com/latest/withtranslation-hoc)**, **[I18nextProvider](https://react.i18next.com/latest/i18nextprovider)**, **[useTranslation](https://react.i18next.com/latest/usetranslation-hook)** and **[Trans](https://react.i18next.com/latest/trans-component)** are exported from [react-i18next](https://react.i18next.com). If your React version does not support hooks (16.8 and above), please use version ^3.
|
|
94
|
+
|
|
95
|
+
### `initI18nWithoutPolyfills({ locale, messages?, asyncMessagesLoader? })`
|
|
96
|
+
|
|
97
|
+
Same as `initI18n`, but without implicitly-loaded polyfills (for environments where polyfills are already preconfigured).
|
|
98
|
+
|
|
99
|
+
It accepts the same options except the one directly related to polyfills: `polyfillRelativeTime`.
|
|
100
|
+
|
|
101
|
+
### `initI18nWithoutICU({ locale, messages?, asyncMessagesLoader? })`
|
|
102
|
+
|
|
103
|
+
Same as `initI18n`, but without ICU logic.
|
|
104
|
+
Since it could be tricky to migrate some non-ICU translations to ICU, the library provides a way to disable ICU logic.
|
|
105
|
+
|
|
106
|
+
To exclude ICU-related libraries and polyfills from the bundle relying on tree-shaking, it's being exported as a separate namespace.
|
|
107
|
+
|
|
108
|
+
It accepts the same options except the ones directly related to ICU: `icuOptions`, `parseErrorHandler`, and `polyfillRelativeTime`.
|
|
109
|
+
|
|
110
|
+
### `createAsyncMessagesLoader({ loadMessages, isDev?, loadDevMessages? })`
|
|
111
|
+
|
|
112
|
+
Creates an `asyncMessagesLoader` from functions that return imported JSON modules:
|
|
113
|
+
|
|
114
|
+
```ts
|
|
115
|
+
const asyncMessagesLoader = createAsyncMessagesLoader({
|
|
116
|
+
loadMessages: (language) => import(`./locales/messages_${language}.json`),
|
|
117
|
+
});
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
Treat returned message dictionaries as read-only. When only one dictionary is available,
|
|
121
|
+
the loader returns it directly. With `isDev: true`, development messages override production
|
|
122
|
+
messages; a new dictionary is allocated only when both exist.
|
|
123
|
+
|
|
124
|
+
Concurrent requests for the same language share one in-flight load within each loader instance.
|
|
125
|
+
Successful and failed requests are removed from the in-flight cache when they settle, so later
|
|
126
|
+
calls can reload resources or retry failures. Share the loader between i18next instances when
|
|
127
|
+
they use the same translation resources and should deduplicate concurrent requests.
|
|
128
|
+
|
|
129
|
+
### ICU cache options
|
|
130
|
+
|
|
131
|
+
`initI18n` accepts these options through `icuOptions`:
|
|
132
|
+
|
|
133
|
+
| Option | Default | Behavior |
|
|
134
|
+
| ----------------------- | ----------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
135
|
+
| `maxCacheSize` | `1000` | Maximum compiled messages across all languages and namespaces in one instance. Least recently used messages are evicted. `0` disables compiled-message caching. |
|
|
136
|
+
| `maxFormatterCacheSize` | `100` | Maximum shared number, date/time, and plural formatters combined in one instance. Least recently used formatters are evicted. `0` disables shared-formatter caching. |
|
|
137
|
+
| `memoize` | `true` | Set to `false` to disable both caches. |
|
|
138
|
+
| `bindI18nStore` | `'added removed'` | Space-separated resource-store events that drop compiled messages for the language and namespace they report. Set to `''` to disable these event bindings. |
|
|
139
|
+
| `bindI18n` | `''` | Space-separated i18next events that drop all compiled messages. Set to `'languageChanged'` to drop them on language switches. |
|
|
140
|
+
|
|
141
|
+
Cache limits must be non-negative safe integers, or `Infinity` to cache without eviction. Adjust
|
|
142
|
+
the limits for your application's working set; eviction trades recompilation work for lower
|
|
143
|
+
memory use.
|
|
144
|
+
|
|
145
|
+
Resource additions, updates, and removals drop compiled messages for just the language and
|
|
146
|
+
namespace they report, so loading one namespace leaves the others compiled. Changes made through
|
|
147
|
+
`ICU.addUserDefinedFormats()` drop every compiled message, since compiled messages capture the
|
|
148
|
+
format definitions. Shared formatters survive all of these, because nothing a formatter depends on
|
|
149
|
+
changes when a translation does. A changed message is recompiled even when its resource update was
|
|
150
|
+
silent. Language switches keep cached messages unless configured otherwise. `ICU.clearMessages()`
|
|
151
|
+
drops compiled messages; `ICU.clearCache()` drops compiled messages and shared formatters.
|
|
152
|
+
Compiled-message storage is private. Replace direct assignments to `ICU.mem` with
|
|
153
|
+
`ICU.clearMessages()`.
|
|
154
|
+
|
|
155
|
+
```ts
|
|
156
|
+
const i18n = initI18n({
|
|
157
|
+
locale: 'en',
|
|
158
|
+
asyncMessagesLoader,
|
|
159
|
+
icuOptions: {
|
|
160
|
+
maxCacheSize: 1000,
|
|
161
|
+
maxFormatterCacheSize: 100,
|
|
162
|
+
},
|
|
163
|
+
});
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
## Changing language
|
|
167
|
+
|
|
168
|
+
`withTranslation` HOC injects the `i18n` object, which can be used to changes language and other configuration once the application is loaded.
|
|
169
|
+
|
|
170
|
+
##### Asynchronous strategy
|
|
171
|
+
|
|
172
|
+
Simply use the `i18n.changeLanguage()` method.
|
|
173
|
+
|
|
174
|
+
##### Synchronous strategy
|
|
175
|
+
|
|
176
|
+
Before calling the `i18n.changeLanguage()` method, resources should be added via the `i18n.addResources()` method. For example, before calling `i18n.changeLanguage('fr')`, we must call `i18n.addResources('fr', 'namespace', { key: 'value' })`.
|
|
177
|
+
|
|
178
|
+
WARNING:
|
|
179
|
+
if you do the language change, make sure `plural rules` (and `relative time format` if you need it) is loaded BEFORE you actually change the language:
|
|
180
|
+
|
|
181
|
+
```js
|
|
182
|
+
import { loadPluralRules, loadRelativeTimeFormat } from '@wix/fed-cli-i18next/polyfills';
|
|
183
|
+
|
|
184
|
+
// only plural rules
|
|
185
|
+
loadPluralRules(newLocale).then(() => {
|
|
186
|
+
i18n.changeLanguage();
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
// plural rules with relative date time format
|
|
190
|
+
Promise.all([loadPluralRules(newLocale), loadRelativeTimeFormat(newLocale)]).then(() => {
|
|
191
|
+
i18n.changeLanguage();
|
|
192
|
+
});
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
## Relative time formatting
|
|
196
|
+
|
|
197
|
+
If you need to format time relative to current time, use `createRtfFormatter` formatter.
|
|
198
|
+
|
|
199
|
+
```js
|
|
200
|
+
import { createRtfFormatter } from '@wix/fed-cli-i18next';
|
|
201
|
+
|
|
202
|
+
const rtf = createRtfFormatter('en');
|
|
203
|
+
rtf(Date.now() - SECOND * 20); // '20 seconds ago'
|
|
204
|
+
rtf(Date.now() - SECOND * 59); // '59 seconds ago'
|
|
205
|
+
rtf(Date.now() - MINUTE * 6); // '6 minutes ago'
|
|
206
|
+
rtf(Date.now() - HOUR * 3); // '3 hours ago'
|
|
207
|
+
|
|
208
|
+
// floor the seconds
|
|
209
|
+
const rtf = createRtfFormatter('en', { floorSeconds: true });
|
|
210
|
+
rtf(Date.now() - SECOND * 20); // '1 second ago'
|
|
211
|
+
rtf(Date.now() - SECOND * 59); // '1 second ago'
|
|
212
|
+
rtf(Date.now() - SECOND * 61); // '1 minute ago'
|
|
213
|
+
|
|
214
|
+
// use extra short expression
|
|
215
|
+
const rtf = createRtfFormatter(
|
|
216
|
+
'en',
|
|
217
|
+
{
|
|
218
|
+
extraShortExpression: true,
|
|
219
|
+
},
|
|
220
|
+
{
|
|
221
|
+
style: 'narrow',
|
|
222
|
+
},
|
|
223
|
+
);
|
|
224
|
+
rtf(Date.now() - SECOND * 20); // '20s'
|
|
225
|
+
rtf(Date.now() - SECOND * 59); // '59s'
|
|
226
|
+
rtf(Date.now() - MINUTE * 6); // '6m'
|
|
227
|
+
rtf(Date.now() - HOUR * 3); // '3h'
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
## Migrating from `^5.0.0` to `^6.0.0`
|
|
231
|
+
|
|
232
|
+
Dropped all polyfills, since [min browsers that wix supports](https://support.wix.com/en/article/supported-browsers) have all the required features.
|
|
233
|
+
|
|
234
|
+
As a reasult following options are dropped:
|
|
235
|
+
|
|
236
|
+
- `polyfillRelativeTime`
|
|
237
|
+
|
|
238
|
+
## Migrating from `^4.0.0` to `^5.0.0`
|
|
239
|
+
|
|
240
|
+
You no longer need to load polyfills i browser by yourself, it's done automatically.
|
|
241
|
+
|
|
242
|
+
The following export was removed
|
|
243
|
+
|
|
244
|
+
```
|
|
245
|
+
- import { loadBrowserPolyfills } from '@wix/fed-cli-i18next/polyfills';
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
Path to node polyfills was changed
|
|
249
|
+
|
|
250
|
+
```
|
|
251
|
+
- import { loadNodePolyfills } from '@wix/fed-cli-i18next/polyfills';
|
|
252
|
+
+ import { loadNodePolyfills } from '@wix/fed-cli-i18next/polyfills/node';
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
## Migrating from `^3.0.0` to `^4.0.0`
|
|
256
|
+
|
|
257
|
+
We are using [React with hooks](https://reactjs.org/docs/hooks-intro.html), and the latest versions of `i18next, react-i18next` as of version 4. If your React version is below 16.8, please use version 3.
|
|
258
|
+
|
|
259
|
+
## Migrating from `^2.0.0` to `^3.0.0`
|
|
260
|
+
|
|
261
|
+
The polyfill loaders are no longer exported through the main `index.js` file since it caused Webpack to create many irrelevant chunks even when the polyfills were not needed.
|
|
262
|
+
|
|
263
|
+
All the polyfill loaders are now available at `@wix/fed-cli-i18next/polyfills`.
|
|
264
|
+
|
|
265
|
+
## Q & A:
|
|
266
|
+
|
|
267
|
+
Q: I want to migrate an existing project to use `fed-cli-i18next`, is there something that I need to change with the translations?
|
|
268
|
+
|
|
269
|
+
A: You have to make sure that all of the formatting and interpolations are using the single curly brackets format, which is defined by ICU. For example, if you have a translation key with this value: `"Click this link: {{link}}"` you should manually transform it in all `messages_xx.json` files to look like this `"Click this link: {link}"`. Also note, that if the parameter that you're interpolating is a `number` or a `date` you should leverage the ICU formatting (e.g. `"Your apple count is: {appleCount, number}"`. You can find more formatting examples [here](https://apps.wix.com/translations-icu-examples-project).
|
|
270
|
+
|
|
271
|
+
---
|
|
272
|
+
|
|
273
|
+
Q: I want to remove the configuration boilerplate from the project's side and move to `fed-cli-i18next`, but can't completely migrate to ICU format. Any options to disable ICU until I finish the migration?
|
|
274
|
+
|
|
275
|
+
A: Yes. You can use `initI18nWithoutICU` for the migration period.
|
|
276
|
+
|
|
277
|
+
---
|
|
278
|
+
|
|
279
|
+
Q: I'm migrating from the old version of those libs. Where is my `InjectedTranslatedProps` interface for React components?
|
|
280
|
+
|
|
281
|
+
A: Use `WithTranslation` instead, since `translate` HOC was renamed to `withTranslation`.
|
|
282
|
+
|
|
283
|
+
---
|
|
284
|
+
|
|
285
|
+
Q: My interpolation with numbers doesn’t work. What should I do?
|
|
286
|
+
|
|
287
|
+
A: Since new version of the lib uses ICU message format, make sure you are using _single curly brackets_ for interpolation.
|
|
288
|
+
|
|
289
|
+
So instead of this:
|
|
290
|
+
|
|
291
|
+
`"some.key": "Knock {{count}} times"`
|
|
292
|
+
|
|
293
|
+
you could do this:
|
|
294
|
+
|
|
295
|
+
`"some.key": "Knock {count} times"`
|
|
296
|
+
|
|
297
|
+
## Further Reading
|
|
298
|
+
|
|
299
|
+
- [ICU Message Syntax](https://formatjs.io/docs/core-concepts/icu-syntax)
|
|
300
|
+
- [Translation @ FED Handbook](https://github.com/wix-private/fed-handbook/blob/master/TRANSLATION.md)
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { Messages } from "./i18n.js";
|
|
2
|
+
//#region src/create-async-messages-loader.d.ts
|
|
3
|
+
type CreateAsyncMessagesLoaderOptions = {
|
|
4
|
+
isDev?: boolean;
|
|
5
|
+
loadMessages: (languageCode: string) => Promise<{
|
|
6
|
+
default: Messages;
|
|
7
|
+
}>;
|
|
8
|
+
loadDevMessages?: (languageCode: string) => Promise<{
|
|
9
|
+
default: Messages;
|
|
10
|
+
}>;
|
|
11
|
+
};
|
|
12
|
+
declare const createAsyncMessagesLoader: ({ isDev, loadMessages, loadDevMessages }: CreateAsyncMessagesLoaderOptions) => (languageCode: string) => Promise<Messages>;
|
|
13
|
+
//#endregion
|
|
14
|
+
export { createAsyncMessagesLoader };
|
|
15
|
+
//# sourceMappingURL=create-async-messages-loader.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"create-async-messages-loader.d.ts","names":[],"sources":["../src/create-async-messages-loader.ts"],"mappings":";;KAiCK;EACH;EACA,eAAe,yBAAyB;IAAU,SAAS;;EAC3D,mBAAmB,yBAAyB;IAAU,SAAS;;;cAGpD,8BAA6B,OAAA,cAAA,mBAIvC,sCA2BO,yBAAuB,QAAQ"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
const e=()=>{let e=new Map;return{getOrInsert:(t,n)=>(e.has(t)||e.set(t,n().finally(()=>{e.delete(t)})),e.get(t))}},t=async e=>{try{let{default:t}=await e();return t}catch{return}},n=({isDev:n,loadMessages:r,loadDevMessages:i})=>{let a=e(),o=async e=>{let[a,o]=await Promise.all([t(()=>r(e)),!n||!i?void 0:t(()=>i(e))]);if(a&&o)return{...a,...o};if(a)return a;if(o)return o;throw Error(`No translation files found for ${e}`)};return e=>a.getOrInsert(e,()=>o(e))};export{n as createAsyncMessagesLoader};
|
|
2
|
+
//# sourceMappingURL=create-async-messages-loader.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"create-async-messages-loader.js","names":[],"sources":["../src/create-async-messages-loader.ts"],"sourcesContent":["import type { Messages } from './i18n';\n\nconst memoWhilePending = <Value>() => {\n const map = new Map<string, Promise<Value>>();\n\n const getOrInsert = (key: string, factory: () => Promise<Value>): Promise<Value> => {\n if (!map.has(key)) {\n map.set(\n key,\n factory().finally(() => {\n map.delete(key);\n }),\n );\n }\n\n return map.get(key)!;\n };\n\n return {\n getOrInsert,\n };\n};\n\nconst load = async (loader: () => Promise<{ default: Messages }>) => {\n try {\n const { default: messages } = await loader();\n\n return messages;\n } catch {\n return undefined;\n }\n};\n\ntype CreateAsyncMessagesLoaderOptions = {\n isDev?: boolean;\n loadMessages: (languageCode: string) => Promise<{ default: Messages }>;\n loadDevMessages?: (languageCode: string) => Promise<{ default: Messages }>;\n};\n\nexport const createAsyncMessagesLoader = ({\n isDev,\n loadMessages,\n loadDevMessages,\n}: CreateAsyncMessagesLoaderOptions) => {\n const pending = memoWhilePending<Messages>();\n\n const loadLanguage = async (languageCode: string): Promise<Messages> => {\n const [messages, devMessages] = await Promise.all([\n load(() => loadMessages(languageCode)),\n !isDev || !loadDevMessages ? undefined : load(() => loadDevMessages(languageCode)),\n ]);\n\n if (messages && devMessages) {\n // devMessages override messages only in development and only if both are present\n return { ...messages, ...devMessages };\n }\n\n if (messages) {\n // callers must treat returned dictionaries as read-only\n return messages;\n }\n\n if (devMessages) {\n // callers must treat returned dictionaries as read-only\n return devMessages;\n }\n\n throw new Error(`No translation files found for ${languageCode}`);\n };\n\n return (languageCode: string): Promise<Messages> => {\n return pending.getOrInsert(languageCode, () => loadLanguage(languageCode));\n };\n};\n"],"mappings":"AAEA,MAAM,MAAgC,CACpC,IAAM,EAAM,IAAI,IAehB,MAAO,CACL,aAdmB,EAAa,KAC3B,EAAI,IAAI,CAAG,GACd,EAAI,IACF,EACA,EAAQ,CAAC,CAAC,YAAc,CACtB,EAAI,OAAO,CAAG,CAChB,CAAC,CACH,EAGK,EAAI,IAAI,CAAG,EAKpB,CACF,EAEM,EAAO,KAAO,IAAiD,CACnE,GAAI,CACF,GAAM,CAAE,QAAS,GAAa,MAAM,EAAO,EAE3C,OAAO,CACT,MAAQ,CACN,MACF,CACF,EAQa,GAA6B,CACxC,QACA,eACA,qBACsC,CACtC,IAAM,EAAU,EAA2B,EAErC,EAAe,KAAO,IAA4C,CACtE,GAAM,CAAC,EAAU,GAAe,MAAM,QAAQ,IAAI,CAChD,MAAW,EAAa,CAAY,CAAC,EACrC,CAAC,GAAS,CAAC,EAAkB,IAAA,GAAY,MAAW,EAAgB,CAAY,CAAC,CACnF,CAAC,EAED,GAAI,GAAY,EAEd,MAAO,CAAE,GAAG,EAAU,GAAG,CAAY,EAGvC,GAAI,EAEF,OAAO,EAGT,GAAI,EAEF,OAAO,EAGT,MAAU,MAAM,kCAAkC,GAAc,CAClE,EAEA,MAAQ,IACC,EAAQ,YAAY,MAAoB,EAAa,CAAY,CAAC,CAE7E"}
|
package/dist/i18n.d.ts
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { IcuConfig } from "./icu.js";
|
|
2
|
+
import { i18n as i18n$1 } from "i18next";
|
|
3
|
+
//#region src/i18n.d.ts
|
|
4
|
+
/** A flat map of translation keys to their translated strings. */
|
|
5
|
+
interface Messages {
|
|
6
|
+
[key: string]: string;
|
|
7
|
+
}
|
|
8
|
+
/** Configuration options for initializing an i18next instance. */
|
|
9
|
+
interface I18nOptions {
|
|
10
|
+
locale: string;
|
|
11
|
+
asyncMessagesLoader?: (language: string) => Promise<Messages>;
|
|
12
|
+
messages?: Messages;
|
|
13
|
+
useSuspense?: boolean;
|
|
14
|
+
/**
|
|
15
|
+
* @deprecated No-op. The underlying `react.wait` option was removed from
|
|
16
|
+
* react-i18next in v10; use `useSuspense` instead. Kept for backward compatibility.
|
|
17
|
+
*/
|
|
18
|
+
wait?: boolean;
|
|
19
|
+
disableAutoInit?: boolean;
|
|
20
|
+
bindI18n?: string;
|
|
21
|
+
}
|
|
22
|
+
type I18nFactory = (options: I18nOptions, icuOptions?: {
|
|
23
|
+
icu?: any;
|
|
24
|
+
}) => i18n$1;
|
|
25
|
+
/**
|
|
26
|
+
* Creates and configures an i18next instance with optional ICU support and async message loading.
|
|
27
|
+
* @param options - Locale, messages, and behavior configuration.
|
|
28
|
+
* @param icuOptions - Optional ICU plugin instance for MessageFormat support.
|
|
29
|
+
* @returns A configured i18next instance.
|
|
30
|
+
*/
|
|
31
|
+
declare const i18n: I18nFactory;
|
|
32
|
+
/**
|
|
33
|
+
* Initializes an i18next instance with ICU MessageFormat support.
|
|
34
|
+
* This is the recommended entry point for apps that need ICU pluralization and formatting.
|
|
35
|
+
* @returns A configured i18next instance with ICU plugin.
|
|
36
|
+
*/
|
|
37
|
+
declare function initI18n({ icuOptions, ...options }: {
|
|
38
|
+
icuOptions?: IcuConfig;
|
|
39
|
+
parseErrorHandler?: any;
|
|
40
|
+
} & I18nOptions): i18n$1;
|
|
41
|
+
/**
|
|
42
|
+
* Initializes an i18next instance without ICU MessageFormat support.
|
|
43
|
+
* Use when ICU formatting is not needed or when bundle size is a concern.
|
|
44
|
+
* @returns A configured i18next instance without ICU plugin.
|
|
45
|
+
*/
|
|
46
|
+
declare function initI18nWithoutICU(options: Omit<I18nOptions, 'icuOptions' | 'parseErrorHandler'>): i18n$1;
|
|
47
|
+
//#endregion
|
|
48
|
+
export { Messages, i18n, initI18n, initI18nWithoutICU };
|
|
49
|
+
//# sourceMappingURL=i18n.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"i18n.d.ts","names":[],"sources":["../src/i18n.ts"],"mappings":";;;;UAMiB;GACd;;;UAIO;EACR;EACA,uBAAuB,qBAAqB,QAAQ;EACpD,WAAW;EACX;;;;;EAKA;EACA;EACA;;KAGG,eAAe,SAAS,aAAa;EAAe;MAAgB;;;;;;;cAQ5D,MAAM;;;;;;iBAsEH,WACd,eACG;EAEH,aAAa;EAEb;IACE,cAAW;;;;;;iBAeC,mBAAmB,SAAS,KAAK,mDAAgD"}
|
package/dist/i18n.js
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import e from"./icu.js";import t from"i18next";const n=({locale:e,asyncMessagesLoader:n,messages:r,useSuspense:i=!1,disableAutoInit:a=!1,bindI18n:o},{icu:s}={})=>{let c={lng:e,fallbackLng:`en`,keySeparator:!1,react:{useSuspense:i,bindI18n:o},_polyfill:{isLoading:!1}},l=t.createInstance(a?c:void 0);return s&&l.use(s),n&&l.use({type:`backend`,read:async(e,t,r)=>{try{return r(null,await n(e))}catch(e){return r(e,null)}}}),r&&(c.resources={[e]:{translation:r}}),a||l.init(c),l};function r({icuOptions:t={},...r}){let{parseErrorHandler:i=void 0}=r,a=new e({parseErrorHandler:i,bindI18n:r.bindI18n,...t});return n(r,{icu:a})}function i(e){return n(e)}export{n as i18n,r as initI18n,i as initI18nWithoutICU};
|
|
2
|
+
//# sourceMappingURL=i18n.js.map
|
package/dist/i18n.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"i18n.js","names":[],"sources":["../src/i18n.ts"],"sourcesContent":["import i18next from 'i18next';\nimport type { InitOptions, i18n as i18nType } from 'i18next';\nimport type { IcuConfig } from './icu';\nimport ICU from './icu';\n\n/** A flat map of translation keys to their translated strings. */\nexport interface Messages {\n [key: string]: string;\n}\n\n/** Configuration options for initializing an i18next instance. */\ninterface I18nOptions {\n locale: string;\n asyncMessagesLoader?: (language: string) => Promise<Messages>;\n messages?: Messages;\n useSuspense?: boolean;\n /**\n * @deprecated No-op. The underlying `react.wait` option was removed from\n * react-i18next in v10; use `useSuspense` instead. Kept for backward compatibility.\n */\n wait?: boolean;\n disableAutoInit?: boolean;\n bindI18n?: string;\n}\n\ntype I18nFactory = (options: I18nOptions, icuOptions?: { icu?: any }) => i18nType;\n\n/**\n * Creates and configures an i18next instance with optional ICU support and async message loading.\n * @param options - Locale, messages, and behavior configuration.\n * @param icuOptions - Optional ICU plugin instance for MessageFormat support.\n * @returns A configured i18next instance.\n */\nexport const i18n: I18nFactory = (\n { locale, asyncMessagesLoader, messages, useSuspense = false, disableAutoInit = false, bindI18n },\n { icu } = {},\n) => {\n const config: InitOptions = {\n // Initial language\n lng: locale,\n fallbackLng: 'en',\n\n // Don't use a key separator (no support for nested translation objects)\n keySeparator: false,\n\n react: {\n useSuspense,\n bindI18n,\n },\n\n // Basically when BM can be loaded without this config - you're good to go.\n // oxlint-disable-next-line @typescript-eslint/ban-ts-comment\n // @ts-expect-error _polyfill is a non-standard BM-specific option not in i18next's InitOptions\n _polyfill: { isLoading: false },\n };\n\n const i18nInstance = i18next.createInstance(disableAutoInit ? config : undefined);\n\n // icu is optional since this function can be called from initI18n or initI18nWithoutICU modules\n if (icu) {\n i18nInstance.use(icu);\n }\n\n if (asyncMessagesLoader) {\n i18nInstance.use({\n type: 'backend' as const,\n read: async (\n language: string,\n _namespace: string,\n callback: (error: null | unknown, messages: null | Record<string, string>) => void,\n ) => {\n // We configure how i18next should fetch a translation resource when it\n // needs it: We use Webpack's dynamic imports to fetch resources without\n // increasing our bundle size.\n //\n // See https://webpack.js.org/guides/code-splitting/#dynamic-imports for\n // more information.\n try {\n const translation = await asyncMessagesLoader(language);\n return callback(null, translation);\n } catch (err) {\n return callback(err, null);\n }\n },\n });\n }\n\n if (messages) {\n config.resources = { [locale]: { translation: messages } };\n }\n\n if (!disableAutoInit) {\n i18nInstance.init(config);\n }\n\n return i18nInstance;\n};\n\n/**\n * Initializes an i18next instance with ICU MessageFormat support.\n * This is the recommended entry point for apps that need ICU pluralization and formatting.\n * @returns A configured i18next instance with ICU plugin.\n */\nexport function initI18n({\n icuOptions = {},\n ...options\n}: {\n icuOptions?: IcuConfig;\n // oxlint-disable-next-line @typescript-eslint/no-explicit-any\n parseErrorHandler?: any;\n} & I18nOptions) {\n const { parseErrorHandler = undefined } = options;\n const icu = new ICU({\n parseErrorHandler,\n bindI18n: options.bindI18n,\n ...icuOptions,\n });\n return i18n(options, { icu });\n}\n\n/**\n * Initializes an i18next instance without ICU MessageFormat support.\n * Use when ICU formatting is not needed or when bundle size is a concern.\n * @returns A configured i18next instance without ICU plugin.\n */\nexport function initI18nWithoutICU(options: Omit<I18nOptions, 'icuOptions' | 'parseErrorHandler'>) {\n return i18n(options);\n}\n"],"mappings":"+CAiCA,MAAa,GACX,CAAE,SAAQ,sBAAqB,WAAU,cAAc,GAAO,kBAAkB,GAAO,YACvF,CAAE,OAAQ,CAAC,IACR,CACH,IAAM,EAAsB,CAE1B,IAAK,EACL,YAAa,KAGb,aAAc,GAEd,MAAO,CACL,cACA,UACF,EAKA,UAAW,CAAE,UAAW,EAAM,CAChC,EAEM,EAAe,EAAQ,eAAe,EAAkB,EAAS,IAAA,EAAS,EAuChF,OApCI,GACF,EAAa,IAAI,CAAG,EAGlB,GACF,EAAa,IAAI,CACf,KAAM,UACN,KAAM,MACJ,EACA,EACA,IACG,CAOH,GAAI,CAEF,OAAO,EAAS,KAAM,MADI,EAAoB,CAAQ,CACrB,CACnC,OAAS,EAAK,CACZ,OAAO,EAAS,EAAK,IAAI,CAC3B,CACF,CACF,CAAC,EAGC,IACF,EAAO,UAAY,EAAG,GAAS,CAAE,YAAa,CAAS,CAAE,GAGtD,GACH,EAAa,KAAK,CAAM,EAGnB,CACT,EAOA,SAAgB,EAAS,CACvB,aAAa,CAAC,EACd,GAAG,GAKY,CACf,GAAM,CAAE,oBAAoB,IAAA,IAAc,EACpC,EAAM,IAAI,EAAI,CAClB,oBACA,SAAU,EAAQ,SAClB,GAAG,CACL,CAAC,EACD,OAAO,EAAK,EAAS,CAAE,KAAI,CAAC,CAC9B,CAOA,SAAgB,EAAmB,EAAgE,CACjG,OAAO,EAAK,CAAO,CACrB"}
|
package/dist/icu.d.ts
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { i18n } from "i18next";
|
|
2
|
+
//#region src/icu.d.ts
|
|
3
|
+
interface IcuFormats {
|
|
4
|
+
number?: {
|
|
5
|
+
[styleName: string]: Intl.NumberFormatOptions;
|
|
6
|
+
};
|
|
7
|
+
date?: {
|
|
8
|
+
[styleName: string]: Intl.DateTimeFormatOptions;
|
|
9
|
+
};
|
|
10
|
+
time?: {
|
|
11
|
+
[styleName: string]: Intl.DateTimeFormatOptions;
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
type StoreEvent = 'added' | 'removed';
|
|
15
|
+
interface IcuConfig {
|
|
16
|
+
memoize?: boolean;
|
|
17
|
+
memoizeFallback?: boolean;
|
|
18
|
+
/** Maximum compiled messages per instance. Defaults to 1000; 0 disables this cache, Infinity disables eviction. */
|
|
19
|
+
maxCacheSize?: number;
|
|
20
|
+
/** Maximum shared Intl formatters per instance. Defaults to 100; 0 disables this cache, Infinity disables eviction. */
|
|
21
|
+
maxFormatterCacheSize?: number;
|
|
22
|
+
formats?: IcuFormats;
|
|
23
|
+
bindI18n?: string;
|
|
24
|
+
/** Space-separated resource-store events. Defaults to 'added removed'. */
|
|
25
|
+
bindI18nStore?: '' | StoreEvent | 'added removed' | 'removed added';
|
|
26
|
+
localeData?: any;
|
|
27
|
+
parseErrorHandler?: (err: Error, key: string, res: string, options: object) => string;
|
|
28
|
+
parseLngForICU?: (lng: string) => string;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Copied from original ICU source code.
|
|
32
|
+
* @source https://github.com/i18next/i18next-icu/blob/7809547c821a7ef3b2bb9ab2610cdbbcfedcb2f9/src/index.js
|
|
33
|
+
*
|
|
34
|
+
* It's done in order to support `IntlMessageFormat` out of the box.
|
|
35
|
+
* `IntlMessageFormat` is adding formatters that are not provided by i18n.
|
|
36
|
+
* For example, `select` formatter - https://formatjs.io/docs/core-concepts/icu-syntax#select-format
|
|
37
|
+
* It allows to use following translation value: 'Welcome back{inputData, select, 0 {} 1 {, John}}'
|
|
38
|
+
* See example in tests: __tests__/i18n.spec.tsx
|
|
39
|
+
*/
|
|
40
|
+
declare class ICU {
|
|
41
|
+
options: IcuConfig | undefined;
|
|
42
|
+
formats: IcuFormats | undefined;
|
|
43
|
+
type: string;
|
|
44
|
+
private cacheRoot;
|
|
45
|
+
private formatterCache;
|
|
46
|
+
private unbindEvents?;
|
|
47
|
+
private formatters;
|
|
48
|
+
constructor(options: IcuConfig);
|
|
49
|
+
init(i18next: i18n | null, options: IcuConfig): void;
|
|
50
|
+
addLocaleData(data: any): void;
|
|
51
|
+
addUserDefinedFormats(formats: IcuFormats): void;
|
|
52
|
+
parse(res: string, options: Record<string, any>, lng: string, ns: string, key: string, info: any): string | undefined;
|
|
53
|
+
addLookupKeys(finalKeys: any): any;
|
|
54
|
+
/** Drops compiled messages, keeping the shared Intl formatters. */
|
|
55
|
+
clearMessages(): void;
|
|
56
|
+
/** Drops compiled messages and the shared Intl formatters. */
|
|
57
|
+
clearCache(): void;
|
|
58
|
+
private clearNamespace;
|
|
59
|
+
/** Re-inserts to mark most recently used: Map.set keeps an existing key's position. */
|
|
60
|
+
private touch;
|
|
61
|
+
private getFormatter;
|
|
62
|
+
}
|
|
63
|
+
//#endregion
|
|
64
|
+
export { IcuConfig, ICU as default };
|
|
65
|
+
//# sourceMappingURL=icu.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"icu.d.ts","names":[],"sources":["../src/icu.ts"],"mappings":";;UAIU;EACR;KACG,oBAAoB,KAAK;;EAE5B;KACG,oBAAoB,KAAK;;EAE5B;KACG,oBAAoB,KAAK;;;KAIzB;UASY;EACf;EACA;;EAEA;;EAEA;EACA,UAAU;EACV;;EAEA,qBAAqB;EACrB;EACA,qBAAqB,KAAK,OAAO,aAAa,aAAa;EAC3D,kBAAkB;;;;;;;;;;;;cA2Bd;EACJ,SAAS;EACT,SAAS;EACT;UACQ;UACA;UAIA;UACA;EAMI,YAAA,SAAS;EAMrB,KAAK,SAAS,aAAa,SAAS;EA8CpC,cAAc;EAWd,sBAAsB,SAAS;EAM/B,MACE,aACA,SAAS,qBACT,aACA,YACA,aACA;EAsCF,cAAc;;EAOd;;EAKA;UAKQ;;UAOA;UAKA"}
|
package/dist/icu.js
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import e from"intl-messageformat";function t(){return{memoize:!0,memoizeFallback:!1,maxCacheSize:1e3,maxFormatterCacheSize:100,bindI18n:``,bindI18nStore:`added removed`,parseErrorHandler:(e,t,n)=>n}}var n=class{options;formats;type;cacheRoot=new Map;formatterCache=new Map;unbindEvents;formatters={getNumberFormat:(...e)=>this.getFormatter(`number`,Intl.NumberFormat,e),getDateTimeFormat:(...e)=>this.getFormatter(`dateTime`,Intl.DateTimeFormat,e),getPluralRules:(...e)=>this.getFormatter(`pluralRules`,Intl.PluralRules,e)};constructor(e){this.type=`i18nFormat`,this.init(null,e)}init(n,i){let a=n&&n.options&&n.options.i18nFormat||{};this.options=r(a,i,this.options||{},t());for(let e of[`maxCacheSize`,`maxFormatterCacheSize`]){let t=this.options[e];if(t!==1/0&&(!Number.isSafeInteger(t)||t<0))throw RangeError(`${e} must be a non-negative safe integer or Infinity`)}if(this.formats=this.options?.formats,this.unbindEvents?.(),this.unbindEvents=void 0,this.clearCache(),n){let{bindI18n:t,bindI18nStore:r}=this.options;n.IntlMessageFormat=e,n.ICU=this;let i=()=>this.clearMessages(),a=(e,t)=>this.clearNamespace(e,t),o=n.store,s=t?t.split(` `):[],c=r?r.split(` `):[];for(let e of s)n.on(e,i);for(let e of c)o.on(e,a);this.unbindEvents=()=>{for(let e of s)n.off(e,i);for(let e of c)o.off(e,a)}}this.options?.localeData&&(Object.prototype.toString.apply(this.options.localeData)===`[object Array]`?this.options.localeData.forEach(e=>this.addLocaleData(e)):this.addLocaleData(this.options.localeData))}addLocaleData(t){(Array.isArray(t)?t:[t]).forEach(t=>{t&&t.locale&&e.__addLocaleData(t)})}addUserDefinedFormats(e){this.formats=this.formats?{...this.formats,...e}:e,this.clearMessages()}parse(t,n,r,i,a,o){let s=this.options?.memoize&&this.options.maxCacheSize>0?JSON.stringify([r,i,a]):void 0,c;if(s!==void 0){let e=this.cacheRoot.get(s);e?.source===t&&(c=e.fc,this.touch(s,e))}try{return c||(c=new e(t,r,this.formats,{formatters:this.formatters}),s!==void 0&&(this.options.memoizeFallback||!o||o.resolved?.res)&&(this.touch(s,{lng:r,ns:i,source:t,fc:c}),this.cacheRoot.size>this.options.maxCacheSize&&this.cacheRoot.delete(this.cacheRoot.keys().next().value))),c.format(n)}catch(e){return this.options?.parseErrorHandler?.(e,a,t,n)}}addLookupKeys(e){return e}clearMessages(){this.cacheRoot.clear()}clearCache(){this.clearMessages(),this.formatterCache.clear()}clearNamespace(e,t){for(let[n,r]of this.cacheRoot)r.lng===e&&r.ns===t&&this.cacheRoot.delete(n)}touch(e,t){this.cacheRoot.delete(e),this.cacheRoot.set(e,t)}getFormatter(e,t,n){if(!this.options?.memoize||this.options.maxFormatterCacheSize===0)return new t(...n);let[r,i]=n,a=JSON.stringify([e,r,Object.entries(i??{}).sort(([e],[t])=>e<t?-1:+(e>t))]),o=this.formatterCache.get(a);if(o)return this.formatterCache.delete(a),this.formatterCache.set(a,o),o;let s=new t(...n);return this.formatterCache.set(a,s),this.formatterCache.size>this.options.maxFormatterCacheSize&&this.formatterCache.delete(this.formatterCache.keys().next().value),s}};n.type=`i18nFormat`;function r(e,...t){let n=e;for(let e of t)if(e)for(let[t,r]of Object.entries(e))n[t]===void 0&&(n[t]=r);return e}export{n as default};
|
|
2
|
+
//# sourceMappingURL=icu.js.map
|
package/dist/icu.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"icu.js","names":[],"sources":["../src/icu.ts"],"sourcesContent":["import IntlMessageFormat from 'intl-messageformat';\nimport type { Formatters } from 'intl-messageformat';\nimport type { i18n } from 'i18next';\n\ninterface IcuFormats {\n number?: {\n [styleName: string]: Intl.NumberFormatOptions;\n };\n date?: {\n [styleName: string]: Intl.DateTimeFormatOptions;\n };\n time?: {\n [styleName: string]: Intl.DateTimeFormatOptions;\n };\n}\n\ntype StoreEvent = 'added' | 'removed';\n\ninterface CachedMessage {\n lng: string;\n ns: string;\n source: string;\n fc: IntlMessageFormat;\n}\n\nexport interface IcuConfig {\n memoize?: boolean;\n memoizeFallback?: boolean;\n /** Maximum compiled messages per instance. Defaults to 1000; 0 disables this cache, Infinity disables eviction. */\n maxCacheSize?: number;\n /** Maximum shared Intl formatters per instance. Defaults to 100; 0 disables this cache, Infinity disables eviction. */\n maxFormatterCacheSize?: number;\n formats?: IcuFormats;\n bindI18n?: string;\n /** Space-separated resource-store events. Defaults to 'added removed'. */\n bindI18nStore?: '' | StoreEvent | 'added removed' | 'removed added';\n localeData?: any;\n parseErrorHandler?: (err: Error, key: string, res: string, options: object) => string;\n parseLngForICU?: (lng: string) => string;\n}\n\nfunction getDefaults(): IcuConfig {\n return {\n memoize: true,\n memoizeFallback: false,\n maxCacheSize: 1000,\n maxFormatterCacheSize: 100,\n bindI18n: '',\n bindI18nStore: 'added removed',\n parseErrorHandler: (_err, _key, res) => {\n return res;\n },\n };\n}\n\n/**\n * Copied from original ICU source code.\n * @source https://github.com/i18next/i18next-icu/blob/7809547c821a7ef3b2bb9ab2610cdbbcfedcb2f9/src/index.js\n *\n * It's done in order to support `IntlMessageFormat` out of the box.\n * `IntlMessageFormat` is adding formatters that are not provided by i18n.\n * For example, `select` formatter - https://formatjs.io/docs/core-concepts/icu-syntax#select-format\n * It allows to use following translation value: 'Welcome back{inputData, select, 0 {} 1 {, John}}'\n * See example in tests: __tests__/i18n.spec.tsx\n */\nclass ICU {\n options: IcuConfig | undefined;\n formats: IcuFormats | undefined;\n type: string;\n private cacheRoot = new Map<string, CachedMessage>();\n private formatterCache = new Map<\n string,\n Intl.NumberFormat | Intl.DateTimeFormat | Intl.PluralRules\n >();\n private unbindEvents?: () => void;\n private formatters: Formatters = {\n getNumberFormat: (...args) => this.getFormatter('number', Intl.NumberFormat, args),\n getDateTimeFormat: (...args) => this.getFormatter('dateTime', Intl.DateTimeFormat, args),\n getPluralRules: (...args) => this.getFormatter('pluralRules', Intl.PluralRules, args),\n };\n\n constructor(options: IcuConfig) {\n this.type = 'i18nFormat';\n\n this.init(null, options);\n }\n\n init(i18next: i18n | null, options: IcuConfig) {\n const i18nextOptions = (i18next && i18next.options && i18next.options.i18nFormat) || {};\n this.options = defaults(i18nextOptions, options, this.options || {}, getDefaults());\n for (const name of ['maxCacheSize', 'maxFormatterCacheSize'] as const) {\n const limit = this.options![name]!;\n if (limit !== Infinity && (!Number.isSafeInteger(limit) || limit < 0)) {\n throw new RangeError(`${name} must be a non-negative safe integer or Infinity`);\n }\n }\n this.formats = this.options?.formats;\n this.unbindEvents?.();\n this.unbindEvents = undefined;\n this.clearCache();\n\n if (i18next) {\n const { bindI18n, bindI18nStore } = this.options!;\n\n // @ts-expect-error fix later\n i18next.IntlMessageFormat = IntlMessageFormat;\n // @ts-expect-error fix later\n i18next.ICU = this;\n\n // Resources can only stale compiled messages, never the shared Intl formatters.\n const clearAll = () => this.clearMessages();\n // Store events report the (lng, ns) that changed, so only that much needs dropping.\n const clearChanged = (lng: string, ns: string) => this.clearNamespace(lng, ns);\n const store = i18next.store;\n const i18nEvents = bindI18n ? bindI18n.split(' ') : [];\n const storeEvents = bindI18nStore ? (bindI18nStore.split(' ') as StoreEvent[]) : [];\n for (const event of i18nEvents) i18next.on(event, clearAll);\n for (const event of storeEvents) store.on(event, clearChanged);\n this.unbindEvents = () => {\n for (const event of i18nEvents) i18next.off(event, clearAll);\n for (const event of storeEvents) store.off(event, clearChanged);\n };\n }\n\n if (this.options?.localeData) {\n if (Object.prototype.toString.apply(this.options.localeData) === '[object Array]') {\n this.options.localeData.forEach((ld: any) => this.addLocaleData(ld));\n } else {\n this.addLocaleData(this.options.localeData);\n }\n }\n }\n\n addLocaleData(data: any) {\n const locales = Array.isArray(data) ? data : [data];\n\n locales.forEach((localeData) => {\n if (localeData && localeData.locale) {\n // Undeclared legacy escape hatch on IntlMessageFormat.\n (IntlMessageFormat as any)['__addLocaleData'](localeData);\n }\n });\n }\n\n addUserDefinedFormats(formats: IcuFormats) {\n this.formats = this.formats ? { ...this.formats, ...formats } : formats;\n // Compiled messages capture `formats`; formatters are keyed by their resolved options.\n this.clearMessages();\n }\n\n parse(\n res: string,\n options: Record<string, any>,\n lng: string,\n ns: string,\n key: string,\n info: any,\n ) {\n // An undefined key means caching is off.\n const cacheKey =\n this.options?.memoize && this.options.maxCacheSize! > 0\n ? JSON.stringify([lng, ns, key])\n : undefined;\n\n let fc: IntlMessageFormat | undefined;\n if (cacheKey !== undefined) {\n const cached = this.cacheRoot.get(cacheKey);\n // A changed source must be recompiled even when no invalidation event fired.\n if (cached?.source === res) {\n fc = cached.fc;\n this.touch(cacheKey, cached);\n }\n }\n\n try {\n if (!fc) {\n fc = new IntlMessageFormat(res, lng, this.formats, { formatters: this.formatters });\n if (\n cacheKey !== undefined &&\n (this.options!.memoizeFallback || !info || info.resolved?.res)\n ) {\n this.touch(cacheKey, { lng, ns, source: res, fc });\n if (this.cacheRoot.size > this.options!.maxCacheSize!) {\n this.cacheRoot.delete(this.cacheRoot.keys().next().value!);\n }\n }\n }\n\n return fc.format(options);\n } catch (err) {\n return this.options?.parseErrorHandler?.(err as Error, key, res, options);\n }\n }\n\n addLookupKeys(finalKeys: any) {\n // no additional keys needed for select or plural\n // so there is no need to add keys to that finalKeys array\n return finalKeys;\n }\n\n /** Drops compiled messages, keeping the shared Intl formatters. */\n clearMessages() {\n this.cacheRoot.clear();\n }\n\n /** Drops compiled messages and the shared Intl formatters. */\n clearCache() {\n this.clearMessages();\n this.formatterCache.clear();\n }\n\n private clearNamespace(lng: string, ns: string) {\n for (const [cacheKey, entry] of this.cacheRoot) {\n if (entry.lng === lng && entry.ns === ns) this.cacheRoot.delete(cacheKey);\n }\n }\n\n /** Re-inserts to mark most recently used: Map.set keeps an existing key's position. */\n private touch(cacheKey: string, entry: CachedMessage) {\n this.cacheRoot.delete(cacheKey);\n this.cacheRoot.set(cacheKey, entry);\n }\n\n private getFormatter<\n T extends Intl.NumberFormat | Intl.DateTimeFormat | Intl.PluralRules,\n Args extends unknown[],\n >(type: string, Formatter: new (...args: Args) => T, args: Args): T {\n if (!this.options?.memoize || this.options.maxFormatterCacheSize === 0) {\n return new Formatter(...args);\n }\n\n const [locales, options] = args;\n // Equivalent option objects should share a formatter regardless of property order.\n const key = JSON.stringify([\n type,\n locales,\n // oxlint-disable-next-line no-array-sort -- Object.entries already returns a fresh array\n Object.entries(options ?? {}).sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0)),\n ]);\n const cached = this.formatterCache.get(key);\n if (cached) {\n this.formatterCache.delete(key);\n this.formatterCache.set(key, cached);\n return cached as T;\n }\n\n const formatter = new Formatter(...args);\n this.formatterCache.set(key, formatter);\n if (this.formatterCache.size > this.options!.maxFormatterCacheSize!) {\n this.formatterCache.delete(this.formatterCache.keys().next().value!);\n }\n return formatter;\n }\n}\n\n// @ts-expect-error fix it later\nICU.type = 'i18nFormat';\n\nexport default ICU;\n\n/** Fills only the keys `obj` leaves undefined, so explicit `undefined` never clobbers a default. */\nfunction defaults(obj: IcuConfig, ...sources: (IcuConfig | undefined)[]): IcuConfig {\n const target = obj as Record<string, unknown>;\n for (const source of sources) {\n if (!source) continue;\n for (const [key, value] of Object.entries(source)) {\n if (target[key] === undefined) target[key] = value;\n }\n }\n return obj;\n}\n"],"mappings":"kCAyCA,SAAS,GAAyB,CAChC,MAAO,CACL,QAAS,GACT,gBAAiB,GACjB,aAAc,IACd,sBAAuB,IACvB,SAAU,GACV,cAAe,gBACf,mBAAoB,EAAM,EAAM,IACvB,CAEX,CACF,CAYA,IAAM,EAAN,KAAU,CACR,QACA,QACA,KACA,UAAoB,IAAI,IACxB,eAAyB,IAAI,IAI7B,aACA,WAAiC,CAC/B,iBAAkB,GAAG,IAAS,KAAK,aAAa,SAAU,KAAK,aAAc,CAAI,EACjF,mBAAoB,GAAG,IAAS,KAAK,aAAa,WAAY,KAAK,eAAgB,CAAI,EACvF,gBAAiB,GAAG,IAAS,KAAK,aAAa,cAAe,KAAK,YAAa,CAAI,CACtF,EAEA,YAAY,EAAoB,CAC9B,KAAK,KAAO,aAEZ,KAAK,KAAK,KAAM,CAAO,CACzB,CAEA,KAAK,EAAsB,EAAoB,CAC7C,IAAM,EAAkB,GAAW,EAAQ,SAAW,EAAQ,QAAQ,YAAe,CAAC,EACtF,KAAK,QAAU,EAAS,EAAgB,EAAS,KAAK,SAAW,CAAC,EAAG,EAAY,CAAC,EAClF,IAAK,IAAM,IAAQ,CAAC,eAAgB,uBAAuB,EAAY,CACrE,IAAM,EAAQ,KAAK,QAAS,GAC5B,GAAI,IAAU,MAAa,CAAC,OAAO,cAAc,CAAK,GAAK,EAAQ,GACjE,MAAU,WAAW,GAAG,EAAK,iDAAiD,CAElF,CAMA,GALA,KAAK,QAAU,KAAK,SAAS,QAC7B,KAAK,eAAe,EACpB,KAAK,aAAe,IAAA,GACpB,KAAK,WAAW,EAEZ,EAAS,CACX,GAAM,CAAE,WAAU,iBAAkB,KAAK,QAGzC,EAAQ,kBAAoB,EAE5B,EAAQ,IAAM,KAGd,IAAM,MAAiB,KAAK,cAAc,EAEpC,GAAgB,EAAa,IAAe,KAAK,eAAe,EAAK,CAAE,EACvE,EAAQ,EAAQ,MAChB,EAAa,EAAW,EAAS,MAAM,GAAG,EAAI,CAAC,EAC/C,EAAc,EAAiB,EAAc,MAAM,GAAG,EAAqB,CAAC,EAClF,IAAK,IAAM,KAAS,EAAY,EAAQ,GAAG,EAAO,CAAQ,EAC1D,IAAK,IAAM,KAAS,EAAa,EAAM,GAAG,EAAO,CAAY,EAC7D,KAAK,iBAAqB,CACxB,IAAK,IAAM,KAAS,EAAY,EAAQ,IAAI,EAAO,CAAQ,EAC3D,IAAK,IAAM,KAAS,EAAa,EAAM,IAAI,EAAO,CAAY,CAChE,CACF,CAEI,KAAK,SAAS,aACZ,OAAO,UAAU,SAAS,MAAM,KAAK,QAAQ,UAAU,IAAM,iBAC/D,KAAK,QAAQ,WAAW,QAAS,GAAY,KAAK,cAAc,CAAE,CAAC,EAEnE,KAAK,cAAc,KAAK,QAAQ,UAAU,EAGhD,CAEA,cAAc,EAAW,EACP,MAAM,QAAQ,CAAI,EAAI,EAAO,CAAC,CAAI,EAAA,CAE1C,QAAS,GAAe,CAC1B,GAAc,EAAW,QAE3B,EAA2B,gBAAmB,CAAU,CAE5D,CAAC,CACH,CAEA,sBAAsB,EAAqB,CACzC,KAAK,QAAU,KAAK,QAAU,CAAE,GAAG,KAAK,QAAS,GAAG,CAAQ,EAAI,EAEhE,KAAK,cAAc,CACrB,CAEA,MACE,EACA,EACA,EACA,EACA,EACA,EACA,CAEA,IAAM,EACJ,KAAK,SAAS,SAAW,KAAK,QAAQ,aAAgB,EAClD,KAAK,UAAU,CAAC,EAAK,EAAI,CAAG,CAAC,EAC7B,IAAA,GAEF,EACJ,GAAI,IAAa,IAAA,GAAW,CAC1B,IAAM,EAAS,KAAK,UAAU,IAAI,CAAQ,EAEtC,GAAQ,SAAW,IACrB,EAAK,EAAO,GACZ,KAAK,MAAM,EAAU,CAAM,EAE/B,CAEA,GAAI,CAcF,OAbK,IACH,EAAK,IAAI,EAAkB,EAAK,EAAK,KAAK,QAAS,CAAE,WAAY,KAAK,UAAW,CAAC,EAEhF,IAAa,IAAA,KACZ,KAAK,QAAS,iBAAmB,CAAC,GAAQ,EAAK,UAAU,OAE1D,KAAK,MAAM,EAAU,CAAE,MAAK,KAAI,OAAQ,EAAK,IAAG,CAAC,EAC7C,KAAK,UAAU,KAAO,KAAK,QAAS,cACtC,KAAK,UAAU,OAAO,KAAK,UAAU,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,KAAM,IAKxD,EAAG,OAAO,CAAO,CAC1B,OAAS,EAAK,CACZ,OAAO,KAAK,SAAS,oBAAoB,EAAc,EAAK,EAAK,CAAO,CAC1E,CACF,CAEA,cAAc,EAAgB,CAG5B,OAAO,CACT,CAGA,eAAgB,CACd,KAAK,UAAU,MAAM,CACvB,CAGA,YAAa,CACX,KAAK,cAAc,EACnB,KAAK,eAAe,MAAM,CAC5B,CAEA,eAAuB,EAAa,EAAY,CAC9C,IAAK,GAAM,CAAC,EAAU,KAAU,KAAK,UAC/B,EAAM,MAAQ,GAAO,EAAM,KAAO,GAAI,KAAK,UAAU,OAAO,CAAQ,CAE5E,CAGA,MAAc,EAAkB,EAAsB,CACpD,KAAK,UAAU,OAAO,CAAQ,EAC9B,KAAK,UAAU,IAAI,EAAU,CAAK,CACpC,CAEA,aAGE,EAAc,EAAqC,EAAe,CAClE,GAAI,CAAC,KAAK,SAAS,SAAW,KAAK,QAAQ,wBAA0B,EACnE,OAAO,IAAI,EAAU,GAAG,CAAI,EAG9B,GAAM,CAAC,EAAS,GAAW,EAErB,EAAM,KAAK,UAAU,CACzB,EACA,EAEA,OAAO,QAAQ,GAAW,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,GAAI,CAAC,KAAQ,EAAI,EAAI,GAAK,IAAI,EAAU,CAC/E,CAAC,EACK,EAAS,KAAK,eAAe,IAAI,CAAG,EAC1C,GAAI,EAGF,OAFA,KAAK,eAAe,OAAO,CAAG,EAC9B,KAAK,eAAe,IAAI,EAAK,CAAM,EAC5B,EAGT,IAAM,EAAY,IAAI,EAAU,GAAG,CAAI,EAKvC,OAJA,KAAK,eAAe,IAAI,EAAK,CAAS,EAClC,KAAK,eAAe,KAAO,KAAK,QAAS,uBAC3C,KAAK,eAAe,OAAO,KAAK,eAAe,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,KAAM,EAE9D,CACT,CACF,EAGA,EAAI,KAAO,aAKX,SAAS,EAAS,EAAgB,GAAG,EAA+C,CAClF,IAAM,EAAS,EACf,IAAK,IAAM,KAAU,EACd,KACL,IAAK,GAAM,CAAC,EAAK,KAAU,OAAO,QAAQ,CAAM,EAC1C,EAAO,KAAS,IAAA,KAAW,EAAO,GAAO,GAGjD,OAAO,CACT"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import ICU, { IcuConfig } from "./icu.js";
|
|
2
|
+
import { Messages, i18n as i18n$1, initI18n, initI18nWithoutICU } from "./i18n.js";
|
|
3
|
+
import { createAsyncMessagesLoader } from "./create-async-messages-loader.js";
|
|
4
|
+
import { createRtfFormatter } from "./relative-time-formatting.js";
|
|
5
|
+
import { TFunction, i18n } from "i18next";
|
|
6
|
+
import { I18nextProvider, Trans, TransProps, UseTranslationResponse, WithTranslation, WithTranslationProps, useTranslation, withTranslation } from "react-i18next";
|
|
7
|
+
export { I18nextProvider, ICU, type IcuConfig, type Messages, type TFunction, Trans, type TransProps, type UseTranslationResponse, type WithTranslation, type WithTranslationProps, createAsyncMessagesLoader, createRtfFormatter, type i18n, initI18n, i18n$1 as initI18nBase, initI18nWithoutICU, useTranslation, withTranslation };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{createAsyncMessagesLoader as e}from"./create-async-messages-loader.js";import t from"./icu.js";import{i18n as n,initI18n as r,initI18nWithoutICU as i}from"./i18n.js";import{createRtfFormatter as a}from"./relative-time-formatting.js";import{I18nextProvider as o,Trans as s,useTranslation as c,withTranslation as l}from"react-i18next";export{o as I18nextProvider,t as ICU,s as Trans,e as createAsyncMessagesLoader,a as createRtfFormatter,r as initI18n,n as initI18nBase,i as initI18nWithoutICU,c as useTranslation,l as withTranslation};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
//#region src/relative-time-formatting.d.ts
|
|
2
|
+
type DateTime = string | number | Date;
|
|
3
|
+
declare function createRtfFormatter(language: string, { floorSeconds, extraShortExpression }?: {
|
|
4
|
+
floorSeconds?: boolean | undefined;
|
|
5
|
+
extraShortExpression?: boolean | undefined;
|
|
6
|
+
}, options?: {}): (time: DateTime) => DateTime;
|
|
7
|
+
//#endregion
|
|
8
|
+
export { createRtfFormatter };
|
|
9
|
+
//# sourceMappingURL=relative-time-formatting.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"relative-time-formatting.d.ts","names":[],"sources":["../src/relative-time-formatting.ts"],"mappings":";KA2RK,6BAA6B;iBAElB,mBACd,oBACE,cAAsB;;;GACxB,gBAGQ,MAAM,aAAQ"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
const e={en:{s:`1s`,ss:`%ds`,m:`1m`,mm:`%dm`,h:`1h`,hh:`%dh`,d:`1d`,dd:`%dd`},nl:{s:`1sec.`,ss:`%dsec.`,m:`1min.`,mm:`%dmin.`,h:`1u.`,hh:`%du.`},cs:{s:`1sek`,ss:`%dsek`,m:`1min`,mm:`%dmin`,h:`1hod`,hh:`%dhod`},da:{s:`1s`,ss:`%ds`,m:`1m`,mm:`%dm`,h:`1t`,hh:`%dt`},de:{s:`1Sek.`,ss:`%dSek.`,m:`1Min.`,mm:`%dMin.`,h:`1Std.`,hh:`%dStd.`},fr:{s:`1s`,ss:`%ds`,m:`1min`,mm:`%dmin`,h:`1h`,hh:`%dh`},hi:{s:`1से`,ss:`%dसे`,m:`1मि`,mm:`%dमि`,h:`1घंटा`,hh:`%dघंटा`},it:{s:`1sec`,ss:`%dsec`,m:`1min`,mm:`%dmin`,h:`1ora`,hh:`%dore`},ja:{s:`1秒`,ss:`%d秒`,m:`1分`,mm:`%d分`,h:`1時間`,hh:`%d時間`},ko:{s:`1초`,ss:`%d초`,m:`1분`,mm:`%d분`,h:`1시간`,hh:`%d시간`},lt:{s:`1s`,ss:`%ds`,m:`1min`,mm:`%dmin`,h:`1val`,hh:`%dval`},nb:{s:`1sek`,ss:`%dsek`,m:`1min`,mm:`%dmin`,h:`1time`,hh:`%dtimer`},pl:{s:`1s`,ss:`%ds`,m:`1min`,mm:`%dmin`,h:`1h`,hh:`%dh`},pt:{s:`1s`,ss:`%ds`,m:`1min`,mm:`%dmin`,h:`1h`,hh:`%dh`},es:{s:`1seg`,ss:`%dseg`,m:`1min`,mm:`%dmin`,h:`1h`,hh:`%dhs`},sv:{s:`1s`,ss:`%ds`,m:`1min`,mm:`%dmin`,h:`1h`,hh:`%dh`},th:{s:`1วินาที`,ss:`%dวินาที`,m:`1นาที`,mm:`%dนาที`,h:`1ชั่วโมง`,hh:`%dชั่วโมง`},tr:{s:`1sn.`,ss:`%dsn.`,m:`1dk.`,mm:`%ddk.`,h:`1sa.`,hh:`%dsa.`},ru:{s:`1сек.`,ss:`%dсек.`,m:`1dмин.`,mm:`%dмин.`,h:`1ч.`,hh:`%dч.`},"zh-cn":{s:`1秒`,ss:`%d秒`,m:`1d分鐘`,mm:`%d分鐘`,h:`1小時`,hh:`%d小時`},uk:{s:`1с.`,ss:`%dс.`,m:`1dхв.`,mm:`%dхв.`,h:`1г.`,hh:`%dг.`}},t=1e3,n=t*60,r=n*60,i=r*24;function a(t,n,r){let i={second:`ss`,minute:`mm`,hour:`hh`,day:`dd`},a=r===-1?i[n][0]:i[n],o=e[t]?e[t][a]:null;return o?o.replace(`%d`,`${-r}`):null}function o(e){let t=e<0?-e:e;switch(!0){case t<n:return`second`;case t<r:return`minute`;case t<i:return`hour`;default:return`day`}}function s(e,a,o){let s;switch(a){case`second`:s=o?1:e/t;break;case`minute`:s=e/n;break;case`hour`:s=e/r;break;case`day`:s=e/i;break;default:s=e}return-Math.round(s)}function c(){return Intl.RelativeTimeFormat!==void 0}function l(e,{floorSeconds:n=!1,extraShortExpression:r=!1}={},i={}){let l;return u=>{!l&&c()&&(l=new Intl.RelativeTimeFormat(e,{...i}));let d=new Date().getTime()-new Date(u).getTime();isNaN(d)&&(console.error(`[@wix/fed-cli-i18next/relative-time-formatter] received invalid time: ${u}`),d=t);let f=o(d),p=s(d,f,n);return(r||!l?a(e,f,p):null)||(l?l.format(p,f):u)}}export{l as createRtfFormatter};
|
|
2
|
+
//# sourceMappingURL=relative-time-formatting.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"relative-time-formatting.js","names":[],"sources":["../src/relative-time-formatting.ts"],"sourcesContent":["type LanguageKey =\n | 'en'\n | 'nl'\n | 'cs'\n | 'da'\n | 'de'\n | 'fr'\n | 'hi'\n | 'it'\n | 'ja'\n | 'ko'\n | 'lt'\n | 'nb'\n | 'pl'\n | 'pt'\n | 'es'\n | 'sv'\n | 'th'\n | 'tr'\n | 'ru'\n | 'zh-cn'\n | 'uk';\n\ninterface ExpressionMap {\n s: string;\n ss: string;\n m: string;\n mm: string;\n h: string;\n hh: string;\n d?: string;\n dd?: string;\n}\n\nconst shortExpressions: Record<LanguageKey, ExpressionMap> = {\n en: {\n s: '1s',\n ss: '%ds',\n m: '1m',\n mm: '%dm',\n h: '1h',\n hh: '%dh',\n d: '1d',\n dd: '%dd',\n },\n nl: {\n s: '1sec.',\n ss: '%dsec.',\n m: '1min.',\n mm: '%dmin.',\n h: '1u.',\n hh: '%du.',\n },\n cs: {\n s: '1sek',\n ss: '%dsek',\n m: '1min',\n mm: '%dmin',\n h: '1hod',\n hh: '%dhod',\n },\n da: {\n s: '1s',\n ss: '%ds',\n m: '1m',\n mm: '%dm',\n h: '1t',\n hh: '%dt',\n },\n de: {\n s: '1Sek.',\n ss: '%dSek.',\n m: '1Min.',\n mm: '%dMin.',\n h: '1Std.',\n hh: '%dStd.',\n },\n fr: {\n s: '1s',\n ss: '%ds',\n m: '1min',\n mm: '%dmin',\n h: '1h',\n hh: '%dh',\n },\n hi: {\n s: '1से',\n ss: '%dसे',\n m: '1मि',\n mm: '%dमि',\n h: '1घंटा',\n hh: '%dघंटा',\n },\n it: {\n s: '1sec',\n ss: '%dsec',\n m: '1min',\n mm: '%dmin',\n h: '1ora',\n hh: '%dore',\n },\n ja: {\n s: '1秒',\n ss: '%d秒',\n m: '1分',\n mm: '%d分',\n h: '1時間',\n hh: '%d時間',\n },\n ko: {\n s: '1초',\n ss: '%d초',\n m: '1분',\n mm: '%d분',\n h: '1시간',\n hh: '%d시간',\n },\n lt: {\n s: '1s',\n ss: '%ds',\n m: '1min',\n mm: '%dmin',\n h: '1val',\n hh: '%dval',\n },\n nb: {\n s: '1sek',\n ss: '%dsek',\n m: '1min',\n mm: '%dmin',\n h: '1time',\n hh: '%dtimer',\n },\n pl: {\n s: '1s',\n ss: '%ds',\n m: '1min',\n mm: '%dmin',\n h: '1h',\n hh: '%dh',\n },\n pt: {\n s: '1s',\n ss: '%ds',\n m: '1min',\n mm: '%dmin',\n h: '1h',\n hh: '%dh',\n },\n es: {\n s: '1seg',\n ss: '%dseg',\n m: '1min',\n mm: '%dmin',\n h: '1h',\n hh: '%dhs',\n },\n sv: {\n s: '1s',\n ss: '%ds',\n m: '1min',\n mm: '%dmin',\n h: '1h',\n hh: '%dh',\n },\n th: {\n s: '1วินาที',\n ss: '%dวินาที',\n m: '1นาที',\n mm: '%dนาที',\n h: '1ชั่วโมง',\n hh: '%dชั่วโมง',\n },\n tr: {\n s: '1sn.',\n ss: '%dsn.',\n m: '1dk.',\n mm: '%ddk.',\n h: '1sa.',\n hh: '%dsa.',\n },\n ru: {\n s: '1сек.',\n ss: '%dсек.',\n m: '1dмин.',\n mm: '%dмин.',\n h: '1ч.',\n hh: '%dч.',\n },\n 'zh-cn': {\n s: '1秒',\n ss: '%d秒',\n m: '1d分鐘',\n mm: '%d分鐘',\n h: '1小時',\n hh: '%d小時',\n },\n uk: {\n s: '1с.',\n ss: '%dс.',\n m: '1dхв.',\n mm: '%dхв.',\n h: '1г.',\n hh: '%dг.',\n },\n};\n\nconst MS_PER_SECOND = 1000;\nconst MS_PER_MINUTE = MS_PER_SECOND * 60;\nconst MS_PER_HOUR = MS_PER_MINUTE * 60;\nconst MS_PER_DAY = MS_PER_HOUR * 24;\n\nenum TIME_UNIT {\n SECONDS = 'second',\n MINUTES = 'minute',\n HOURS = 'hour',\n DAYS = 'day',\n}\n\nfunction applyShortenedExpression(language: LanguageKey, timeUnit: TIME_UNIT, value: number) {\n const placeholder = '%d';\n const unitToShorthand: Record<TIME_UNIT, keyof ExpressionMap> = {\n [TIME_UNIT.SECONDS]: 'ss',\n [TIME_UNIT.MINUTES]: 'mm',\n [TIME_UNIT.HOURS]: 'hh',\n [TIME_UNIT.DAYS]: 'dd',\n };\n\n const shorthand: keyof ExpressionMap =\n value === -1\n ? (unitToShorthand[timeUnit][0] as keyof ExpressionMap)\n : unitToShorthand[timeUnit];\n const expression = shortExpressions[language] ? shortExpressions[language][shorthand] : null;\n if (expression) {\n return expression.replace(placeholder, `${-value}`);\n }\n\n return null;\n}\n\nfunction getTimeUnit(time: number) {\n const timeAdjusted = time < 0 ? -time : time;\n switch (true) {\n case timeAdjusted < MS_PER_MINUTE:\n return TIME_UNIT.SECONDS;\n case timeAdjusted < MS_PER_HOUR:\n return TIME_UNIT.MINUTES;\n case timeAdjusted < MS_PER_DAY:\n return TIME_UNIT.HOURS;\n default:\n return TIME_UNIT.DAYS;\n }\n}\n\nfunction getRelativeTime(time: number, unit: TIME_UNIT, floorSeconds: boolean) {\n let returnTime;\n switch (unit) {\n case TIME_UNIT.SECONDS:\n if (floorSeconds) {\n returnTime = 1;\n } else {\n returnTime = time / MS_PER_SECOND;\n }\n break;\n case TIME_UNIT.MINUTES:\n returnTime = time / MS_PER_MINUTE;\n break;\n case TIME_UNIT.HOURS:\n returnTime = time / MS_PER_HOUR;\n break;\n case TIME_UNIT.DAYS:\n returnTime = time / MS_PER_DAY;\n break;\n default:\n returnTime = time;\n }\n return -Math.round(returnTime);\n}\n\nfunction isLibAvailable() {\n return typeof Intl.RelativeTimeFormat !== 'undefined';\n}\n\ntype DateTime = string | number | Date;\n\nexport function createRtfFormatter(\n language: string,\n { floorSeconds = false, extraShortExpression = false } = {},\n options = {},\n) {\n let rtf: Intl.RelativeTimeFormat;\n return (time: DateTime) => {\n if (!rtf && isLibAvailable()) {\n rtf = new Intl.RelativeTimeFormat(language, { ...options });\n }\n let timeDiff = new Date().getTime() - new Date(time).getTime();\n if (isNaN(timeDiff)) {\n console.error(\n `[@wix/fed-cli-i18next/relative-time-formatter] received invalid time: ${time}`,\n );\n timeDiff = MS_PER_SECOND;\n }\n const unit = getTimeUnit(timeDiff);\n const relativeTime = getRelativeTime(timeDiff, unit, floorSeconds);\n\n const shortExpression =\n extraShortExpression || !rtf\n ? applyShortenedExpression(language as LanguageKey, unit, relativeTime)\n : null;\n\n if (shortExpression) {\n return shortExpression;\n }\n\n if (rtf) {\n return rtf.format(relativeTime, unit);\n }\n\n return time;\n };\n}\n"],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@wix/fed-cli-i18next",
|
|
3
|
+
"version": "1.6.38",
|
|
4
|
+
"files": [
|
|
5
|
+
"dist"
|
|
6
|
+
],
|
|
7
|
+
"type": "module",
|
|
8
|
+
"sideEffects": false,
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"module": "./dist/index.js",
|
|
13
|
+
"default": "./dist/index.js"
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"publishConfig": {
|
|
17
|
+
"registry": "https://registry.npmjs.org/",
|
|
18
|
+
"access": "public"
|
|
19
|
+
},
|
|
20
|
+
"scripts": {
|
|
21
|
+
"build": "yarn typecheck && rolldown -c",
|
|
22
|
+
"typecheck": "tsc --noEmit",
|
|
23
|
+
"lint": "oxlint",
|
|
24
|
+
"lint:fix": "oxlint --fix",
|
|
25
|
+
"fmt": "oxfmt",
|
|
26
|
+
"fmt:check": "oxfmt --check",
|
|
27
|
+
"test": "vitest run"
|
|
28
|
+
},
|
|
29
|
+
"dependencies": {
|
|
30
|
+
"i18next": "^26.4.2",
|
|
31
|
+
"intl-messageformat": "^7.8.4",
|
|
32
|
+
"react-i18next": "^15.7.4"
|
|
33
|
+
},
|
|
34
|
+
"devDependencies": {
|
|
35
|
+
"@testing-library/dom": "^10.4.2",
|
|
36
|
+
"@testing-library/react": "^16.3.3",
|
|
37
|
+
"@types/react": "^18.3.31",
|
|
38
|
+
"@types/react-dom": "^18.3.7",
|
|
39
|
+
"@wix/fed-cli-oxlint": "1.0.0",
|
|
40
|
+
"@wix/fed-cli-rolldown": "1.0.0",
|
|
41
|
+
"oxfmt": "^0.58.0",
|
|
42
|
+
"oxlint": "^1.83.0",
|
|
43
|
+
"react": "^18.3.1",
|
|
44
|
+
"react-dom": "^18.3.1",
|
|
45
|
+
"rolldown": "^1.2.8",
|
|
46
|
+
"typescript": "^6.0.3",
|
|
47
|
+
"vitest": "^4.1.11"
|
|
48
|
+
},
|
|
49
|
+
"peerDependencies": {
|
|
50
|
+
"react": "^16 || ^17 || ^18 || ^19",
|
|
51
|
+
"react-dom": "^16 || ^17 || ^18 || ^19"
|
|
52
|
+
},
|
|
53
|
+
"wix": {
|
|
54
|
+
"artifact": {
|
|
55
|
+
"groupId": "com.wixpress.npm",
|
|
56
|
+
"artifactId": "fed-cli-i18next"
|
|
57
|
+
},
|
|
58
|
+
"validations": {
|
|
59
|
+
"sourceOnly": [
|
|
60
|
+
"lint",
|
|
61
|
+
"fmt:check"
|
|
62
|
+
]
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
"falconPackageHash": "f6ecd03a579a299b272812c8f6acf0a20d051dc9662b06cd06a8f366"
|
|
66
|
+
}
|