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.
package/dist/index.js.map CHANGED
@@ -1 +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"]}
1
+ {"version":3,"sources":["../src/utils/create-translation-module-map.ts","../src/utils/event-emitter.ts","../src/utils/smart-merge.ts","../src/utils/create-translation-store.ts","../src/utils/create-plural-selector.ts"],"names":["namespaceState"],"mappings":"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","file":"index.js","sourcesContent":["import type { TranslationModuleMap } from '../types/create-translation-module-map';\n\n/**\n * Creates a map of translation module loaders for all combinations of translations and locales.\n * This map is used internally by the translation store to lazy-load translation modules.\n *\n * @template T - Type of translations object (e.g., { common: 'common', errors: 'errors' })\n * @template L - Type of locales object (e.g., { en: 'en', ru: 'ru' })\n * @template Module - Type of the raw module loaded from the module loader\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 namespace\n * @returns Immutable map where each namespace key contains an object with loader functions for each locale\n * @throws {TypeError} If translations or locales are empty objects\n *\n * @example\n * ```ts\n * const translations = { common: 'common', errors: 'errors' } as const;\n * const locales = { en: 'en', ru: 'ru' } as const;\n * const loadModule = async (locale, namespace) => import(`./${namespace}/${locale}.json`);\n *\n * const moduleMap = createTranslationModuleMap(translations, locales, loadModule);\n * moduleMap.common.en() will load './common/en.json'\n * moduleMap.errors.ru() will load './errors/ru.json'\n * ```\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, namespace: keyof T) => Promise<Module>,\n): TranslationModuleMap<T, L, Module> => {\n\tif (!translations || Object.keys(translations).length === 0) {\n\t\tthrow new TypeError('translations must be a non-empty object');\n\t}\n\n\tif (!locales || Object.keys(locales).length === 0) {\n\t\tthrow new TypeError('locales must be a non-empty object');\n\t}\n\n\tif (typeof loadModule !== 'function') {\n\t\tthrow new TypeError('loadModule must be a function');\n\t}\n\n\tconst namespaceModules = {} as Record<keyof T, Record<keyof L, () => Promise<Module>>>;\n\n\tfor (const namespaceKey of Object.keys(translations) as (keyof T)[]) {\n\t\tnamespaceModules[namespaceKey] = {} as Record<keyof L, () => Promise<Module>>;\n\n\t\tfor (const localeKey of Object.keys(locales) as (keyof L)[]) {\n\t\t\tnamespaceModules[namespaceKey][localeKey] = () => loadModule(localeKey, namespaceKey);\n\t\t}\n\t}\n\n\treturn namespaceModules as TranslationModuleMap<T, L, Module>;\n};\n","import type { Listener, EventMap } from '../types/event-emitter';\n\n/**\n * Event emitter class for managing event listeners.\n * Supports typed events with type-safe listeners.\n *\n * @template Events - Type of event map that maps event names to their argument types\n *\n * @example\n * ```ts\n * type MyEvents = {\n * 'user-login': [userId: string, timestamp: number];\n * 'user-logout': [userId: string];\n * };\n *\n * const emitter = new EventEmitter<MyEvents>();\n * emitter.on('user-login', (userId, timestamp) => {\n * console.log(`User ${userId} logged in at ${timestamp}`);\n * });\n * emitter.emit('user-login', 'user123', Date.now());\n * ```\n */\nexport class EventEmitter<Events extends EventMap = EventMap> {\n\tprivate listeners: {\n\t\t[K in keyof Events]?: Set<Listener<Events[K]>>;\n\t} = {};\n\n\t/**\n\t * Registers an event listener for the specified event.\n\t *\n\t * @param event - Event name\n\t * @param listener - Listener function\n\t * @returns This instance for method chaining\n\t */\n\ton<K extends keyof Events>(event: K, listener: Listener<Events[K]>): this {\n\t\tif (!this.listeners[event]) {\n\t\t\tthis.listeners[event] = new Set();\n\t\t}\n\t\tthis.listeners[event].add(listener);\n\t\treturn this;\n\t}\n\n\t/**\n\t * Registers a one-time event listener that will be automatically removed after the first call.\n\t *\n\t * @param event - Event name\n\t * @param listener - Listener function\n\t * @returns This instance for method chaining\n\t */\n\tonce<K extends keyof Events>(event: K, listener: Listener<Events[K]>): this {\n\t\tconst wrapper: Listener<Events[K]> = (...args) => {\n\t\t\tthis.off(event, wrapper);\n\t\t\tlistener(...args);\n\t\t};\n\t\treturn this.on(event, wrapper);\n\t}\n\n\t/**\n\t * Removes an event listener. If no listener is provided, removes all listeners for the event.\n\t *\n\t * @param event - Event name\n\t * @param listener - Optional listener function to remove\n\t * @returns This instance for method chaining\n\t */\n\toff<K extends keyof Events>(event: K, listener?: Listener<Events[K]>): this {\n\t\tconst set = this.listeners[event];\n\t\tif (!set) {\n\t\t\treturn this;\n\t\t}\n\n\t\tif (!listener) {\n\t\t\tset.clear();\n\t\t} else {\n\t\t\tset.delete(listener);\n\t\t}\n\n\t\tif (set.size === 0) {\n\t\t\tdelete this.listeners[event];\n\t\t}\n\t\treturn this;\n\t}\n\n\t/**\n\t * Emits an event, calling all registered listeners with the provided arguments.\n\t *\n\t * @param event - Event name\n\t * @param args - Event arguments\n\t * @returns True if there were listeners, false otherwise\n\t */\n\temit<K extends keyof Events>(event: K, ...args: Events[K]): boolean {\n\t\tconst set = this.listeners[event];\n\t\tif (!set || set.size === 0) {\n\t\t\treturn false;\n\t\t}\n\n\t\t[...set].forEach((listener) => listener(...args));\n\t\treturn true;\n\t}\n\n\t/**\n\t * Returns the number of listeners registered for the specified event.\n\t *\n\t * @param event - Event name\n\t * @returns Number of listeners\n\t */\n\tlistenerCount<K extends keyof Events>(event: K): number {\n\t\treturn this.listeners[event]?.size ?? 0;\n\t}\n\n\t/**\n\t * Removes all event listeners from all events.\n\t *\n\t * @returns This instance for method chaining\n\t */\n\tremoveAllListeners(): this {\n\t\tthis.listeners = {};\n\t\treturn this;\n\t}\n\n\t/**\n\t * Returns an array of all event names that have registered listeners.\n\t *\n\t * @returns Array of event names\n\t */\n\teventNames(): (keyof Events)[] {\n\t\treturn Object.keys(this.listeners) as (keyof Events)[];\n\t}\n}\n","/**\n * Performs a smart deep merge of two objects.\n * If structures differ at a key, the fallback value is used from that point.\n * Otherwise, current values are preserved.\n *\n * This function is used internally for merging translations with fallback locale.\n *\n * @param current - Current translation object\n * @param fallback - Fallback translation object\n * @returns Merged object with fallback values where structures differ\n *\n * @example\n * ```ts\n * const current = { a: { b: 1, c: 2 }, d: 3 };\n * const fallback = { a: { b: 1, c: 3 }, d: 3 };\n * const result = smartDeepMerge(current, fallback);\n * // Result: { a: { b: 1, c: 3 }, d: 3 } - only c is replaced from fallback\n * ```\n */\nexport function smartDeepMerge(current: any, fallback: any): any {\n\t// If either is null or undefined, return the other\n\tif (current == null) return fallback;\n\tif (fallback == null) return current;\n\n\t// Check if types differ (object vs primitive, or different object types)\n\tconst currentIsObject = typeof current === 'object' && !Array.isArray(current);\n\tconst fallbackIsObject = typeof fallback === 'object' && !Array.isArray(fallback);\n\tconst currentIsArray = Array.isArray(current);\n\tconst fallbackIsArray = Array.isArray(fallback);\n\n\t// If structure types differ (object vs primitive, or array vs object), use fallback\n\tif (\n\t\t(currentIsObject && !fallbackIsObject && !fallbackIsArray) ||\n\t\t(!currentIsObject && !currentIsArray && fallbackIsObject) ||\n\t\t(currentIsArray && !fallbackIsArray) ||\n\t\t(!currentIsArray && fallbackIsArray)\n\t) {\n\t\treturn fallback;\n\t}\n\n\t// If both are primitives or arrays, use current if it exists, otherwise fallback\n\tif (!currentIsObject && !fallbackIsObject) {\n\t\treturn current != null ? current : fallback;\n\t}\n\n\t// Both are objects - merge recursively\n\tconst result: any = { ...current };\n\n\t// Add missing keys from fallback\n\tfor (const key in fallback) {\n\t\tif (!(key in result)) {\n\t\t\t// Key doesn't exist in current, add from fallback\n\t\t\tresult[key] = fallback[key];\n\t\t} else {\n\t\t\t// Key exists in both - check if structures differ\n\t\t\tconst currentValue = result[key];\n\t\t\tconst fallbackValue = fallback[key];\n\n\t\t\t// Check if structures differ at this level\n\t\t\tconst currentValueIsObject = currentValue != null && typeof currentValue === 'object' && !Array.isArray(currentValue);\n\t\t\tconst fallbackValueIsObject = fallbackValue != null && typeof fallbackValue === 'object' && !Array.isArray(fallbackValue);\n\t\t\tconst currentValueIsArray = Array.isArray(currentValue);\n\t\t\tconst fallbackValueIsArray = Array.isArray(fallbackValue);\n\n\t\t\t// If structures differ (object vs primitive, or array vs object), use fallback from this point\n\t\t\tif (\n\t\t\t\t(currentValueIsObject && !fallbackValueIsObject && !fallbackValueIsArray) ||\n\t\t\t\t(!currentValueIsObject && !currentValueIsArray && fallbackValueIsObject) ||\n\t\t\t\t(currentValueIsArray && !fallbackValueIsArray) ||\n\t\t\t\t(!currentValueIsArray && fallbackValueIsArray)\n\t\t\t) {\n\t\t\t\tresult[key] = fallbackValue;\n\t\t\t} else if (currentValueIsObject && fallbackValueIsObject) {\n\t\t\t\t// Both are objects - merge recursively\n\t\t\t\tresult[key] = smartDeepMerge(currentValue, fallbackValue);\n\t\t\t} else if (currentValue == null) {\n\t\t\t\t// Current value is null/undefined, use fallback\n\t\t\t\tresult[key] = fallbackValue;\n\t\t\t}\n\t\t\t// If current value exists and is not null and structures match, keep it (already in result)\n\t\t}\n\t}\n\n\treturn result;\n}\n","import type { TranslationStore } from '../types/translation-store';\nimport type { CreateTranslationStoreOptions } from '../types/create-translation-store';\nimport type { TranslationModuleMap } from '../types/create-translation-module-map';\nimport { createTranslationModuleMap } from './create-translation-module-map';\n\nimport { EventEmitter } from './event-emitter';\nimport { smartDeepMerge } from './smart-merge';\n\n/**\n * Creates a translation store factory with typed translations for different locales.\n * The store supports lazy loading, caching, error handling, and fallback locale merging.\n *\n * @template T - Type of translations object (e.g., { common: 'common', errors: 'errors' })\n * @template L - Type of locales object (e.g., { en: 'en', ru: 'ru' })\n * @template Module - Type of the raw module loaded from the module loader\n *\n * @param options - Configuration options for the translation store\n * @returns Object with a `type()` method for creating a typed translation store\n * @throws {TypeError} If required options are invalid\n *\n * @example\n * ```ts\n * const translations = { common: 'common', errors: 'errors' } as const;\n * const locales = { en: 'en', ru: 'ru' } as const;\n *\n * const storeFactory = createTranslationStore({\n * translations,\n * locales,\n * loadModule: async (locale, namespace) => import(`./${namespace}/${locale}.json`),\n * extractTranslation: (module) => module.default || module,\n * defaultLocale: 'en',\n * useFallback: true,\n * fallbackLocale: 'en',\n * });\n *\n * const store = storeFactory.type<{\n * common: { greeting: string };\n * errors: { notFound: string };\n * }>();\n * ```\n */\nexport const createTranslationStore = <T extends Record<string, string>, L extends Record<string, string>, Module = unknown>({\n\ttranslations,\n\tlocales,\n\tloadModule,\n\textractTranslation,\n\tdeleteOtherLocalesAfterLoad = false,\n\tloadFromCache = true,\n\tdefaultLocale,\n\tuseFallback = false,\n\tfallbackLocale = defaultLocale,\n\tchangeLocaleEventName = 'change-locale',\n}: CreateTranslationStoreOptions<T, L, Module>) => {\n\t// Validate inputs\n\tif (!translations || Object.keys(translations).length === 0) {\n\t\tthrow new TypeError('translations must be a non-empty object');\n\t}\n\n\tif (!locales || Object.keys(locales).length === 0) {\n\t\tthrow new TypeError('locales must be a non-empty object');\n\t}\n\n\tif (typeof loadModule !== 'function') {\n\t\tthrow new TypeError('loadModule must be a function');\n\t}\n\n\tif (typeof extractTranslation !== 'function') {\n\t\tthrow new TypeError('extractTranslation must be a function');\n\t}\n\n\tif (!(defaultLocale in locales)) {\n\t\tthrow new TypeError(`defaultLocale '${String(defaultLocale)}' must be a key in locales`);\n\t}\n\n\tif (useFallback && !(fallbackLocale in locales)) {\n\t\tthrow new TypeError(`fallbackLocale '${String(fallbackLocale)}' must be a key in locales`);\n\t}\n\n\treturn {\n\t\t/**\n\t\t * Creates a typed translation store.\n\t\t * The store provides methods to load and access translations for each locale.\n\t\t * When useFallback is enabled, translations are automatically merged with fallback locale.\n\t\t *\n\t\t * @template M - Type of translation modules mapping 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\t * @example\n\t\t * ```ts\n\t\t * const store = storeFactory.type<{\n\t\t * common: { greeting: string; goodbye: string };\n\t\t * errors: { notFound: string; unauthorized: string };\n\t\t * }>();\n\t\t *\n\t\t * await store.common.load('ru');\n\t\t * // If useFallback is true and 'ru' translation is missing some keys,\n\t\t * // they will be filled from fallback locale (e.g., 'en')\n\t\t * const greeting = store.common.translations.ru.namespace?.greeting;\n\t\t * ```\n\t\t */\n\t\ttype: <M extends { [K in keyof T]: any }>(): TranslationStore<T, L, M> => {\n\t\t\tconst namespaceModuleMap: TranslationModuleMap<T, L, Module> = createTranslationModuleMap(translations, locales, loadModule);\n\t\t\tconst emitter = new EventEmitter();\n\n\t\t\tconst store = {\n\t\t\t\tcurrentLocale: defaultLocale,\n\t\t\t\tlocales,\n\t\t\t\ttranslationsMap: translations,\n\t\t\t\ttranslations: {},\n\t\t\t\taddChangeLocaleListener: (listener) => {\n\t\t\t\t\temitter.on(changeLocaleEventName, listener);\n\t\t\t\t},\n\t\t\t\tremoveChangeLocaleListener: (listener) => {\n\t\t\t\t\temitter.off(changeLocaleEventName, listener);\n\t\t\t\t},\n\t\t\t\tchangeLocale: (locale) => {\n\t\t\t\t\tstore.currentLocale = locale;\n\t\t\t\t\temitter.emit(changeLocaleEventName, locale);\n\t\t\t\t},\n\t\t\t} as TranslationStore<T, L, M>;\n\n\t\t\t// Initialize store structure for each translation key\n\t\t\tfor (const namespaceKey of Object.keys(translations) as (keyof T)[]) {\n\t\t\t\t// Create initial state for all locales\n\n\t\t\t\tstore.translations[namespaceKey] = {\n\t\t\t\t\tcurrentTranslation: undefined,\n\t\t\t\t\tcurrentLocale: undefined,\n\t\t\t\t\ttranslations: Object.fromEntries(\n\t\t\t\t\t\tObject.keys(locales).map((localeKey) => [\n\t\t\t\t\t\t\tlocaleKey,\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tnamespace: undefined,\n\t\t\t\t\t\t\t\tisLoading: false,\n\t\t\t\t\t\t\t\tisError: false,\n\t\t\t\t\t\t\t\tloadingPromise: undefined,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t]),\n\t\t\t\t\t) as TranslationStore<T, L, M>['translations'][keyof T]['translations'],\n\t\t\t\t\tload: async (\n\t\t\t\t\t\tlocale: keyof L = store.currentLocale || defaultLocale,\n\t\t\t\t\t\tfromCache: boolean = loadFromCache,\n\t\t\t\t\t): Promise<void> => {\n\t\t\t\t\t\t// Validate locale\n\t\t\t\t\t\tif (!(locale in locales)) {\n\t\t\t\t\t\t\tthrow new TypeError(`Invalid locale: '${String(locale)}' is not a valid locale key`);\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tconst namespaceState = store.translations[namespaceKey].translations[locale];\n\n\t\t\t\t\t\tif (namespaceState.loadingPromise) {\n\t\t\t\t\t\t\treturn namespaceState.loadingPromise;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// Check if already loading to prevent duplicate requests\n\t\t\t\t\t\tif (namespaceState.isLoading) {\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// Check cache if enabled\n\t\t\t\t\t\tconst shouldUseCache = namespaceState.namespace && fromCache !== false;\n\t\t\t\t\t\tif (shouldUseCache) {\n\t\t\t\t\t\t\tstore.translations[namespaceKey].currentTranslation = namespaceState.namespace;\n\t\t\t\t\t\t\tstore.translations[namespaceKey].currentLocale = locale;\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// Reset error state and set loading state\n\t\t\t\t\t\tnamespaceState.isError = false;\n\t\t\t\t\t\tnamespaceState.isLoading = true;\n\n\t\t\t\t\t\tnamespaceState.loadingPromise = (async () => {\n\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\tconst namespaceState = store.translations[namespaceKey].translations[locale];\n\n\t\t\t\t\t\t\t\t// Load current locale translation\n\t\t\t\t\t\t\t\tconst loadedModule = await namespaceModuleMap[namespaceKey][locale]();\n\t\t\t\t\t\t\t\tlet currentTranslation = (await extractTranslation(\n\t\t\t\t\t\t\t\t\tloadedModule,\n\t\t\t\t\t\t\t\t\tlocale,\n\t\t\t\t\t\t\t\t\tnamespaceKey,\n\t\t\t\t\t\t\t\t)) as M[typeof namespaceKey];\n\n\t\t\t\t\t\t\t\t// Load fallback if enabled and different from current locale\n\t\t\t\t\t\t\t\tif (useFallback && locale !== fallbackLocale) {\n\t\t\t\t\t\t\t\t\tconst fallbackState = store.translations[namespaceKey].translations[fallbackLocale];\n\n\t\t\t\t\t\t\t\t\t// Check if fallback is already loaded\n\t\t\t\t\t\t\t\t\tlet fallbackTranslation: M[typeof namespaceKey] | undefined = fallbackState.namespace;\n\n\t\t\t\t\t\t\t\t\t// If fallback is not loaded, load it\n\t\t\t\t\t\t\t\t\tif (!fallbackTranslation) {\n\t\t\t\t\t\t\t\t\t\t// Check if fallback is already loading\n\t\t\t\t\t\t\t\t\t\tif (fallbackState.loadingPromise) {\n\t\t\t\t\t\t\t\t\t\t\tawait fallbackState.loadingPromise;\n\t\t\t\t\t\t\t\t\t\t\tfallbackTranslation = fallbackState.namespace;\n\t\t\t\t\t\t\t\t\t\t} else if (!fallbackState.isLoading) {\n\t\t\t\t\t\t\t\t\t\t\t// Load fallback with proper promise handling\n\t\t\t\t\t\t\t\t\t\t\tfallbackState.isError = false;\n\t\t\t\t\t\t\t\t\t\t\tfallbackState.isLoading = true;\n\n\t\t\t\t\t\t\t\t\t\t\tfallbackState.loadingPromise = (async () => {\n\t\t\t\t\t\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\t\t\t\t\t\tconst fallbackModule = await namespaceModuleMap[namespaceKey][fallbackLocale]();\n\t\t\t\t\t\t\t\t\t\t\t\t\tfallbackTranslation = (await extractTranslation(\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tfallbackModule,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tfallbackLocale,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tnamespaceKey,\n\t\t\t\t\t\t\t\t\t\t\t\t\t)) as M[typeof namespaceKey];\n\t\t\t\t\t\t\t\t\t\t\t\t\tfallbackState.namespace = fallbackTranslation;\n\t\t\t\t\t\t\t\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\t\t\t\t\t\t\t\tfallbackState.isError = true;\n\t\t\t\t\t\t\t\t\t\t\t\t\t// If fallback fails, continue with current translation\n\t\t\t\t\t\t\t\t\t\t\t\t} finally {\n\t\t\t\t\t\t\t\t\t\t\t\t\tfallbackState.isLoading = false;\n\t\t\t\t\t\t\t\t\t\t\t\t\tfallbackState.loadingPromise = undefined;\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t})();\n\n\t\t\t\t\t\t\t\t\t\t\tawait fallbackState.loadingPromise;\n\t\t\t\t\t\t\t\t\t\t\tfallbackTranslation = fallbackState.namespace;\n\t\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\t\t// Fallback is loading but no promise - this is a race condition edge case\n\t\t\t\t\t\t\t\t\t\t\t// Skip fallback merge and use current translation only to avoid infinite waiting\n\t\t\t\t\t\t\t\t\t\t\t// This shouldn't happen in normal flow, but we handle it gracefully\n\t\t\t\t\t\t\t\t\t\t\tfallbackTranslation = undefined;\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\t\t// Merge current with fallback using smart merge\n\t\t\t\t\t\t\t\t\tif (fallbackTranslation) {\n\t\t\t\t\t\t\t\t\t\tcurrentTranslation = smartDeepMerge(\n\t\t\t\t\t\t\t\t\t\t\tcurrentTranslation,\n\t\t\t\t\t\t\t\t\t\t\tfallbackTranslation,\n\t\t\t\t\t\t\t\t\t\t) as M[typeof namespaceKey];\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\tnamespaceState.namespace = currentTranslation;\n\n\t\t\t\t\t\t\t\tif (deleteOtherLocalesAfterLoad) {\n\t\t\t\t\t\t\t\t\tfor (const otherLocaleKey of Object.keys(\n\t\t\t\t\t\t\t\t\t\tstore.translations[namespaceKey].translations,\n\t\t\t\t\t\t\t\t\t) as (keyof L)[]) {\n\t\t\t\t\t\t\t\t\t\tif (otherLocaleKey !== locale && otherLocaleKey !== store.currentLocale) {\n\t\t\t\t\t\t\t\t\t\t\tstore.translations[namespaceKey].translations[otherLocaleKey].namespace = undefined;\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tstore.translations[namespaceKey].currentTranslation = namespaceState.namespace;\n\t\t\t\t\t\t\t\tstore.translations[namespaceKey].currentLocale = locale;\n\t\t\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\t\t\tnamespaceState.isError = true;\n\t\t\t\t\t\t\t\tthrow error;\n\t\t\t\t\t\t\t} finally {\n\t\t\t\t\t\t\t\tnamespaceState.isLoading = false;\n\t\t\t\t\t\t\t\tnamespaceState.loadingPromise = undefined; //\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t})();\n\n\t\t\t\t\t\treturn namespaceState.loadingPromise;\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 type { PluralVariants, PluralCategory } from '../types/plural-variants';\nimport type { CreatePluralSelectorOptions } from '../types/create-plural-selector';\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 * using Unicode CLDR plural rules.\n *\n * @param locale - Locale string (e.g., 'en', 'ru', 'fr', 'uk-UA')\n * @param options - Configuration options\n * @returns Function that takes a count and plural variants, returns the matching variant\n * @throws {TypeError} If locale is not a valid string\n * @throws {Error} If strict mode is enabled and 'other' variant is missing\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 *\n * @example\n * ```ts\n * const selectPlural = createPluralSelector('ru');\n * selectPlural(1, { one: 'элемент', few: 'элемента', many: 'элементов', other: 'элементов' }); // => 'элемент'\n * selectPlural(2, { one: 'элемент', few: 'элемента', many: 'элементов', other: 'элементов' }); // => 'элемента'\n * selectPlural(5, { one: 'элемент', few: 'элемента', many: 'элементов', other: 'элементов' }); // => 'элементов'\n * ```\n */\nexport const createPluralSelector = (\n\tlocale: string,\n\toptions: CreatePluralSelectorOptions = {},\n): ((count: number, variants: PluralVariants) => string) => {\n\tif (typeof locale !== 'string' || locale.trim().length === 0) {\n\t\tthrow new TypeError(`Invalid locale: expected non-empty string, got ${typeof locale}`);\n\t}\n\n\tconst { strict = false } = options;\n\n\tlet pluralRules: Intl.PluralRules;\n\ttry {\n\t\tpluralRules = new Intl.PluralRules(locale);\n\t} catch (error) {\n\t\tthrow new TypeError(`Invalid locale format: ${locale}. ${error instanceof Error ? error.message : String(error)}`);\n\t}\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 (must be finite)\n\t * @param variants - Object containing plural form variants (must include 'other')\n\t * @returns The selected variant string, or 'other' variant as fallback\n\t * @throws {TypeError} If count is not a finite number\n\t * @throws {Error} If strict mode is enabled and 'other' variant is missing\n\t */\n\treturn (count: number, variants: PluralVariants): string => {\n\t\tif (!Number.isFinite(count)) {\n\t\t\tthrow new TypeError(`Invalid count: expected finite number, got ${count}`);\n\t\t}\n\n\t\tif (strict && !variants.other) {\n\t\t\tthrow new Error(\"Plural variants must include 'other' variant when strict mode is enabled\");\n\t\t}\n\n\t\tconst pluralCategory = pluralRules.select(count) as PluralCategory;\n\t\tconst selectedVariant = variants[pluralCategory];\n\n\t\t// Return the selected variant if available, otherwise fallback to 'other'\n\t\t// 'other' is required in PluralVariants type, but we check at runtime for safety\n\t\tif (selectedVariant !== undefined && selectedVariant !== null) {\n\t\t\treturn selectedVariant;\n\t\t}\n\n\t\t// Fallback to 'other' if the specific category is not provided\n\t\t// This is the standard CLDR behavior\n\t\tif (variants.other !== undefined) {\n\t\t\treturn variants.other;\n\t\t}\n\n\t\t// Last resort: return empty string (should not happen if types are correct)\n\t\treturn '';\n\t};\n};\n"]}
package/dist/index.mjs CHANGED
@@ -1,38 +1,334 @@
1
1
  // src/utils/create-translation-module-map.ts
