i18n-typed-store 0.1.1 → 0.1.3
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 +383 -98
- package/package.json +13 -1
package/README.md
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
# i18n-typed-store
|
|
2
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.
|
|
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. Designed to work with TypeScript classes for translations, providing full IDE support (go-to definition, autocomplete) and support for JSX elements and methods in translations.
|
|
4
4
|
|
|
5
5
|
## Features
|
|
6
6
|
|
|
7
7
|
- ✅ **Full TypeScript support** - Complete type safety for translations and locales
|
|
8
|
+
- ✅ **IDE integration** - Go-to definition, autocomplete, and refactoring support with translation classes
|
|
8
9
|
- ✅ **Lazy loading** - Load translations only when needed
|
|
9
10
|
- ✅ **Type-safe API** - Compile-time validation of translation keys and locales
|
|
11
|
+
- ✅ **Translation classes/objects** - Use TypeScript classes or objects for translations with JSX support and methods
|
|
10
12
|
- ✅ **Pluralization support** - Built-in plural form selector using `Intl.PluralRules`
|
|
11
13
|
- ✅ **Flexible module loading** - Support for any module format (ESM, CommonJS, dynamic imports)
|
|
12
14
|
- ✅ **Zero runtime dependencies** - Lightweight and framework-agnostic
|
|
@@ -29,6 +31,17 @@ yarn add i18n-typed-store
|
|
|
29
31
|
pnpm add i18n-typed-store
|
|
30
32
|
```
|
|
31
33
|
|
|
34
|
+
## Example Project
|
|
35
|
+
|
|
36
|
+
For a complete working example, check out the [React example project](https://github.com/ialexanderlvov/i18n-typed-store-react-example):
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
git clone https://github.com/ialexanderlvov/i18n-typed-store-react-example
|
|
40
|
+
cd i18n-typed-store-react-example
|
|
41
|
+
yarn
|
|
42
|
+
yarn dev
|
|
43
|
+
```
|
|
44
|
+
|
|
32
45
|
## Quick Start
|
|
33
46
|
|
|
34
47
|
### Basic Usage
|
|
@@ -36,6 +49,10 @@ pnpm add i18n-typed-store
|
|
|
36
49
|
```typescript
|
|
37
50
|
import { createTranslationStore } from 'i18n-typed-store';
|
|
38
51
|
|
|
52
|
+
// Import translation types for type safety
|
|
53
|
+
import type CommonTranslationsEn from './translations/common/en';
|
|
54
|
+
import type ErrorsTranslationsEn from './translations/errors/en';
|
|
55
|
+
|
|
39
56
|
// Define your translation keys
|
|
40
57
|
const translations = {
|
|
41
58
|
common: 'common',
|
|
@@ -48,29 +65,23 @@ const locales = {
|
|
|
48
65
|
ru: 'ru',
|
|
49
66
|
} as const;
|
|
50
67
|
|
|
51
|
-
// Define your translation data structure
|
|
52
|
-
|
|
53
|
-
common:
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
};
|
|
57
|
-
errors: {
|
|
58
|
-
notFound: string;
|
|
59
|
-
};
|
|
60
|
-
};
|
|
68
|
+
// Define your translation data structure using imported types
|
|
69
|
+
interface TranslationData extends Record<keyof typeof translations, any> {
|
|
70
|
+
common: CommonTranslationsEn;
|
|
71
|
+
errors: ErrorsTranslationsEn;
|
|
72
|
+
}
|
|
61
73
|
|
|
62
74
|
// Create the store factory
|
|
63
75
|
const storeFactory = createTranslationStore({
|
|
64
76
|
translations,
|
|
65
77
|
locales,
|
|
66
78
|
loadModule: async (locale, translation) => {
|
|
67
|
-
// Load translation
|
|
68
|
-
|
|
69
|
-
return module;
|
|
79
|
+
// Load translation class dynamically
|
|
80
|
+
return await import(`./translations/${translation}/${locale}.ts`);
|
|
70
81
|
},
|
|
71
|
-
extractTranslation: (module
|
|
72
|
-
//
|
|
73
|
-
return module.default
|
|
82
|
+
extractTranslation: (module) => {
|
|
83
|
+
// Instantiate the translation class
|
|
84
|
+
return new module.default();
|
|
74
85
|
},
|
|
75
86
|
defaultLocale: 'en',
|
|
76
87
|
useFallback: true,
|
|
@@ -82,33 +93,61 @@ const store = storeFactory.type<TranslationData>();
|
|
|
82
93
|
|
|
83
94
|
// Load and use translations
|
|
84
95
|
await store.translations.common.load('en');
|
|
85
|
-
const title = store.translations.common.currentTranslation?.title; // Type-safe access
|
|
96
|
+
const title = store.translations.common.currentTranslation?.title; // Type-safe access with IDE go-to support
|
|
86
97
|
```
|
|
87
98
|
|
|
88
99
|
### React Usage
|
|
89
100
|
|
|
90
|
-
```
|
|
101
|
+
```typescript
|
|
102
|
+
// constants.ts
|
|
103
|
+
export const TRANSLATIONS = {
|
|
104
|
+
common: 'common',
|
|
105
|
+
} as const;
|
|
106
|
+
|
|
107
|
+
export const LOCALES = {
|
|
108
|
+
en: 'en',
|
|
109
|
+
ru: 'ru',
|
|
110
|
+
} as const;
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
```typescript
|
|
114
|
+
// store.ts
|
|
91
115
|
import { createTranslationStore } from 'i18n-typed-store';
|
|
92
|
-
import
|
|
116
|
+
import type CommonTranslationsEn from './translations/common/en';
|
|
117
|
+
import { TRANSLATIONS, LOCALES } from './constants';
|
|
93
118
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
119
|
+
export interface ITranslationStoreTypes extends Record<keyof typeof TRANSLATIONS, any> {
|
|
120
|
+
common: CommonTranslationsEn;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export const store = createTranslationStore({
|
|
124
|
+
translations: TRANSLATIONS,
|
|
125
|
+
locales: LOCALES,
|
|
98
126
|
loadModule: async (locale, translation) => {
|
|
99
|
-
return await import(`./
|
|
127
|
+
return await import(`./translations/${translation}/${locale}.tsx`);
|
|
100
128
|
},
|
|
101
|
-
extractTranslation: (module) => module.default,
|
|
129
|
+
extractTranslation: (module) => new module.default(),
|
|
102
130
|
defaultLocale: 'en',
|
|
103
|
-
});
|
|
131
|
+
}).type<ITranslationStoreTypes>();
|
|
132
|
+
```
|
|
104
133
|
|
|
105
|
-
|
|
106
|
-
|
|
134
|
+
```typescript
|
|
135
|
+
// hooks/useTranslation.ts
|
|
136
|
+
import { useI18nTranslation } from 'i18n-typed-store/react/useI18nTranslation';
|
|
137
|
+
import type { TRANSLATIONS, LOCALES } from '../constants';
|
|
138
|
+
import type { ITranslationStoreTypes } from '../store';
|
|
139
|
+
|
|
140
|
+
export const useTranslation = <K extends keyof typeof TRANSLATIONS>(translation: K) => {
|
|
141
|
+
return useI18nTranslation<typeof TRANSLATIONS, typeof LOCALES, ITranslationStoreTypes, K>(translation);
|
|
107
142
|
};
|
|
143
|
+
```
|
|
108
144
|
|
|
109
|
-
|
|
145
|
+
```tsx
|
|
146
|
+
// App.tsx
|
|
147
|
+
import { I18nTypedStoreProvider, useI18nLocale } from 'i18n-typed-store/react';
|
|
148
|
+
import { store } from './store';
|
|
149
|
+
import { useTranslation } from './hooks/useTranslation';
|
|
110
150
|
|
|
111
|
-
// In your App component
|
|
112
151
|
function App() {
|
|
113
152
|
return (
|
|
114
153
|
<I18nTypedStoreProvider store={store}>
|
|
@@ -116,10 +155,12 @@ function App() {
|
|
|
116
155
|
</I18nTypedStoreProvider>
|
|
117
156
|
);
|
|
118
157
|
}
|
|
158
|
+
```
|
|
119
159
|
|
|
120
|
-
|
|
160
|
+
```tsx
|
|
161
|
+
// MyComponent.tsx
|
|
121
162
|
function MyComponent() {
|
|
122
|
-
const translations =
|
|
163
|
+
const translations = useTranslation('common');
|
|
123
164
|
const { locale, setLocale } = useI18nLocale();
|
|
124
165
|
|
|
125
166
|
if (!translations) {
|
|
@@ -138,13 +179,24 @@ function MyComponent() {
|
|
|
138
179
|
|
|
139
180
|
### React Suspense Support
|
|
140
181
|
|
|
182
|
+
```typescript
|
|
183
|
+
// hooks/useTranslationLazy.ts
|
|
184
|
+
import { useI18nTranslationLazy } from 'i18n-typed-store/react/useI18nTranslationLazy';
|
|
185
|
+
import type { TRANSLATIONS, LOCALES } from '../constants';
|
|
186
|
+
import type { ITranslationStoreTypes } from '../store';
|
|
187
|
+
|
|
188
|
+
export const useTranslationLazy = <K extends keyof typeof TRANSLATIONS>(translation: K) => {
|
|
189
|
+
return useI18nTranslationLazy<typeof TRANSLATIONS, typeof LOCALES, ITranslationStoreTypes, K>(translation);
|
|
190
|
+
};
|
|
191
|
+
```
|
|
192
|
+
|
|
141
193
|
```tsx
|
|
142
|
-
|
|
143
|
-
import {
|
|
194
|
+
// MyComponent.tsx
|
|
195
|
+
import { useTranslationLazy } from './hooks/useTranslationLazy';
|
|
144
196
|
|
|
145
197
|
function MyComponent() {
|
|
146
198
|
// This hook throws a promise if translation is not loaded (for Suspense)
|
|
147
|
-
const translations =
|
|
199
|
+
const translations = useTranslationLazy('common');
|
|
148
200
|
|
|
149
201
|
return (
|
|
150
202
|
<div>
|
|
@@ -153,6 +205,14 @@ function MyComponent() {
|
|
|
153
205
|
</div>
|
|
154
206
|
);
|
|
155
207
|
}
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
```tsx
|
|
211
|
+
// App.tsx
|
|
212
|
+
import { Suspense } from 'react';
|
|
213
|
+
import { I18nTypedStoreProvider } from 'i18n-typed-store/react';
|
|
214
|
+
import { store } from './store';
|
|
215
|
+
import { MyComponent } from './MyComponent';
|
|
156
216
|
|
|
157
217
|
function App() {
|
|
158
218
|
return (
|
|
@@ -170,7 +230,8 @@ function App() {
|
|
|
170
230
|
```typescript
|
|
171
231
|
// lib/i18n.ts
|
|
172
232
|
import { createTranslationStore } from 'i18n-typed-store';
|
|
173
|
-
import
|
|
233
|
+
import type CommonTranslationsEn from './translations/common/en';
|
|
234
|
+
import type ErrorsTranslationsEn from './translations/errors/en';
|
|
174
235
|
|
|
175
236
|
const translations = { common: 'common', errors: 'errors' } as const;
|
|
176
237
|
const locales = { en: 'en', ru: 'ru' } as const;
|
|
@@ -179,16 +240,16 @@ export const storeFactory = createTranslationStore({
|
|
|
179
240
|
translations,
|
|
180
241
|
locales,
|
|
181
242
|
loadModule: async (locale, translation) => {
|
|
182
|
-
return await import(`./
|
|
243
|
+
return await import(`./translations/${translation}/${locale}.tsx`);
|
|
183
244
|
},
|
|
184
|
-
extractTranslation: (module) => module.default,
|
|
245
|
+
extractTranslation: (module) => new module.default(),
|
|
185
246
|
defaultLocale: 'en',
|
|
186
247
|
});
|
|
187
248
|
|
|
188
|
-
|
|
189
|
-
common:
|
|
190
|
-
errors:
|
|
191
|
-
}
|
|
249
|
+
interface TranslationData extends Record<keyof typeof translations, any> {
|
|
250
|
+
common: CommonTranslationsEn;
|
|
251
|
+
errors: ErrorsTranslationsEn;
|
|
252
|
+
}
|
|
192
253
|
|
|
193
254
|
export type Store = ReturnType<typeof storeFactory.type<TranslationData>>;
|
|
194
255
|
```
|
|
@@ -308,28 +369,30 @@ function createTranslationStore<T, L, Module>(options: {
|
|
|
308
369
|
**Example:**
|
|
309
370
|
|
|
310
371
|
```typescript
|
|
372
|
+
import type CommonTranslationsEn from './translations/common/en';
|
|
373
|
+
|
|
311
374
|
const storeFactory = createTranslationStore({
|
|
312
375
|
translations: { common: 'common' },
|
|
313
376
|
locales: { en: 'en', ru: 'ru' },
|
|
314
377
|
loadModule: async (locale, translation) => {
|
|
315
|
-
return await import(`./
|
|
378
|
+
return await import(`./translations/${translation}/${locale}.tsx`);
|
|
316
379
|
},
|
|
317
|
-
extractTranslation: (module
|
|
380
|
+
extractTranslation: (module) => new module.default(),
|
|
318
381
|
defaultLocale: 'en',
|
|
319
382
|
useFallback: true,
|
|
320
383
|
fallbackLocale: 'en',
|
|
321
384
|
});
|
|
322
385
|
|
|
323
|
-
|
|
324
|
-
common:
|
|
325
|
-
}
|
|
386
|
+
interface TranslationData extends Record<keyof typeof translations, any> {
|
|
387
|
+
common: CommonTranslationsEn;
|
|
388
|
+
}
|
|
326
389
|
|
|
327
390
|
const store = storeFactory.type<TranslationData>();
|
|
328
391
|
|
|
329
392
|
// Load translation
|
|
330
393
|
await store.translations.common.load('en');
|
|
331
394
|
|
|
332
|
-
// Access translation (type-safe)
|
|
395
|
+
// Access translation (type-safe with IDE go-to support)
|
|
333
396
|
const title = store.translations.common.currentTranslation?.title;
|
|
334
397
|
```
|
|
335
398
|
|
|
@@ -352,7 +415,7 @@ const moduleMap = createTranslationModuleMap(
|
|
|
352
415
|
{ common: 'common' },
|
|
353
416
|
{ en: 'en', ru: 'ru' },
|
|
354
417
|
async (locale, translation) => {
|
|
355
|
-
return await import(`./
|
|
418
|
+
return await import(`./translations/${translation}/${locale}.tsx`);
|
|
356
419
|
}
|
|
357
420
|
);
|
|
358
421
|
|
|
@@ -432,7 +495,20 @@ Provider component that wraps your application to provide translation store cont
|
|
|
432
495
|
Hook for accessing translations with automatic loading. Returns `undefined` if translation is not yet loaded.
|
|
433
496
|
|
|
434
497
|
```tsx
|
|
498
|
+
// Direct usage
|
|
435
499
|
const translations = useI18nTranslation('common', fromCache?: boolean);
|
|
500
|
+
|
|
501
|
+
// Typed wrapper (recommended)
|
|
502
|
+
import { useI18nTranslation } from 'i18n-typed-store/react/useI18nTranslation';
|
|
503
|
+
import type { TRANSLATIONS, LOCALES } from './constants';
|
|
504
|
+
import type { ITranslationStoreTypes } from './store';
|
|
505
|
+
|
|
506
|
+
export const useTranslation = <K extends keyof typeof TRANSLATIONS>(translation: K) => {
|
|
507
|
+
return useI18nTranslation<typeof TRANSLATIONS, typeof LOCALES, ITranslationStoreTypes, K>(translation);
|
|
508
|
+
};
|
|
509
|
+
|
|
510
|
+
// Usage
|
|
511
|
+
const translations = useTranslation('common');
|
|
436
512
|
```
|
|
437
513
|
|
|
438
514
|
### `useI18nTranslationLazy`
|
|
@@ -440,7 +516,20 @@ const translations = useI18nTranslation('common', fromCache?: boolean);
|
|
|
440
516
|
Hook for accessing translations with React Suspense support. Throws a promise if translation is not loaded.
|
|
441
517
|
|
|
442
518
|
```tsx
|
|
519
|
+
// Direct usage
|
|
443
520
|
const translations = useI18nTranslationLazy('common', fromCache?: boolean);
|
|
521
|
+
|
|
522
|
+
// Typed wrapper (recommended)
|
|
523
|
+
import { useI18nTranslationLazy } from 'i18n-typed-store/react/useI18nTranslationLazy';
|
|
524
|
+
import type { TRANSLATIONS, LOCALES } from './constants';
|
|
525
|
+
import type { ITranslationStoreTypes } from './store';
|
|
526
|
+
|
|
527
|
+
export const useTranslationLazy = <K extends keyof typeof TRANSLATIONS>(translation: K) => {
|
|
528
|
+
return useI18nTranslationLazy<typeof TRANSLATIONS, typeof LOCALES, ITranslationStoreTypes, K>(translation);
|
|
529
|
+
};
|
|
530
|
+
|
|
531
|
+
// Usage
|
|
532
|
+
const translations = useTranslationLazy('common');
|
|
444
533
|
```
|
|
445
534
|
|
|
446
535
|
### `useI18nLocale`
|
|
@@ -510,6 +599,172 @@ function initializeStore<T, L, M>(
|
|
|
510
599
|
|
|
511
600
|
## Advanced Usage
|
|
512
601
|
|
|
602
|
+
### Translation Classes Structure
|
|
603
|
+
|
|
604
|
+
The library is designed to work with TypeScript classes for translations, providing full type safety and IDE support (go-to definition, autocomplete). Here's an example of a translation class:
|
|
605
|
+
|
|
606
|
+
```typescript
|
|
607
|
+
// translations/common/en.tsx
|
|
608
|
+
import { createPluralSelector } from 'i18n-typed-store';
|
|
609
|
+
|
|
610
|
+
const plur = createPluralSelector('en');
|
|
611
|
+
|
|
612
|
+
export default class CommonTranslationsEn {
|
|
613
|
+
title = 'Welcome';
|
|
614
|
+
loading = 'Loading...';
|
|
615
|
+
error = 'An error occurred';
|
|
616
|
+
|
|
617
|
+
greeting = (
|
|
618
|
+
<>
|
|
619
|
+
Hello, <strong>World</strong>!
|
|
620
|
+
</>
|
|
621
|
+
);
|
|
622
|
+
|
|
623
|
+
buttons = {
|
|
624
|
+
save: 'Save',
|
|
625
|
+
cancel: 'Cancel',
|
|
626
|
+
delete: 'Delete',
|
|
627
|
+
};
|
|
628
|
+
|
|
629
|
+
messages = {
|
|
630
|
+
notFound: 'Not found',
|
|
631
|
+
unauthorized: (
|
|
632
|
+
<>
|
|
633
|
+
You are <strong>not authorized</strong> to perform this action
|
|
634
|
+
</>
|
|
635
|
+
),
|
|
636
|
+
};
|
|
637
|
+
|
|
638
|
+
// Pluralization method
|
|
639
|
+
items = (count: number) =>
|
|
640
|
+
count + ' ' + plur(count, {
|
|
641
|
+
one: 'item',
|
|
642
|
+
other: 'items',
|
|
643
|
+
});
|
|
644
|
+
}
|
|
645
|
+
```
|
|
646
|
+
|
|
647
|
+
```typescript
|
|
648
|
+
// lib/i18n.ts
|
|
649
|
+
import { createTranslationStore } from 'i18n-typed-store';
|
|
650
|
+
import type CommonTranslationsEn from './translations/common/en';
|
|
651
|
+
import type MainTranslationsEn from './translations/main/en';
|
|
652
|
+
import type NewsTranslationsEn from './translations/news/en';
|
|
653
|
+
import type SettingsTranslationsEn from './translations/settings/en';
|
|
654
|
+
|
|
655
|
+
const translations = {
|
|
656
|
+
common: 'common',
|
|
657
|
+
main: 'main',
|
|
658
|
+
news: 'news',
|
|
659
|
+
settings: 'settings',
|
|
660
|
+
} as const;
|
|
661
|
+
|
|
662
|
+
const locales = {
|
|
663
|
+
en: 'en',
|
|
664
|
+
ru: 'ru',
|
|
665
|
+
} as const;
|
|
666
|
+
|
|
667
|
+
export interface ITranslationStoreTypes extends Record<keyof typeof translations, any> {
|
|
668
|
+
common: CommonTranslationsEn;
|
|
669
|
+
main: MainTranslationsEn;
|
|
670
|
+
news: NewsTranslationsEn;
|
|
671
|
+
settings: SettingsTranslationsEn;
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
export const store = createTranslationStore({
|
|
675
|
+
translations,
|
|
676
|
+
locales,
|
|
677
|
+
loadModule: (locale, namespace) => {
|
|
678
|
+
return import(`./translations/${namespace}/${locale}.tsx`);
|
|
679
|
+
},
|
|
680
|
+
extractTranslation: (module) => new module.default(),
|
|
681
|
+
defaultLocale: 'en',
|
|
682
|
+
deleteOtherLocalesAfterLoad: false,
|
|
683
|
+
loadFromCache: false,
|
|
684
|
+
}).type<ITranslationStoreTypes>();
|
|
685
|
+
```
|
|
686
|
+
|
|
687
|
+
**Benefits of using classes:**
|
|
688
|
+
|
|
689
|
+
- ✅ Full TypeScript type safety with IDE go-to definition support
|
|
690
|
+
- ✅ Support for JSX elements in translations
|
|
691
|
+
- ✅ Methods for pluralization and dynamic translations
|
|
692
|
+
- ✅ Better code organization and maintainability
|
|
693
|
+
- ✅ Compile-time validation of translation keys
|
|
694
|
+
|
|
695
|
+
### Creating Typed Hook Wrappers
|
|
696
|
+
|
|
697
|
+
For better type safety and IDE support, it's recommended to create typed wrapper hooks. This ensures full type inference and autocomplete when using translations in your components.
|
|
698
|
+
|
|
699
|
+
```typescript
|
|
700
|
+
// hooks/useTranslation.ts
|
|
701
|
+
import { useI18nTranslation } from 'i18n-typed-store/react/useI18nTranslation';
|
|
702
|
+
import type { TRANSLATIONS, LOCALES } from '../constants';
|
|
703
|
+
import type { ITranslationStoreTypes } from '../store';
|
|
704
|
+
|
|
705
|
+
export const useTranslation = <K extends keyof typeof TRANSLATIONS>(translation: K) => {
|
|
706
|
+
return useI18nTranslation<typeof TRANSLATIONS, typeof LOCALES, ITranslationStoreTypes, K>(translation);
|
|
707
|
+
};
|
|
708
|
+
```
|
|
709
|
+
|
|
710
|
+
```typescript
|
|
711
|
+
// hooks/useTranslationLazy.ts
|
|
712
|
+
import { useI18nTranslationLazy } from 'i18n-typed-store/react/useI18nTranslationLazy';
|
|
713
|
+
import type { TRANSLATIONS, LOCALES } from '../constants';
|
|
714
|
+
import type { ITranslationStoreTypes } from '../store';
|
|
715
|
+
|
|
716
|
+
export const useTranslationLazy = <K extends keyof typeof TRANSLATIONS>(translation: K) => {
|
|
717
|
+
return useI18nTranslationLazy<typeof TRANSLATIONS, typeof LOCALES, ITranslationStoreTypes, K>(translation);
|
|
718
|
+
};
|
|
719
|
+
```
|
|
720
|
+
|
|
721
|
+
```typescript
|
|
722
|
+
// constants.ts
|
|
723
|
+
export const TRANSLATIONS = {
|
|
724
|
+
common: 'common',
|
|
725
|
+
main: 'main',
|
|
726
|
+
news: 'news',
|
|
727
|
+
settings: 'settings',
|
|
728
|
+
} as const;
|
|
729
|
+
|
|
730
|
+
export const LOCALES = {
|
|
731
|
+
en: 'en',
|
|
732
|
+
ru: 'ru',
|
|
733
|
+
} as const;
|
|
734
|
+
```
|
|
735
|
+
|
|
736
|
+
**Usage in components:**
|
|
737
|
+
|
|
738
|
+
```tsx
|
|
739
|
+
import { useTranslation } from './hooks/useTranslation';
|
|
740
|
+
import { useTranslationLazy } from './hooks/useTranslationLazy';
|
|
741
|
+
|
|
742
|
+
// With useTranslation (returns undefined if not loaded)
|
|
743
|
+
function MyComponent() {
|
|
744
|
+
const translations = useTranslation('common');
|
|
745
|
+
|
|
746
|
+
if (!translations) {
|
|
747
|
+
return <div>Loading...</div>;
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
return <div>{translations.title}</div>;
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
// With useTranslationLazy (for Suspense)
|
|
754
|
+
function MyComponentLazy() {
|
|
755
|
+
const translations = useTranslationLazy('common');
|
|
756
|
+
|
|
757
|
+
return <div>{translations.title}</div>;
|
|
758
|
+
}
|
|
759
|
+
```
|
|
760
|
+
|
|
761
|
+
**Benefits:**
|
|
762
|
+
|
|
763
|
+
- ✅ Full type safety with autocomplete
|
|
764
|
+
- ✅ IDE go-to definition support
|
|
765
|
+
- ✅ Compile-time validation of translation keys
|
|
766
|
+
- ✅ Consistent API across your application
|
|
767
|
+
|
|
513
768
|
### Working with Dynamic Imports
|
|
514
769
|
|
|
515
770
|
```typescript
|
|
@@ -520,7 +775,7 @@ const storeFactory = createTranslationStore({
|
|
|
520
775
|
// Dynamic import with error handling
|
|
521
776
|
try {
|
|
522
777
|
const module = await import(
|
|
523
|
-
`./
|
|
778
|
+
`./translations/${translation}/${locale}.tsx`
|
|
524
779
|
);
|
|
525
780
|
return module;
|
|
526
781
|
} catch (error) {
|
|
@@ -528,8 +783,9 @@ const storeFactory = createTranslationStore({
|
|
|
528
783
|
throw error;
|
|
529
784
|
}
|
|
530
785
|
},
|
|
531
|
-
extractTranslation: (module
|
|
532
|
-
|
|
786
|
+
extractTranslation: (module) => {
|
|
787
|
+
// Instantiate the translation class
|
|
788
|
+
return new module.default();
|
|
533
789
|
},
|
|
534
790
|
defaultLocale: 'en',
|
|
535
791
|
});
|
|
@@ -544,16 +800,16 @@ const storeFactory = createTranslationStore({
|
|
|
544
800
|
translations,
|
|
545
801
|
locales,
|
|
546
802
|
loadModule: async (locale, translation) => {
|
|
547
|
-
|
|
803
|
+
// Special handling for certain namespaces
|
|
804
|
+
if (translation === 'lang') {
|
|
805
|
+
return await import(`./translations/${translation}/index.tsx`);
|
|
806
|
+
}
|
|
807
|
+
return await import(`./translations/${translation}/${locale}.tsx`);
|
|
548
808
|
},
|
|
549
|
-
extractTranslation: (module
|
|
550
|
-
//
|
|
809
|
+
extractTranslation: (module) => {
|
|
810
|
+
// Instantiate the translation class
|
|
551
811
|
// You can use locale and translation parameters for custom logic
|
|
552
|
-
|
|
553
|
-
// Special handling for English common translations
|
|
554
|
-
return module.default?.en || module.default;
|
|
555
|
-
}
|
|
556
|
-
return module.default || module;
|
|
812
|
+
return new module.default();
|
|
557
813
|
},
|
|
558
814
|
defaultLocale: 'en',
|
|
559
815
|
});
|
|
@@ -562,6 +818,11 @@ const storeFactory = createTranslationStore({
|
|
|
562
818
|
### Handling Multiple Translation Namespaces
|
|
563
819
|
|
|
564
820
|
```typescript
|
|
821
|
+
import type CommonTranslationsEn from './translations/common/en';
|
|
822
|
+
import type ErrorsTranslationsEn from './translations/errors/en';
|
|
823
|
+
import type UiTranslationsEn from './translations/ui/en';
|
|
824
|
+
import type AdminTranslationsEn from './translations/admin/en';
|
|
825
|
+
|
|
565
826
|
const translations = {
|
|
566
827
|
common: 'common',
|
|
567
828
|
errors: 'errors',
|
|
@@ -569,12 +830,12 @@ const translations = {
|
|
|
569
830
|
admin: 'admin',
|
|
570
831
|
} as const;
|
|
571
832
|
|
|
572
|
-
|
|
573
|
-
common:
|
|
574
|
-
errors:
|
|
575
|
-
ui:
|
|
576
|
-
admin:
|
|
577
|
-
}
|
|
833
|
+
interface TranslationData extends Record<keyof typeof translations, any> {
|
|
834
|
+
common: CommonTranslationsEn;
|
|
835
|
+
errors: ErrorsTranslationsEn;
|
|
836
|
+
ui: UiTranslationsEn;
|
|
837
|
+
admin: AdminTranslationsEn;
|
|
838
|
+
}
|
|
578
839
|
|
|
579
840
|
const store = storeFactory.type<TranslationData>();
|
|
580
841
|
|
|
@@ -582,7 +843,7 @@ const store = storeFactory.type<TranslationData>();
|
|
|
582
843
|
await store.translations.common.load('en');
|
|
583
844
|
await store.translations.ui.load('en');
|
|
584
845
|
|
|
585
|
-
// Access translations
|
|
846
|
+
// Access translations (with full IDE support)
|
|
586
847
|
const title = store.translations.common.currentTranslation?.title;
|
|
587
848
|
const saveButton = store.translations.ui.currentTranslation?.buttons.save;
|
|
588
849
|
```
|
|
@@ -596,9 +857,9 @@ const storeFactory = createTranslationStore({
|
|
|
596
857
|
translations: { common: 'common' },
|
|
597
858
|
locales: { en: 'en', ru: 'ru' },
|
|
598
859
|
loadModule: async (locale, translation) => {
|
|
599
|
-
return await import(`./
|
|
860
|
+
return await import(`./translations/${translation}/${locale}.tsx`);
|
|
600
861
|
},
|
|
601
|
-
extractTranslation: (module) => module.default,
|
|
862
|
+
extractTranslation: (module) => new module.default(),
|
|
602
863
|
defaultLocale: 'en',
|
|
603
864
|
useFallback: true,
|
|
604
865
|
fallbackLocale: 'en',
|
|
@@ -648,9 +909,17 @@ The library uses `Intl.PluralRules` for plural form selection, supporting all Un
|
|
|
648
909
|
|
|
649
910
|
## Examples
|
|
650
911
|
|
|
912
|
+
### Complete Example Project
|
|
913
|
+
|
|
914
|
+
For a full-featured example with React, TypeScript, and all features demonstrated, see the [example repository](https://github.com/ialexanderlvov/i18n-typed-store-react-example).
|
|
915
|
+
|
|
651
916
|
### Example: E-commerce Application
|
|
652
917
|
|
|
653
918
|
```typescript
|
|
919
|
+
import type ProductsTranslationsEn from './translations/products/en';
|
|
920
|
+
import type CartTranslationsEn from './translations/cart/en';
|
|
921
|
+
import type CheckoutTranslationsEn from './translations/checkout/en';
|
|
922
|
+
|
|
654
923
|
const translations = {
|
|
655
924
|
products: 'products',
|
|
656
925
|
cart: 'cart',
|
|
@@ -663,30 +932,19 @@ const locales = {
|
|
|
663
932
|
de: 'de',
|
|
664
933
|
} as const;
|
|
665
934
|
|
|
666
|
-
|
|
667
|
-
products:
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
};
|
|
672
|
-
cart: {
|
|
673
|
-
title: string;
|
|
674
|
-
empty: string;
|
|
675
|
-
total: string;
|
|
676
|
-
};
|
|
677
|
-
checkout: {
|
|
678
|
-
title: string;
|
|
679
|
-
placeOrder: string;
|
|
680
|
-
};
|
|
681
|
-
};
|
|
935
|
+
interface TranslationData extends Record<keyof typeof translations, any> {
|
|
936
|
+
products: ProductsTranslationsEn;
|
|
937
|
+
cart: CartTranslationsEn;
|
|
938
|
+
checkout: CheckoutTranslationsEn;
|
|
939
|
+
}
|
|
682
940
|
|
|
683
941
|
const storeFactory = createTranslationStore({
|
|
684
942
|
translations,
|
|
685
943
|
locales,
|
|
686
944
|
loadModule: async (locale, translation) => {
|
|
687
|
-
return await import(`./
|
|
945
|
+
return await import(`./translations/${translation}/${locale}.ts`);
|
|
688
946
|
},
|
|
689
|
-
extractTranslation: (module) => module.default,
|
|
947
|
+
extractTranslation: (module) => new module.default(),
|
|
690
948
|
defaultLocale: 'en',
|
|
691
949
|
});
|
|
692
950
|
|
|
@@ -696,7 +954,7 @@ const store = storeFactory.type<TranslationData>();
|
|
|
696
954
|
await store.translations.products.load('en');
|
|
697
955
|
await store.translations.cart.load('en');
|
|
698
956
|
|
|
699
|
-
// Use translations
|
|
957
|
+
// Use translations (with full IDE go-to support)
|
|
700
958
|
const productTitle = store.translations.products.currentTranslation?.title;
|
|
701
959
|
const cartTitle = store.translations.cart.currentTranslation?.title;
|
|
702
960
|
```
|
|
@@ -704,20 +962,43 @@ const cartTitle = store.translations.cart.currentTranslation?.title;
|
|
|
704
962
|
### Example: Pluralization in Product List
|
|
705
963
|
|
|
706
964
|
```typescript
|
|
965
|
+
// translations/products/en.tsx
|
|
707
966
|
import { createPluralSelector } from 'i18n-typed-store';
|
|
708
967
|
|
|
709
|
-
const
|
|
968
|
+
const plur = createPluralSelector('en');
|
|
710
969
|
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
970
|
+
export default class ProductsTranslationsEn {
|
|
971
|
+
title = 'Products';
|
|
972
|
+
addToCart = 'Add to Cart';
|
|
973
|
+
price = 'Price';
|
|
974
|
+
|
|
975
|
+
// Pluralization method
|
|
976
|
+
productCount = (count: number) =>
|
|
977
|
+
count + ' ' + plur(count, {
|
|
978
|
+
one: 'product',
|
|
979
|
+
other: 'products',
|
|
980
|
+
});
|
|
981
|
+
|
|
982
|
+
itemsInCart = (count: number) =>
|
|
983
|
+
count + ' ' + plur(count, {
|
|
984
|
+
zero: 'No items',
|
|
985
|
+
one: 'item',
|
|
986
|
+
other: 'items',
|
|
987
|
+
}) + ' in cart';
|
|
716
988
|
}
|
|
717
989
|
|
|
718
|
-
// Usage
|
|
719
|
-
|
|
720
|
-
|
|
990
|
+
// Usage in component with typed hook
|
|
991
|
+
import { useTranslation } from './hooks/useTranslation';
|
|
992
|
+
|
|
993
|
+
const translations = useTranslation('products');
|
|
994
|
+
|
|
995
|
+
if (translations) {
|
|
996
|
+
translations.productCount(1); // => "1 product"
|
|
997
|
+
translations.productCount(5); // => "5 products"
|
|
998
|
+
translations.itemsInCart(0); // => "0 No items in cart"
|
|
999
|
+
translations.itemsInCart(1); // => "1 item in cart"
|
|
1000
|
+
translations.itemsInCart(5); // => "5 items in cart"
|
|
1001
|
+
}
|
|
721
1002
|
```
|
|
722
1003
|
|
|
723
1004
|
## API Reference
|
|
@@ -785,3 +1066,7 @@ Alexander Lvov
|
|
|
785
1066
|
## Repository
|
|
786
1067
|
|
|
787
1068
|
[GitHub](https://github.com/ialexanderlvov/i18n-typed-store)
|
|
1069
|
+
|
|
1070
|
+
## Example Project Git
|
|
1071
|
+
|
|
1072
|
+
[React Example](https://github.com/ialexanderlvov/i18n-typed-store-react-example) - Complete working example with React, TypeScript, and all features demonstrated.
|
package/package.json
CHANGED
|
@@ -1,10 +1,22 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "i18n-typed-store",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "Type-safe translation store for managing i18n locales with full TypeScript support",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.mjs",
|
|
7
7
|
"types": "dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.mjs",
|
|
12
|
+
"require": "./dist/index.js"
|
|
13
|
+
},
|
|
14
|
+
"./react": {
|
|
15
|
+
"types": "./dist/react/index.d.ts",
|
|
16
|
+
"import": "./dist/react/index.mjs",
|
|
17
|
+
"require": "./dist/react/index.js"
|
|
18
|
+
}
|
|
19
|
+
},
|
|
8
20
|
"files": [
|
|
9
21
|
"dist"
|
|
10
22
|
],
|