i18n-typed-store 0.1.0 → 0.1.1

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.
@@ -0,0 +1,91 @@
1
+ /**
2
+ * Translation store structure.
3
+ * Manages translations for multiple namespace keys and locales.
4
+ *
5
+ * @template T - Type of translations object (e.g., { common: 'common', errors: 'errors' })
6
+ * @template L - Type of locales object (e.g., { en: 'en', ru: 'ru' })
7
+ * @template M - Type of translation modules mapping (e.g., { common: { greeting: string }, errors: { notFound: string } })
8
+ */
9
+ type TranslationStore<T extends Record<string, string>, L extends Record<string, string>, M extends {
10
+ [K in keyof T]: any;
11
+ }> = {
12
+ /** Currently active locale */
13
+ currentLocale: keyof L;
14
+ /** Available locales */
15
+ locales: L;
16
+ /** Translations map (namespace keys) */
17
+ translationsMap: T;
18
+ /**
19
+ * Adds a listener for locale change events.
20
+ *
21
+ * @param listener - Function to call when locale changes
22
+ */
23
+ addChangeLocaleListener: (listener: (locale: keyof L) => void) => void;
24
+ /**
25
+ * Removes a locale change listener.
26
+ *
27
+ * @param listener - Listener function to remove
28
+ */
29
+ removeChangeLocaleListener: (listener: (locale: keyof L) => void) => void;
30
+ /**
31
+ * Changes the current locale and notifies all listeners.
32
+ *
33
+ * @param locale - New locale key
34
+ */
35
+ changeLocale: (locale: keyof L) => void;
36
+ /** Translations organized by namespace key */
37
+ translations: {
38
+ [K in keyof T]: {
39
+ /** Currently active translation for this namespace */
40
+ currentTranslation?: M[K];
41
+ /** Locale of the current translation */
42
+ currentLocale?: keyof L;
43
+ /** Translations for all locales for this namespace */
44
+ translations: Record<keyof L, {
45
+ /** Loaded translation data, undefined if not loaded yet */
46
+ namespace: M[K] | undefined;
47
+ /** Whether translation is currently being loaded */
48
+ isLoading: boolean;
49
+ /** Whether an error occurred during loading */
50
+ isError: boolean;
51
+ /** Promise for the ongoing loading operation */
52
+ loadingPromise?: Promise<void>;
53
+ }>;
54
+ /**
55
+ * Loads translation for a specific locale.
56
+ *
57
+ * @param locale - Locale key to load translation for
58
+ * @param fromCache - Whether to use cached translation if available (default: true)
59
+ * @returns Promise that resolves when translation is loaded
60
+ * @throws Error if loading fails
61
+ */
62
+ load: (locale: keyof L, fromCache?: boolean) => Promise<void>;
63
+ };
64
+ };
65
+ };
66
+
67
+ /**
68
+ * Context value for I18n typed store.
69
+ * Provides access to translation store, locale management, and loading state.
70
+ *
71
+ * @template T - Type of translations object (e.g., { common: 'common', errors: 'errors' })
72
+ * @template L - Type of locales object (e.g., { en: 'en', ru: 'ru' })
73
+ * @template M - Type of translation modules mapping (e.g., { common: { greeting: string }, errors: { notFound: string } })
74
+ */
75
+ interface II18nTypedStoreContext<T extends Record<string, string> = Record<string, string>, L extends Record<string, string> = Record<string, string>, M extends {
76
+ [K in keyof T]: any;
77
+ } = {
78
+ [K in keyof T]: any;
79
+ }> {
80
+ /** Translation store instance */
81
+ store: TranslationStore<T, L, M>;
82
+ /**
83
+ * Suspense mode for translation loading:
84
+ * - 'once' - suspend only on first load
85
+ * - 'first-load-locale' - suspend on first load for each locale
86
+ * - 'change-locale' - suspend on every locale change
87
+ */
88
+ suspenseMode: 'once' | 'first-load-locale' | 'change-locale';
89
+ }
90
+
91
+ export type { II18nTypedStoreContext as I, TranslationStore as T };
@@ -0,0 +1,91 @@
1
+ /**
2
+ * Translation store structure.
3
+ * Manages translations for multiple namespace keys and locales.
4
+ *
5
+ * @template T - Type of translations object (e.g., { common: 'common', errors: 'errors' })
6
+ * @template L - Type of locales object (e.g., { en: 'en', ru: 'ru' })
7
+ * @template M - Type of translation modules mapping (e.g., { common: { greeting: string }, errors: { notFound: string } })
8
+ */
9
+ type TranslationStore<T extends Record<string, string>, L extends Record<string, string>, M extends {
10
+ [K in keyof T]: any;
11
+ }> = {
12
+ /** Currently active locale */
13
+ currentLocale: keyof L;
14
+ /** Available locales */
15
+ locales: L;
16
+ /** Translations map (namespace keys) */
17
+ translationsMap: T;
18
+ /**
19
+ * Adds a listener for locale change events.
20
+ *
21
+ * @param listener - Function to call when locale changes
22
+ */
23
+ addChangeLocaleListener: (listener: (locale: keyof L) => void) => void;
24
+ /**
25
+ * Removes a locale change listener.
26
+ *
27
+ * @param listener - Listener function to remove
28
+ */
29
+ removeChangeLocaleListener: (listener: (locale: keyof L) => void) => void;
30
+ /**
31
+ * Changes the current locale and notifies all listeners.
32
+ *
33
+ * @param locale - New locale key
34
+ */
35
+ changeLocale: (locale: keyof L) => void;
36
+ /** Translations organized by namespace key */
37
+ translations: {
38
+ [K in keyof T]: {
39
+ /** Currently active translation for this namespace */
40
+ currentTranslation?: M[K];
41
+ /** Locale of the current translation */
42
+ currentLocale?: keyof L;
43
+ /** Translations for all locales for this namespace */
44
+ translations: Record<keyof L, {
45
+ /** Loaded translation data, undefined if not loaded yet */
46
+ namespace: M[K] | undefined;
47
+ /** Whether translation is currently being loaded */
48
+ isLoading: boolean;
49
+ /** Whether an error occurred during loading */
50
+ isError: boolean;
51
+ /** Promise for the ongoing loading operation */
52
+ loadingPromise?: Promise<void>;
53
+ }>;
54
+ /**
55
+ * Loads translation for a specific locale.
56
+ *
57
+ * @param locale - Locale key to load translation for
58
+ * @param fromCache - Whether to use cached translation if available (default: true)
59
+ * @returns Promise that resolves when translation is loaded
60
+ * @throws Error if loading fails
61
+ */
62
+ load: (locale: keyof L, fromCache?: boolean) => Promise<void>;
63
+ };
64
+ };
65
+ };
66
+
67
+ /**
68
+ * Context value for I18n typed store.
69
+ * Provides access to translation store, locale management, and loading state.
70
+ *
71
+ * @template T - Type of translations object (e.g., { common: 'common', errors: 'errors' })
72
+ * @template L - Type of locales object (e.g., { en: 'en', ru: 'ru' })
73
+ * @template M - Type of translation modules mapping (e.g., { common: { greeting: string }, errors: { notFound: string } })
74
+ */
75
+ interface II18nTypedStoreContext<T extends Record<string, string> = Record<string, string>, L extends Record<string, string> = Record<string, string>, M extends {
76
+ [K in keyof T]: any;
77
+ } = {
78
+ [K in keyof T]: any;
79
+ }> {
80
+ /** Translation store instance */
81
+ store: TranslationStore<T, L, M>;
82
+ /**
83
+ * Suspense mode for translation loading:
84
+ * - 'once' - suspend only on first load
85
+ * - 'first-load-locale' - suspend on first load for each locale
86
+ * - 'change-locale' - suspend on every locale change
87
+ */
88
+ suspenseMode: 'once' | 'first-load-locale' | 'change-locale';
89
+ }
90
+
91
+ export type { II18nTypedStoreContext as I, TranslationStore as T };
package/dist/index.d.mts CHANGED
@@ -1,56 +1,208 @@
1
+ import { T as TranslationStore } from './context-Dp43aQ0V.mjs';
2
+ export { I as II18nTypedStoreContext } from './context-Dp43aQ0V.mjs';
3
+
4
+ /**
5
+ * Map of translation module loaders.
6
+ * Each namespace key maps to an object where each locale key maps to a loader function.
7
+ *
8
+ * @template T - Type of translations object (e.g., { common: 'common', errors: 'errors' })
9
+ * @template L - Type of locales object (e.g., { en: 'en', ru: 'ru' })
10
+ * @template Module - Type of the raw module loaded from the module loader
11
+ */
12
+ type TranslationModuleMap<T extends Record<string, string>, L extends Record<string, string>, Module = unknown> = Record<keyof T, Record<keyof L, () => Promise<Module>>>;
13
+
1
14
  /**
2
15
  * Creates a map of translation module loaders for all combinations of translations and locales.
16
+ * This map is used internally by the translation store to lazy-load translation modules.
17
+ *
18
+ * @template T - Type of translations object (e.g., { common: 'common', errors: 'errors' })
19
+ * @template L - Type of locales object (e.g., { en: 'en', ru: 'ru' })
20
+ * @template Module - Type of the raw module loaded from the module loader
3
21
  *
4
22
  * @param translations - Object with translation keys
5
23
  * @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
24
+ * @param loadModule - Function to load a translation module for a specific locale and namespace
25
+ * @returns Immutable map where each namespace key contains an object with loader functions for each locale
26
+ * @throws {TypeError} If translations or locales are empty objects
27
+ *
28
+ * @example
29
+ * ```ts
30
+ * const translations = { common: 'common', errors: 'errors' } as const;
31
+ * const locales = { en: 'en', ru: 'ru' } as const;
32
+ * const loadModule = async (locale, namespace) => import(`./${namespace}/${locale}.json`);
33
+ *
34
+ * const moduleMap = createTranslationModuleMap(translations, locales, loadModule);
35
+ * moduleMap.common.en() will load './common/en.json'
36
+ * moduleMap.errors.ru() will load './errors/ru.json'
37
+ * ```
8
38
  */
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>>>;
39
+ declare const createTranslationModuleMap: <T extends Record<string, string>, L extends Record<string, string>, Module = unknown>(translations: T, locales: L, loadModule: (locale: keyof L, namespace: keyof T) => Promise<Module>) => TranslationModuleMap<T, L, Module>;
10
40
 
