@vielzeug/i18nit 1.1.4 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/README.md +52 -710
  2. package/dist/core.cjs +2 -0
  3. package/dist/core.cjs.map +1 -0
  4. package/dist/core.d.ts +35 -0
  5. package/dist/core.d.ts.map +1 -0
  6. package/dist/core.js +53 -0
  7. package/dist/core.js.map +1 -0
  8. package/dist/helpers.cjs +2 -0
  9. package/dist/helpers.cjs.map +1 -0
  10. package/dist/helpers.d.ts +20 -0
  11. package/dist/helpers.d.ts.map +1 -0
  12. package/dist/helpers.js +47 -0
  13. package/dist/helpers.js.map +1 -0
  14. package/dist/i18n.cjs +2 -0
  15. package/dist/i18n.cjs.map +1 -0
  16. package/dist/i18n.d.ts +78 -0
  17. package/dist/i18n.d.ts.map +1 -0
  18. package/dist/i18n.js +218 -0
  19. package/dist/i18n.js.map +1 -0
  20. package/dist/i18nit.cjs +2 -2
  21. package/dist/i18nit.cjs.map +1 -1
  22. package/dist/i18nit.d.ts +3 -0
  23. package/dist/i18nit.d.ts.map +1 -0
  24. package/dist/i18nit.js +2 -222
  25. package/dist/i18nit.js.map +1 -1
  26. package/dist/index.cjs +1 -2
  27. package/dist/index.d.ts +4 -75
  28. package/dist/index.d.ts.map +1 -0
  29. package/dist/index.js +2 -5
  30. package/dist/interpolate.cjs +2 -0
  31. package/dist/interpolate.cjs.map +1 -0
  32. package/dist/interpolate.d.ts +11 -0
  33. package/dist/interpolate.d.ts.map +1 -0
  34. package/dist/interpolate.js +13 -0
  35. package/dist/interpolate.js.map +1 -0
  36. package/dist/intl.cjs +2 -0
  37. package/dist/intl.cjs.map +1 -0
  38. package/dist/intl.d.ts +16 -0
  39. package/dist/intl.d.ts.map +1 -0
  40. package/dist/intl.js +65 -0
  41. package/dist/intl.js.map +1 -0
  42. package/dist/types.d.ts +97 -0
  43. package/dist/types.d.ts.map +1 -0
  44. package/package.json +19 -9
  45. package/dist/index.cjs.map +0 -1
  46. package/dist/index.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"i18nit.js","sources":["../src/i18nit.ts"],"sourcesContent":["/* ============================================\n i18nit - Lightweight, type-safe i18n library\n ============================================ */\n\n/* -------------------- Core Types -------------------- */\n\nexport type Locale = string;\n\nexport type PluralForm = 'zero' | 'one' | 'two' | 'few' | 'many' | 'other';\n\nexport type PluralMessages = Partial<Record<PluralForm, string>> & { other: string };\n\nexport type MessageValue = string | PluralMessages;\n\nexport type Messages = Record<string, MessageValue>;\n\nexport type TranslateOptions = {\n locale?: Locale;\n escape?: boolean;\n};\n\nexport type I18nConfig = {\n locale?: Locale;\n fallback?: Locale | Locale[];\n messages?: Record<Locale, Messages>;\n loaders?: Record<Locale, () => Promise<Messages>>;\n escape?: boolean;\n};\n\n/* -------------------- Path Resolution -------------------- */\n\nfunction escapeHtml(str: string): string {\n return str\n .replace(/&/g, '&amp;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;')\n .replace(/\"/g, '&quot;')\n .replace(/'/g, '&#39;');\n}\n\n/**\n * Resolves nested properties using dot notation and bracket notation.\n * Supports: 'user.name', 'items[0]', 'user.items[0].name'\n */\nfunction resolvePath(obj: Record<string, unknown>, path: string): unknown {\n // Try direct access first (handles keys with dots)\n if (path in obj) return obj[path];\n\n // Parse path segments\n const parts = path.match(/[^.[\\]]+/g) || [];\n let value: unknown = obj;\n\n for (const part of parts) {\n if (value == null || typeof value !== 'object') return undefined;\n\n if (Array.isArray(value)) {\n const index = Number(part);\n if (Number.isNaN(index) || index < 0 || index >= value.length) {\n return undefined;\n }\n value = value[index];\n } else {\n value = (value as Record<string, unknown>)[part];\n }\n }\n\n return value;\n}\n\n/* -------------------- List Formatting -------------------- */\n\n/**\n * Formats an array as a natural language list using Intl.ListFormat.\n * Automatically handles locale-specific conjunctions and grammar.\n */\nfunction formatList(items: unknown[], locale: string, type: 'conjunction' | 'disjunction'): string {\n if (items.length === 0) return '';\n\n const stringItems = items.map(String);\n\n try {\n const formatter = new Intl.ListFormat(locale, { style: 'long', type });\n return formatter.format(stringItems);\n } catch {\n // Fallback for environments without Intl.ListFormat\n if (stringItems.length === 1) return stringItems[0];\n if (stringItems.length === 2) {\n const separator = type === 'conjunction' ? 'and' : 'or';\n return `${stringItems[0]} ${separator} ${stringItems[1]}`;\n }\n const separator = type === 'conjunction' ? 'and' : 'or';\n const last = stringItems.pop()!;\n return `${stringItems.join(', ')} ${separator} ${last}`;\n }\n}\n\n/* -------------------- Variable Interpolation -------------------- */\n\n/**\n * Interpolates variables into a template string.\n *\n * Supported formats:\n * - {name} - Simple variable\n * - {user.name} - Nested property\n * - {items[0]} - Array index\n * - {items} - Array (comma-separated)\n * - {items|and} - Array with locale-aware \"and\"\n * - {items|or} - Array with locale-aware \"or\"\n * - {items| - } - Array with custom separator\n * - {items.length} - Array length\n */\nfunction interpolate(template: string, vars: Record<string, unknown>, locale: string): string {\n return template.replace(/\\{([\\w.[\\]]+)(?:\\|([^}]+))?\\}/g, (_match, key: string, separator?: string) => {\n // Handle .length property\n const isLength = key.endsWith('.length');\n const actualKey = isLength ? key.slice(0, -7) : key;\n\n const value = resolvePath(vars, actualKey);\n\n // Missing variables are replaced with empty string\n if (value == null) return '';\n\n // Handle arrays\n if (Array.isArray(value)) {\n if (isLength) return String(value.length);\n\n if (separator !== undefined) {\n if (separator === 'and') return formatList(value, locale, 'conjunction');\n if (separator === 'or') return formatList(value, locale, 'disjunction');\n return value.map(String).join(separator);\n }\n\n return value.map(String).join(', ');\n }\n\n // Format numbers with locale\n if (typeof value === 'number') {\n try {\n return new Intl.NumberFormat(locale).format(value);\n } catch {\n return String(value);\n }\n }\n\n return String(value);\n });\n}\n\n/* -------------------- Pluralization -------------------- */\n\n/**\n * Gets the plural form for a number using Intl.PluralRules.\n * Automatically handles all locale-specific plural rules.\n */\nfunction getPluralForm(locale: Locale, count: number): PluralForm {\n const n = Math.abs(Math.floor(count));\n\n try {\n const rules = new Intl.PluralRules(locale);\n return rules.select(n) as PluralForm;\n } catch {\n // Fallback to English-like behavior\n return n === 1 ? 'one' : 'other';\n }\n}\n\n/* -------------------- I18n Class -------------------- */\n\nclass I18n {\n private locale: Locale;\n private fallbacks: Locale[];\n private escape: boolean;\n private catalogs = new Map<Locale, Messages>();\n private loaders = new Map<Locale, () => Promise<Messages>>();\n private loading = new Map<Locale, Promise<void>>();\n private subscribers = new Set<(locale: Locale) => void>();\n\n constructor(config: I18nConfig = {}) {\n this.locale = config.locale ?? 'en';\n this.fallbacks = Array.isArray(config.fallback) ? config.fallback : config.fallback ? [config.fallback] : [];\n this.escape = config.escape ?? false;\n\n if (config.messages) {\n for (const [locale, messages] of Object.entries(config.messages)) {\n this.catalogs.set(locale, messages);\n }\n }\n\n if (config.loaders) {\n for (const [locale, loader] of Object.entries(config.loaders)) {\n this.loaders.set(locale, loader);\n }\n }\n }\n\n /* -------------------- Locale Management -------------------- */\n\n setLocale(locale: Locale): void {\n if (this.locale === locale) return;\n this.locale = locale;\n this.notifySubscribers();\n }\n\n getLocale(): Locale {\n return this.locale;\n }\n\n /* -------------------- Message Management -------------------- */\n\n /**\n * Adds messages to a locale (merges with existing).\n */\n add(locale: Locale, messages: Messages): void {\n const existing = this.catalogs.get(locale) ?? {};\n this.catalogs.set(locale, { ...existing, ...messages });\n this.notifySubscribers();\n }\n\n /**\n * Sets messages for a locale (replaces existing).\n */\n set(locale: Locale, messages: Messages): void {\n this.catalogs.set(locale, messages);\n this.notifySubscribers();\n }\n\n getMessages(locale: Locale): Messages | undefined {\n return this.catalogs.get(locale);\n }\n\n hasLocale(locale: Locale): boolean {\n return this.catalogs.has(locale);\n }\n\n has(key: string, locale?: Locale): boolean {\n return this.findMessage(key, locale ?? this.locale) !== undefined;\n }\n\n /* -------------------- Async Loaders -------------------- */\n\n async load(locale: Locale): Promise<void> {\n // Return existing loading promise\n if (this.loading.has(locale)) return this.loading.get(locale);\n\n // Already loaded\n if (this.catalogs.has(locale)) return;\n\n const loader = this.loaders.get(locale);\n if (!loader) return;\n\n const promise = (async () => {\n try {\n const messages = await loader();\n this.add(locale, messages);\n } catch (error) {\n console.warn(`[I18n] Failed to load locale '${locale}':`, error);\n throw error;\n } finally {\n this.loading.delete(locale);\n }\n })();\n\n this.loading.set(locale, promise);\n return promise;\n }\n\n register(locale: Locale, loader: () => Promise<Messages>): void {\n this.loaders.set(locale, loader);\n }\n\n async hasAsync(key: string, locale?: Locale): Promise<boolean> {\n const targetLocale = locale ?? this.locale;\n\n if (!this.catalogs.has(targetLocale) && this.loaders.has(targetLocale)) {\n await this.load(targetLocale);\n }\n\n return this.has(key, targetLocale);\n }\n\n /**\n * Load multiple locales in parallel.\n * Useful for preloading all needed locales at app startup.\n */\n async loadAll(locales: Locale[]): Promise<void> {\n await Promise.all(locales.map((locale) => this.load(locale)));\n }\n\n /* -------------------- Translation -------------------- */\n\n /**\n * Translates a key with optional variables and options.\n * Synchronous - locale must be loaded first via load() or provided in config.\n */\n t(key: string, vars?: Record<string, unknown>, options?: TranslateOptions): string {\n const targetLocale = options?.locale ?? this.locale;\n const shouldEscape = options?.escape ?? this.escape;\n\n const message = this.findMessage(key, targetLocale);\n if (message === undefined) return key;\n\n const result = this.formatMessage(message, vars ?? {}, targetLocale);\n return shouldEscape ? escapeHtml(result) : result;\n }\n\n /* -------------------- Formatting Helpers -------------------- */\n\n number(value: number, options?: Intl.NumberFormatOptions, locale?: Locale): string {\n try {\n return new Intl.NumberFormat(locale ?? this.locale, options).format(value);\n } catch {\n return String(value);\n }\n }\n\n date(value: Date | number, options?: Intl.DateTimeFormatOptions, locale?: Locale): string {\n const date = typeof value === 'number' ? new Date(value) : value;\n try {\n return new Intl.DateTimeFormat(locale ?? this.locale, options).format(date);\n } catch {\n return date.toString();\n }\n }\n\n /* -------------------- Namespaced Translator -------------------- */\n\n namespace(ns: string) {\n return {\n t: (key: string, vars?: Record<string, unknown>, options?: TranslateOptions) =>\n this.t(`${ns}.${key}`, vars, options),\n };\n }\n\n /* -------------------- Subscriptions -------------------- */\n\n subscribe(handler: (locale: Locale) => void): () => void {\n this.subscribers.add(handler);\n\n // Call handler immediately with the current locale\n try {\n handler(this.locale);\n } catch {\n // Ignore handler errors\n }\n\n return () => this.subscribers.delete(handler);\n }\n\n private notifySubscribers(): void {\n for (const handler of this.subscribers) {\n try {\n handler(this.locale);\n } catch {\n // Ignore handler errors\n }\n }\n }\n\n /* -------------------- Internal Helpers -------------------- */\n\n private findMessage(key: string, locale: Locale): MessageValue | undefined {\n const locales = this.getLocaleChain(locale);\n\n for (const loc of locales) {\n const messages = this.catalogs.get(loc);\n if (!messages) continue;\n\n const value = resolvePath(messages, key);\n if (value !== undefined) return value as MessageValue;\n }\n\n return undefined;\n }\n\n private getLocaleChain(locale: Locale): Locale[] {\n const chain: Locale[] = [locale];\n\n // Add base language (e.g., 'en' from 'en-US')\n const lang = locale.split('-')[0];\n if (lang !== locale) chain.push(lang);\n\n // Add fallback locales\n for (const fallback of this.fallbacks) {\n chain.push(fallback);\n const fallbackLang = fallback.split('-')[0];\n if (fallbackLang !== fallback) chain.push(fallbackLang);\n }\n\n return chain;\n }\n\n private formatMessage(message: MessageValue, vars: Record<string, unknown>, locale: Locale): string {\n // Plural messages\n if (typeof message === 'object' && 'other' in message) {\n const count = Number(vars.count ?? 0);\n const pluralMsg = message as PluralMessages;\n\n // Prefer an explicit 'zero' form\n let form: PluralForm;\n if (count === 0 && pluralMsg.zero !== undefined) {\n form = 'zero';\n } else {\n form = getPluralForm(locale, count);\n }\n\n const template = pluralMsg[form] ?? pluralMsg.other;\n return interpolate(template, vars, locale);\n }\n\n // String messages\n if (typeof message === 'string') {\n return interpolate(message, vars, locale);\n }\n\n return '';\n }\n}\n\n/* -------------------- Factory Function -------------------- */\n\nexport function createI18n(config?: I18nConfig): I18n {\n return new I18n(config);\n}\n"],"names":["escapeHtml","str","resolvePath","obj","path","parts","value","part","index","formatList","items","locale","type","stringItems","separator","last","interpolate","template","vars","_match","key","isLength","actualKey","getPluralForm","count","n","I18n","config","messages","loader","existing","promise","error","targetLocale","locales","options","shouldEscape","message","result","date","ns","handler","loc","chain","lang","fallback","fallbackLang","pluralMsg","form","createI18n"],"mappings":"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"}
