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.
Files changed (2) hide show
  1. package/README.md +383 -98
  2. 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
- type TranslationData = {
53
- common: {
54
- title: string;
55
- description: string;
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 module dynamically
68
- const module = await import(`./locales/${locale}/${translation}.json`);
69
- return module;
79
+ // Load translation class dynamically
80
+ return await import(`./translations/${translation}/${locale}.ts`);
70
81
  },
71
- extractTranslation: (module, locale, translation) => {
72
- // Extract translation data from module
73
- return module.default || module;
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
- ```tsx
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 { I18nTypedStoreProvider, useI18nTranslation, useI18nLocale } from 'i18n-typed-store/react';
116
+ import type CommonTranslationsEn from './translations/common/en';
117
+ import { TRANSLATIONS, LOCALES } from './constants';
93
118
 
94
- // Setup store (same as above)
95
- const storeFactory = createTranslationStore({
96
- translations: { common: 'common' },
97
- locales: { en: 'en', ru: 'ru' },
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(`./locales/${locale}/${translation}.json`);
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
- type TranslationData = {
106
- common: { greeting: string; title: string };
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
- const store = storeFactory.type<TranslationData>();
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
- // In your components
160
+ ```tsx
161
+ // MyComponent.tsx
121
162
  function MyComponent() {
122
- const translations = useI18nTranslation('common');
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
- import { Suspense } from 'react';
143
- import { useI18nTranslationLazy } from 'i18n-typed-store/react';
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 = useI18nTranslationLazy('common');
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 { getLocaleFromRequest, initializeStore } from 'i18n-typed-store/react';
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(`./locales/${locale}/${translation}.json`);
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
- type TranslationData = {
189
- common: { title: string };
190
- errors: { notFound: string };
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(`./locales/${locale}/${translation}.json`);
378
+ return await import(`./translations/${translation}/${locale}.tsx`);
316
379
  },
317
- extractTranslation: (module, locale, translation) => module.default,
380
+ extractTranslation: (module) => new module.default(),
318
381
  defaultLocale: 'en',
319
382
  useFallback: true,
320
383
  fallbackLocale: 'en',
321
384
  });
322
385
 
323
- type TranslationData = {
324
- common: { title: string; description: string };
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(`./locales/${locale}/${translation}.json`);
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
- `./locales/${locale}/${translation}.json`
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, locale, translation) => {
532
- return module.default || module;
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
- return await import(`./locales/${locale}/${translation}.ts`);
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, locale, translation) => {
550
- // Extract from module.default or module
809
+ extractTranslation: (module) => {
810
+ // Instantiate the translation class
551
811
  // You can use locale and translation parameters for custom logic
552
- if (locale === 'en' && translation === 'common') {
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
- type TranslationData = {
573
- common: { title: string };
574
- errors: { notFound: string };
575
- ui: { buttons: { save: string } };
576
- admin: { dashboard: { title: string } };
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(`./locales/${locale}/${translation}.json`);
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
- type TranslationData = {
667
- products: {
668
- title: string;
669
- addToCart: string;
670
- price: string;
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(`./locales/${locale}/${translation}.json`);
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 selectPlural = createPluralSelector('en');
968
+ const plur = createPluralSelector('en');
710
969
 
711
- function getProductCountText(count: number): string {
712
- return selectPlural(count, {
713
- one: `${count} product`,
714
- other: `${count} products`,
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
- getProductCountText(1); // => "1 product"
720
- getProductCountText(5); // => "5 products"
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.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
  ],