i18n-typed-store 0.1.0
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/LICENSE +19 -0
- package/README.md +452 -0
- package/dist/index.d.mts +64 -0
- package/dist/index.d.ts +64 -0
- package/dist/index.js +63 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +59 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +49 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
Copyright (c) 2025 Alexander Lvov
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
4
|
+
of this software and associated documentation files (the “Software”), to deal
|
|
5
|
+
in the Software without restriction, including without limitation the rights
|
|
6
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
7
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
8
|
+
furnished to do so, subject to the following conditions:
|
|
9
|
+
|
|
10
|
+
The above copyright notice and this permission notice shall be included in all
|
|
11
|
+
copies or substantial portions of the Software.
|
|
12
|
+
|
|
13
|
+
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
16
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
17
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
18
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
19
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,452 @@
|
|
|
1
|
+
# i18n-typed-store
|
|
2
|
+
|
|
3
|
+
Type-safe translation store for managing i18n locales with full TypeScript support. A lightweight, zero-dependency library for handling internationalization with compile-time type safety.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- ✅ **Full TypeScript support** - Complete type safety for translations and locales
|
|
8
|
+
- ✅ **Lazy loading** - Load translations only when needed
|
|
9
|
+
- ✅ **Type-safe API** - Compile-time validation of translation keys and locales
|
|
10
|
+
- ✅ **Pluralization support** - Built-in plural form selector using `Intl.PluralRules`
|
|
11
|
+
- ✅ **Flexible module loading** - Support for any module format (ESM, CommonJS, dynamic imports)
|
|
12
|
+
- ✅ **Zero runtime dependencies** - Lightweight and framework-agnostic
|
|
13
|
+
|
|
14
|
+
## Installation
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
npm install i18n-typed-store
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
yarn add i18n-typed-store
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
pnpm add i18n-typed-store
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Quick Start
|
|
29
|
+
|
|
30
|
+
### Basic Usage
|
|
31
|
+
|
|
32
|
+
```typescript
|
|
33
|
+
import { createTranslationStore } from 'i18n-typed-store';
|
|
34
|
+
|
|
35
|
+
// Define your translation keys
|
|
36
|
+
const translations = {
|
|
37
|
+
common: 'common',
|
|
38
|
+
errors: 'errors',
|
|
39
|
+
} as const;
|
|
40
|
+
|
|
41
|
+
// Define your locales
|
|
42
|
+
const locales = {
|
|
43
|
+
en: 'en',
|
|
44
|
+
ru: 'ru',
|
|
45
|
+
} as const;
|
|
46
|
+
|
|
47
|
+
// Define your translation data structure
|
|
48
|
+
type TranslationData = {
|
|
49
|
+
common: {
|
|
50
|
+
title: string;
|
|
51
|
+
description: string;
|
|
52
|
+
};
|
|
53
|
+
errors: {
|
|
54
|
+
notFound: string;
|
|
55
|
+
};
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
// Create the store
|
|
59
|
+
const storeFactory = createTranslationStore(
|
|
60
|
+
translations,
|
|
61
|
+
locales,
|
|
62
|
+
async (locale, translation) => {
|
|
63
|
+
// Load translation module dynamically
|
|
64
|
+
const module = await import(`./locales/${locale}/${translation}.json`);
|
|
65
|
+
return module.default;
|
|
66
|
+
},
|
|
67
|
+
(module, locale, translation) => module // Extract translation data
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
// Create typed store
|
|
71
|
+
const store = storeFactory.type<TranslationData>();
|
|
72
|
+
|
|
73
|
+
// Load and use translations
|
|
74
|
+
await store.common.load('en');
|
|
75
|
+
console.log(store.common.translation?.title); // Type-safe access
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## Core API
|
|
79
|
+
|
|
80
|
+
### `createTranslationStore`
|
|
81
|
+
|
|
82
|
+
Creates a type-safe translation store with lazy loading support.
|
|
83
|
+
|
|
84
|
+
```typescript
|
|
85
|
+
const storeFactory = createTranslationStore<T, L, Module>(
|
|
86
|
+
translations: T,
|
|
87
|
+
locales: L,
|
|
88
|
+
loadModule: (locale: keyof L, translation: keyof T) => Promise<Module>,
|
|
89
|
+
extractTranslation: (module: Module, locale: keyof L, translation: keyof T) => unknown
|
|
90
|
+
);
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
**Parameters:**
|
|
94
|
+
|
|
95
|
+
- `translations` - Object with translation keys (e.g., `{ common: 'common', errors: 'errors' }`)
|
|
96
|
+
- `locales` - Object with locale keys (e.g., `{ en: 'en', ru: 'ru' }`)
|
|
97
|
+
- `loadModule` - Async function to load a translation module
|
|
98
|
+
- `extractTranslation` - Function to extract translation data from the loaded module. Receives the module, locale, and translation key as parameters, allowing for locale-specific or translation-specific extraction logic.
|
|
99
|
+
|
|
100
|
+
**Returns:** Object with `type<M>()` method that creates a typed store.
|
|
101
|
+
|
|
102
|
+
**Example:**
|
|
103
|
+
|
|
104
|
+
```typescript
|
|
105
|
+
const storeFactory = createTranslationStore(
|
|
106
|
+
{ common: 'common' },
|
|
107
|
+
{ en: 'en', ru: 'ru' },
|
|
108
|
+
async (locale, translation) => {
|
|
109
|
+
return await import(`./locales/${locale}/${translation}.json`);
|
|
110
|
+
},
|
|
111
|
+
(module, locale, translation) => module.default
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
type TranslationData = {
|
|
115
|
+
common: { title: string; description: string };
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
const store = storeFactory.type<TranslationData>();
|
|
119
|
+
|
|
120
|
+
// Load translation
|
|
121
|
+
await store.common.load('en');
|
|
122
|
+
|
|
123
|
+
// Access translation (type-safe)
|
|
124
|
+
const title = store.common.translation?.title;
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
### `createTranslationModuleMap`
|
|
128
|
+
|
|
129
|
+
Creates a map of translation module loaders for all combinations of translations and locales.
|
|
130
|
+
|
|
131
|
+
```typescript
|
|
132
|
+
const moduleMap = createTranslationModuleMap<T, L, Module>(
|
|
133
|
+
translations: T,
|
|
134
|
+
locales: L,
|
|
135
|
+
loadModule: (locale: keyof L, translation: keyof T) => Promise<Module>
|
|
136
|
+
);
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
**Example:**
|
|
140
|
+
|
|
141
|
+
```typescript
|
|
142
|
+
const moduleMap = createTranslationModuleMap(
|
|
143
|
+
{ common: 'common' },
|
|
144
|
+
{ en: 'en', ru: 'ru' },
|
|
145
|
+
async (locale, translation) => {
|
|
146
|
+
return await import(`./locales/${locale}/${translation}.json`);
|
|
147
|
+
}
|
|
148
|
+
);
|
|
149
|
+
|
|
150
|
+
// Access loader
|
|
151
|
+
const loader = moduleMap.common.en;
|
|
152
|
+
const module = await loader();
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
### `createPluralSelector`
|
|
156
|
+
|
|
157
|
+
Creates a plural form selector function for a specific locale using `Intl.PluralRules`.
|
|
158
|
+
|
|
159
|
+
```typescript
|
|
160
|
+
const selectPlural = createPluralSelector(locale: string);
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
**Example:**
|
|
164
|
+
|
|
165
|
+
```typescript
|
|
166
|
+
import { createPluralSelector } from 'i18n-typed-store';
|
|
167
|
+
import type { PluralVariants } from 'i18n-typed-store';
|
|
168
|
+
|
|
169
|
+
const selectPlural = createPluralSelector('en');
|
|
170
|
+
|
|
171
|
+
const variants: PluralVariants = {
|
|
172
|
+
one: 'item',
|
|
173
|
+
other: 'items',
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
selectPlural(1, variants); // => 'item'
|
|
177
|
+
selectPlural(5, variants); // => 'items'
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
**Russian example:**
|
|
181
|
+
|
|
182
|
+
```typescript
|
|
183
|
+
const selectPlural = createPluralSelector('ru');
|
|
184
|
+
|
|
185
|
+
const variants: PluralVariants = {
|
|
186
|
+
one: 'яблоко',
|
|
187
|
+
few: 'яблока',
|
|
188
|
+
many: 'яблок',
|
|
189
|
+
other: 'яблок',
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
selectPlural(1, variants); // => 'яблоко'
|
|
193
|
+
selectPlural(2, variants); // => 'яблока'
|
|
194
|
+
selectPlural(5, variants); // => 'яблок'
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
## Advanced Usage
|
|
198
|
+
|
|
199
|
+
### Working with Dynamic Imports
|
|
200
|
+
|
|
201
|
+
```typescript
|
|
202
|
+
const storeFactory = createTranslationStore(
|
|
203
|
+
translations,
|
|
204
|
+
locales,
|
|
205
|
+
async (locale, translation) => {
|
|
206
|
+
// Dynamic import with error handling
|
|
207
|
+
try {
|
|
208
|
+
const module = await import(
|
|
209
|
+
`./locales/${locale}/${translation}.json`
|
|
210
|
+
);
|
|
211
|
+
return module.default;
|
|
212
|
+
} catch (error) {
|
|
213
|
+
console.error(`Failed to load ${translation} for ${locale}`);
|
|
214
|
+
throw error;
|
|
215
|
+
}
|
|
216
|
+
},
|
|
217
|
+
(module, locale, translation) => module
|
|
218
|
+
);
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
### Custom Module Extraction
|
|
222
|
+
|
|
223
|
+
The `extractTranslation` function receives the module, locale, and translation key, allowing for advanced extraction logic:
|
|
224
|
+
|
|
225
|
+
```typescript
|
|
226
|
+
const storeFactory = createTranslationStore(
|
|
227
|
+
translations,
|
|
228
|
+
locales,
|
|
229
|
+
async (locale, translation) => {
|
|
230
|
+
// Load module that exports default
|
|
231
|
+
return await import(`./locales/${locale}/${translation}.ts`);
|
|
232
|
+
},
|
|
233
|
+
(module, locale, translation) => {
|
|
234
|
+
// Extract from module.default or module
|
|
235
|
+
// You can use locale and translation parameters for custom logic
|
|
236
|
+
if (locale === 'en' && translation === 'common') {
|
|
237
|
+
// Special handling for English common translations
|
|
238
|
+
return module.default?.en || module.default;
|
|
239
|
+
}
|
|
240
|
+
return module.default || module;
|
|
241
|
+
}
|
|
242
|
+
);
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
### Handling Multiple Translation Namespaces
|
|
246
|
+
|
|
247
|
+
```typescript
|
|
248
|
+
const translations = {
|
|
249
|
+
common: 'common',
|
|
250
|
+
errors: 'errors',
|
|
251
|
+
ui: 'ui',
|
|
252
|
+
admin: 'admin',
|
|
253
|
+
} as const;
|
|
254
|
+
|
|
255
|
+
type TranslationData = {
|
|
256
|
+
common: { title: string };
|
|
257
|
+
errors: { notFound: string };
|
|
258
|
+
ui: { buttons: { save: string } };
|
|
259
|
+
admin: { dashboard: { title: string } };
|
|
260
|
+
};
|
|
261
|
+
|
|
262
|
+
const store = storeFactory.type<TranslationData>();
|
|
263
|
+
|
|
264
|
+
// Load specific translations
|
|
265
|
+
await store.common.load('en');
|
|
266
|
+
await store.ui.load('en');
|
|
267
|
+
|
|
268
|
+
// Access translations
|
|
269
|
+
const title = store.common.translation?.title;
|
|
270
|
+
const saveButton = store.ui.translation?.buttons.save;
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
## Type Safety
|
|
274
|
+
|
|
275
|
+
The library provides complete type safety:
|
|
276
|
+
|
|
277
|
+
```typescript
|
|
278
|
+
// ✅ TypeScript knows all available translation keys
|
|
279
|
+
const title = store.common.translation?.title;
|
|
280
|
+
|
|
281
|
+
// ❌ TypeScript error: 'invalidKey' doesn't exist
|
|
282
|
+
const invalid = store.common.translation?.invalidKey;
|
|
283
|
+
|
|
284
|
+
// ✅ TypeScript knows all available locales
|
|
285
|
+
await store.common.load('en');
|
|
286
|
+
|
|
287
|
+
// ❌ TypeScript error: 'fr' is not a valid locale
|
|
288
|
+
await store.common.load('fr');
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
## Pluralization
|
|
292
|
+
|
|
293
|
+
The library uses `Intl.PluralRules` for plural form selection, supporting all Unicode CLDR plural rules:
|
|
294
|
+
|
|
295
|
+
- `zero` - For languages with explicit zero form (e.g., Arabic)
|
|
296
|
+
- `one` - Singular form
|
|
297
|
+
- `two` - Dual form (e.g., Arabic, Slovenian)
|
|
298
|
+
- `few` - Few form (e.g., Russian, Polish)
|
|
299
|
+
- `many` - Many form (e.g., Russian, Polish)
|
|
300
|
+
- `other` - Default/plural form
|
|
301
|
+
|
|
302
|
+
**Supported locales:**
|
|
303
|
+
|
|
304
|
+
- English, German, French, Spanish, etc. (one/other)
|
|
305
|
+
- Russian, Ukrainian, Serbian, etc. (one/few/many/other)
|
|
306
|
+
- Polish (one/few/many/other)
|
|
307
|
+
- Arabic (zero/one/two/few/many/other)
|
|
308
|
+
- And many more...
|
|
309
|
+
|
|
310
|
+
## API Reference
|
|
311
|
+
|
|
312
|
+
### `createTranslationStore`
|
|
313
|
+
|
|
314
|
+
```typescript
|
|
315
|
+
function createTranslationStore<
|
|
316
|
+
T extends Record<string, string>,
|
|
317
|
+
L extends Record<string, string>,
|
|
318
|
+
Module = unknown
|
|
319
|
+
>(
|
|
320
|
+
translations: T,
|
|
321
|
+
locales: L,
|
|
322
|
+
loadModule: (locale: keyof L, translation: keyof T) => Promise<Module>,
|
|
323
|
+
extractTranslation: (module: Module, locale: keyof L, translation: keyof T) => unknown
|
|
324
|
+
): {
|
|
325
|
+
type<M extends { [K in keyof T]: Record<string, unknown> }>(): TranslationStore<T, L, M>;
|
|
326
|
+
}
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
### `createTranslationModuleMap`
|
|
330
|
+
|
|
331
|
+
```typescript
|
|
332
|
+
function createTranslationModuleMap<
|
|
333
|
+
T extends Record<string, string>,
|
|
334
|
+
L extends Record<string, string>,
|
|
335
|
+
Module = unknown
|
|
336
|
+
>(
|
|
337
|
+
translations: T,
|
|
338
|
+
locales: L,
|
|
339
|
+
loadModule: (locale: keyof L, translation: keyof T) => Promise<Module>
|
|
340
|
+
): Record<keyof T, Record<keyof L, () => Promise<Module>>>
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
### `createPluralSelector`
|
|
344
|
+
|
|
345
|
+
```typescript
|
|
346
|
+
function createPluralSelector(locale: string): (
|
|
347
|
+
count: number,
|
|
348
|
+
variants: PluralVariants
|
|
349
|
+
) => string
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
### `PluralVariants`
|
|
353
|
+
|
|
354
|
+
```typescript
|
|
355
|
+
type PluralVariants = {
|
|
356
|
+
zero?: string;
|
|
357
|
+
one?: string;
|
|
358
|
+
two?: string;
|
|
359
|
+
few?: string;
|
|
360
|
+
many?: string;
|
|
361
|
+
other?: string;
|
|
362
|
+
};
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
## Examples
|
|
366
|
+
|
|
367
|
+
### Example: E-commerce Application
|
|
368
|
+
|
|
369
|
+
```typescript
|
|
370
|
+
const translations = {
|
|
371
|
+
products: 'products',
|
|
372
|
+
cart: 'cart',
|
|
373
|
+
checkout: 'checkout',
|
|
374
|
+
} as const;
|
|
375
|
+
|
|
376
|
+
const locales = {
|
|
377
|
+
en: 'en',
|
|
378
|
+
ru: 'ru',
|
|
379
|
+
de: 'de',
|
|
380
|
+
} as const;
|
|
381
|
+
|
|
382
|
+
type TranslationData = {
|
|
383
|
+
products: {
|
|
384
|
+
title: string;
|
|
385
|
+
addToCart: string;
|
|
386
|
+
price: string;
|
|
387
|
+
};
|
|
388
|
+
cart: {
|
|
389
|
+
title: string;
|
|
390
|
+
empty: string;
|
|
391
|
+
total: string;
|
|
392
|
+
};
|
|
393
|
+
checkout: {
|
|
394
|
+
title: string;
|
|
395
|
+
placeOrder: string;
|
|
396
|
+
};
|
|
397
|
+
};
|
|
398
|
+
|
|
399
|
+
const storeFactory = createTranslationStore(
|
|
400
|
+
translations,
|
|
401
|
+
locales,
|
|
402
|
+
async (locale, translation) => {
|
|
403
|
+
return await import(`./locales/${locale}/${translation}.json`);
|
|
404
|
+
},
|
|
405
|
+
(module, locale, translation) => module.default
|
|
406
|
+
);
|
|
407
|
+
|
|
408
|
+
const store = storeFactory.type<TranslationData>();
|
|
409
|
+
|
|
410
|
+
// Load translations
|
|
411
|
+
await store.products.load('en');
|
|
412
|
+
await store.cart.load('en');
|
|
413
|
+
|
|
414
|
+
// Use translations
|
|
415
|
+
const productTitle = store.products.translation?.title;
|
|
416
|
+
const cartTitle = store.cart.translation?.title;
|
|
417
|
+
```
|
|
418
|
+
|
|
419
|
+
### Example: Pluralization in Product List
|
|
420
|
+
|
|
421
|
+
```typescript
|
|
422
|
+
import { createPluralSelector } from 'i18n-typed-store';
|
|
423
|
+
|
|
424
|
+
const selectPlural = createPluralSelector('en');
|
|
425
|
+
|
|
426
|
+
function getProductCountText(count: number): string {
|
|
427
|
+
return selectPlural(count, {
|
|
428
|
+
one: `${count} product`,
|
|
429
|
+
other: `${count} products`,
|
|
430
|
+
});
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
// Usage
|
|
434
|
+
getProductCountText(1); // => "1 product"
|
|
435
|
+
getProductCountText(5); // => "5 products"
|
|
436
|
+
```
|
|
437
|
+
|
|
438
|
+
## Contributing
|
|
439
|
+
|
|
440
|
+
Contributions are welcome! Please feel free to submit a Pull Request.
|
|
441
|
+
|
|
442
|
+
## License
|
|
443
|
+
|
|
444
|
+
MIT
|
|
445
|
+
|
|
446
|
+
## Author
|
|
447
|
+
|
|
448
|
+
Alexander Lvov
|
|
449
|
+
|
|
450
|
+
## Repository
|
|
451
|
+
|
|
452
|
+
[GitHub](https://github.com/ialexanderlvov/i18n-typed-store)
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Creates a map of translation module loaders for all combinations of translations and locales.
|
|
3
|
+
*
|
|
4
|
+
* @param translations - Object with translation keys
|
|
5
|
+
* @param locales - Object with locale keys
|
|
6
|
+
* @param loadModule - Function to load a translation module for a specific locale and translation
|
|
7
|
+
* @returns Map where each translation key contains an object with loader functions for each locale
|
|
8
|
+
*/
|
|
9
|
+
declare const createTranslationModuleMap: <T extends Record<string, string>, L extends Record<string, string>, Module = unknown>(translations: T, locales: L, loadModule: (locale: keyof L, translation: keyof T) => Promise<Module>) => Record<keyof T, Record<keyof L, () => Promise<Module>>>;
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Creates a translation store with typed translations for different locales.
|
|
13
|
+
*
|
|
14
|
+
* @param translations - Object with translation keys
|
|
15
|
+
* @param locales - Object with locale keys
|
|
16
|
+
* @param loadModule - Function to load a translation module
|
|
17
|
+
* @param extractTranslation - Function to extract translation data from the loaded module.
|
|
18
|
+
* Receives three parameters: (module, locale, translation) allowing for locale-specific
|
|
19
|
+
* or translation-specific extraction logic.
|
|
20
|
+
* @returns Object with a type() method for creating a typed translation store
|
|
21
|
+
*/
|
|
22
|
+
declare const createTranslationStore: <T extends Record<string, string>, L extends Record<string, string>, Module = unknown>(translations: T, locales: L, loadModule: (locale: keyof L, translation: keyof T) => Promise<Module>, extractTranslation: (module: Module, locale: keyof L, translation: keyof T) => unknown) => {
|
|
23
|
+
/**
|
|
24
|
+
* Creates a typed translation store.
|
|
25
|
+
*
|
|
26
|
+
* @template M - Type of translation object where each key corresponds to a key from translations
|
|
27
|
+
* @returns Store with methods to load translations for each locale
|
|
28
|
+
*/
|
|
29
|
+
type: <M extends { [K in keyof T]: any; }>() => { [K in keyof T]: {
|
|
30
|
+
translation?: M[K];
|
|
31
|
+
load: (locale: keyof L) => Promise<void>;
|
|
32
|
+
}; };
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Plural form variants for different plural categories.
|
|
37
|
+
* Based on Unicode CLDR plural rules: zero, one, two, few, many, other.
|
|
38
|
+
*/
|
|
39
|
+
type PluralVariants = {
|
|
40
|
+
zero?: string;
|
|
41
|
+
one?: string;
|
|
42
|
+
two?: string;
|
|
43
|
+
few?: string;
|
|
44
|
+
many?: string;
|
|
45
|
+
other?: string;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Creates a plural selector function for a specific locale.
|
|
50
|
+
* The returned function selects the appropriate plural form based on the count.
|
|
51
|
+
*
|
|
52
|
+
* @param locale - Locale string (e.g., 'en', 'ru', 'fr')
|
|
53
|
+
* @returns Function that takes a count and plural variants, returns the matching variant
|
|
54
|
+
*
|
|
55
|
+
* @example
|
|
56
|
+
* ```ts
|
|
57
|
+
* const selectPlural = createPluralSelector('en');
|
|
58
|
+
* selectPlural(1, { one: 'item', other: 'items' }); // => 'item'
|
|
59
|
+
* selectPlural(5, { one: 'item', other: 'items' }); // => 'items'
|
|
60
|
+
* ```
|
|
61
|
+
*/
|
|
62
|
+
declare const createPluralSelector: (locale: string) => (count: number, variants: PluralVariants) => string;
|
|
63
|
+
|
|
64
|
+
export { createPluralSelector, createTranslationModuleMap, createTranslationStore };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Creates a map of translation module loaders for all combinations of translations and locales.
|
|
3
|
+
*
|
|
4
|
+
* @param translations - Object with translation keys
|
|
5
|
+
* @param locales - Object with locale keys
|
|
6
|
+
* @param loadModule - Function to load a translation module for a specific locale and translation
|
|
7
|
+
* @returns Map where each translation key contains an object with loader functions for each locale
|
|
8
|
+
*/
|
|
9
|
+
declare const createTranslationModuleMap: <T extends Record<string, string>, L extends Record<string, string>, Module = unknown>(translations: T, locales: L, loadModule: (locale: keyof L, translation: keyof T) => Promise<Module>) => Record<keyof T, Record<keyof L, () => Promise<Module>>>;
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Creates a translation store with typed translations for different locales.
|
|
13
|
+
*
|
|
14
|
+
* @param translations - Object with translation keys
|
|
15
|
+
* @param locales - Object with locale keys
|
|
16
|
+
* @param loadModule - Function to load a translation module
|
|
17
|
+
* @param extractTranslation - Function to extract translation data from the loaded module.
|
|
18
|
+
* Receives three parameters: (module, locale, translation) allowing for locale-specific
|
|
19
|
+
* or translation-specific extraction logic.
|
|
20
|
+
* @returns Object with a type() method for creating a typed translation store
|
|
21
|
+
*/
|
|
22
|
+
declare const createTranslationStore: <T extends Record<string, string>, L extends Record<string, string>, Module = unknown>(translations: T, locales: L, loadModule: (locale: keyof L, translation: keyof T) => Promise<Module>, extractTranslation: (module: Module, locale: keyof L, translation: keyof T) => unknown) => {
|
|
23
|
+
/**
|
|
24
|
+
* Creates a typed translation store.
|
|
25
|
+
*
|
|
26
|
+
* @template M - Type of translation object where each key corresponds to a key from translations
|
|
27
|
+
* @returns Store with methods to load translations for each locale
|
|
28
|
+
*/
|
|
29
|
+
type: <M extends { [K in keyof T]: any; }>() => { [K in keyof T]: {
|
|
30
|
+
translation?: M[K];
|
|
31
|
+
load: (locale: keyof L) => Promise<void>;
|
|
32
|
+
}; };
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Plural form variants for different plural categories.
|
|
37
|
+
* Based on Unicode CLDR plural rules: zero, one, two, few, many, other.
|
|
38
|
+
*/
|
|
39
|
+
type PluralVariants = {
|
|
40
|
+
zero?: string;
|
|
41
|
+
one?: string;
|
|
42
|
+
two?: string;
|
|
43
|
+
few?: string;
|
|
44
|
+
many?: string;
|
|
45
|
+
other?: string;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Creates a plural selector function for a specific locale.
|
|
50
|
+
* The returned function selects the appropriate plural form based on the count.
|
|
51
|
+
*
|
|
52
|
+
* @param locale - Locale string (e.g., 'en', 'ru', 'fr')
|
|
53
|
+
* @returns Function that takes a count and plural variants, returns the matching variant
|
|
54
|
+
*
|
|
55
|
+
* @example
|
|
56
|
+
* ```ts
|
|
57
|
+
* const selectPlural = createPluralSelector('en');
|
|
58
|
+
* selectPlural(1, { one: 'item', other: 'items' }); // => 'item'
|
|
59
|
+
* selectPlural(5, { one: 'item', other: 'items' }); // => 'items'
|
|
60
|
+
* ```
|
|
61
|
+
*/
|
|
62
|
+
declare const createPluralSelector: (locale: string) => (count: number, variants: PluralVariants) => string;
|
|
63
|
+
|
|
64
|
+
export { createPluralSelector, createTranslationModuleMap, createTranslationStore };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// src/utils/create-translation-module-map.ts
|
|
4
|
+
var createTranslationModuleMap = (translations, locales, loadModule) => {
|
|
5
|
+
const translationModules = {};
|
|
6
|
+
for (const translationKey of Object.keys(translations)) {
|
|
7
|
+
translationModules[translationKey] = {};
|
|
8
|
+
for (const localeKey of Object.keys(locales)) {
|
|
9
|
+
translationModules[translationKey][localeKey] = () => loadModule(localeKey, translationKey);
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
return translationModules;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
// src/utils/create-translation-store.ts
|
|
16
|
+
var createTranslationStore = (translations, locales, loadModule, extractTranslation) => {
|
|
17
|
+
return {
|
|
18
|
+
/**
|
|
19
|
+
* Creates a typed translation store.
|
|
20
|
+
*
|
|
21
|
+
* @template M - Type of translation object where each key corresponds to a key from translations
|
|
22
|
+
* @returns Store with methods to load translations for each locale
|
|
23
|
+
*/
|
|
24
|
+
type: () => {
|
|
25
|
+
const translationModuleMap = createTranslationModuleMap(translations, locales, loadModule);
|
|
26
|
+
const store = {};
|
|
27
|
+
for (const translationKey of Object.keys(translations)) {
|
|
28
|
+
store[translationKey] = {
|
|
29
|
+
translation: void 0,
|
|
30
|
+
load: async (locale) => {
|
|
31
|
+
const moduleLoader = translationModuleMap[translationKey][locale];
|
|
32
|
+
const loadedModule = await moduleLoader();
|
|
33
|
+
store[translationKey].translation = extractTranslation(
|
|
34
|
+
loadedModule,
|
|
35
|
+
locale,
|
|
36
|
+
translationKey
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
return store;
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
// src/utils/create-plural-selector.ts
|
|
47
|
+
var createPluralSelector = (locale) => {
|
|
48
|
+
const pluralRules = new Intl.PluralRules(locale);
|
|
49
|
+
return (count, variants) => {
|
|
50
|
+
const pluralCategory = pluralRules.select(count);
|
|
51
|
+
const selectedVariant = variants[pluralCategory];
|
|
52
|
+
if (selectedVariant) {
|
|
53
|
+
return selectedVariant;
|
|
54
|
+
}
|
|
55
|
+
return variants.other ?? "";
|
|
56
|
+
};
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
exports.createPluralSelector = createPluralSelector;
|
|
60
|
+
exports.createTranslationModuleMap = createTranslationModuleMap;
|
|
61
|
+
exports.createTranslationStore = createTranslationStore;
|
|
62
|
+
//# sourceMappingURL=index.js.map
|
|
63
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/utils/create-translation-module-map.ts","../src/utils/create-translation-store.ts","../src/utils/create-plural-selector.ts"],"names":[],"mappings":";;;AAQO,IAAM,0BAAA,GAA6B,CACzC,YAAA,EACA,OAAA,EACA,UAAA,KACI;AAGJ,EAAA,MAAM,qBAAqB,EAAC;AAE5B,EAAA,KAAA,MAAW,cAAA,IAAkB,MAAA,CAAO,IAAA,CAAK,YAAY,CAAA,EAAkB;AACtE,IAAA,kBAAA,CAAmB,cAAc,IAAI,EAAC;AAEtC,IAAA,KAAA,MAAW,SAAA,IAAa,MAAA,CAAO,IAAA,CAAK,OAAO,CAAA,EAAkB;AAC5D,MAAA,kBAAA,CAAmB,cAAc,CAAA,CAAE,SAAS,IAAI,MAAM,UAAA,CAAW,WAAW,cAAc,CAAA;AAAA,IAC3F;AAAA,EACD;AAEA,EAAA,OAAO,kBAAA;AACR;;;ACbO,IAAM,sBAAA,GAAyB,CACrC,YAAA,EACA,OAAA,EACA,YACA,kBAAA,KACI;AACJ,EAAA,OAAO;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAON,MAAM,MAAyC;AAC9C,MAAA,MAAM,oBAAA,GAAuB,0BAAA,CAA2B,YAAA,EAAc,OAAA,EAAS,UAAU,CAAA;AASzF,MAAA,MAAM,QAAQ,EAAC;AAEf,MAAA,KAAA,MAAW,cAAA,IAAkB,MAAA,CAAO,IAAA,CAAK,YAAY,CAAA,EAAkB;AACtE,QAAA,KAAA,CAAM,cAAc,CAAA,GAAI;AAAA,UACvB,WAAA,EAAa,MAAA;AAAA,UACb,IAAA,EAAM,OAAO,MAAA,KAAoB;AAChC,YAAA,MAAM,YAAA,GAAe,oBAAA,CAAqB,cAAc,CAAA,CAAE,MAAM,CAAA;AAChE,YAAA,MAAM,YAAA,GAAe,MAAM,YAAA,EAAa;AACxC,YAAA,KAAA,CAAM,cAAc,EAAE,WAAA,GAAc,kBAAA;AAAA,cACnC,YAAA;AAAA,cACA,MAAA;AAAA,cACA;AAAA,aACD;AAAA,UACD;AAAA,SACD;AAAA,MACD;AAEA,MAAA,OAAO,KAAA;AAAA,IACR;AAAA,GACD;AACD;;;ACxCO,IAAM,oBAAA,GAAuB,CAAC,MAAA,KAAmB;AACvD,EAAA,MAAM,WAAA,GAAc,IAAI,IAAA,CAAK,WAAA,CAAY,MAAM,CAAA;AAS/C,EAAA,OAAO,CAAC,OAAe,QAAA,KAAqC;AAC3D,IAAA,MAAM,cAAA,GAAiB,WAAA,CAAY,MAAA,CAAO,KAAK,CAAA;AAC/C,IAAA,MAAM,eAAA,GAAkB,SAAS,cAAc,CAAA;AAE/C,IAAA,IAAI,eAAA,EAAiB;AACpB,MAAA,OAAO,eAAA;AAAA,IACR;AAGA,IAAA,OAAO,SAAS,KAAA,IAAS,EAAA;AAAA,EAC1B,CAAA;AACD","file":"index.js","sourcesContent":["/**\n * Creates a map of translation module loaders for all combinations of translations and locales.\n *\n * @param translations - Object with translation keys\n * @param locales - Object with locale keys\n * @param loadModule - Function to load a translation module for a specific locale and translation\n * @returns Map where each translation key contains an object with loader functions for each locale\n */\nexport const createTranslationModuleMap = <T extends Record<string, string>, L extends Record<string, string>, Module = unknown>(\n\ttranslations: T,\n\tlocales: L,\n\tloadModule: (locale: keyof L, translation: keyof T) => Promise<Module>,\n) => {\n\ttype TranslationLoadModules = Record<keyof T, Record<keyof L, () => Promise<Module>>>;\n\n\tconst translationModules = {} as TranslationLoadModules;\n\n\tfor (const translationKey of Object.keys(translations) as (keyof T)[]) {\n\t\ttranslationModules[translationKey] = {} as TranslationLoadModules[keyof T];\n\n\t\tfor (const localeKey of Object.keys(locales) as (keyof L)[]) {\n\t\t\ttranslationModules[translationKey][localeKey] = () => loadModule(localeKey, translationKey);\n\t\t}\n\t}\n\n\treturn translationModules;\n};\n","import { createTranslationModuleMap } from './create-translation-module-map';\n\n/**\n * Creates a translation store with typed translations for different locales.\n *\n * @param translations - Object with translation keys\n * @param locales - Object with locale keys\n * @param loadModule - Function to load a translation module\n * @param extractTranslation - Function to extract translation data from the loaded module.\n * Receives three parameters: (module, locale, translation) allowing for locale-specific\n * or translation-specific extraction logic.\n * @returns Object with a type() method for creating a typed translation store\n */\nexport const createTranslationStore = <T extends Record<string, string>, L extends Record<string, string>, Module = unknown>(\n\ttranslations: T,\n\tlocales: L,\n\tloadModule: (locale: keyof L, translation: keyof T) => Promise<Module>,\n\textractTranslation: (module: Module, locale: keyof L, translation: keyof T) => unknown,\n) => {\n\treturn {\n\t\t/**\n\t\t * Creates a typed translation store.\n\t\t *\n\t\t * @template M - Type of translation object where each key corresponds to a key from translations\n\t\t * @returns Store with methods to load translations for each locale\n\t\t */\n\t\ttype: <M extends { [K in keyof T]: any }>() => {\n\t\t\tconst translationModuleMap = createTranslationModuleMap(translations, locales, loadModule);\n\n\t\t\ttype TranslationStore = {\n\t\t\t\t[K in keyof T]: {\n\t\t\t\t\ttranslation?: M[K];\n\t\t\t\t\tload: (locale: keyof L) => Promise<void>;\n\t\t\t\t};\n\t\t\t};\n\n\t\t\tconst store = {} as TranslationStore;\n\n\t\t\tfor (const translationKey of Object.keys(translations) as (keyof T)[]) {\n\t\t\t\tstore[translationKey] = {\n\t\t\t\t\ttranslation: undefined,\n\t\t\t\t\tload: async (locale: keyof L) => {\n\t\t\t\t\t\tconst moduleLoader = translationModuleMap[translationKey][locale];\n\t\t\t\t\t\tconst loadedModule = await moduleLoader();\n\t\t\t\t\t\tstore[translationKey].translation = extractTranslation(\n\t\t\t\t\t\t\tloadedModule,\n\t\t\t\t\t\t\tlocale,\n\t\t\t\t\t\t\ttranslationKey,\n\t\t\t\t\t\t) as M[typeof translationKey];\n\t\t\t\t\t},\n\t\t\t\t};\n\t\t\t}\n\n\t\t\treturn store;\n\t\t},\n\t};\n};\n","import { PluralVariants } from '../types/plural-variants';\n\n/**\n * Creates a plural selector function for a specific locale.\n * The returned function selects the appropriate plural form based on the count.\n *\n * @param locale - Locale string (e.g., 'en', 'ru', 'fr')\n * @returns Function that takes a count and plural variants, returns the matching variant\n *\n * @example\n * ```ts\n * const selectPlural = createPluralSelector('en');\n * selectPlural(1, { one: 'item', other: 'items' }); // => 'item'\n * selectPlural(5, { one: 'item', other: 'items' }); // => 'items'\n * ```\n */\nexport const createPluralSelector = (locale: string) => {\n\tconst pluralRules = new Intl.PluralRules(locale);\n\n\t/**\n\t * Selects the appropriate plural form variant based on the count.\n\t *\n\t * @param count - Number to determine plural form for\n\t * @param variants - Object containing plural form variants\n\t * @returns The selected variant string, or 'other' variant as fallback, or empty string if no variant found\n\t */\n\treturn (count: number, variants: PluralVariants): string => {\n\t\tconst pluralCategory = pluralRules.select(count) as keyof PluralVariants;\n\t\tconst selectedVariant = variants[pluralCategory];\n\n\t\tif (selectedVariant) {\n\t\t\treturn selectedVariant;\n\t\t}\n\n\t\t// Fallback to 'other' if the specific category is not provided\n\t\treturn variants.other ?? '';\n\t};\n};\n"]}
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
// src/utils/create-translation-module-map.ts
|
|
2
|
+
var createTranslationModuleMap = (translations, locales, loadModule) => {
|
|
3
|
+
const translationModules = {};
|
|
4
|
+
for (const translationKey of Object.keys(translations)) {
|
|
5
|
+
translationModules[translationKey] = {};
|
|
6
|
+
for (const localeKey of Object.keys(locales)) {
|
|
7
|
+
translationModules[translationKey][localeKey] = () => loadModule(localeKey, translationKey);
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
return translationModules;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
// src/utils/create-translation-store.ts
|
|
14
|
+
var createTranslationStore = (translations, locales, loadModule, extractTranslation) => {
|
|
15
|
+
return {
|
|
16
|
+
/**
|
|
17
|
+
* Creates a typed translation store.
|
|
18
|
+
*
|
|
19
|
+
* @template M - Type of translation object where each key corresponds to a key from translations
|
|
20
|
+
* @returns Store with methods to load translations for each locale
|
|
21
|
+
*/
|
|
22
|
+
type: () => {
|
|
23
|
+
const translationModuleMap = createTranslationModuleMap(translations, locales, loadModule);
|
|
24
|
+
const store = {};
|
|
25
|
+
for (const translationKey of Object.keys(translations)) {
|
|
26
|
+
store[translationKey] = {
|
|
27
|
+
translation: void 0,
|
|
28
|
+
load: async (locale) => {
|
|
29
|
+
const moduleLoader = translationModuleMap[translationKey][locale];
|
|
30
|
+
const loadedModule = await moduleLoader();
|
|
31
|
+
store[translationKey].translation = extractTranslation(
|
|
32
|
+
loadedModule,
|
|
33
|
+
locale,
|
|
34
|
+
translationKey
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
return store;
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
// src/utils/create-plural-selector.ts
|
|
45
|
+
var createPluralSelector = (locale) => {
|
|
46
|
+
const pluralRules = new Intl.PluralRules(locale);
|
|
47
|
+
return (count, variants) => {
|
|
48
|
+
const pluralCategory = pluralRules.select(count);
|
|
49
|
+
const selectedVariant = variants[pluralCategory];
|
|
50
|
+
if (selectedVariant) {
|
|
51
|
+
return selectedVariant;
|
|
52
|
+
}
|
|
53
|
+
return variants.other ?? "";
|
|
54
|
+
};
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
export { createPluralSelector, createTranslationModuleMap, createTranslationStore };
|
|
58
|
+
//# sourceMappingURL=index.mjs.map
|
|
59
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/utils/create-translation-module-map.ts","../src/utils/create-translation-store.ts","../src/utils/create-plural-selector.ts"],"names":[],"mappings":";AAQO,IAAM,0BAAA,GAA6B,CACzC,YAAA,EACA,OAAA,EACA,UAAA,KACI;AAGJ,EAAA,MAAM,qBAAqB,EAAC;AAE5B,EAAA,KAAA,MAAW,cAAA,IAAkB,MAAA,CAAO,IAAA,CAAK,YAAY,CAAA,EAAkB;AACtE,IAAA,kBAAA,CAAmB,cAAc,IAAI,EAAC;AAEtC,IAAA,KAAA,MAAW,SAAA,IAAa,MAAA,CAAO,IAAA,CAAK,OAAO,CAAA,EAAkB;AAC5D,MAAA,kBAAA,CAAmB,cAAc,CAAA,CAAE,SAAS,IAAI,MAAM,UAAA,CAAW,WAAW,cAAc,CAAA;AAAA,IAC3F;AAAA,EACD;AAEA,EAAA,OAAO,kBAAA;AACR;;;ACbO,IAAM,sBAAA,GAAyB,CACrC,YAAA,EACA,OAAA,EACA,YACA,kBAAA,KACI;AACJ,EAAA,OAAO;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAON,MAAM,MAAyC;AAC9C,MAAA,MAAM,oBAAA,GAAuB,0BAAA,CAA2B,YAAA,EAAc,OAAA,EAAS,UAAU,CAAA;AASzF,MAAA,MAAM,QAAQ,EAAC;AAEf,MAAA,KAAA,MAAW,cAAA,IAAkB,MAAA,CAAO,IAAA,CAAK,YAAY,CAAA,EAAkB;AACtE,QAAA,KAAA,CAAM,cAAc,CAAA,GAAI;AAAA,UACvB,WAAA,EAAa,MAAA;AAAA,UACb,IAAA,EAAM,OAAO,MAAA,KAAoB;AAChC,YAAA,MAAM,YAAA,GAAe,oBAAA,CAAqB,cAAc,CAAA,CAAE,MAAM,CAAA;AAChE,YAAA,MAAM,YAAA,GAAe,MAAM,YAAA,EAAa;AACxC,YAAA,KAAA,CAAM,cAAc,EAAE,WAAA,GAAc,kBAAA;AAAA,cACnC,YAAA;AAAA,cACA,MAAA;AAAA,cACA;AAAA,aACD;AAAA,UACD;AAAA,SACD;AAAA,MACD;AAEA,MAAA,OAAO,KAAA;AAAA,IACR;AAAA,GACD;AACD;;;ACxCO,IAAM,oBAAA,GAAuB,CAAC,MAAA,KAAmB;AACvD,EAAA,MAAM,WAAA,GAAc,IAAI,IAAA,CAAK,WAAA,CAAY,MAAM,CAAA;AAS/C,EAAA,OAAO,CAAC,OAAe,QAAA,KAAqC;AAC3D,IAAA,MAAM,cAAA,GAAiB,WAAA,CAAY,MAAA,CAAO,KAAK,CAAA;AAC/C,IAAA,MAAM,eAAA,GAAkB,SAAS,cAAc,CAAA;AAE/C,IAAA,IAAI,eAAA,EAAiB;AACpB,MAAA,OAAO,eAAA;AAAA,IACR;AAGA,IAAA,OAAO,SAAS,KAAA,IAAS,EAAA;AAAA,EAC1B,CAAA;AACD","file":"index.mjs","sourcesContent":["/**\n * Creates a map of translation module loaders for all combinations of translations and locales.\n *\n * @param translations - Object with translation keys\n * @param locales - Object with locale keys\n * @param loadModule - Function to load a translation module for a specific locale and translation\n * @returns Map where each translation key contains an object with loader functions for each locale\n */\nexport const createTranslationModuleMap = <T extends Record<string, string>, L extends Record<string, string>, Module = unknown>(\n\ttranslations: T,\n\tlocales: L,\n\tloadModule: (locale: keyof L, translation: keyof T) => Promise<Module>,\n) => {\n\ttype TranslationLoadModules = Record<keyof T, Record<keyof L, () => Promise<Module>>>;\n\n\tconst translationModules = {} as TranslationLoadModules;\n\n\tfor (const translationKey of Object.keys(translations) as (keyof T)[]) {\n\t\ttranslationModules[translationKey] = {} as TranslationLoadModules[keyof T];\n\n\t\tfor (const localeKey of Object.keys(locales) as (keyof L)[]) {\n\t\t\ttranslationModules[translationKey][localeKey] = () => loadModule(localeKey, translationKey);\n\t\t}\n\t}\n\n\treturn translationModules;\n};\n","import { createTranslationModuleMap } from './create-translation-module-map';\n\n/**\n * Creates a translation store with typed translations for different locales.\n *\n * @param translations - Object with translation keys\n * @param locales - Object with locale keys\n * @param loadModule - Function to load a translation module\n * @param extractTranslation - Function to extract translation data from the loaded module.\n * Receives three parameters: (module, locale, translation) allowing for locale-specific\n * or translation-specific extraction logic.\n * @returns Object with a type() method for creating a typed translation store\n */\nexport const createTranslationStore = <T extends Record<string, string>, L extends Record<string, string>, Module = unknown>(\n\ttranslations: T,\n\tlocales: L,\n\tloadModule: (locale: keyof L, translation: keyof T) => Promise<Module>,\n\textractTranslation: (module: Module, locale: keyof L, translation: keyof T) => unknown,\n) => {\n\treturn {\n\t\t/**\n\t\t * Creates a typed translation store.\n\t\t *\n\t\t * @template M - Type of translation object where each key corresponds to a key from translations\n\t\t * @returns Store with methods to load translations for each locale\n\t\t */\n\t\ttype: <M extends { [K in keyof T]: any }>() => {\n\t\t\tconst translationModuleMap = createTranslationModuleMap(translations, locales, loadModule);\n\n\t\t\ttype TranslationStore = {\n\t\t\t\t[K in keyof T]: {\n\t\t\t\t\ttranslation?: M[K];\n\t\t\t\t\tload: (locale: keyof L) => Promise<void>;\n\t\t\t\t};\n\t\t\t};\n\n\t\t\tconst store = {} as TranslationStore;\n\n\t\t\tfor (const translationKey of Object.keys(translations) as (keyof T)[]) {\n\t\t\t\tstore[translationKey] = {\n\t\t\t\t\ttranslation: undefined,\n\t\t\t\t\tload: async (locale: keyof L) => {\n\t\t\t\t\t\tconst moduleLoader = translationModuleMap[translationKey][locale];\n\t\t\t\t\t\tconst loadedModule = await moduleLoader();\n\t\t\t\t\t\tstore[translationKey].translation = extractTranslation(\n\t\t\t\t\t\t\tloadedModule,\n\t\t\t\t\t\t\tlocale,\n\t\t\t\t\t\t\ttranslationKey,\n\t\t\t\t\t\t) as M[typeof translationKey];\n\t\t\t\t\t},\n\t\t\t\t};\n\t\t\t}\n\n\t\t\treturn store;\n\t\t},\n\t};\n};\n","import { PluralVariants } from '../types/plural-variants';\n\n/**\n * Creates a plural selector function for a specific locale.\n * The returned function selects the appropriate plural form based on the count.\n *\n * @param locale - Locale string (e.g., 'en', 'ru', 'fr')\n * @returns Function that takes a count and plural variants, returns the matching variant\n *\n * @example\n * ```ts\n * const selectPlural = createPluralSelector('en');\n * selectPlural(1, { one: 'item', other: 'items' }); // => 'item'\n * selectPlural(5, { one: 'item', other: 'items' }); // => 'items'\n * ```\n */\nexport const createPluralSelector = (locale: string) => {\n\tconst pluralRules = new Intl.PluralRules(locale);\n\n\t/**\n\t * Selects the appropriate plural form variant based on the count.\n\t *\n\t * @param count - Number to determine plural form for\n\t * @param variants - Object containing plural form variants\n\t * @returns The selected variant string, or 'other' variant as fallback, or empty string if no variant found\n\t */\n\treturn (count: number, variants: PluralVariants): string => {\n\t\tconst pluralCategory = pluralRules.select(count) as keyof PluralVariants;\n\t\tconst selectedVariant = variants[pluralCategory];\n\n\t\tif (selectedVariant) {\n\t\t\treturn selectedVariant;\n\t\t}\n\n\t\t// Fallback to 'other' if the specific category is not provided\n\t\treturn variants.other ?? '';\n\t};\n};\n"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "i18n-typed-store",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Type-safe translation store for managing i18n locales with full TypeScript support",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"module": "dist/index.mjs",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"files": [
|
|
9
|
+
"dist"
|
|
10
|
+
],
|
|
11
|
+
"scripts": {
|
|
12
|
+
"build": "tsup",
|
|
13
|
+
"test": "vitest",
|
|
14
|
+
"push": "npm run test && npm run build && npm publish"
|
|
15
|
+
},
|
|
16
|
+
"keywords": [
|
|
17
|
+
"i18n",
|
|
18
|
+
"internationalization",
|
|
19
|
+
"localization",
|
|
20
|
+
"locale",
|
|
21
|
+
"translation",
|
|
22
|
+
"typescript",
|
|
23
|
+
"type-safe",
|
|
24
|
+
"typed",
|
|
25
|
+
"store",
|
|
26
|
+
"translations"
|
|
27
|
+
],
|
|
28
|
+
"author": "Alexander Lvov",
|
|
29
|
+
"repository": {
|
|
30
|
+
"type": "git",
|
|
31
|
+
"url": "git+https://github.com/ialexanderlvov/i18n-typed-store.git"
|
|
32
|
+
},
|
|
33
|
+
"license": "MIT",
|
|
34
|
+
"type": "commonjs",
|
|
35
|
+
"devDependencies": {
|
|
36
|
+
"@eslint/js": "^9.39.1",
|
|
37
|
+
"@eslint/json": "^0.14.0",
|
|
38
|
+
"@eslint/markdown": "^7.5.1",
|
|
39
|
+
"@types/node": "^24.10.1",
|
|
40
|
+
"@vitest/coverage-v8": "^4.0.14",
|
|
41
|
+
"eslint": "^9.39.1",
|
|
42
|
+
"globals": "^16.5.0",
|
|
43
|
+
"prettier": "^3.6.2",
|
|
44
|
+
"tsup": "^8.5.1",
|
|
45
|
+
"typescript": "^5.9.3",
|
|
46
|
+
"typescript-eslint": "^8.48.0",
|
|
47
|
+
"vitest": "^4.0.14"
|
|
48
|
+
}
|
|
49
|
+
}
|