1
+ {"version":3,"file":"i18nit.js","names":["#core","#fixedLocale","#prefix","#key","#cap","#caches","#view","#locale","#fallbacks","#onMissing","#onDiagnostic","#catalogs","#loaders","#core","#findMessage","#translate","#localesCache","#notify","#getLocaleChain","#loadOne","#loadersCache","#batchDepth","#pendingNotify","#subscribers","#diagnoseSubscriber","#disposed","#loading","#chainCache","#diagnoseLoader"],"sources":["../src/core.ts","../src/helpers.ts","../src/intl.ts","../src/interpolate.ts","../src/i18n.ts"],"sourcesContent":["import type { BoundI18n, Locale, MessageValue, Messages, NamespaceKeys, TranslationKeyParam, Vars } from './types';\n\n/* -------------------- I18nCore -------------------- */\n\n/**\n * Module-private interface given to every `BoundView` — exposes only the operations views need.\n * Created once per `I18n` instance so `scope()` / `withLocale()` allocate no closures per call.\n */\nexport type I18nCore = {\n checkOwn(key: string, locale: Locale): boolean;\n findMessage(key: string, locale: Locale): MessageValue | undefined;\n formatDate(value: Date | number, options: Intl.DateTimeFormatOptions | undefined, locale: Locale): string;\n formatList(items: unknown[], locale: string, type: 'and' | 'or'): string;\n formatNumber(value: number, options: Intl.NumberFormatOptions | undefined, locale: Locale): string;\n formatRelative(\n value: number,\n unit: Intl.RelativeTimeFormatUnit,\n options: Intl.RelativeTimeFormatOptions | undefined,\n locale: Locale,\n ): string;\n getLocale(): Locale;\n translate(key: string, vars: Vars | undefined, locale: Locale): string;\n};\n\n/* -------------------- BoundView -------------------- */\n\n/**\n * Lightweight view over an `I18n` instance, fixed to a locale and/or key namespace prefix.\n * All methods live on the prototype — no closures are allocated per `scope()` / `withLocale()` call.\n */\nexport class BoundView<T extends Messages = Messages> implements BoundI18n<T> {\n readonly #core: I18nCore;\n readonly #fixedLocale: Locale | null;\n readonly #prefix: string | undefined;\n\n constructor(core: I18nCore, fixedLocale: Locale | null, prefix?: string) {\n this.#core = core;\n this.#fixedLocale = fixedLocale;\n this.#prefix = prefix;\n }\n\n get locale(): Locale {\n return this.#fixedLocale ?? this.#core.getLocale();\n }\n\n #key(key: string): string {\n return this.#prefix ? `${this.#prefix}.${key}` : key;\n }\n\n t(key: TranslationKeyParam<T>, vars?: Vars): string {\n return this.#core.translate(this.#key(key as string), vars, this.locale);\n }\n\n has(key: string): boolean {\n return this.#core.findMessage(this.#key(key), this.locale) !== undefined;\n }\n\n hasOwn(key: string): boolean {\n return this.#core.checkOwn(this.#key(key), this.locale);\n }\n\n number(value: number, options?: Intl.NumberFormatOptions): string {\n return this.#core.formatNumber(value, options, this.locale);\n }\n\n date(value: Date | number, options?: Intl.DateTimeFormatOptions): string {\n return this.#core.formatDate(value, options, this.locale);\n }\n\n list(items: unknown[], type: 'and' | 'or' = 'and'): string {\n return this.#core.formatList(items, this.locale, type);\n }\n\n relative(value: number, unit: Intl.RelativeTimeFormatUnit, options?: Intl.RelativeTimeFormatOptions): string {\n return this.#core.formatRelative(value, unit, options, this.locale);\n }\n\n currency(value: number, currency: string, options?: Omit<Intl.NumberFormatOptions, 'style' | 'currency'>): string {\n return this.#core.formatNumber(value, { ...options, currency, style: 'currency' }, this.locale);\n }\n\n scope<K extends NamespaceKeys<T>>(ns: K): BoundI18n<T[K] & Messages> {\n return new BoundView(\n this.#core,\n this.#fixedLocale,\n this.#prefix ? `${this.#prefix}.${String(ns)}` : String(ns),\n ) as BoundI18n<T[K] & Messages>;\n }\n\n withLocale(locale: Locale): BoundI18n<T> {\n return new BoundView<T>(this.#core, locale, this.#prefix);\n }\n}\n","import type { MessageValue, Messages } from './types';\n\n/* -------------------- Path Resolution -------------------- */\n\n/**\n * Resolves nested properties using dot notation and bracket notation.\n * Supports: 'user.name', 'items[0]', 'user.items[0].name'\n */\nexport function resolvePath(obj: Record<string, unknown>, path: string): unknown {\n // Try direct access first (handles keys with literal dots)\n if (path in obj) return obj[path];\n\n const parts = path.match(/[^.[\\]]+/gu) ?? [];\n let value: unknown = obj;\n\n for (const part of parts) {\n if (value == null || typeof value !== 'object') return undefined;\n\n value = (value as Record<string, unknown>)[part];\n }\n\n return value;\n}\n\n/* -------------------- Message Value Guard -------------------- */\n\nexport const PLURAL_FORMS = new Set<string>(['zero', 'one', 'two', 'few', 'many', 'other']);\n\nexport function isMessageValue(value: unknown): value is MessageValue {\n if (typeof value === 'string') return true;\n\n if (typeof value !== 'object' || value === null || Array.isArray(value)) return false;\n\n const obj = value as Record<string, unknown>;\n\n if (!('other' in obj)) return false;\n\n const keys = Object.keys(obj);\n\n if (keys.length > PLURAL_FORMS.size) return false;\n\n return keys.every((k) => PLURAL_FORMS.has(k)) && Object.values(obj).every((v) => typeof v === 'string');\n}\n\n/* -------------------- Deep Merge -------------------- */\n\nexport function deepMerge(target: Messages, source: Messages): Messages {\n const result = { ...target };\n\n for (const [key, val] of Object.entries(source)) {\n const existing = result[key];\n\n if (!isMessageValue(val) && !isMessageValue(existing) && typeof existing === 'object' && existing !== null) {\n result[key] = deepMerge(existing as Messages, val as Messages);\n } else {\n // Clone PluralMessages objects to prevent external mutations from corrupting the catalog.\n result[key] = typeof val === 'object' && val !== null ? ({ ...(val as object) } as MessageValue) : val;\n }\n }\n\n return result;\n}\n\n/* -------------------- BoundedMap -------------------- */\n\n/**\n * Size-bounded Map that evicts the oldest entry (insertion order) when the cap is reached.\n * Used by I18n's chain cache to prevent unbounded growth in long-lived SSR singletons when\n * locale tags are derived from arbitrary user input (e.g. Accept-Language headers).\n */\nexport class BoundedMap<K, V> extends Map<K, V> {\n readonly #cap: number;\n\n constructor(cap: number) {\n super();\n this.#cap = cap;\n }\n\n override set(key: K, value: V): this {\n if (!this.has(key) && this.size >= this.#cap) {\n this.delete(this.keys().next().value as K);\n }\n\n return super.set(key, value);\n }\n}\n","import type { Locale, PluralForm } from './types';\n\n/* -------------------- Cache Container -------------------- */\n\n/** Holds all Intl formatter caches for one I18n instance — GC'd with the instance. */\nexport type IntlCaches = {\n dateFormat: Map<string, Intl.DateTimeFormat>;\n listFormat: Map<string, Intl.ListFormat>;\n numberFormat: Map<string, Intl.NumberFormat>;\n pluralRules: Map<string, Intl.PluralRules>;\n relativeTimeFormat: Map<string, Intl.RelativeTimeFormat>;\n};\n\nexport function makeIntlCaches(): IntlCaches {\n return {\n dateFormat: new Map(),\n listFormat: new Map(),\n numberFormat: new Map(),\n pluralRules: new Map(),\n relativeTimeFormat: new Map(),\n };\n}\n\n/* -------------------- Cache Helpers -------------------- */\n\nfunction intlFmt<F extends object>(cache: Map<string, F>, key: string, build: () => F): F {\n let fmt = cache.get(key);\n\n if (!fmt) {\n fmt = build();\n cache.set(key, fmt);\n }\n\n return fmt;\n}\n\n/**\n * Builds a stable string key for an Intl formatter cache.\n * Call this once per formatter construction path — not on every format call — so key\n * serialization cost is paid only on cache misses.\n */\nfunction intlKey(locale: string, options?: object): string {\n return options ? `${locale}:${JSON.stringify(options, Object.keys(options).sort())}` : locale;\n}\n\n/* -------------------- Format Functions -------------------- */\n\nexport function formatNumber(\n caches: IntlCaches,\n value: number,\n options: Intl.NumberFormatOptions | undefined,\n locale: Locale,\n): string {\n const key = intlKey(locale, options);\n\n try {\n return intlFmt(caches.numberFormat, key, () => new Intl.NumberFormat(locale, options)).format(value);\n } catch {\n return String(value);\n }\n}\n\nexport function formatDate(\n caches: IntlCaches,\n value: Date | number,\n options: Intl.DateTimeFormatOptions | undefined,\n locale: Locale,\n): string {\n const d = typeof value === 'number' ? new Date(value) : value;\n const key = intlKey(locale, options);\n\n try {\n return intlFmt(caches.dateFormat, key, () => new Intl.DateTimeFormat(locale, options)).format(d);\n } catch {\n return d.toString();\n }\n}\n\nexport function formatRelative(\n caches: IntlCaches,\n value: number,\n unit: Intl.RelativeTimeFormatUnit,\n options: Intl.RelativeTimeFormatOptions | undefined,\n locale: Locale,\n): string {\n const key = intlKey(locale, options);\n\n try {\n return intlFmt(caches.relativeTimeFormat, key, () => new Intl.RelativeTimeFormat(locale, options)).format(\n value,\n unit,\n );\n } catch {\n return String(value);\n }\n}\n\nexport function formatList(caches: IntlCaches, items: unknown[], locale: string, type: 'and' | 'or'): string {\n if (items.length === 0) return '';\n\n const stringItems = items.map(String);\n const intlType = type === 'and' ? 'conjunction' : 'disjunction';\n\n try {\n return intlFmt(\n caches.listFormat,\n `${locale}:${intlType}`,\n () => new Intl.ListFormat(locale, { style: 'long', type: intlType }),\n ).format(stringItems);\n } catch {\n // Fallback for environments without Intl.ListFormat\n if (stringItems.length === 1) return stringItems[0];\n\n if (stringItems.length === 2) return `${stringItems[0]} ${type} ${stringItems[1]}`;\n\n return `${stringItems.slice(0, -1).join(', ')} ${type} ${stringItems.at(-1)}`;\n }\n}\n\nexport function getPluralForm(caches: IntlCaches, locale: Locale, count: number): PluralForm {\n const n = Math.floor(Math.abs(count));\n\n try {\n return intlFmt(caches.pluralRules, locale, () => new Intl.PluralRules(locale)).select(n) as PluralForm;\n } catch {\n return n === 1 ? 'one' : 'other';\n }\n}\n","import type { Vars } from './types';\n\nimport { resolvePath } from './helpers';\nimport { type IntlCaches, formatList, formatNumber } from './intl';\n\n/* -------------------- Token Resolution -------------------- */\n\nfunction resolveToken(caches: IntlCaches, value: unknown, separator: string | undefined, locale: string): string {\n if (value == null) return '';\n\n if (Array.isArray(value)) {\n if (separator === 'and') return formatList(caches, value, locale, 'and');\n\n if (separator === 'or') return formatList(caches, value, locale, 'or');\n\n if (separator !== undefined) return value.map(String).join(separator);\n\n return value.map(String).join(', ');\n }\n\n if (typeof value === 'number') {\n return formatNumber(caches, value, undefined, locale);\n }\n\n return String(value);\n}\n\n/* -------------------- Interpolation -------------------- */\n\n/**\n * Interpolates variables into a template string. Supports Unicode variable names\n * via `\\p{ID_Continue}` so non-ASCII identifiers like `{prénom}` or `{名前}` work correctly.\n *\n * Supported formats: `{name}` · `{user.name}` · `{items[0]}` · `{items}` ·\n * `{items|and}` · `{items|or}` · `{items| - }` · `{items.length}`\n */\nexport function interpolate(template: string, vars: Vars, locale: string, caches: IntlCaches): string {\n if (!template.includes('{')) return template;\n\n return template.replace(/\\{([\\p{ID_Continue}\\-.[\\]]+)(?:\\|([^}]+))?\\}/gu, (_match, key: string, separator?: string) =>\n resolveToken(caches, resolvePath(vars, key), separator, locale),\n );\n}\n","import type {\n BoundI18n,\n DiagnosticEvent,\n I18nOptions,\n Loader,\n Locale,\n LocaleChangeEvent,\n LocaleChangeReason,\n MessageValue,\n Messages,\n NamespaceKeys,\n TranslationKeyParam,\n Unsubscribe,\n Vars,\n} from './types';\n\nimport { BoundView, type I18nCore } from './core';\nimport { BoundedMap, deepMerge, isMessageValue, resolvePath } from './helpers';\nimport { interpolate } from './interpolate';\nimport {\n type IntlCaches,\n formatDate,\n formatList,\n formatNumber,\n formatRelative,\n getPluralForm,\n makeIntlCaches,\n} from './intl';\n\nexport class I18n<T extends Messages = Messages> implements BoundI18n<T> {\n #locale: Locale;\n #fallbacks: Locale[];\n #catalogs = new Map<Locale, Messages>();\n #loaders = new Map<Locale, Loader>();\n #loading = new Map<Locale, Promise<void>>();\n #subscribers = new Set<(event: LocaleChangeEvent) => void>();\n /** Bounded at 128 entries — prevents unbounded growth when locale tags come from user input. */\n #chainCache = new BoundedMap<Locale, Locale[]>(128);\n #onMissing?: (key: string, locale: Locale) => string | undefined;\n #onDiagnostic?: (event: DiagnosticEvent) => void;\n #localesCache: Locale[] | null = null;\n #loadersCache: Locale[] | null = null;\n #disposed = false;\n #batchDepth = 0;\n #pendingNotify: LocaleChangeReason | null = null;\n #core: I18nCore;\n\n // Instance-scoped Intl caches — GC'd with the instance (important for SSR with many locales).\n readonly #caches: IntlCaches = makeIntlCaches();\n\n /** Internal BoundView — I18n delegates its BoundI18n surface here to avoid duplicating every method. */\n readonly #view: BoundView<T>;\n\n constructor({ fallback, loaders, locale = 'en', messages, onDiagnostic, onMissing }: I18nOptions<T> = {}) {\n this.#locale = locale;\n this.#fallbacks = Array.isArray(fallback) ? fallback : fallback ? [fallback] : [];\n this.#onMissing = onMissing;\n this.#onDiagnostic = onDiagnostic;\n\n if (messages) {\n for (const [l, m] of Object.entries(messages)) {\n // Deep-clone at init so external mutations to the source object can't corrupt the catalog.\n this.#catalogs.set(l, structuredClone(m) as Messages);\n }\n }\n\n if (loaders) for (const [l, fn] of Object.entries(loaders)) this.#loaders.set(l, fn);\n\n this.#core = {\n checkOwn: (key: string, locale: Locale) => {\n const catalog = this.#catalogs.get(locale);\n\n if (!catalog) return false;\n\n const value = resolvePath(catalog, key);\n\n return value !== undefined && isMessageValue(value);\n },\n findMessage: (key: string, locale: Locale) => this.#findMessage(key, locale),\n formatDate: (value, options, locale) => formatDate(this.#caches, value, options, locale),\n formatList: (items, locale, type) => formatList(this.#caches, items, locale, type),\n formatNumber: (value, options, locale) => formatNumber(this.#caches, value, options, locale),\n formatRelative: (value, unit, options, locale) => formatRelative(this.#caches, value, unit, options, locale),\n getLocale: () => this.#locale,\n translate: (key, vars, locale) => this.#translate(key, vars, locale),\n };\n\n this.#view = new BoundView<T>(this.#core, null);\n }\n\n /* -------------------- Locale -------------------- */\n\n get locale(): Locale {\n return this.#locale;\n }\n\n get locales(): Locale[] {\n this.#localesCache ??= [...this.#catalogs.keys()];\n\n return this.#localesCache;\n }\n\n set locale(value: Locale) {\n if (this.#locale === value) return;\n\n if (import.meta.env?.DEV && !this.#catalogs.has(value) && this.#loaders.has(value)) {\n console.warn(\n `[i18nit] locale \"${value}\" has a registered loader but is not loaded. ` +\n 'Use setLocale() to load and switch atomically.',\n );\n }\n\n this.#locale = value;\n this.#notify('locale-change');\n }\n\n async setLocale(locale: Locale): Promise<void> {\n if (locale === this.#locale) return;\n\n await this.load(locale);\n this.locale = locale;\n }\n\n /* -------------------- Message Management -------------------- */\n\n /** Deep-merges messages into an existing locale catalog. */\n add(locale: Locale, messages: Messages): void {\n const existing = this.#catalogs.get(locale) ?? {};\n\n this.#catalogs.set(locale, deepMerge(existing, messages));\n this.#localesCache = null;\n\n if (this.#getLocaleChain(this.#locale).includes(locale)) this.#notify('catalog-update');\n }\n\n /** Replaces the entire locale catalog for `locale` with a deep clone of `messages`. */\n replace(locale: Locale, messages: Messages): void {\n this.#catalogs.set(locale, structuredClone(messages));\n this.#localesCache = null;\n\n if (this.#getLocaleChain(this.#locale).includes(locale)) this.#notify('catalog-update');\n }\n\n has(key: string): boolean {\n return this.#view.has(key);\n }\n\n /** Like `has()`, but only checks the exact locale without walking the fallback chain. */\n hasOwn(key: string): boolean {\n return this.#view.hasOwn(key);\n }\n\n hasLocale(locale: Locale): boolean {\n return this.#catalogs.has(locale);\n }\n\n /* -------------------- Async Loaders -------------------- */\n\n async load(...locales: Locale[]): Promise<void> {\n await Promise.all(locales.map((locale) => this.#loadOne(locale)));\n }\n\n /**\n * Force-reloads a locale catalog even if already populated. Useful for hot-reload and forced bundle refresh.\n * No-op (with a dev warning) when no loader is registered for the locale, to prevent silently clearing the catalog.\n */\n async reload(locale: Locale): Promise<void> {\n if (!this.#loaders.has(locale)) {\n if (import.meta.env?.DEV) {\n console.warn(`[i18nit] reload(\"${locale}\") skipped — no loader registered for this locale.`);\n }\n\n return;\n }\n\n this.#catalogs.delete(locale);\n this.#localesCache = null;\n await this.#loadOne(locale);\n }\n\n registerLoader(locale: Locale, loader: Loader): void {\n this.#loaders.set(locale, loader);\n this.#loadersCache = null;\n }\n\n /** Returns the locale keys for which a loader has been registered. */\n get loadableLocales(): Locale[] {\n this.#loadersCache ??= [...this.#loaders.keys()];\n\n return this.#loadersCache;\n }\n\n /* -------------------- BoundI18n surface (delegated to #view) -------------------- */\n\n /**\n * Translates a key with optional interpolation variables.\n * Locale must be loaded first via `load()` or provided via `messages` in config.\n * For a per-call locale override use `withLocale(locale).t(key, vars)`.\n */\n t(key: TranslationKeyParam<T>, vars?: Vars): string {\n return this.#view.t(key, vars);\n }\n\n number(value: number, options?: Intl.NumberFormatOptions): string {\n return this.#view.number(value, options);\n }\n\n date(value: Date | number, options?: Intl.DateTimeFormatOptions): string {\n return this.#view.date(value, options);\n }\n\n list(items: unknown[], type: 'and' | 'or' = 'and'): string {\n return this.#view.list(items, type);\n }\n\n relative(value: number, unit: Intl.RelativeTimeFormatUnit, options?: Intl.RelativeTimeFormatOptions): string {\n return this.#view.relative(value, unit, options);\n }\n\n currency(value: number, currency: string, options?: Omit<Intl.NumberFormatOptions, 'style' | 'currency'>): string {\n return this.#view.currency(value, currency, options);\n }\n\n /**\n * Returns a bound interface that translates in the given locale without\n * changing the active locale on the instance. Useful for SSR and\n * multi-locale rendering in a single pass.\n */\n withLocale(locale: Locale): BoundI18n<T> {\n return this.#view.withLocale(locale);\n }\n\n /**\n * Returns a translator scoped to a key namespace prefix. Reacts to locale changes on the\n * instance. When `T` is a concrete message type, the returned `BoundI18n` is narrowed to\n * the subtree type so `t()` autocomplete works within the scope.\n * Only keys whose values are nested message objects are valid scope targets.\n */\n scope<K extends NamespaceKeys<T>>(ns: K): BoundI18n<T[K] & Messages> {\n return this.#view.scope(ns);\n }\n\n /* -------------------- Subscriptions -------------------- */\n\n /**\n * Executes `fn` while deferring subscriber notifications. A single notification fires\n * after `fn` completes, collapsing any number of `add()` / `replace()` calls made within.\n * Nested `batch()` calls are supported; notification fires when the outermost batch exits.\n * If both a locale change and a catalog update are triggered, `'locale-change'` takes priority.\n *\n * @remarks\n * `batch()` is synchronous. Async operations (e.g. `load()`) started inside `fn` complete\n * after the batch exits and will notify subscribers individually. To batch-load multiple\n * locales and notify once, await `load()` before entering the batch:\n * ```ts\n * await i18n.load('fr', 'de');\n * i18n.batch(() => { i18n.locale = 'fr'; });\n * ```\n */\n batch(fn: () => void): void {\n this.#batchDepth++;\n\n try {\n fn();\n } finally {\n this.#batchDepth--;\n\n if (this.#batchDepth === 0 && this.#pendingNotify !== null) {\n const reason = this.#pendingNotify;\n\n this.#pendingNotify = null;\n this.#notify(reason);\n }\n }\n }\n\n subscribe(listener: (event: LocaleChangeEvent) => void, immediate?: boolean): Unsubscribe {\n this.#subscribers.add(listener);\n\n if (immediate) {\n try {\n listener({ locale: this.#locale, reason: 'locale-change' });\n } catch (err) {\n this.#diagnoseSubscriber(err);\n }\n }\n\n return () => this.#subscribers.delete(listener);\n }\n\n /** Releases all resources held by this instance. */\n dispose(): void {\n this.#disposed = true;\n this.#subscribers.clear();\n this.#catalogs.clear();\n this.#loaders.clear();\n this.#loading.clear();\n this.#chainCache.clear();\n this.#localesCache = null;\n this.#loadersCache = null;\n }\n\n /** Enables `using i18n = createI18n(...)` for deterministic resource release. */\n [Symbol.dispose](): void {\n this.dispose();\n }\n\n /**\n * Awaits any in-flight `load()` calls and then releases all resources.\n * Enables `await using i18n = createI18n(...)` in environments that support `Symbol.asyncDispose`.\n */\n async [Symbol.asyncDispose](): Promise<void> {\n await Promise.allSettled([...this.#loading.values()]);\n this.dispose();\n }\n\n /* -------------------- Private -------------------- */\n\n #diagnoseSubscriber(error: unknown): void {\n if (this.#onDiagnostic) {\n this.#onDiagnostic({ error, kind: 'subscriber-error' });\n } else {\n console.error('[i18nit] Subscriber threw:', error);\n }\n }\n\n #diagnoseLoader(error: unknown, locale: Locale): void {\n if (this.#onDiagnostic) {\n this.#onDiagnostic({ error, kind: 'loader-error', locale });\n } else {\n console.warn('[i18nit] Loader error:', error);\n }\n }\n\n #notify(reason: LocaleChangeReason): void {\n if (this.#batchDepth > 0) {\n // 'locale-change' takes priority over 'catalog-update' if both occur in one batch\n if (this.#pendingNotify !== 'locale-change') this.#pendingNotify = reason;\n\n return;\n }\n\n const event: LocaleChangeEvent = { locale: this.#locale, reason };\n\n for (const listener of this.#subscribers) {\n try {\n listener(event);\n } catch (err) {\n this.#diagnoseSubscriber(err);\n }\n }\n }\n\n #findMessage(key: string, locale: Locale): MessageValue | undefined {\n for (const loc of this.#getLocaleChain(locale)) {\n const messages = this.#catalogs.get(loc);\n\n if (!messages) continue;\n\n const value = resolvePath(messages, key);\n\n if (value !== undefined && isMessageValue(value)) return value;\n }\n\n return undefined;\n }\n\n #getLocaleChain(locale: Locale): Locale[] {\n const cached = this.#chainCache.get(locale);\n\n if (cached) return cached;\n\n const seen = new Set<Locale>();\n const push = (l: Locale) => {\n seen.add(l);\n\n const parts = l.split('-');\n\n for (let i = parts.length - 1; i > 0; i--) {\n seen.add(parts.slice(0, i).join('-'));\n }\n };\n\n push(locale);\n for (const fallback of this.#fallbacks) push(fallback);\n\n const chain = [...seen];\n\n this.#chainCache.set(locale, chain);\n\n return chain;\n }\n\n #translate(key: string, vars: Vars | undefined, locale: Locale): string {\n const message = this.#findMessage(key, locale);\n\n if (message === undefined) return this.#onMissing?.(key, locale) ?? key;\n\n if (typeof message === 'string') return interpolate(message, vars ?? {}, locale, this.#caches);\n\n const v = vars ?? {};\n\n if (import.meta.env?.DEV && v.count === undefined) {\n console.warn(`[i18nit] Key \"${key}\" is a plural message but vars.count is missing. Defaulting to 0.`);\n }\n\n const count = Number(v.count ?? 0);\n const form = count === 0 && message.zero !== undefined ? 'zero' : getPluralForm(this.#caches, locale, count);\n\n return interpolate(message[form] ?? message.other, v, locale, this.#caches);\n }\n\n #loadOne(locale: Locale): Promise<void> {\n if (this.#loading.has(locale)) return this.#loading.get(locale)!;\n\n if (this.#catalogs.has(locale)) return Promise.resolve();\n\n const loader = this.#loaders.get(locale);\n\n if (!loader) return Promise.resolve();\n\n const promise = (async () => {\n try {\n const messages = await loader(locale);\n\n // Use replace() so the loader result is the authoritative catalog for this locale,\n // not merged on top of any pre-seeded static messages.\n if (!this.#disposed) this.replace(locale, messages);\n } catch (error) {\n this.#diagnoseLoader(error, locale);\n throw error;\n } finally {\n this.#loading.delete(locale);\n }\n })();\n\n this.#loading.set(locale, promise);\n\n return promise;\n }\n}\n\nexport function createI18n<T extends Messages = Messages>(config?: I18nOptions<T>): I18n<T> {\n return new I18n<T>(config);\n}\n"],"mappings":"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"}
package/dist/index.cjs CHANGED
@@ -1,2 +1 @@
1
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("./i18nit.cjs");exports.createI18n=e.createI18n;
2
- //# sourceMappingURL=index.cjs.map
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require(`./i18n.cjs`);exports.I18n=e.I18n,exports.createI18n=e.createI18n;
package/dist/index.d.ts CHANGED
@@ -1,75 +1,4 @@
1
- export declare function createI18n(config?: I18nConfig): I18n;
2
-
3
- declare class I18n {
4
- private locale;
5
- private fallbacks;
6
- private escape;
7
- private catalogs;
8
- private loaders;
9
- private loading;
10
- private subscribers;
11
- constructor(config?: I18nConfig);
12
- setLocale(locale: Locale): void;
13
- getLocale(): Locale;
14
- /**
15
- * Adds messages to a locale (merges with existing).
16
- */
17
- add(locale: Locale, messages: Messages): void;
18
- /**
19
- * Sets messages for a locale (replaces existing).
20
- */
21
- set(locale: Locale, messages: Messages): void;
22
- getMessages(locale: Locale): Messages | undefined;
23
- hasLocale(locale: Locale): boolean;
24
- has(key: string, locale?: Locale): boolean;
25
- load(locale: Locale): Promise<void>;
26
- register(locale: Locale, loader: () => Promise<Messages>): void;
27
- hasAsync(key: string, locale?: Locale): Promise<boolean>;
28
- /**
29
- * Load multiple locales in parallel.
30
- * Useful for preloading all needed locales at app startup.
31
- */
32
- loadAll(locales: Locale[]): Promise<void>;
33
- /**
34
- * Translates a key with optional variables and options.
35
- * Synchronous - locale must be loaded first via load() or provided in config.
36
- */
37
- t(key: string, vars?: Record<string, unknown>, options?: TranslateOptions): string;
38
- number(value: number, options?: Intl.NumberFormatOptions, locale?: Locale): string;
39
- date(value: Date | number, options?: Intl.DateTimeFormatOptions, locale?: Locale): string;
40
- namespace(ns: string): {
41
- t: (key: string, vars?: Record<string, unknown>, options?: TranslateOptions) => string;
42
- };
43
- subscribe(handler: (locale: Locale) => void): () => void;
44
- private notifySubscribers;
45
- private findMessage;
46
- private getLocaleChain;
47
- private formatMessage;
48
- }
49
-
50
- export declare type I18nConfig = {
51
- locale?: Locale;
52
- fallback?: Locale | Locale[];
53
- messages?: Record<Locale, Messages>;
54
- loaders?: Record<Locale, () => Promise<Messages>>;
55
- escape?: boolean;
56
- };
57
-
58
- export declare type Locale = string;
59
-
60
- export declare type Messages = Record<string, MessageValue>;
61
-
62
- export declare type MessageValue = string | PluralMessages;
63
-
64
- export declare type PluralForm = 'zero' | 'one' | 'two' | 'few' | 'many' | 'other';
65
-
66
- export declare type PluralMessages = Partial<Record<PluralForm, string>> & {
67
- other: string;
68
- };
69
-
70
- export declare type TranslateOptions = {
71
- locale?: Locale;
72
- escape?: boolean;
73
- };
74
-
75
- export { }
1
+ /** @vielzeug/i18nit Lightweight, type-safe i18n library. */
2
+ export type { BoundI18n, DeepPartialMessages, DiagnosticEvent, I18nOptions, Loader, Locale, LocaleChangeEvent, LocaleChangeListener, LocaleChangeReason, Messages, MessageValue, NamespaceKeys, PluralForm, PluralKeys, PluralMessages, TranslationKey, TranslationKeyParam, Unsubscribe, Vars, } from './types';
3
+ export { createI18n, I18n } from './i18n';
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,8DAA8D;AAG9D,YAAY,EACV,SAAS,EACT,mBAAmB,EACnB,eAAe,EACf,WAAW,EACX,MAAM,EACN,MAAM,EACN,iBAAiB,EACjB,oBAAoB,EACpB,kBAAkB,EAClB,QAAQ,EACR,YAAY,EACZ,aAAa,EACb,UAAU,EACV,UAAU,EACV,cAAc,EACd,cAAc,EACd,mBAAmB,EACnB,WAAW,EACX,IAAI,GACL,MAAM,SAAS,CAAC;AAGjB,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC"}
package/dist/index.js CHANGED
@@ -1,5 +1,2 @@
1
- import { createI18n as o } from "./i18nit.js";
2
- export {
3
- o as createI18n
4
- };
5
- //# sourceMappingURL=index.js.map
1
+ import { I18n as e, createI18n as t } from "./i18n.js";
2
+ export { e as I18n, t as createI18n };
@@ -0,0 +1,2 @@
1
+ const e=require(`./helpers.cjs`),t=require(`./intl.cjs`);function n(e,n,r,i){return n==null?``:Array.isArray(n)?r===`and`?t.formatList(e,n,i,`and`):r===`or`?t.formatList(e,n,i,`or`):r===void 0?n.map(String).join(`, `):n.map(String).join(r):typeof n==`number`?t.formatNumber(e,n,void 0,i):String(n)}function r(t,r,i,a){return t.includes(`{`)?t.replace(/\{([\p{ID_Continue}\-.[\]]+)(?:\|([^}]+))?\}/gu,(t,o,s)=>n(a,e.resolvePath(r,o),s,i)):t}exports.interpolate=r;
2
+ //# sourceMappingURL=interpolate.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"interpolate.cjs","names":[],"sources":["../src/interpolate.ts"],"sourcesContent":["import type { Vars } from './types';\n\nimport { resolvePath } from './helpers';\nimport { type IntlCaches, formatList, formatNumber } from './intl';\n\n/* -------------------- Token Resolution -------------------- */\n\nfunction resolveToken(caches: IntlCaches, value: unknown, separator: string | undefined, locale: string): string {\n if (value == null) return '';\n\n if (Array.isArray(value)) {\n if (separator === 'and') return formatList(caches, value, locale, 'and');\n\n if (separator === 'or') return formatList(caches, value, locale, 'or');\n\n if (separator !== undefined) return value.map(String).join(separator);\n\n return value.map(String).join(', ');\n }\n\n if (typeof value === 'number') {\n return formatNumber(caches, value, undefined, locale);\n }\n\n return String(value);\n}\n\n/* -------------------- Interpolation -------------------- */\n\n/**\n * Interpolates variables into a template string. Supports Unicode variable names\n * via `\\p{ID_Continue}` so non-ASCII identifiers like `{prénom}` or `{名前}` work correctly.\n *\n * Supported formats: `{name}` · `{user.name}` · `{items[0]}` · `{items}` ·\n * `{items|and}` · `{items|or}` · `{items| - }` · `{items.length}`\n */\nexport function interpolate(template: string, vars: Vars, locale: string, caches: IntlCaches): string {\n if (!template.includes('{')) return template;\n\n return template.replace(/\\{([\\p{ID_Continue}\\-.[\\]]+)(?:\\|([^}]+))?\\}/gu, (_match, key: string, separator?: string) =>\n resolveToken(caches, resolvePath(vars, key), separator, locale),\n );\n}\n"],"mappings":"yDAOA,SAAS,EAAa,EAAoB,EAAgB,EAA+B,EAAwB,CAiB/G,OAhBI,GAAS,KAAa,GAEtB,MAAM,QAAQ,EAAM,CAClB,IAAc,MAAc,EAAA,WAAW,EAAQ,EAAO,EAAQ,MAAM,CAEpE,IAAc,KAAa,EAAA,WAAW,EAAQ,EAAO,EAAQ,KAAK,CAElE,IAAc,IAAA,GAEX,EAAM,IAAI,OAAO,CAAC,KAAK,KAAK,CAFC,EAAM,IAAI,OAAO,CAAC,KAAK,EAAU,CAKnE,OAAO,GAAU,SACZ,EAAA,aAAa,EAAQ,EAAO,IAAA,GAAW,EAAO,CAGhD,OAAO,EAAM,CAYtB,SAAgB,EAAY,EAAkB,EAAY,EAAgB,EAA4B,CAGpG,OAFK,EAAS,SAAS,IAAI,CAEpB,EAAS,QAAQ,kDAAmD,EAAQ,EAAa,IAC9F,EAAa,EAAQ,EAAA,YAAY,EAAM,EAAI,CAAE,EAAW,EAAO,CAChE,CAJmC"}
@@ -0,0 +1,11 @@
1
+ import type { Vars } from './types';
2
+ import { type IntlCaches } from './intl';
3
+ /**
4
+ * Interpolates variables into a template string. Supports Unicode variable names
5
+ * via `\p{ID_Continue}` so non-ASCII identifiers like `{prénom}` or `{名前}` work correctly.
6
+ *
7
+ * Supported formats: `{name}` · `{user.name}` · `{items[0]}` · `{items}` ·
8
+ * `{items|and}` · `{items|or}` · `{items| - }` · `{items.length}`
9
+ */
10
+ export declare function interpolate(template: string, vars: Vars, locale: string, caches: IntlCaches): string;
11
+ //# sourceMappingURL=interpolate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"interpolate.d.ts","sourceRoot":"","sources":["../src/interpolate.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,SAAS,CAAC;AAGpC,OAAO,EAAE,KAAK,UAAU,EAA4B,MAAM,QAAQ,CAAC;AA0BnE;;;;;;GAMG;AACH,wBAAgB,WAAW,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,GAAG,MAAM,CAMpG"}
@@ -0,0 +1,13 @@
1
+ import { resolvePath as e } from "./helpers.js";
2
+ import { formatList as t, formatNumber as n } from "./intl.js";
3
+ //#region src/interpolate.ts
4
+ function r(e, r, i, a) {
5
+ return r == null ? "" : Array.isArray(r) ? i === "and" ? t(e, r, a, "and") : i === "or" ? t(e, r, a, "or") : i === void 0 ? r.map(String).join(", ") : r.map(String).join(i) : typeof r == "number" ? n(e, r, void 0, a) : String(r);
6
+ }
7
+ function i(t, n, i, a) {
8
+ return t.includes("{") ? t.replace(/\{([\p{ID_Continue}\-.[\]]+)(?:\|([^}]+))?\}/gu, (t, o, s) => r(a, e(n, o), s, i)) : t;
9
+ }
10
+ //#endregion
11
+ export { i as interpolate };
12
+
13
+ //# sourceMappingURL=interpolate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"interpolate.js","names":[],"sources":["../src/interpolate.ts"],"sourcesContent":["import type { Vars } from './types';\n\nimport { resolvePath } from './helpers';\nimport { type IntlCaches, formatList, formatNumber } from './intl';\n\n/* -------------------- Token Resolution -------------------- */\n\nfunction resolveToken(caches: IntlCaches, value: unknown, separator: string | undefined, locale: string): string {\n if (value == null) return '';\n\n if (Array.isArray(value)) {\n if (separator === 'and') return formatList(caches, value, locale, 'and');\n\n if (separator === 'or') return formatList(caches, value, locale, 'or');\n\n if (separator !== undefined) return value.map(String).join(separator);\n\n return value.map(String).join(', ');\n }\n\n if (typeof value === 'number') {\n return formatNumber(caches, value, undefined, locale);\n }\n\n return String(value);\n}\n\n/* -------------------- Interpolation -------------------- */\n\n/**\n * Interpolates variables into a template string. Supports Unicode variable names\n * via `\\p{ID_Continue}` so non-ASCII identifiers like `{prénom}` or `{名前}` work correctly.\n *\n * Supported formats: `{name}` · `{user.name}` · `{items[0]}` · `{items}` ·\n * `{items|and}` · `{items|or}` · `{items| - }` · `{items.length}`\n */\nexport function interpolate(template: string, vars: Vars, locale: string, caches: IntlCaches): string {\n if (!template.includes('{')) return template;\n\n return template.replace(/\\{([\\p{ID_Continue}\\-.[\\]]+)(?:\\|([^}]+))?\\}/gu, (_match, key: string, separator?: string) =>\n resolveToken(caches, resolvePath(vars, key), separator, locale),\n );\n}\n"],"mappings":";;;AAOA,SAAS,EAAa,GAAoB,GAAgB,GAA+B,GAAwB;AAiB/G,QAhBI,KAAS,OAAa,KAEtB,MAAM,QAAQ,EAAM,GAClB,MAAc,QAAc,EAAW,GAAQ,GAAO,GAAQ,MAAM,GAEpE,MAAc,OAAa,EAAW,GAAQ,GAAO,GAAQ,KAAK,GAElE,MAAc,KAAA,IAEX,EAAM,IAAI,OAAO,CAAC,KAAK,KAAK,GAFC,EAAM,IAAI,OAAO,CAAC,KAAK,EAAU,GAKnE,OAAO,KAAU,WACZ,EAAa,GAAQ,GAAO,KAAA,GAAW,EAAO,GAGhD,OAAO,EAAM;;AAYtB,SAAgB,EAAY,GAAkB,GAAY,GAAgB,GAA4B;AAGpG,QAFK,EAAS,SAAS,IAAI,GAEpB,EAAS,QAAQ,mDAAmD,GAAQ,GAAa,MAC9F,EAAa,GAAQ,EAAY,GAAM,EAAI,EAAE,GAAW,EAAO,CAChE,GAJmC"}
package/dist/intl.cjs ADDED
@@ -0,0 +1,2 @@
1
+ function e(){return{dateFormat:new Map,listFormat:new Map,numberFormat:new Map,pluralRules:new Map,relativeTimeFormat:new Map}}function t(e,t,n){let r=e.get(t);return r||(r=n(),e.set(t,r)),r}function n(e,t){return t?`${e}:${JSON.stringify(t,Object.keys(t).sort())}`:e}function r(e,r,i,a){let o=n(a,i);try{return t(e.numberFormat,o,()=>new Intl.NumberFormat(a,i)).format(r)}catch{return String(r)}}function i(e,r,i,a){let o=typeof r==`number`?new Date(r):r,s=n(a,i);try{return t(e.dateFormat,s,()=>new Intl.DateTimeFormat(a,i)).format(o)}catch{return o.toString()}}function a(e,r,i,a,o){let s=n(o,a);try{return t(e.relativeTimeFormat,s,()=>new Intl.RelativeTimeFormat(o,a)).format(r,i)}catch{return String(r)}}function o(e,n,r,i){if(n.length===0)return``;let a=n.map(String),o=i===`and`?`conjunction`:`disjunction`;try{return t(e.listFormat,`${r}:${o}`,()=>new Intl.ListFormat(r,{style:`long`,type:o})).format(a)}catch{return a.length===1?a[0]:a.length===2?`${a[0]} ${i} ${a[1]}`:`${a.slice(0,-1).join(`, `)} ${i} ${a.at(-1)}`}}function s(e,n,r){let i=Math.floor(Math.abs(r));try{return t(e.pluralRules,n,()=>new Intl.PluralRules(n)).select(i)}catch{return i===1?`one`:`other`}}exports.formatDate=i,exports.formatList=o,exports.formatNumber=r,exports.formatRelative=a,exports.getPluralForm=s,exports.makeIntlCaches=e;
2
+ //# sourceMappingURL=intl.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"intl.cjs","names":[],"sources":["../src/intl.ts"],"sourcesContent":["import type { Locale, PluralForm } from './types';\n\n/* -------------------- Cache Container -------------------- */\n\n/** Holds all Intl formatter caches for one I18n instance — GC'd with the instance. */\nexport type IntlCaches = {\n dateFormat: Map<string, Intl.DateTimeFormat>;\n listFormat: Map<string, Intl.ListFormat>;\n numberFormat: Map<string, Intl.NumberFormat>;\n pluralRules: Map<string, Intl.PluralRules>;\n relativeTimeFormat: Map<string, Intl.RelativeTimeFormat>;\n};\n\nexport function makeIntlCaches(): IntlCaches {\n return {\n dateFormat: new Map(),\n listFormat: new Map(),\n numberFormat: new Map(),\n pluralRules: new Map(),\n relativeTimeFormat: new Map(),\n };\n}\n\n/* -------------------- Cache Helpers -------------------- */\n\nfunction intlFmt<F extends object>(cache: Map<string, F>, key: string, build: () => F): F {\n let fmt = cache.get(key);\n\n if (!fmt) {\n fmt = build();\n cache.set(key, fmt);\n }\n\n return fmt;\n}\n\n/**\n * Builds a stable string key for an Intl formatter cache.\n * Call this once per formatter construction path — not on every format call — so key\n * serialization cost is paid only on cache misses.\n */\nfunction intlKey(locale: string, options?: object): string {\n return options ? `${locale}:${JSON.stringify(options, Object.keys(options).sort())}` : locale;\n}\n\n/* -------------------- Format Functions -------------------- */\n\nexport function formatNumber(\n caches: IntlCaches,\n value: number,\n options: Intl.NumberFormatOptions | undefined,\n locale: Locale,\n): string {\n const key = intlKey(locale, options);\n\n try {\n return intlFmt(caches.numberFormat, key, () => new Intl.NumberFormat(locale, options)).format(value);\n } catch {\n return String(value);\n }\n}\n\nexport function formatDate(\n caches: IntlCaches,\n value: Date | number,\n options: Intl.DateTimeFormatOptions | undefined,\n locale: Locale,\n): string {\n const d = typeof value === 'number' ? new Date(value) : value;\n const key = intlKey(locale, options);\n\n try {\n return intlFmt(caches.dateFormat, key, () => new Intl.DateTimeFormat(locale, options)).format(d);\n } catch {\n return d.toString();\n }\n}\n\nexport function formatRelative(\n caches: IntlCaches,\n value: number,\n unit: Intl.RelativeTimeFormatUnit,\n options: Intl.RelativeTimeFormatOptions | undefined,\n locale: Locale,\n): string {\n const key = intlKey(locale, options);\n\n try {\n return intlFmt(caches.relativeTimeFormat, key, () => new Intl.RelativeTimeFormat(locale, options)).format(\n value,\n unit,\n );\n } catch {\n return String(value);\n }\n}\n\nexport function formatList(caches: IntlCaches, items: unknown[], locale: string, type: 'and' | 'or'): string {\n if (items.length === 0) return '';\n\n const stringItems = items.map(String);\n const intlType = type === 'and' ? 'conjunction' : 'disjunction';\n\n try {\n return intlFmt(\n caches.listFormat,\n `${locale}:${intlType}`,\n () => new Intl.ListFormat(locale, { style: 'long', type: intlType }),\n ).format(stringItems);\n } catch {\n // Fallback for environments without Intl.ListFormat\n if (stringItems.length === 1) return stringItems[0];\n\n if (stringItems.length === 2) return `${stringItems[0]} ${type} ${stringItems[1]}`;\n\n return `${stringItems.slice(0, -1).join(', ')} ${type} ${stringItems.at(-1)}`;\n }\n}\n\nexport function getPluralForm(caches: IntlCaches, locale: Locale, count: number): PluralForm {\n const n = Math.floor(Math.abs(count));\n\n try {\n return intlFmt(caches.pluralRules, locale, () => new Intl.PluralRules(locale)).select(n) as PluralForm;\n } catch {\n return n === 1 ? 'one' : 'other';\n }\n}\n"],"mappings":"AAaA,SAAgB,GAA6B,CAC3C,MAAO,CACL,WAAY,IAAI,IAChB,WAAY,IAAI,IAChB,aAAc,IAAI,IAClB,YAAa,IAAI,IACjB,mBAAoB,IAAI,IACzB,CAKH,SAAS,EAA0B,EAAuB,EAAa,EAAmB,CACxF,IAAI,EAAM,EAAM,IAAI,EAAI,CAOxB,OALK,IACH,EAAM,GAAO,CACb,EAAM,IAAI,EAAK,EAAI,EAGd,EAQT,SAAS,EAAQ,EAAgB,EAA0B,CACzD,OAAO,EAAU,GAAG,EAAO,GAAG,KAAK,UAAU,EAAS,OAAO,KAAK,EAAQ,CAAC,MAAM,CAAC,GAAK,EAKzF,SAAgB,EACd,EACA,EACA,EACA,EACQ,CACR,IAAM,EAAM,EAAQ,EAAQ,EAAQ,CAEpC,GAAI,CACF,OAAO,EAAQ,EAAO,aAAc,MAAW,IAAI,KAAK,aAAa,EAAQ,EAAQ,CAAC,CAAC,OAAO,EAAM,MAC9F,CACN,OAAO,OAAO,EAAM,EAIxB,SAAgB,EACd,EACA,EACA,EACA,EACQ,CACR,IAAM,EAAI,OAAO,GAAU,SAAW,IAAI,KAAK,EAAM,CAAG,EAClD,EAAM,EAAQ,EAAQ,EAAQ,CAEpC,GAAI,CACF,OAAO,EAAQ,EAAO,WAAY,MAAW,IAAI,KAAK,eAAe,EAAQ,EAAQ,CAAC,CAAC,OAAO,EAAE,MAC1F,CACN,OAAO,EAAE,UAAU,EAIvB,SAAgB,EACd,EACA,EACA,EACA,EACA,EACQ,CACR,IAAM,EAAM,EAAQ,EAAQ,EAAQ,CAEpC,GAAI,CACF,OAAO,EAAQ,EAAO,mBAAoB,MAAW,IAAI,KAAK,mBAAmB,EAAQ,EAAQ,CAAC,CAAC,OACjG,EACA,EACD,MACK,CACN,OAAO,OAAO,EAAM,EAIxB,SAAgB,EAAW,EAAoB,EAAkB,EAAgB,EAA4B,CAC3G,GAAI,EAAM,SAAW,EAAG,MAAO,GAE/B,IAAM,EAAc,EAAM,IAAI,OAAO,CAC/B,EAAW,IAAS,MAAQ,cAAgB,cAElD,GAAI,CACF,OAAO,EACL,EAAO,WACP,GAAG,EAAO,GAAG,QACP,IAAI,KAAK,WAAW,EAAQ,CAAE,MAAO,OAAQ,KAAM,EAAU,CAAC,CACrE,CAAC,OAAO,EAAY,MACf,CAMN,OAJI,EAAY,SAAW,EAAU,EAAY,GAE7C,EAAY,SAAW,EAAU,GAAG,EAAY,GAAG,GAAG,EAAK,GAAG,EAAY,KAEvE,GAAG,EAAY,MAAM,EAAG,GAAG,CAAC,KAAK,KAAK,CAAC,GAAG,EAAK,GAAG,EAAY,GAAG,GAAG,IAI/E,SAAgB,EAAc,EAAoB,EAAgB,EAA2B,CAC3F,IAAM,EAAI,KAAK,MAAM,KAAK,IAAI,EAAM,CAAC,CAErC,GAAI,CACF,OAAO,EAAQ,EAAO,YAAa,MAAc,IAAI,KAAK,YAAY,EAAO,CAAC,CAAC,OAAO,EAAE,MAClF,CACN,OAAO,IAAM,EAAI,MAAQ"}
package/dist/intl.d.ts ADDED
@@ -0,0 +1,16 @@
1
+ import type { Locale, PluralForm } from './types';
2
+ /** Holds all Intl formatter caches for one I18n instance — GC'd with the instance. */
3
+ export type IntlCaches = {
4
+ dateFormat: Map<string, Intl.DateTimeFormat>;
5
+ listFormat: Map<string, Intl.ListFormat>;
6
+ numberFormat: Map<string, Intl.NumberFormat>;
7
+ pluralRules: Map<string, Intl.PluralRules>;
8
+ relativeTimeFormat: Map<string, Intl.RelativeTimeFormat>;
9
+ };
10
+ export declare function makeIntlCaches(): IntlCaches;
11
+ export declare function formatNumber(caches: IntlCaches, value: number, options: Intl.NumberFormatOptions | undefined, locale: Locale): string;
12
+ export declare function formatDate(caches: IntlCaches, value: Date | number, options: Intl.DateTimeFormatOptions | undefined, locale: Locale): string;
13
+ export declare function formatRelative(caches: IntlCaches, value: number, unit: Intl.RelativeTimeFormatUnit, options: Intl.RelativeTimeFormatOptions | undefined, locale: Locale): string;
14
+ export declare function formatList(caches: IntlCaches, items: unknown[], locale: string, type: 'and' | 'or'): string;
15
+ export declare function getPluralForm(caches: IntlCaches, locale: Locale, count: number): PluralForm;
16
+ //# sourceMappingURL=intl.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"intl.d.ts","sourceRoot":"","sources":["../src/intl.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAIlD,sFAAsF;AACtF,MAAM,MAAM,UAAU,GAAG;IACvB,UAAU,EAAE,GAAG,CAAC,MAAM,EAAE,IAAI,CAAC,cAAc,CAAC,CAAC;IAC7C,UAAU,EAAE,GAAG,CAAC,MAAM,EAAE,IAAI,CAAC,UAAU,CAAC,CAAC;IACzC,YAAY,EAAE,GAAG,CAAC,MAAM,EAAE,IAAI,CAAC,YAAY,CAAC,CAAC;IAC7C,WAAW,EAAE,GAAG,CAAC,MAAM,EAAE,IAAI,CAAC,WAAW,CAAC,CAAC;IAC3C,kBAAkB,EAAE,GAAG,CAAC,MAAM,EAAE,IAAI,CAAC,kBAAkB,CAAC,CAAC;CAC1D,CAAC;AAEF,wBAAgB,cAAc,IAAI,UAAU,CAQ3C;AA0BD,wBAAgB,YAAY,CAC1B,MAAM,EAAE,UAAU,EAClB,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,IAAI,CAAC,mBAAmB,GAAG,SAAS,EAC7C,MAAM,EAAE,MAAM,GACb,MAAM,CAQR;AAED,wBAAgB,UAAU,CACxB,MAAM,EAAE,UAAU,EAClB,KAAK,EAAE,IAAI,GAAG,MAAM,EACpB,OAAO,EAAE,IAAI,CAAC,qBAAqB,GAAG,SAAS,EAC/C,MAAM,EAAE,MAAM,GACb,MAAM,CASR;AAED,wBAAgB,cAAc,CAC5B,MAAM,EAAE,UAAU,EAClB,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,IAAI,CAAC,sBAAsB,EACjC,OAAO,EAAE,IAAI,CAAC,yBAAyB,GAAG,SAAS,EACnD,MAAM,EAAE,MAAM,GACb,MAAM,CAWR;AAED,wBAAgB,UAAU,CAAC,MAAM,EAAE,UAAU,EAAE,KAAK,EAAE,OAAO,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,GAAG,IAAI,GAAG,MAAM,CAoB3G;AAED,wBAAgB,aAAa,CAAC,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,UAAU,CAQ3F"}
package/dist/intl.js ADDED
@@ -0,0 +1,65 @@
1
+ //#region src/intl.ts
2
+ function e() {
3
+ return {
4
+ dateFormat: /* @__PURE__ */ new Map(),
5
+ listFormat: /* @__PURE__ */ new Map(),
6
+ numberFormat: /* @__PURE__ */ new Map(),
7
+ pluralRules: /* @__PURE__ */ new Map(),
8
+ relativeTimeFormat: /* @__PURE__ */ new Map()
9
+ };
10
+ }
11
+ function t(e, t, n) {
12
+ let r = e.get(t);
13
+ return r || (r = n(), e.set(t, r)), r;
14
+ }
15
+ function n(e, t) {
16
+ return t ? `${e}:${JSON.stringify(t, Object.keys(t).sort())}` : e;
17
+ }
18
+ function r(e, r, i, a) {
19
+ let o = n(a, i);
20
+ try {
21
+ return t(e.numberFormat, o, () => new Intl.NumberFormat(a, i)).format(r);
22
+ } catch {
23
+ return String(r);
24
+ }
25
+ }
26
+ function i(e, r, i, a) {
27
+ let o = typeof r == "number" ? new Date(r) : r, s = n(a, i);
28
+ try {
29
+ return t(e.dateFormat, s, () => new Intl.DateTimeFormat(a, i)).format(o);
30
+ } catch {
31
+ return o.toString();
32
+ }
33
+ }
34
+ function a(e, r, i, a, o) {
35
+ let s = n(o, a);
36
+ try {
37
+ return t(e.relativeTimeFormat, s, () => new Intl.RelativeTimeFormat(o, a)).format(r, i);
38
+ } catch {
39
+ return String(r);
40
+ }
41
+ }
42
+ function o(e, n, r, i) {
43
+ if (n.length === 0) return "";
44
+ let a = n.map(String), o = i === "and" ? "conjunction" : "disjunction";
45
+ try {
46
+ return t(e.listFormat, `${r}:${o}`, () => new Intl.ListFormat(r, {
47
+ style: "long",
48
+ type: o
49
+ })).format(a);
50
+ } catch {
51
+ return a.length === 1 ? a[0] : a.length === 2 ? `${a[0]} ${i} ${a[1]}` : `${a.slice(0, -1).join(", ")} ${i} ${a.at(-1)}`;
52
+ }
53
+ }
54
+ function s(e, n, r) {
55
+ let i = Math.floor(Math.abs(r));
56
+ try {
57
+ return t(e.pluralRules, n, () => new Intl.PluralRules(n)).select(i);
58
+ } catch {
59
+ return i === 1 ? "one" : "other";
60
+ }
61
+ }
62
+ //#endregion
63
+ export { i as formatDate, o as formatList, r as formatNumber, a as formatRelative, s as getPluralForm, e as makeIntlCaches };
64
+
65
+ //# sourceMappingURL=intl.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"intl.js","names":[],"sources":["../src/intl.ts"],"sourcesContent":["import type { Locale, PluralForm } from './types';\n\n/* -------------------- Cache Container -------------------- */\n\n/** Holds all Intl formatter caches for one I18n instance — GC'd with the instance. */\nexport type IntlCaches = {\n dateFormat: Map<string, Intl.DateTimeFormat>;\n listFormat: Map<string, Intl.ListFormat>;\n numberFormat: Map<string, Intl.NumberFormat>;\n pluralRules: Map<string, Intl.PluralRules>;\n relativeTimeFormat: Map<string, Intl.RelativeTimeFormat>;\n};\n\nexport function makeIntlCaches(): IntlCaches {\n return {\n dateFormat: new Map(),\n listFormat: new Map(),\n numberFormat: new Map(),\n pluralRules: new Map(),\n relativeTimeFormat: new Map(),\n };\n}\n\n/* -------------------- Cache Helpers -------------------- */\n\nfunction intlFmt<F extends object>(cache: Map<string, F>, key: string, build: () => F): F {\n let fmt = cache.get(key);\n\n if (!fmt) {\n fmt = build();\n cache.set(key, fmt);\n }\n\n return fmt;\n}\n\n/**\n * Builds a stable string key for an Intl formatter cache.\n * Call this once per formatter construction path — not on every format call — so key\n * serialization cost is paid only on cache misses.\n */\nfunction intlKey(locale: string, options?: object): string {\n return options ? `${locale}:${JSON.stringify(options, Object.keys(options).sort())}` : locale;\n}\n\n/* -------------------- Format Functions -------------------- */\n\nexport function formatNumber(\n caches: IntlCaches,\n value: number,\n options: Intl.NumberFormatOptions | undefined,\n locale: Locale,\n): string {\n const key = intlKey(locale, options);\n\n try {\n return intlFmt(caches.numberFormat, key, () => new Intl.NumberFormat(locale, options)).format(value);\n } catch {\n return String(value);\n }\n}\n\nexport function formatDate(\n caches: IntlCaches,\n value: Date | number,\n options: Intl.DateTimeFormatOptions | undefined,\n locale: Locale,\n): string {\n const d = typeof value === 'number' ? new Date(value) : value;\n const key = intlKey(locale, options);\n\n try {\n return intlFmt(caches.dateFormat, key, () => new Intl.DateTimeFormat(locale, options)).format(d);\n } catch {\n return d.toString();\n }\n}\n\nexport function formatRelative(\n caches: IntlCaches,\n value: number,\n unit: Intl.RelativeTimeFormatUnit,\n options: Intl.RelativeTimeFormatOptions | undefined,\n locale: Locale,\n): string {\n const key = intlKey(locale, options);\n\n try {\n return intlFmt(caches.relativeTimeFormat, key, () => new Intl.RelativeTimeFormat(locale, options)).format(\n value,\n unit,\n );\n } catch {\n return String(value);\n }\n}\n\nexport function formatList(caches: IntlCaches, items: unknown[], locale: string, type: 'and' | 'or'): string {\n if (items.length === 0) return '';\n\n const stringItems = items.map(String);\n const intlType = type === 'and' ? 'conjunction' : 'disjunction';\n\n try {\n return intlFmt(\n caches.listFormat,\n `${locale}:${intlType}`,\n () => new Intl.ListFormat(locale, { style: 'long', type: intlType }),\n ).format(stringItems);\n } catch {\n // Fallback for environments without Intl.ListFormat\n if (stringItems.length === 1) return stringItems[0];\n\n if (stringItems.length === 2) return `${stringItems[0]} ${type} ${stringItems[1]}`;\n\n return `${stringItems.slice(0, -1).join(', ')} ${type} ${stringItems.at(-1)}`;\n }\n}\n\nexport function getPluralForm(caches: IntlCaches, locale: Locale, count: number): PluralForm {\n const n = Math.floor(Math.abs(count));\n\n try {\n return intlFmt(caches.pluralRules, locale, () => new Intl.PluralRules(locale)).select(n) as PluralForm;\n } catch {\n return n === 1 ? 'one' : 'other';\n }\n}\n"],"mappings":";AAaA,SAAgB,IAA6B;AAC3C,QAAO;EACL,4BAAY,IAAI,KAAK;EACrB,4BAAY,IAAI,KAAK;EACrB,8BAAc,IAAI,KAAK;EACvB,6BAAa,IAAI,KAAK;EACtB,oCAAoB,IAAI,KAAK;EAC9B;;AAKH,SAAS,EAA0B,GAAuB,GAAa,GAAmB;CACxF,IAAI,IAAM,EAAM,IAAI,EAAI;AAOxB,QALK,MACH,IAAM,GAAO,EACb,EAAM,IAAI,GAAK,EAAI,GAGd;;AAQT,SAAS,EAAQ,GAAgB,GAA0B;AACzD,QAAO,IAAU,GAAG,EAAO,GAAG,KAAK,UAAU,GAAS,OAAO,KAAK,EAAQ,CAAC,MAAM,CAAC,KAAK;;AAKzF,SAAgB,EACd,GACA,GACA,GACA,GACQ;CACR,IAAM,IAAM,EAAQ,GAAQ,EAAQ;AAEpC,KAAI;AACF,SAAO,EAAQ,EAAO,cAAc,SAAW,IAAI,KAAK,aAAa,GAAQ,EAAQ,CAAC,CAAC,OAAO,EAAM;SAC9F;AACN,SAAO,OAAO,EAAM;;;AAIxB,SAAgB,EACd,GACA,GACA,GACA,GACQ;CACR,IAAM,IAAI,OAAO,KAAU,WAAW,IAAI,KAAK,EAAM,GAAG,GAClD,IAAM,EAAQ,GAAQ,EAAQ;AAEpC,KAAI;AACF,SAAO,EAAQ,EAAO,YAAY,SAAW,IAAI,KAAK,eAAe,GAAQ,EAAQ,CAAC,CAAC,OAAO,EAAE;SAC1F;AACN,SAAO,EAAE,UAAU;;;AAIvB,SAAgB,EACd,GACA,GACA,GACA,GACA,GACQ;CACR,IAAM,IAAM,EAAQ,GAAQ,EAAQ;AAEpC,KAAI;AACF,SAAO,EAAQ,EAAO,oBAAoB,SAAW,IAAI,KAAK,mBAAmB,GAAQ,EAAQ,CAAC,CAAC,OACjG,GACA,EACD;SACK;AACN,SAAO,OAAO,EAAM;;;AAIxB,SAAgB,EAAW,GAAoB,GAAkB,GAAgB,GAA4B;AAC3G,KAAI,EAAM,WAAW,EAAG,QAAO;CAE/B,IAAM,IAAc,EAAM,IAAI,OAAO,EAC/B,IAAW,MAAS,QAAQ,gBAAgB;AAElD,KAAI;AACF,SAAO,EACL,EAAO,YACP,GAAG,EAAO,GAAG,WACP,IAAI,KAAK,WAAW,GAAQ;GAAE,OAAO;GAAQ,MAAM;GAAU,CAAC,CACrE,CAAC,OAAO,EAAY;SACf;AAMN,SAJI,EAAY,WAAW,IAAU,EAAY,KAE7C,EAAY,WAAW,IAAU,GAAG,EAAY,GAAG,GAAG,EAAK,GAAG,EAAY,OAEvE,GAAG,EAAY,MAAM,GAAG,GAAG,CAAC,KAAK,KAAK,CAAC,GAAG,EAAK,GAAG,EAAY,GAAG,GAAG;;;AAI/E,SAAgB,EAAc,GAAoB,GAAgB,GAA2B;CAC3F,IAAM,IAAI,KAAK,MAAM,KAAK,IAAI,EAAM,CAAC;AAErC,KAAI;AACF,SAAO,EAAQ,EAAO,aAAa,SAAc,IAAI,KAAK,YAAY,EAAO,CAAC,CAAC,OAAO,EAAE;SAClF;AACN,SAAO,MAAM,IAAI,QAAQ"}
@@ -0,0 +1,97 @@
1
+ export type Locale = string;
2
+ export type Unsubscribe = () => void;
3
+ export type PluralForm = 'zero' | 'one' | 'two' | 'few' | 'many' | 'other';
4
+ export type PluralMessages = Partial<Record<PluralForm, string>> & {
5
+ other: string;
6
+ };
7
+ export type MessageValue = string | PluralMessages;
8
+ export type Messages = {
9
+ [key: string]: MessageValue | Messages;
10
+ };
11
+ export type Vars = Record<string, unknown>;
12
+ /**
13
+ * Recursively makes all message keys optional, allowing partial locale catalogs.
14
+ * Use when a secondary locale only translates a subset of the primary locale's messages.
15
+ */
16
+ export type DeepPartialMessages<T extends Messages> = {
17
+ [K in keyof T]?: T[K] extends MessageValue ? MessageValue : T[K] extends Messages ? DeepPartialMessages<T[K]> : MessageValue;
18
+ };
19
+ /**
20
+ * Recursive type that extracts all dot-notation keys from a Messages shape for type-safe translation.
21
+ * Type-safe resolution is provided up to 8 levels of nesting; deeper paths resolve to `string`.
22
+ */
23
+ export type TranslationKey<T extends Messages, P extends string = '', D extends readonly 0[] = []> = D['length'] extends 8 ? string : {
24
+ [K in keyof T & string]: T[K] extends MessageValue ? P extends '' ? K : `${P}.${K}` : T[K] extends Messages ? TranslationKey<T[K], P extends '' ? K : `${P}.${K}`, [...D, 0]> : never;
25
+ }[keyof T & string];
26
+ /** The `key` parameter type for `t()` — enforces known dot-notation paths when `T` is concrete, allows any string otherwise. */
27
+ export type TranslationKeyParam<T extends Messages> = [TranslationKey<T>] extends [never] ? string : TranslationKey<T> | (string & {});
28
+ /** Extracts dot-notation keys from `T` whose final value is `PluralMessages`, for compile-time `count` enforcement. */
29
+ export type PluralKeys<T extends Messages, P extends string = '', D extends readonly 0[] = []> = D['length'] extends 8 ? never : {
30
+ [K in keyof T & string]: T[K] extends PluralMessages ? P extends '' ? K : `${P}.${K}` : T[K] extends Messages ? PluralKeys<T[K], P extends '' ? K : `${P}.${K}`, [...D, 0]> : never;
31
+ }[keyof T & string];
32
+ export type Loader = (locale: Locale) => Promise<Messages>;
33
+ /** The reason a `subscribe()` listener was notified. */
34
+ export type LocaleChangeReason = 'locale-change' | 'catalog-update';
35
+ /** Payload passed to every `subscribe()` listener. */
36
+ export type LocaleChangeEvent = {
37
+ locale: Locale;
38
+ reason: LocaleChangeReason;
39
+ };
40
+ /** Type alias for a `subscribe()` listener function. */
41
+ export type LocaleChangeListener = (event: LocaleChangeEvent) => void;
42
+ /**
43
+ * Diagnostic event passed to `onDiagnostic`. Each `kind` carries relevant context:
44
+ * - `'subscriber-error'` — a subscriber callback threw; treat as a programming error.
45
+ * - `'loader-error'` — a locale loader rejected; treat as a recoverable I/O failure.
46
+ */
47
+ export type DiagnosticEvent = {
48
+ error: unknown;
49
+ kind: 'subscriber-error';
50
+ } | {
51
+ error: unknown;
52
+ kind: 'loader-error';
53
+ locale: Locale;
54
+ };
55
+ /** Keys of `T` whose values are nested `Messages` objects (i.e. valid scope targets). */
56
+ export type NamespaceKeys<T extends Messages> = string extends keyof T ? string : {
57
+ [K in keyof T & string]: T[K] extends Messages ? K : never;
58
+ }[keyof T & string];
59
+ export type I18nOptions<T extends Messages = Messages> = {
60
+ fallback?: Locale | Locale[];
61
+ loaders?: Record<Locale, Loader>;
62
+ locale?: Locale;
63
+ /**
64
+ * Static message bundles. Each locale can be a full or partial catalog.
65
+ * For a partial secondary locale, annotate it with `DeepPartialMessages<M>` to get compile-time
66
+ * checks that the subset matches the primary locale's shape.
67
+ */
68
+ messages?: Record<string, T | DeepPartialMessages<T>>;
69
+ /**
70
+ * Receives diagnostic events for both subscriber errors and loader failures.
71
+ * `'subscriber-error'` events indicate a programming error in a listener.
72
+ * `'loader-error'` events are recoverable I/O failures and include the failing `locale`.
73
+ * Defaults to `console.error` for subscriber errors and `console.warn` for loader errors.
74
+ */
75
+ onDiagnostic?: (event: DiagnosticEvent) => void;
76
+ onMissing?: (key: string, locale: Locale) => string | undefined;
77
+ };
78
+ export type BoundI18n<T extends Messages = Messages> = {
79
+ currency(value: number, currency: string, options?: Omit<Intl.NumberFormatOptions, 'style' | 'currency'>): string;
80
+ date(value: Date | number, options?: Intl.DateTimeFormatOptions): string;
81
+ has(key: string): boolean;
82
+ /** Like `has()`, but only checks the exact locale without walking the fallback chain. */
83
+ hasOwn(key: string): boolean;
84
+ list(items: unknown[], type?: 'and' | 'or'): string;
85
+ readonly locale: Locale;
86
+ number(value: number, options?: Intl.NumberFormatOptions): string;
87
+ relative(value: number, unit: Intl.RelativeTimeFormatUnit, options?: Intl.RelativeTimeFormatOptions): string;
88
+ /** Returns a translator scoped to a namespace key. Only keys whose values are nested message objects are valid. */
89
+ scope<K extends NamespaceKeys<T>>(ns: K): BoundI18n<T[K] & Messages>;
90
+ /** Translates a plural key — requires `{ count: number }` when `T` is concrete and the key resolves to `PluralMessages`. */
91
+ t<K extends PluralKeys<T>>(key: K, vars: {
92
+ count: number;
93
+ } & Vars): string;
94
+ t(key: TranslationKeyParam<T>, vars?: Vars): string;
95
+ withLocale(locale: Locale): BoundI18n<T>;
96
+ };
97
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vielzeug/i18nit",
3
- "version": "1.1.4",
3
+ "version": "2.0.0",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "dist"
@@ -10,15 +10,25 @@
10
10
  "types": "dist/index.d.ts",
