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 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)
@@ -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 };
@@ -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
+ }