11
41
  /**
12
- * Creates a translation store with typed translations for different locales.
42
+ * Options for creating a translation store.
13
43
  *
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
44
+ * @template T - Type of translations object (e.g., { common: 'common', errors: 'errors' })
45
+ * @template L - Type of locales object (e.g., { en: 'en', ru: 'ru' })
46
+ * @template Module - Type of the raw module loaded from the module loader
47
+ */
48
+ interface CreateTranslationStoreOptions<T extends Record<string, string>, L extends Record<string, string>, Module = unknown> {
49
+ /** Object with translation keys */
50
+ translations: T;
51
+ /** Object with locale keys */
52
+ locales: L;
53
+ /** Function to load a translation module for a specific locale and namespace */
54
+ loadModule: (locale: keyof L, namespace: keyof T) => Promise<Module>;
55
+ /**
56
+ * Function to extract translation data from the loaded module.
57
+ * Receives three parameters: (module, locale, namespace) allowing for locale-specific
58
+ * or namespace-specific extraction logic.
59
+ */
60
+ extractTranslation: (module: Module, locale: keyof L, namespace: keyof T) => unknown | Promise<unknown>;
61
+ /**
62
+ * Whether to delete translations for other locales after loading a new one.
63
+ * Useful for memory-constrained environments.
64
+ * @default false
65
+ */
66
+ deleteOtherLocalesAfterLoad?: boolean;
67
+ /**
68
+ * Whether to load translations from cache by default.
69
+ * If false, will always reload even if translation is already cached.
70
+ * @default true
71
+ */
72
+ loadFromCache?: boolean;
73
+ /** Default locale key to use */
74
+ defaultLocale: keyof L;
75
+ /**
76
+ * Whether to use fallback locale for missing translations.
77
+ * When enabled, translations will be merged with fallback locale translations.
78
+ * @default false
79
+ */
80
+ useFallback?: boolean;
81
+ /**
82
+ * Fallback locale key to use when useFallback is true.
83
+ * If not provided, defaultLocale will be used as fallback.
84
+ * @default defaultLocale
85
+ */
86
+ fallbackLocale?: keyof L;
87
+ /**
88
+ * Event name for locale change events.
89
+ * @default 'change-locale'
90
+ */
91
+ changeLocaleEventName?: string;
92
+ }
93
+
94
+ /**
95
+ * Creates a translation store factory with typed translations for different locales.
96
+ * The store supports lazy loading, caching, error handling, and fallback locale merging.
97
+ *
98
+ * @template T - Type of translations object (e.g., { common: 'common', errors: 'errors' })
99
+ * @template L - Type of locales object (e.g., { en: 'en', ru: 'ru' })
100
+ * @template Module - Type of the raw module loaded from the module loader
101
+ *
102
+ * @param options - Configuration options for the translation store
103
+ * @returns Object with a `type()` method for creating a typed translation store
104
+ * @throws {TypeError} If required options are invalid
105
+ *
106
+ * @example
107
+ * ```ts
108
+ * const translations = { common: 'common', errors: 'errors' } as const;
109
+ * const locales = { en: 'en', ru: 'ru' } as const;
110
+ *
111
+ * const storeFactory = createTranslationStore({
112
+ * translations,
113
+ * locales,
114
+ * loadModule: async (locale, namespace) => import(`./${namespace}/${locale}.json`),
115
+ * extractTranslation: (module) => module.default || module,
116
+ * defaultLocale: 'en',
117
+ * useFallback: true,
118
+ * fallbackLocale: 'en',
119
+ * });
120
+ *
121
+ * const store = storeFactory.type<{
122
+ * common: { greeting: string };
123
+ * errors: { notFound: string };
124
+ * }>();
125
+ * ```
21
126
  */
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) => {
127
+ declare const createTranslationStore: <T extends Record<string, string>, L extends Record<string, string>, Module = unknown>({ translations, locales, loadModule, extractTranslation, deleteOtherLocalesAfterLoad, loadFromCache, defaultLocale, useFallback, fallbackLocale, changeLocaleEventName, }: CreateTranslationStoreOptions<T, L, Module>) => {
23
128
  /**
24
129
  * Creates a typed translation store.
130
+ * The store provides methods to load and access translations for each locale.
131
+ * When useFallback is enabled, translations are automatically merged with fallback locale.
25
132
  *
26
- * @template M - Type of translation object where each key corresponds to a key from translations
133
+ * @template M - Type of translation modules mapping where each key corresponds to a key from translations
27
134
  * @returns Store with methods to load translations for each locale
135
+ *
136
+ * @example
137
+ * ```ts
138
+ * const store = storeFactory.type<{
139
+ * common: { greeting: string; goodbye: string };
140
+ * errors: { notFound: string; unauthorized: string };
141
+ * }>();
142
+ *
143
+ * await store.common.load('ru');
144
+ * // If useFallback is true and 'ru' translation is missing some keys,
145
+ * // they will be filled from fallback locale (e.g., 'en')
146
+ * const greeting = store.common.translations.ru.namespace?.greeting;
147
+ * ```
28
148
  */
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
- }; };
149
+ type: <M extends { [K in keyof T]: any; }>() => TranslationStore<T, L, M>;
33
150
  };