2
2
  var createTranslationModuleMap = (translations, locales, loadModule) => {
3
- const translationModules = {};
4
- for (const translationKey of Object.keys(translations)) {
5
- translationModules[translationKey] = {};
3
+ if (!translations || Object.keys(translations).length === 0) {
4
+ throw new TypeError("translations must be a non-empty object");
5
+ }
6
+ if (!locales || Object.keys(locales).length === 0) {
7
+ throw new TypeError("locales must be a non-empty object");
8
+ }
9
+ if (typeof loadModule !== "function") {
10
+ throw new TypeError("loadModule must be a function");
11
+ }
12
+ const namespaceModules = {};
13
+ for (const namespaceKey of Object.keys(translations)) {
14
+ namespaceModules[namespaceKey] = {};
6
15
  for (const localeKey of Object.keys(locales)) {
7
- translationModules[translationKey][localeKey] = () => loadModule(localeKey, translationKey);
16
+ namespaceModules[namespaceKey][localeKey] = () => loadModule(localeKey, namespaceKey);
8
17
  }
9
18
  }
10
- return translationModules;
19
+ return namespaceModules;
11
20
  };
12
21
 
22
+ // src/utils/event-emitter.ts
23
+ var EventEmitter = class {
24
+ constructor() {
25
+ this.listeners = {};
26
+ }
27
+ /**
28
+ * Registers an event listener for the specified event.
29
+ *
30
+ * @param event - Event name
31
+ * @param listener - Listener function
32
+ * @returns This instance for method chaining
33
+ */
34
+ on(event, listener) {
35
+ if (!this.listeners[event]) {
36
+ this.listeners[event] = /* @__PURE__ */ new Set();
37
+ }
38
+ this.listeners[event].add(listener);
39
+ return this;
40
+ }
41
+ /**
42
+ * Registers a one-time event listener that will be automatically removed after the first call.
43
+ *
44
+ * @param event - Event name
45
+ * @param listener - Listener function
46
+ * @returns This instance for method chaining
47
+ */
48
+ once(event, listener) {
49
+ const wrapper = (...args) => {
50
+ this.off(event, wrapper);
51
+ listener(...args);
52
+ };
53
+ return this.on(event, wrapper);
54
+ }
55
+ /**
56
+ * Removes an event listener. If no listener is provided, removes all listeners for the event.
57
+ *
58
+ * @param event - Event name
59
+ * @param listener - Optional listener function to remove
60
+ * @returns This instance for method chaining
61
+ */
62
+ off(event, listener) {
63
+ const set = this.listeners[event];
64
+ if (!set) {
65
+ return this;
66
+ }
67
+ if (!listener) {
68
+ set.clear();
69
+ } else {
70
+ set.delete(listener);
71
+ }
72
+ if (set.size === 0) {
73
+ delete this.listeners[event];
74
+ }
75
+ return this;
76
+ }
77
+ /**
78
+ * Emits an event, calling all registered listeners with the provided arguments.
79
+ *
80
+ * @param event - Event name
81
+ * @param args - Event arguments
82
+ * @returns True if there were listeners, false otherwise
83
+ */
84
+ emit(event, ...args) {
85
+ const set = this.listeners[event];
86
+ if (!set || set.size === 0) {
87
+ return false;
88
+ }
89
+ [...set].forEach((listener) => listener(...args));
90
+ return true;
91
+ }
92
+ /**
93
+ * Returns the number of listeners registered for the specified event.
94
+ *
95
+ * @param event - Event name
96
+ * @returns Number of listeners
97
+ */
98
+ listenerCount(event) {
99
+ return this.listeners[event]?.size ?? 0;
100
+ }
101
+ /**
102
+ * Removes all event listeners from all events.
103
+ *
104
+ * @returns This instance for method chaining
105
+ */
106
+ removeAllListeners() {
107
+ this.listeners = {};
108
+ return this;
109
+ }
110
+ /**
111
+ * Returns an array of all event names that have registered listeners.
112
+ *
113
+ * @returns Array of event names
114
+ */
115
+ eventNames() {
116
+ return Object.keys(this.listeners);
117
+ }
118
+ };
119
+
120
+ // src/utils/smart-merge.ts
121
+ function smartDeepMerge(current, fallback) {
122
+ if (current == null) return fallback;
123
+ if (fallback == null) return current;
124
+ const currentIsObject = typeof current === "object" && !Array.isArray(current);
125
+ const fallbackIsObject = typeof fallback === "object" && !Array.isArray(fallback);
126
+ const currentIsArray = Array.isArray(current);
127
+ const fallbackIsArray = Array.isArray(fallback);
128
+ if (currentIsObject && !fallbackIsObject && !fallbackIsArray || !currentIsObject && !currentIsArray && fallbackIsObject || currentIsArray && !fallbackIsArray || !currentIsArray && fallbackIsArray) {
129
+ return fallback;
130
+ }
131
+ if (!currentIsObject && !fallbackIsObject) {
132
+ return current != null ? current : fallback;
133
+ }
134
+ const result = { ...current };
135
+ for (const key in fallback) {
136
+ if (!(key in result)) {
137
+ result[key] = fallback[key];
138
+ } else {
139
+ const currentValue = result[key];
140
+ const fallbackValue = fallback[key];
141
+ const currentValueIsObject = currentValue != null && typeof currentValue === "object" && !Array.isArray(currentValue);
142
+ const fallbackValueIsObject = fallbackValue != null && typeof fallbackValue === "object" && !Array.isArray(fallbackValue);
143
+ const currentValueIsArray = Array.isArray(currentValue);
144
+ const fallbackValueIsArray = Array.isArray(fallbackValue);
145
+ if (currentValueIsObject && !fallbackValueIsObject && !fallbackValueIsArray || !currentValueIsObject && !currentValueIsArray && fallbackValueIsObject || currentValueIsArray && !fallbackValueIsArray || !currentValueIsArray && fallbackValueIsArray) {
146
+ result[key] = fallbackValue;
147
+ } else if (currentValueIsObject && fallbackValueIsObject) {
148
+ result[key] = smartDeepMerge(currentValue, fallbackValue);
149
+ } else if (currentValue == null) {
150
+ result[key] = fallbackValue;
151
+ }
152
+ }
153
+ }
154
+ return result;
155
+ }
156
+
13
157
  // src/utils/create-translation-store.ts
14
- var createTranslationStore = (translations, locales, loadModule, extractTranslation) => {
158
+ var createTranslationStore = ({
159
+ translations,
160
+ locales,
161
+ loadModule,
162
+ extractTranslation,
163
+ deleteOtherLocalesAfterLoad = false,
164
+ loadFromCache = true,
165
+ defaultLocale,
166
+ useFallback = false,
167
+ fallbackLocale = defaultLocale,
168
+ changeLocaleEventName = "change-locale"
169
+ }) => {
170
+ if (!translations || Object.keys(translations).length === 0) {
171
+ throw new TypeError("translations must be a non-empty object");
172
+ }
173
+ if (!locales || Object.keys(locales).length === 0) {
174
+ throw new TypeError("locales must be a non-empty object");
175
+ }
176
+ if (typeof loadModule !== "function") {
177
+ throw new TypeError("loadModule must be a function");
178
+ }
179
+ if (typeof extractTranslation !== "function") {
180
+ throw new TypeError("extractTranslation must be a function");
181
+ }
182
+ if (!(defaultLocale in locales)) {
183
+ throw new TypeError(`defaultLocale '${String(defaultLocale)}' must be a key in locales`);
184
+ }
185
+ if (useFallback && !(fallbackLocale in locales)) {
186
+ throw new TypeError(`fallbackLocale '${String(fallbackLocale)}' must be a key in locales`);
187
+ }
15
188
  return {
16
189
  /**
17
190
  * Creates a typed translation store.
191
+ * The store provides methods to load and access translations for each locale.
192
+ * When useFallback is enabled, translations are automatically merged with fallback locale.
18
193
  *
19
- * @template M - Type of translation object where each key corresponds to a key from translations
194
+ * @template M - Type of translation modules mapping where each key corresponds to a key from translations
20
195
  * @returns Store with methods to load translations for each locale
196
+ *
197
+ * @example
198
+ * ```ts
199
+ * const store = storeFactory.type<{
200
+ * common: { greeting: string; goodbye: string };
201
+ * errors: { notFound: string; unauthorized: string };
202
+ * }>();
203
+ *
204
+ * await store.common.load('ru');
205
+ * // If useFallback is true and 'ru' translation is missing some keys,
206
+ * // they will be filled from fallback locale (e.g., 'en')
207
+ * const greeting = store.common.translations.ru.namespace?.greeting;
208
+ * ```
21
209
  */
22
210
  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
- );
211
+ const namespaceModuleMap = createTranslationModuleMap(translations, locales, loadModule);
212
+ const emitter = new EventEmitter();
213
+ const store = {
214
+ currentLocale: defaultLocale,
215
+ locales,
216
+ translationsMap: translations,
217
+ translations: {},
218
+ addChangeLocaleListener: (listener) => {
219
+ emitter.on(changeLocaleEventName, listener);
220
+ },
221
+ removeChangeLocaleListener: (listener) => {
222
+ emitter.off(changeLocaleEventName, listener);
223
+ },
224
+ changeLocale: (locale) => {
225
+ store.currentLocale = locale;
226
+ emitter.emit(changeLocaleEventName, locale);
227
+ }
228
+ };
229
+ for (const namespaceKey of Object.keys(translations)) {
230
+ store.translations[namespaceKey] = {
231
+ currentTranslation: void 0,
232
+ currentLocale: void 0,
233
+ translations: Object.fromEntries(
234
+ Object.keys(locales).map((localeKey) => [
235
+ localeKey,
236
+ {
237
+ namespace: void 0,
238
+ isLoading: false,
239
+ isError: false,
240
+ loadingPromise: void 0
241
+ }
242
+ ])
243
+ ),
244
+ load: async (locale = store.currentLocale || defaultLocale, fromCache = loadFromCache) => {
245
+ if (!(locale in locales)) {
246
+ throw new TypeError(`Invalid locale: '${String(locale)}' is not a valid locale key`);
247
+ }
248
+ const namespaceState = store.translations[namespaceKey].translations[locale];
249
+ if (namespaceState.loadingPromise) {
250
+ return namespaceState.loadingPromise;
251
+ }
252
+ if (namespaceState.isLoading) {
253
+ return;
254
+ }
255
+ const shouldUseCache = namespaceState.namespace && fromCache !== false;
256
+ if (shouldUseCache) {
257
+ store.translations[namespaceKey].currentTranslation = namespaceState.namespace;
258
+ store.translations[namespaceKey].currentLocale = locale;
259
+ return;
260
+ }
261
+ namespaceState.isError = false;
262
+ namespaceState.isLoading = true;
263
+ namespaceState.loadingPromise = (async () => {
264
+ try {
265
+ const namespaceState2 = store.translations[namespaceKey].translations[locale];
266
+ const loadedModule = await namespaceModuleMap[namespaceKey][locale]();
267
+ let currentTranslation = await extractTranslation(
268
+ loadedModule,
269
+ locale,
270
+ namespaceKey
271
+ );
272
+ if (useFallback && locale !== fallbackLocale) {
273
+ const fallbackState = store.translations[namespaceKey].translations[fallbackLocale];
274
+ let fallbackTranslation = fallbackState.namespace;
275
+ if (!fallbackTranslation) {
276
+ if (fallbackState.loadingPromise) {
277
+ await fallbackState.loadingPromise;
278
+ fallbackTranslation = fallbackState.namespace;
279
+ } else if (!fallbackState.isLoading) {
280
+ fallbackState.isError = false;
281
+ fallbackState.isLoading = true;
282
+ fallbackState.loadingPromise = (async () => {
283
+ try {
284
+ const fallbackModule = await namespaceModuleMap[namespaceKey][fallbackLocale]();
285
+ fallbackTranslation = await extractTranslation(
286
+ fallbackModule,
287
+ fallbackLocale,
288
+ namespaceKey
289
+ );
290
+ fallbackState.namespace = fallbackTranslation;
291
+ } catch (error) {
292
+ fallbackState.isError = true;
293
+ } finally {
294
+ fallbackState.isLoading = false;
295
+ fallbackState.loadingPromise = void 0;
296
+ }
297
+ })();
298
+ await fallbackState.loadingPromise;
299
+ fallbackTranslation = fallbackState.namespace;
300
+ } else {
301
+ fallbackTranslation = void 0;
302
+ }
303
+ }
304
+ if (fallbackTranslation) {
305
+ currentTranslation = smartDeepMerge(
306
+ currentTranslation,
307
+ fallbackTranslation
308
+ );
309
+ }
310
+ }
311
+ namespaceState2.namespace = currentTranslation;
312
+ if (deleteOtherLocalesAfterLoad) {
313
+ for (const otherLocaleKey of Object.keys(
314
+ store.translations[namespaceKey].translations
315
+ )) {
316
+ if (otherLocaleKey !== locale && otherLocaleKey !== store.currentLocale) {
317
+ store.translations[namespaceKey].translations[otherLocaleKey].namespace = void 0;
318
+ }
319
+ }
320
+ }
321
+ store.translations[namespaceKey].currentTranslation = namespaceState2.namespace;
322
+ store.translations[namespaceKey].currentLocale = locale;
323
+ } catch (error) {
324
+ namespaceState.isError = true;
325
+ throw error;
326
+ } finally {
327
+ namespaceState.isLoading = false;
328
+ namespaceState.loadingPromise = void 0;
329
+ }
330
+ })();
331
+ return namespaceState.loadingPromise;
36
332
  }
37
333
  };
38
334
  }
@@ -42,15 +338,33 @@ var createTranslationStore = (translations, locales, loadModule, extractTranslat
42
338
  };
43
339
 
44
340
  // src/utils/create-plural-selector.ts
45
- var createPluralSelector = (locale) => {
46
- const pluralRules = new Intl.PluralRules(locale);
341
+ var createPluralSelector = (locale, options = {}) => {
342
+ if (typeof locale !== "string" || locale.trim().length === 0) {
343
+ throw new TypeError(`Invalid locale: expected non-empty string, got ${typeof locale}`);
344
+ }
345
+ const { strict = false } = options;
346
+ let pluralRules;
347
+ try {
348
+ pluralRules = new Intl.PluralRules(locale);
349
+ } catch (error) {
350
+ throw new TypeError(`Invalid locale format: ${locale}. ${error instanceof Error ? error.message : String(error)}`);
351
+ }
47
352
  return (count, variants) => {
353
+ if (!Number.isFinite(count)) {
354
+ throw new TypeError(`Invalid count: expected finite number, got ${count}`);
355
+ }
356
+ if (strict && !variants.other) {
357
+ throw new Error("Plural variants must include 'other' variant when strict mode is enabled");
358
+ }
48
359
  const pluralCategory = pluralRules.select(count);
49
360
  const selectedVariant = variants[pluralCategory];
50
- if (selectedVariant) {
361
+ if (selectedVariant !== void 0 && selectedVariant !== null) {
51
362
  return selectedVariant;
52
363
  }
53
- return variants.other ?? "";
364
+ if (variants.other !== void 0) {
365
+ return variants.other;
366
+ }
367
+ return "";
54
368
  };
55
369
  };
56
370
 
@@ -1 +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"]}
1
+ {"version":3,"sources":["../src/utils/create-translation-module-map.ts","../src/utils/event-emitter.ts","../src/utils/smart-merge.ts","../src/utils/create-translation-store.ts","../src/utils/create-plural-selector.ts"],"names":["namespaceState"],"mappings":"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","file":"index.mjs","sourcesContent":["import type { TranslationModuleMap } from '../types/create-translation-module-map';\n\n/**\n * Creates a map of translation module loaders for all combinations of translations and locales.\n * This map is used internally by the translation store to lazy-load translation modules.\n *\n * @template T - Type of translations object (e.g., { common: 'common', errors: 'errors' })\n * @template L - Type of locales object (e.g., { en: 'en', ru: 'ru' })\n * @template Module - Type of the raw module loaded from the module loader\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 namespace\n * @returns Immutable map where each namespace key contains an object with loader functions for each locale\n * @throws {TypeError} If translations or locales are empty objects\n *\n * @example\n * ```ts\n * const translations = { common: 'common', errors: 'errors' } as const;\n * const locales = { en: 'en', ru: 'ru' } as const;\n * const loadModule = async (locale, namespace) => import(`./${namespace}/${locale}.json`);\n *\n * const moduleMap = createTranslationModuleMap(translations, locales, loadModule);\n * moduleMap.common.en() will load './common/en.json'\n * moduleMap.errors.ru() will load './errors/ru.json'\n * ```\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, namespace: keyof T) => Promise<Module>,\n): TranslationModuleMap<T, L, Module> => {\n\tif (!translations || Object.keys(translations).length === 0) {\n\t\tthrow new TypeError('translations must be a non-empty object');\n\t}\n\n\tif (!locales || Object.keys(locales).length === 0) {\n\t\tthrow new TypeError('locales must be a non-empty object');\n\t}\n\n\tif (typeof loadModule !== 'function') {\n\t\tthrow new TypeError('loadModule must be a function');\n\t}\n\n\tconst namespaceModules = {} as Record<keyof T, Record<keyof L, () => Promise<Module>>>;\n\n\tfor (const namespaceKey of Object.keys(translations) as (keyof T)[]) {\n\t\tnamespaceModules[namespaceKey] = {} as Record<keyof L, () => Promise<Module>>;\n\n\t\tfor (const localeKey of Object.keys(locales) as (keyof L)[]) {\n\t\t\tnamespaceModules[namespaceKey][localeKey] = () => loadModule(localeKey, namespaceKey);\n\t\t}\n\t}\n\n\treturn namespaceModules as TranslationModuleMap<T, L, Module>;\n};\n","import type { Listener, EventMap } from '../types/event-emitter';\n\n/**\n * Event emitter class for managing event listeners.\n * Supports typed events with type-safe listeners.\n *\n * @template Events - Type of event map that maps event names to their argument types\n *\n * @example\n * ```ts\n * type MyEvents = {\n * 'user-login': [userId: string, timestamp: number];\n * 'user-logout': [userId: string];\n * };\n *\n * const emitter = new EventEmitter<MyEvents>();\n * emitter.on('user-login', (userId, timestamp) => {\n * console.log(`User ${userId} logged in at ${timestamp}`);\n * });\n * emitter.emit('user-login', 'user123', Date.now());\n * ```\n */\nexport class EventEmitter<Events extends EventMap = EventMap> {\n\tprivate listeners: {\n\t\t[K in keyof Events]?: Set<Listener<Events[K]>>;\n\t} = {};\n\n\t/**\n\t * Registers an event listener for the specified event.\n\t *\n\t * @param event - Event name\n\t * @param listener - Listener function\n\t * @returns This instance for method chaining\n\t */\n\ton<K extends keyof Events>(event: K, listener: Listener<Events[K]>): this {\n\t\tif (!this.listeners[event]) {\n\t\t\tthis.listeners[event] = new Set();\n\t\t}\n\t\tthis.listeners[event].add(listener);\n\t\treturn this;\n\t}\n\n\t/**\n\t * Registers a one-time event listener that will be automatically removed after the first call.\n\t *\n\t * @param event - Event name\n\t * @param listener - Listener function\n\t * @returns This instance for method chaining\n\t */\n\tonce<K extends keyof Events>(event: K, listener: Listener<Events[K]>): this {\n\t\tconst wrapper: Listener<Events[K]> = (...args) => {\n\t\t\tthis.off(event, wrapper);\n\t\t\tlistener(...args);\n\t\t};\n\t\treturn this.on(event, wrapper);\n\t}\n\n\t/**\n\t * Removes an event listener. If no listener is provided, removes all listeners for the event.\n\t *\n\t * @param event - Event name\n\t * @param listener - Optional listener function to remove\n\t * @returns This instance for method chaining\n\t */\n\toff<K extends keyof Events>(event: K, listener?: Listener<Events[K]>): this {\n\t\tconst set = this.listeners[event];\n\t\tif (!set) {\n\t\t\treturn this;\n\t\t}\n\n\t\tif (!listener) {\n\t\t\tset.clear();\n\t\t} else {\n\t\t\tset.delete(listener);\n\t\t}\n\n\t\tif (set.size === 0) {\n\t\t\tdelete this.listeners[event];\n\t\t}\n\t\treturn this;\n\t}\n\n\t/**\n\t * Emits an event, calling all registered listeners with the provided arguments.\n\t *\n\t * @param event - Event name\n\t * @param args - Event arguments\n\t * @returns True if there were listeners, false otherwise\n\t */\n\temit<K extends keyof Events>(event: K, ...args: Events[K]): boolean {\n\t\tconst set = this.listeners[event];\n\t\tif (!set || set.size === 0) {\n\t\t\treturn false;\n\t\t}\n\n\t\t[...set].forEach((listener) => listener(...args));\n\t\treturn true;\n\t}\n\n\t/**\n\t * Returns the number of listeners registered for the specified event.\n\t *\n\t * @param event - Event name\n\t * @returns Number of listeners\n\t */\n\tlistenerCount<K extends keyof Events>(event: K): number {\n\t\treturn this.listeners[event]?.size ?? 0;\n\t}\n\n\t/**\n\t * Removes all event listeners from all events.\n\t *\n\t * @returns This instance for method chaining\n\t */\n\tremoveAllListeners(): this {\n\t\tthis.listeners = {};\n\t\treturn this;\n\t}\n\n\t/**\n\t * Returns an array of all event names that have registered listeners.\n\t *\n\t * @returns Array of event names\n\t */\n\teventNames(): (keyof Events)[] {\n\t\treturn Object.keys(this.listeners) as (keyof Events)[];\n\t}\n}\n","/**\n * Performs a smart deep merge of two objects.\n * If structures differ at a key, the fallback value is used from that point.\n * Otherwise, current values are preserved.\n *\n * This function is used internally for merging translations with fallback locale.\n *\n * @param current - Current translation object\n * @param fallback - Fallback translation object\n * @returns Merged object with fallback values where structures differ\n *\n * @example\n * ```ts\n * const current = { a: { b: 1, c: 2 }, d: 3 };\n * const fallback = { a: { b: 1, c: 3 }, d: 3 };\n * const result = smartDeepMerge(current, fallback);\n * // Result: { a: { b: 1, c: 3 }, d: 3 } - only c is replaced from fallback\n * ```\n */\nexport function smartDeepMerge(current: any, fallback: any): any {\n\t// If either is null or undefined, return the other\n\tif (current == null) return fallback;\n\tif (fallback == null) return current;\n\n\t// Check if types differ (object vs primitive, or different object types)\n\tconst currentIsObject = typeof current === 'object' && !Array.isArray(current);\n\tconst fallbackIsObject = typeof fallback === 'object' && !Array.isArray(fallback);\n\tconst currentIsArray = Array.isArray(current);\n\tconst fallbackIsArray = Array.isArray(fallback);\n\n\t// If structure types differ (object vs primitive, or array vs object), use fallback\n\tif (\n\t\t(currentIsObject && !fallbackIsObject && !fallbackIsArray) ||\n\t\t(!currentIsObject && !currentIsArray && fallbackIsObject) ||\n\t\t(currentIsArray && !fallbackIsArray) ||\n\t\t(!currentIsArray && fallbackIsArray)\n\t) {\n\t\treturn fallback;\n\t}\n\n\t// If both are primitives or arrays, use current if it exists, otherwise fallback\n\tif (!currentIsObject && !fallbackIsObject) {\n\t\treturn current != null ? current : fallback;\n\t}\n\n\t// Both are objects - merge recursively\n\tconst result: any = { ...current };\n\n\t// Add missing keys from fallback\n\tfor (const key in fallback) {\n\t\tif (!(key in result)) {\n\t\t\t// Key doesn't exist in current, add from fallback\n\t\t\tresult[key] = fallback[key];\n\t\t} else {\n\t\t\t// Key exists in both - check if structures differ\n\t\t\tconst currentValue = result[key];\n\t\t\tconst fallbackValue = fallback[key];\n\n\t\t\t// Check if structures differ at this level\n\t\t\tconst currentValueIsObject = currentValue != null && typeof currentValue === 'object' && !Array.isArray(currentValue);\n\t\t\tconst fallbackValueIsObject = fallbackValue != null && typeof fallbackValue === 'object' && !Array.isArray(fallbackValue);\n\t\t\tconst currentValueIsArray = Array.isArray(currentValue);\n\t\t\tconst fallbackValueIsArray = Array.isArray(fallbackValue);\n\n\t\t\t// If structures differ (object vs primitive, or array vs object), use fallback from this point\n\t\t\tif (\n\t\t\t\t(currentValueIsObject && !fallbackValueIsObject && !fallbackValueIsArray) ||\n\t\t\t\t(!currentValueIsObject && !currentValueIsArray && fallbackValueIsObject) ||\n\t\t\t\t(currentValueIsArray && !fallbackValueIsArray) ||\n\t\t\t\t(!currentValueIsArray && fallbackValueIsArray)\n\t\t\t) {\n\t\t\t\tresult[key] = fallbackValue;\n\t\t\t} else if (currentValueIsObject && fallbackValueIsObject) {\n\t\t\t\t// Both are objects - merge recursively\n\t\t\t\tresult[key] = smartDeepMerge(currentValue, fallbackValue);\n\t\t\t} else if (currentValue == null) {\n\t\t\t\t// Current value is null/undefined, use fallback\n\t\t\t\tresult[key] = fallbackValue;\n\t\t\t}\n\t\t\t// If current value exists and is not null and structures match, keep it (already in result)\n\t\t}\n\t}\n\n\treturn result;\n}\n","import type { TranslationStore } from '../types/translation-store';\nimport type { CreateTranslationStoreOptions } from '../types/create-translation-store';\nimport type { TranslationModuleMap } from '../types/create-translation-module-map';\nimport { createTranslationModuleMap } from './create-translation-module-map';\n\nimport { EventEmitter } from './event-emitter';\nimport { smartDeepMerge } from './smart-merge';\n\n/**\n * Creates a translation store factory with typed translations for different locales.\n * The store supports lazy loading, caching, error handling, and fallback locale merging.\n *\n * @template T - Type of translations object (e.g., { common: 'common', errors: 'errors' })\n * @template L - Type of locales object (e.g., { en: 'en', ru: 'ru' })\n * @template Module - Type of the raw module loaded from the module loader\n *\n * @param options - Configuration options for the translation store\n * @returns Object with a `type()` method for creating a typed translation store\n * @throws {TypeError} If required options are invalid\n *\n * @example\n * ```ts\n * const translations = { common: 'common', errors: 'errors' } as const;\n * const locales = { en: 'en', ru: 'ru' } as const;\n *\n * const storeFactory = createTranslationStore({\n * translations,\n * locales,\n * loadModule: async (locale, namespace) => import(`./${namespace}/${locale}.json`),\n * extractTranslation: (module) => module.default || module,\n * defaultLocale: 'en',\n * useFallback: true,\n * fallbackLocale: 'en',\n * });\n *\n * const store = storeFactory.type<{\n * common: { greeting: string };\n * errors: { notFound: string };\n * }>();\n * ```\n */\nexport const createTranslationStore = <T extends Record<string, string>, L extends Record<string, string>, Module = unknown>({\n\ttranslations,\n\tlocales,\n\tloadModule,\n\textractTranslation,\n\tdeleteOtherLocalesAfterLoad = false,\n\tloadFromCache = true,\n\tdefaultLocale,\n\tuseFallback = false,\n\tfallbackLocale = defaultLocale,\n\tchangeLocaleEventName = 'change-locale',\n}: CreateTranslationStoreOptions<T, L, Module>) => {\n\t// Validate inputs\n\tif (!translations || Object.keys(translations).length === 0) {\n\t\tthrow new TypeError('translations must be a non-empty object');\n\t}\n\n\tif (!locales || Object.keys(locales).length === 0) {\n\t\tthrow new TypeError('locales must be a non-empty object');\n\t}\n\n\tif (typeof loadModule !== 'function') {\n\t\tthrow new TypeError('loadModule must be a function');\n\t}\n\n\tif (typeof extractTranslation !== 'function') {\n\t\tthrow new TypeError('extractTranslation must be a function');\n\t}\n\n\tif (!(defaultLocale in locales)) {\n\t\tthrow new TypeError(`defaultLocale '${String(defaultLocale)}' must be a key in locales`);\n\t}\n\n\tif (useFallback && !(fallbackLocale in locales)) {\n\t\tthrow new TypeError(`fallbackLocale '${String(fallbackLocale)}' must be a key in locales`);\n\t}\n\n\treturn {\n\t\t/**\n\t\t * Creates a typed translation store.\n\t\t * The store provides methods to load and access translations for each locale.\n\t\t * When useFallback is enabled, translations are automatically merged with fallback locale.\n\t\t *\n\t\t * @template M - Type of translation modules mapping 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\t * @example\n\t\t * ```ts\n\t\t * const store = storeFactory.type<{\n\t\t * common: { greeting: string; goodbye: string };\n\t\t * errors: { notFound: string; unauthorized: string };\n\t\t * }>();\n\t\t *\n\t\t * await store.common.load('ru');\n\t\t * // If useFallback is true and 'ru' translation is missing some keys,\n\t\t * // they will be filled from fallback locale (e.g., 'en')\n\t\t * const greeting = store.common.translations.ru.namespace?.greeting;\n\t\t * ```\n\t\t */\n\t\ttype: <M extends { [K in keyof T]: any }>(): TranslationStore<T, L, M> => {\n\t\t\tconst namespaceModuleMap: TranslationModuleMap<T, L, Module> = createTranslationModuleMap(translations, locales, loadModule);\n\t\t\tconst emitter = new EventEmitter();\n\n\t\t\tconst store = {\n\t\t\t\tcurrentLocale: defaultLocale,\n\t\t\t\tlocales,\n\t\t\t\ttranslationsMap: translations,\n\t\t\t\ttranslations: {},\n\t\t\t\taddChangeLocaleListener: (listener) => {\n\t\t\t\t\temitter.on(changeLocaleEventName, listener);\n\t\t\t\t},\n\t\t\t\tremoveChangeLocaleListener: (listener) => {\n\t\t\t\t\temitter.off(changeLocaleEventName, listener);\n\t\t\t\t},\n\t\t\t\tchangeLocale: (locale) => {\n\t\t\t\t\tstore.currentLocale = locale;\n\t\t\t\t\temitter.emit(changeLocaleEventName, locale);\n\t\t\t\t},\n\t\t\t} as TranslationStore<T, L, M>;\n\n\t\t\t// Initialize store structure for each translation key\n\t\t\tfor (const namespaceKey of Object.keys(translations) as (keyof T)[]) {\n\t\t\t\t// Create initial state for all locales\n\n\t\t\t\tstore.translations[namespaceKey] = {\n\t\t\t\t\tcurrentTranslation: undefined,\n\t\t\t\t\tcurrentLocale: undefined,\n\t\t\t\t\ttranslations: Object.fromEntries(\n\t\t\t\t\t\tObject.keys(locales).map((localeKey) => [\n\t\t\t\t\t\t\tlocaleKey,\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tnamespace: undefined,\n\t\t\t\t\t\t\t\tisLoading: false,\n\t\t\t\t\t\t\t\tisError: false,\n\t\t\t\t\t\t\t\tloadingPromise: undefined,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t]),\n\t\t\t\t\t) as TranslationStore<T, L, M>['translations'][keyof T]['translations'],\n\t\t\t\t\tload: async (\n\t\t\t\t\t\tlocale: keyof L = store.currentLocale || defaultLocale,\n\t\t\t\t\t\tfromCache: boolean = loadFromCache,\n\t\t\t\t\t): Promise<void> => {\n\t\t\t\t\t\t// Validate locale\n\t\t\t\t\t\tif (!(locale in locales)) {\n\t\t\t\t\t\t\tthrow new TypeError(`Invalid locale: '${String(locale)}' is not a valid locale key`);\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tconst namespaceState = store.translations[namespaceKey].translations[locale];\n\n\t\t\t\t\t\tif (namespaceState.loadingPromise) {\n\t\t\t\t\t\t\treturn namespaceState.loadingPromise;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// Check if already loading to prevent duplicate requests\n\t\t\t\t\t\tif (namespaceState.isLoading) {\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// Check cache if enabled\n\t\t\t\t\t\tconst shouldUseCache = namespaceState.namespace && fromCache !== false;\n\t\t\t\t\t\tif (shouldUseCache) {\n\t\t\t\t\t\t\tstore.translations[namespaceKey].currentTranslation = namespaceState.namespace;\n\t\t\t\t\t\t\tstore.translations[namespaceKey].currentLocale = locale;\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// Reset error state and set loading state\n\t\t\t\t\t\tnamespaceState.isError = false;\n\t\t\t\t\t\tnamespaceState.isLoading = true;\n\n\t\t\t\t\t\tnamespaceState.loadingPromise = (async () => {\n\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\tconst namespaceState = store.translations[namespaceKey].translations[locale];\n\n\t\t\t\t\t\t\t\t// Load current locale translation\n\t\t\t\t\t\t\t\tconst loadedModule = await namespaceModuleMap[namespaceKey][locale]();\n\t\t\t\t\t\t\t\tlet currentTranslation = (await extractTranslation(\n\t\t\t\t\t\t\t\t\tloadedModule,\n\t\t\t\t\t\t\t\t\tlocale,\n\t\t\t\t\t\t\t\t\tnamespaceKey,\n\t\t\t\t\t\t\t\t)) as M[typeof namespaceKey];\n\n\t\t\t\t\t\t\t\t// Load fallback if enabled and different from current locale\n\t\t\t\t\t\t\t\tif (useFallback && locale !== fallbackLocale) {\n\t\t\t\t\t\t\t\t\tconst fallbackState = store.translations[namespaceKey].translations[fallbackLocale];\n\n\t\t\t\t\t\t\t\t\t// Check if fallback is already loaded\n\t\t\t\t\t\t\t\t\tlet fallbackTranslation: M[typeof namespaceKey] | undefined = fallbackState.namespace;\n\n\t\t\t\t\t\t\t\t\t// If fallback is not loaded, load it\n\t\t\t\t\t\t\t\t\tif (!fallbackTranslation) {\n\t\t\t\t\t\t\t\t\t\t// Check if fallback is already loading\n\t\t\t\t\t\t\t\t\t\tif (fallbackState.loadingPromise) {\n\t\t\t\t\t\t\t\t\t\t\tawait fallbackState.loadingPromise;\n\t\t\t\t\t\t\t\t\t\t\tfallbackTranslation = fallbackState.namespace;\n\t\t\t\t\t\t\t\t\t\t} else if (!fallbackState.isLoading) {\n\t\t\t\t\t\t\t\t\t\t\t// Load fallback with proper promise handling\n\t\t\t\t\t\t\t\t\t\t\tfallbackState.isError = false;\n\t\t\t\t\t\t\t\t\t\t\tfallbackState.isLoading = true;\n\n\t\t\t\t\t\t\t\t\t\t\tfallbackState.loadingPromise = (async () => {\n\t\t\t\t\t\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\t\t\t\t\t\tconst fallbackModule = await namespaceModuleMap[namespaceKey][fallbackLocale]();\n\t\t\t\t\t\t\t\t\t\t\t\t\tfallbackTranslation = (await extractTranslation(\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tfallbackModule,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tfallbackLocale,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tnamespaceKey,\n\t\t\t\t\t\t\t\t\t\t\t\t\t)) as M[typeof namespaceKey];\n\t\t\t\t\t\t\t\t\t\t\t\t\tfallbackState.namespace = fallbackTranslation;\n\t\t\t\t\t\t\t\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\t\t\t\t\t\t\t\tfallbackState.isError = true;\n\t\t\t\t\t\t\t\t\t\t\t\t\t// If fallback fails, continue with current translation\n\t\t\t\t\t\t\t\t\t\t\t\t} finally {\n\t\t\t\t\t\t\t\t\t\t\t\t\tfallbackState.isLoading = false;\n\t\t\t\t\t\t\t\t\t\t\t\t\tfallbackState.loadingPromise = undefined;\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t})();\n\n\t\t\t\t\t\t\t\t\t\t\tawait fallbackState.loadingPromise;\n\t\t\t\t\t\t\t\t\t\t\tfallbackTranslation = fallbackState.namespace;\n\t\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\t\t// Fallback is loading but no promise - this is a race condition edge case\n\t\t\t\t\t\t\t\t\t\t\t// Skip fallback merge and use current translation only to avoid infinite waiting\n\t\t\t\t\t\t\t\t\t\t\t// This shouldn't happen in normal flow, but we handle it gracefully\n\t\t\t\t\t\t\t\t\t\t\tfallbackTranslation = undefined;\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\t\t// Merge current with fallback using smart merge\n\t\t\t\t\t\t\t\t\tif (fallbackTranslation) {\n\t\t\t\t\t\t\t\t\t\tcurrentTranslation = smartDeepMerge(\n\t\t\t\t\t\t\t\t\t\t\tcurrentTranslation,\n\t\t\t\t\t\t\t\t\t\t\tfallbackTranslation,\n\t\t\t\t\t\t\t\t\t\t) as M[typeof namespaceKey];\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\tnamespaceState.namespace = currentTranslation;\n\n\t\t\t\t\t\t\t\tif (deleteOtherLocalesAfterLoad) {\n\t\t\t\t\t\t\t\t\tfor (const otherLocaleKey of Object.keys(\n\t\t\t\t\t\t\t\t\t\tstore.translations[namespaceKey].translations,\n\t\t\t\t\t\t\t\t\t) as (keyof L)[]) {\n\t\t\t\t\t\t\t\t\t\tif (otherLocaleKey !== locale && otherLocaleKey !== store.currentLocale) {\n\t\t\t\t\t\t\t\t\t\t\tstore.translations[namespaceKey].translations[otherLocaleKey].namespace = undefined;\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tstore.translations[namespaceKey].currentTranslation = namespaceState.namespace;\n\t\t\t\t\t\t\t\tstore.translations[namespaceKey].currentLocale = locale;\n\t\t\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\t\t\tnamespaceState.isError = true;\n\t\t\t\t\t\t\t\tthrow error;\n\t\t\t\t\t\t\t} finally {\n\t\t\t\t\t\t\t\tnamespaceState.isLoading = false;\n\t\t\t\t\t\t\t\tnamespaceState.loadingPromise = undefined; //\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t})();\n\n\t\t\t\t\t\treturn namespaceState.loadingPromise;\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 type { PluralVariants, PluralCategory } from '../types/plural-variants';\nimport type { CreatePluralSelectorOptions } from '../types/create-plural-selector';\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 * using Unicode CLDR plural rules.\n *\n * @param locale - Locale string (e.g., 'en', 'ru', 'fr', 'uk-UA')\n * @param options - Configuration options\n * @returns Function that takes a count and plural variants, returns the matching variant\n * @throws {TypeError} If locale is not a valid string\n * @throws {Error} If strict mode is enabled and 'other' variant is missing\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 *\n * @example\n * ```ts\n * const selectPlural = createPluralSelector('ru');\n * selectPlural(1, { one: 'элемент', few: 'элемента', many: 'элементов', other: 'элементов' }); // => 'элемент'\n * selectPlural(2, { one: 'элемент', few: 'элемента', many: 'элементов', other: 'элементов' }); // => 'элемента'\n * selectPlural(5, { one: 'элемент', few: 'элемента', many: 'элементов', other: 'элементов' }); // => 'элементов'\n * ```\n */\nexport const createPluralSelector = (\n\tlocale: string,\n\toptions: CreatePluralSelectorOptions = {},\n): ((count: number, variants: PluralVariants) => string) => {\n\tif (typeof locale !== 'string' || locale.trim().length === 0) {\n\t\tthrow new TypeError(`Invalid locale: expected non-empty string, got ${typeof locale}`);\n\t}\n\n\tconst { strict = false } = options;\n\n\tlet pluralRules: Intl.PluralRules;\n\ttry {\n\t\tpluralRules = new Intl.PluralRules(locale);\n\t} catch (error) {\n\t\tthrow new TypeError(`Invalid locale format: ${locale}. ${error instanceof Error ? error.message : String(error)}`);\n\t}\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 (must be finite)\n\t * @param variants - Object containing plural form variants (must include 'other')\n\t * @returns The selected variant string, or 'other' variant as fallback\n\t * @throws {TypeError} If count is not a finite number\n\t * @throws {Error} If strict mode is enabled and 'other' variant is missing\n\t */\n\treturn (count: number, variants: PluralVariants): string => {\n\t\tif (!Number.isFinite(count)) {\n\t\t\tthrow new TypeError(`Invalid count: expected finite number, got ${count}`);\n\t\t}\n\n\t\tif (strict && !variants.other) {\n\t\t\tthrow new Error(\"Plural variants must include 'other' variant when strict mode is enabled\");\n\t\t}\n\n\t\tconst pluralCategory = pluralRules.select(count) as PluralCategory;\n\t\tconst selectedVariant = variants[pluralCategory];\n\n\t\t// Return the selected variant if available, otherwise fallback to 'other'\n\t\t// 'other' is required in PluralVariants type, but we check at runtime for safety\n\t\tif (selectedVariant !== undefined && selectedVariant !== null) {\n\t\t\treturn selectedVariant;\n\t\t}\n\n\t\t// Fallback to 'other' if the specific category is not provided\n\t\t// This is the standard CLDR behavior\n\t\tif (variants.other !== undefined) {\n\t\t\treturn variants.other;\n\t\t}\n\n\t\t// Last resort: return empty string (should not happen if types are correct)\n\t\treturn '';\n\t};\n};\n"]}