11
11
  "exports": {
12
12
  ".": {
13
+ "source": "./src/index.ts",
14
+ "types": "./dist/index.d.ts",
13
15
  "import": "./dist/index.js",
14
16
  "require": "./dist/index.cjs"
17
+ },
18
+ "./core": {
19
+ "source": "./src/index.ts",
20
+ "types": "./dist/index.d.ts",
21
+ "import": "./dist/i18nit.js",
22
+ "require": "./dist/i18nit.cjs"
15
23
  }
16
24
  },
17
25
  "scripts": {
18
- "build": "tsc && vite build",
19
- "fix": "biome check --write --unsafe src",
20
- "lint": "biome check src",
21
- "prepublishOnly": "npm run build",
26
+ "build": "vite build && pnpm run build:core && pnpm run build:types",
27
+ "build:core": "vite build --config vite.bundle.config.ts",
28
+ "build:types": "tsc -p tsconfig.declarations.json",
29
+ "fix": "eslint --fix src",
30
+ "lint": "eslint src",
31
+ "prepublishOnly": "pnpm run build",
22
32
  "preview": "vite preview",
23
33
  "test": "vitest"
24
34
  },
@@ -27,9 +37,9 @@
27
37
  "registry": "https://registry.npmjs.org/"
28
38
  },
29
39
  "devDependencies": {
30
- "typescript": "~5.9.3",
31
- "vite": "^7.3.1",
32
- "vite-plugin-dts": "^4.5.4",
33
- "vitest": "^4.0.18"
40
+ "@types/node": "^25.5.0",
41
+ "typescript": "~6.0.2",
42
+ "vite": "^8.0.2",
43
+ "vitest": "^4.1.1"
34
44
  }
35
45
  }
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.cjs","sources":[],"sourcesContent":[],"names":[],"mappings":""}
package/dist/index.js.map DELETED
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";"}