34
151
 
35
152
  /**
36
153
  * Plural form variants for different plural categories.
37
154
  * Based on Unicode CLDR plural rules: zero, one, two, few, many, other.
155
+ *
156
+ * @see https://unicode-org.github.io/cldr-staging/charts/latest/supplemental/language_plural_rules.html
157
+ *
158
+ * @example
159
+ * ```ts
160
+ * const variants: PluralVariants = {
161
+ * one: 'item',
162
+ * other: 'items'
163
+ * };
164
+ * ```
38
165
  */
39
166
  type PluralVariants = {
167
+ /** Used for count = 0 (in some languages) */
40
168
  zero?: string;
169
+ /** Used for count = 1 (in most languages) */
41
170
  one?: string;
171
+ /** Used for count = 2 (in some languages like Welsh) */
42
172
  two?: string;
173
+ /** Used for small numbers (e.g., 3-10 in Russian) */
43
174
  few?: string;
175
+ /** Used for large numbers or fractional values */
44
176
  many?: string;
45
- other?: string;
177
+ /** Default/fallback variant - MUST be provided for correct pluralization */
178
+ other: string;
46
179
  };
180
+ /**
181
+ * Valid plural category names according to CLDR.
182
+ */
183
+ type PluralCategory = keyof PluralVariants;
184
+
185
+ /**
186
+ * Options for creating a plural selector.
187
+ */
188
+ interface CreatePluralSelectorOptions {
189
+ /**
190
+ * Whether to throw an error if 'other' variant is missing.
191
+ * @default false
192
+ */
193
+ strict?: boolean;
194
+ }
47
195
 
48
196
  /**
49
197
  * Creates a plural selector function for a specific locale.
50
- * The returned function selects the appropriate plural form based on the count.
198
+ * The returned function selects the appropriate plural form based on the count
199
+ * using Unicode CLDR plural rules.
51
200
  *
52
- * @param locale - Locale string (e.g., 'en', 'ru', 'fr')
201
+ * @param locale - Locale string (e.g., 'en', 'ru', 'fr', 'uk-UA')
202
+ * @param options - Configuration options
53
203
  * @returns Function that takes a count and plural variants, returns the matching variant
204
+ * @throws {TypeError} If locale is not a valid string
205
+ * @throws {Error} If strict mode is enabled and 'other' variant is missing
54
206
  *
55
207
  * @example
56
208
  * ```ts
@@ -58,7 +210,34 @@ type PluralVariants = {
58
210
  * selectPlural(1, { one: 'item', other: 'items' }); // => 'item'
59
211
  * selectPlural(5, { one: 'item', other: 'items' }); // => 'items'
60
212
  * ```
213
+ *
214
+ * @example
215
+ * ```ts
216
+ * const selectPlural = createPluralSelector('ru');
217
+ * selectPlural(1, { one: 'элемент', few: 'элемента', many: 'элементов', other: 'элементов' }); // => 'элемент'
218
+ * selectPlural(2, { one: 'элемент', few: 'элемента', many: 'элементов', other: 'элементов' }); // => 'элемента'
219
+ * selectPlural(5, { one: 'элемент', few: 'элемента', many: 'элементов', other: 'элементов' }); // => 'элементов'
220
+ * ```
221
+ */
222
+ declare const createPluralSelector: (locale: string, options?: CreatePluralSelectorOptions) => ((count: number, variants: PluralVariants) => string);
223
+
224
+ /**
225
+ * Event listener function type.
226
+ *
227
+ * @template T - Type of event arguments array
228
+ */
229
+ type Listener<T extends any[] = any[]> = (...args: T) => void;
230
+ /**
231
+ * Event map type that maps event names to their argument types.
232
+ *
233
+ * @example
234
+ * ```ts
235
+ * type MyEvents = {
236
+ * 'user-login': [userId: string, timestamp: number];
237
+ * 'user-logout': [userId: string];
238
+ * };
239
+ * ```
61
240
  */
62
- declare const createPluralSelector: (locale: string) => (count: number, variants: PluralVariants) => string;
241
+ type EventMap = Record<PropertyKey, any[]>;
63
242
 
64
- export { createPluralSelector, createTranslationModuleMap, createTranslationStore };
243
+ export { type CreatePluralSelectorOptions, type CreateTranslationStoreOptions, type EventMap, type Listener, type PluralCategory, type PluralVariants, type TranslationModuleMap, TranslationStore, createPluralSelector, createTranslationModuleMap, createTranslationStore };