@vielzeug/i18nit 1.1.2 → 1.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/i18nit.cjs +1 -1
- package/dist/i18nit.cjs.map +1 -1
- package/dist/i18nit.js +60 -50
- package/dist/i18nit.js.map +1 -1
- package/dist/index.d.ts +18 -9
- package/package.json +1 -1
package/dist/i18nit.cjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});class m extends Error{key;variable;locale;constructor(t,e,s){super(`Missing variable '${e}' for key '${t}' in locale '${s}'`),this.name="MissingVariableError",this.key=t,this.variable=e,this.locale=s}}const b={"'":"'",'"':""","&":"&","<":"<",">":">"}
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});class m extends Error{key;variable;locale;constructor(t,e,s){super(`Missing variable '${e}' for key '${t}' in locale '${s}'`),this.name="MissingVariableError",this.key=t,this.variable=e,this.locale=s}}const b={"'":"'",'"':""","&":"&","<":"<",">":">"};function h(i){return i.replace(/[&<>"']/g,t=>b[t])}function d(i,t){if(t in i)return i[t];const e=t.match(/[^.[\]]+/g)||[];let s=i;for(const r of e){if(s==null||typeof s!="object")return;if(Array.isArray(s)){const n=Number(r);if(Number.isNaN(n)||n<0||n>=s.length)return;s=s[n]}else s=s[r]}return s}function f(i,t,e){if(i.length===0)return"";const s=i.map(String);try{return new Intl.ListFormat(t,{style:"long",type:e}).format(s)}catch{if(s.length===1)return s[0];if(s.length===2){const a=e==="conjunction"?"and":"or";return`${s[0]} ${a} ${s[1]}`}const r=e==="conjunction"?"and":"or",n=s.pop();return`${s.join(", ")} ${r} ${n}`}}function g(i,t,e={}){const s=e.missingVar??"empty";return i.replace(/\{([\w.[\]]+)(?:\|([^}]+))?\}/g,(r,n,a)=>{let o=n,l=!1;n.endsWith(".length")&&(o=n.slice(0,-7),l=!0);const c=d(t,o);if(c==null){if(s==="preserve")return r;if(s==="error")throw new m(e.key??"unknown",n,e.locale??"unknown");return""}if(Array.isArray(c)){if(l)return String(c.length);if(a!==void 0){const u=e.locale||"en";return a==="and"?f(c,u,"conjunction"):a==="or"?f(c,u,"disjunction"):c.map(String).join(a)}return c.map(String).join(", ")}if(typeof c=="number"&&e.locale)try{return new Intl.NumberFormat(e.locale).format(c)}catch{}return String(c)})}function y(i,t){const e=Math.abs(Math.floor(t));try{return new Intl.PluralRules(i).select(e)}catch{return e===1?"one":"other"}}class p{locale;fallbacks;catalogs=new Map;loaders=new Map;loading=new Map;subscribers=new Set;escape;missingKey;missingVar;constructor(t={}){if(this.locale=t.locale??"en",this.fallbacks=Array.isArray(t.fallback)?t.fallback:t.fallback?[t.fallback]:[],this.escape=t.escape??!1,this.missingKey=t.missingKey??(e=>e),this.missingVar=t.missingVar??"empty",t.messages)for(const[e,s]of Object.entries(t.messages))this.catalogs.set(e,s);if(t.loaders)for(const[e,s]of Object.entries(t.loaders))this.loaders.set(e,s)}setLocale(t){this.locale!==t&&(this.locale=t,this.notifySubscribers())}getLocale(){return this.locale}add(t,e){const s=this.catalogs.get(t)??{};this.catalogs.set(t,{...s,...e}),this.notifySubscribers()}set(t,e){this.catalogs.set(t,e),this.notifySubscribers()}getMessages(t){return this.catalogs.get(t)}hasLocale(t){return this.catalogs.has(t)}has(t,e){return this.findMessage(t,e??this.locale)!==void 0}async load(t){if(this.loading.has(t))return this.loading.get(t);if(this.catalogs.has(t))return;const e=this.loaders.get(t);if(!e)return;const s=(async()=>{try{const r=await e();this.add(t,r)}catch(r){throw console.warn(`[I18n] Failed to load locale '${t}':`,r),r}finally{this.loading.delete(t)}})();return this.loading.set(t,s),s}register(t,e){this.loaders.set(t,e)}async hasAsync(t,e){const s=e??this.locale;return!this.catalogs.has(s)&&this.loaders.has(s)&&await this.load(s),this.has(t,s)}t(t,e,s){const r=s??{},n=r.locale??this.locale,a=r.escape??this.escape,o=this.findMessage(t,n);return o===void 0?r.fallback??this.missingKey(t,n):this.formatMessage(o,e??{},n,a,t)}async tl(t,e,s){const r=s?.locale??this.locale;if(!this.catalogs.has(r)&&this.loaders.has(r))try{await this.load(r)}catch{}return this.t(t,e,s)}number(t,e,s){try{return new Intl.NumberFormat(s??this.locale,e).format(t)}catch{return String(t)}}date(t,e,s){const r=typeof t=="number"?new Date(t):t;try{return new Intl.DateTimeFormat(s??this.locale,e).format(r)}catch{return r.toString()}}namespace(t){return{t:(e,s,r)=>this.t(`${t}.${e}`,s,r),tl:(e,s,r)=>this.tl(`${t}.${e}`,s,r)}}subscribe(t){this.subscribers.add(t);try{t(this.locale)}catch{}return()=>this.subscribers.delete(t)}notifySubscribers(){for(const t of this.subscribers)try{t(this.locale)}catch{}}findMessage(t,e){const s=this.getLocaleChain(e);for(const r of s){const n=this.catalogs.get(r);if(!n)continue;const a=d(n,t);if(a!==void 0)return a}}getLocaleChain(t){const e=[t],s=t.split("-")[0];s!==t&&e.push(s);for(const r of this.fallbacks){e.push(r);const n=r.split("-")[0];n!==r&&e.push(n)}return e}formatMessage(t,e,s,r,n){if(typeof t=="function")try{const a=t(e,{date:(o,l)=>this.date(o,l,s),number:(o,l)=>this.number(o,l,s)});return r?h(a):a}catch{return""}if(typeof t=="object"&&"other"in t){const a=Number(e.count??0),o=t;let l;a===0&&o.zero!==void 0?l="zero":l=y(s,a);const c=o[l]??o.other,u=g(c,e,{key:n,locale:s,missingVar:this.missingVar});return r?h(u):u}if(typeof t=="string"){const a=g(t,e,{key:n,locale:s,missingVar:this.missingVar});return r?h(a):a}return""}}function w(i){return new p(i)}exports.MissingVariableError=m;exports.createI18n=w;
|
|
2
2
|
//# sourceMappingURL=i18nit.cjs.map
|
package/dist/i18nit.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"i18nit.cjs","sources":["../src/i18nit.ts"],"sourcesContent":["export 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 MessageFunction = (\n vars: Record<string, unknown>,\n helpers: {\n number: (value: number, options?: Intl.NumberFormatOptions) => string;\n date: (value: Date | number, options?: Intl.DateTimeFormatOptions) => string;\n },\n) => string;\n\nexport type MessageValue = string | PluralMessages | MessageFunction;\n\nexport type Messages = Record<string, MessageValue>;\n\nexport type TranslateParams = {\n locale?: Locale;\n fallback?: string;\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 missingKey?: (key: string, locale: Locale) => string;\n missingVar?: 'preserve' | 'empty' | 'error';\n};\n\n/**\n * Error thrown when a required variable is missing during interpolation.\n */\nexport class MissingVariableError extends Error {\n readonly key: string;\n readonly variable: string;\n readonly locale: Locale;\n\n constructor(key: string, variable: string, locale: Locale) {\n super(`Missing variable '${variable}' for key '${key}' in locale '${locale}'`);\n this.name = 'MissingVariableError';\n this.key = key;\n this.variable = variable;\n this.locale = locale;\n }\n}\n\n/* Helpers */\n\nconst HTML_ENTITIES: Record<string, string> = {\n \"'\": ''',\n '\"': '"',\n '&': '&',\n '<': '<',\n '>': '>',\n};\n\nconst escapeHtml = (str: string): string => str.replace(/[&<>\"']/g, (char) => HTML_ENTITIES[char]);\n\n/**\n * Join array elements with natural language formatting using Intl.ListFormat.\n * Automatically supports 100+ languages with proper grammar and conjunctions.\n *\n * Uses the browser/Node.js built-in Intl.ListFormat API which handles:\n * - Locale-specific conjunctions (and/or/etc)\n * - Proper grammar for each language\n * - Oxford comma rules\n * - Right-to-left languages\n *\n * @param items - Array items to join\n * @param locale - Target locale\n * @param type - List type ('conjunction' for \"and\", 'disjunction' for \"or\")\n * @returns Formatted list string\n */\nconst 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 // Use Intl.ListFormat for automatic locale-aware formatting\n const formatter = new Intl.ListFormat(locale, { style: 'long', type });\n return formatter.format(stringItems);\n } catch {\n // Fallback for environments without Intl.ListFormat support (very rare)\n if (stringItems.length === 1) return stringItems[0];\n if (stringItems.length === 2) {\n const conjunction = type === 'conjunction' ? 'and' : 'or';\n return `${stringItems[0]} ${conjunction} ${stringItems[1]}`;\n }\n const conjunction = type === 'conjunction' ? 'and' : 'or';\n const last = stringItems[stringItems.length - 1];\n const rest = stringItems.slice(0, -1);\n return `${rest.join(', ')} ${conjunction} ${last}`;\n }\n};\n\n/**\n * Resolve nested properties using dot notation and numeric bracket notation.\n * Safely handles array access - returns undefined for out-of-bounds indices.\n *\n * @param obj - Object to traverse\n * @param path - Path string to resolve\n * @returns Value at a path or undefined if not found\n */\nconst resolvePath = (obj: Record<string, unknown>, path: string): unknown => {\n // Try direct access first (supports literal keys with dots)\n if (path in obj) return obj[path];\n\n // Parse and traverse path - matches: word characters, numbers\n // Regex: /[^.[\\]]+/g matches segments between dots and brackets\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 // Safe array access - check bounds\n if (Array.isArray(value)) {\n const index = Number(part);\n if (!Number.isNaN(index) && index >= 0 && index < value.length) {\n value = value[index];\n } else {\n return undefined;\n }\n } else {\n value = (value as Record<string, unknown>)[part];\n }\n }\n\n return value;\n};\n\n/**\n * Interpolate variables into a template string.\n *\n * Template format:\n * - {variableName} - Simple variable\n * - {nested.path} - Nested object access\n * - {array[0]} - Array index (safe - returns empty if out of bounds)\n * - {array} - Array join with default separator (', ')\n * - {array|and} - Array join with locale-aware 'and' (automatically supports 100+ languages via Intl.ListFormat)\n * - {array|or} - Array join with locale-aware 'or' (automatically supports 100+ languages via Intl.ListFormat)\n * - {array| - } - Array join with custom separator\n * - {array.length} - Array length\n *\n * Uses Intl.ListFormat for locale-aware list formatting, which automatically handles:\n * - All languages supported by the browser/runtime (100+ languages)\n * - Proper conjunctions for each language\n * - Oxford comma rules\n * - Right-to-left languages\n * - No manual language configuration needed\n *\n * @param template - Template string with {variable} placeholders\n * @param vars - Variables object\n * @param options - Interpolation options\n * @returns Interpolated string\n * @throws {MissingVariableError} When missingVar is 'error' and a variable is not found\n */\nconst interpolate = (\n template: string,\n vars: Record<string, unknown>,\n options: {\n locale?: Locale;\n missingVar?: 'preserve' | 'empty' | 'error';\n key?: string; // For better error messages\n } = {},\n): string => {\n const missingVar = options.missingVar ?? 'empty';\n\n // biome-ignore lint/complexity/noExcessiveCognitiveComplexity: Variable interpolation requires conditional logic\n return template.replace(/\\{([\\w.[\\]]+)(?:\\|([^}]+))?\\}/g, (match, key, separator) => {\n // Handle array.length special case\n let isLengthAccess = false;\n let actualKey = key;\n if (key.endsWith('.length')) {\n isLengthAccess = true;\n actualKey = key.slice(0, -7); // Remove '.length'\n }\n\n const value = resolvePath(vars, actualKey);\n\n if (value == null) {\n if (missingVar === 'preserve') return match;\n if (missingVar === 'error') {\n throw new MissingVariableError(options.key ?? 'unknown', key, options.locale ?? 'unknown');\n }\n return '';\n }\n\n // Handle arrays\n if (Array.isArray(value)) {\n // Array length\n if (isLengthAccess) {\n return String(value.length);\n }\n\n // Array joining with separator\n if (separator !== undefined) {\n // Locale-aware special separators using Intl.ListFormat\n if (separator === 'and') {\n const locale = options.locale || 'en';\n return formatList(value, locale, 'conjunction');\n }\n if (separator === 'or') {\n const locale = options.locale || 'en';\n return formatList(value, locale, 'disjunction');\n }\n // Custom separator\n return value.map(String).join(separator);\n }\n\n // Default array join with comma and space\n return value.map(String).join(', ');\n }\n\n // Format numbers with locale\n if (typeof value === 'number' && options.locale) {\n try {\n return new Intl.NumberFormat(options.locale).format(value);\n } catch {\n // Fall through to string conversion\n }\n }\n\n return String(value);\n });\n};\n\n/* Pluralization */\n\ntype PluralCategory = 'zero' | 'one' | 'two' | 'few' | 'many' | 'other';\n\n/**\n * Get the plural form for a number in a given locale using Intl.PluralRules API.\n *\n * Automatically handles all locale-specific plural rules including:\n * - English: one/other\n * - Arabic: zero/one/two/few/many/other\n * - Russian/Polish: one/few/many/other\n * - And 100+ other languages\n *\n * @param locale - Locale string (e.g., 'en-US', 'fr')\n * @param count - Number to pluralize\n * @returns Plural category\n */\nconst getPluralForm = (locale: Locale, count: number): PluralCategory => {\n const n = Math.abs(Math.floor(count));\n\n try {\n const pluralRules = new Intl.PluralRules(locale);\n return pluralRules.select(n) as PluralCategory;\n } catch {\n // Fallback to English-like behavior if locale is invalid\n return n === 1 ? 'one' : 'other';\n }\n};\n\ntype LocaleChangeHandler = (locale: Locale) => void;\n\nclass I18n {\n private locale: Locale;\n private fallbacks: Locale[];\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<LocaleChangeHandler>();\n\n private escape: boolean;\n private missingKey: (key: string, locale: Locale) => string;\n private missingVar: 'preserve' | 'empty' | 'error';\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\n this.escape = config.escape ?? false;\n this.missingKey = config.missingKey ?? ((key) => key);\n this.missingVar = config.missingVar ?? 'empty';\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 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 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) !== undefined;\n }\n\n // Async Loaders\n\n async load(locale: Locale): Promise<void> {\n if (this.loading.has(locale)) return this.loading.get(locale);\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 // Log loader failures for visibility\n console.warn(`[I18n] Failed to load locale '${locale}':`, error);\n // Re-throw so callers can handle errors\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 if (!this.catalogs.has(targetLocale) && this.loaders.has(targetLocale)) {\n await this.load(targetLocale);\n }\n return this.has(key, targetLocale);\n }\n\n // Translation\n\n t(key: string, vars?: Record<string, unknown>, options?: TranslateParams): string {\n const opts = options ?? {};\n const targetLocale = opts.locale ?? this.locale;\n const shouldEscape = opts.escape ?? this.escape;\n\n const message = this.findMessage(key, targetLocale);\n if (message === undefined) {\n return opts.fallback ?? this.missingKey(key, targetLocale);\n }\n\n return this.formatMessage(message, vars ?? {}, targetLocale, shouldEscape, key);\n }\n\n async tl(key: string, vars?: Record<string, unknown>, options?: TranslateParams): Promise<string> {\n const targetLocale = options?.locale ?? this.locale;\n\n if (!this.catalogs.has(targetLocale) && this.loaders.has(targetLocale)) {\n try {\n await this.load(targetLocale);\n } catch {\n // Loader errors are already logged in load(), continue with fallback\n // This catch prevents the error from propagating to the caller\n }\n }\n\n return this.t(key, vars, options);\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?: TranslateParams) =>\n this.t(`${ns}.${key}`, vars, options),\n tl: (key: string, vars?: Record<string, unknown>, options?: TranslateParams) =>\n this.tl(`${ns}.${key}`, vars, options),\n };\n }\n\n // Subscriptions\n\n subscribe(handler: LocaleChangeHandler): () => void {\n this.subscribers.add(handler);\n try {\n handler(this.locale);\n } catch {\n // Ignore handler errors\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 ?? this.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 // biome-ignore lint/complexity/noExcessiveCognitiveComplexity: -\n private formatMessage(\n message: MessageValue,\n vars: Record<string, unknown>,\n locale: Locale,\n shouldEscape: boolean,\n key?: string,\n ): string {\n // Handle function messages\n if (typeof message === 'function') {\n try {\n const result = message(vars, {\n date: (d, opts) => this.date(d, opts, locale),\n number: (v, opts) => this.number(v, opts, locale),\n });\n return shouldEscape ? escapeHtml(result) : result;\n } catch {\n return '';\n }\n }\n\n // Handle 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 when the count is 0\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 const result = interpolate(template, vars, { key, locale, missingVar: this.missingVar });\n return shouldEscape ? escapeHtml(result) : result;\n }\n\n // Handle string messages\n if (typeof message === 'string') {\n const result = interpolate(message, vars, { key, locale, missingVar: this.missingVar });\n return shouldEscape ? escapeHtml(result) : result;\n }\n\n return '';\n }\n}\n\nexport function createI18n(config?: I18nConfig): I18n {\n return new I18n(config);\n}\n"],"names":["MissingVariableError","key","variable","locale","HTML_ENTITIES","escapeHtml","str","char","formatList","items","type","stringItems","conjunction","last","resolvePath","obj","path","parts","value","part","index","interpolate","template","vars","options","missingVar","match","separator","isLengthAccess","actualKey","getPluralForm","count","n","I18n","config","messages","loader","existing","promise","error","targetLocale","opts","shouldEscape","message","date","ns","handler","locales","loc","chain","lang","fallback","fallbackLang","result","d","v","pluralMsg","form","createI18n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"i18nit.cjs","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 MessageFunction = (\n vars: Record<string, unknown>,\n helpers: {\n number: (value: number, options?: Intl.NumberFormatOptions) => string;\n date: (value: Date | number, options?: Intl.DateTimeFormatOptions) => string;\n },\n) => string;\n\nexport type MessageValue = string | PluralMessages | MessageFunction;\n\nexport type Messages = Record<string, MessageValue>;\n\nexport type TranslateOptions = {\n locale?: Locale;\n fallback?: string;\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 missingKey?: (key: string, locale: Locale) => string;\n missingVar?: 'preserve' | 'empty' | 'error';\n};\n\nexport type LocaleChangeHandler = (locale: Locale) => void;\n\n/* -------------------- Errors -------------------- */\n\nexport class MissingVariableError extends Error {\n readonly key: string;\n readonly variable: string;\n readonly locale: Locale;\n\n constructor(key: string, variable: string, locale: Locale) {\n super(`Missing variable '${variable}' for key '${key}' in locale '${locale}'`);\n this.name = 'MissingVariableError';\n this.key = key;\n this.variable = variable;\n this.locale = locale;\n }\n}\n\n/* -------------------- HTML Escaping -------------------- */\n\nconst HTML_ENTITIES: Record<string, string> = {\n \"'\": ''',\n '\"': '"',\n '&': '&',\n '<': '<',\n '>': '>',\n};\n\nfunction escapeHtml(str: string): string {\n return str.replace(/[&<>\"']/g, (char) => HTML_ENTITIES[char]);\n}\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 */\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\ntype InterpolateOptions = {\n locale?: Locale;\n missingVar?: 'preserve' | 'empty' | 'error';\n key?: string;\n};\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>, options: InterpolateOptions = {}): string {\n const missingVar = options.missingVar ?? 'empty';\n\n // biome-ignore lint/complexity/noExcessiveCognitiveComplexity: Legitimate complexity for variable interpolation\n return template.replace(/\\{([\\w.[\\]]+)(?:\\|([^}]+))?\\}/g, (match, key: string, separator?: string) => {\n // Handle .length property\n let actualKey = key;\n let isLength = false;\n\n if (key.endsWith('.length')) {\n actualKey = key.slice(0, -7);\n isLength = true;\n }\n\n const value = resolvePath(vars, actualKey);\n\n // Handle missing variables\n if (value == null) {\n if (missingVar === 'preserve') return match;\n if (missingVar === 'error') {\n throw new MissingVariableError(options.key ?? 'unknown', key, options.locale ?? 'unknown');\n }\n return '';\n }\n\n // Handle arrays\n if (Array.isArray(value)) {\n if (isLength) return String(value.length);\n\n if (separator !== undefined) {\n const locale = options.locale || 'en';\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' && options.locale) {\n try {\n return new Intl.NumberFormat(options.locale).format(value);\n } catch {\n // Fall through\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 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<LocaleChangeHandler>();\n\n private escape: boolean;\n private missingKey: (key: string, locale: Locale) => string;\n private missingVar: 'preserve' | 'empty' | 'error';\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 this.missingKey = config.missingKey ?? ((key) => key);\n this.missingVar = config.missingVar ?? 'empty';\n\n // Load initial messages\n if (config.messages) {\n for (const [locale, messages] of Object.entries(config.messages)) {\n this.catalogs.set(locale, messages);\n }\n }\n\n // Register loaders\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 /* -------------------- Translation -------------------- */\n\n /**\n * Translates a key with optional variables.\n */\n t(key: string, vars?: Record<string, unknown>, options?: TranslateOptions): string {\n const opts = options ?? {};\n const targetLocale = opts.locale ?? this.locale;\n const shouldEscape = opts.escape ?? this.escape;\n\n const message = this.findMessage(key, targetLocale);\n\n if (message === undefined) {\n return opts.fallback ?? this.missingKey(key, targetLocale);\n }\n\n return this.formatMessage(message, vars ?? {}, targetLocale, shouldEscape, key);\n }\n\n /**\n * Translates a key with automatic async loading.\n */\n async tl(key: string, vars?: Record<string, unknown>, options?: TranslateOptions): Promise<string> {\n const targetLocale = options?.locale ?? this.locale;\n\n if (!this.catalogs.has(targetLocale) && this.loaders.has(targetLocale)) {\n try {\n await this.load(targetLocale);\n } catch {\n // Error already logged in load()\n }\n }\n\n return this.t(key, vars, options);\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 tl: (key: string, vars?: Record<string, unknown>, options?: TranslateOptions) =>\n this.tl(`${ns}.${key}`, vars, options),\n };\n }\n\n /* -------------------- Subscriptions -------------------- */\n\n subscribe(handler: LocaleChangeHandler): () => 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 // biome-ignore lint/complexity/noExcessiveCognitiveComplexity: Legitimate complexity for message formatting\n private formatMessage(\n message: MessageValue,\n vars: Record<string, unknown>,\n locale: Locale,\n shouldEscape: boolean,\n key?: string,\n ): string {\n // Function messages\n if (typeof message === 'function') {\n try {\n const result = message(vars, {\n date: (d, opts) => this.date(d, opts, locale),\n number: (v, opts) => this.number(v, opts, locale),\n });\n return shouldEscape ? escapeHtml(result) : result;\n } catch {\n return '';\n }\n }\n\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 const result = interpolate(template, vars, { key, locale, missingVar: this.missingVar });\n return shouldEscape ? escapeHtml(result) : result;\n }\n\n // String messages\n if (typeof message === 'string') {\n const result = interpolate(message, vars, { key, locale, missingVar: this.missingVar });\n return shouldEscape ? escapeHtml(result) : result;\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":["MissingVariableError","key","variable","locale","HTML_ENTITIES","escapeHtml","str","char","resolvePath","obj","path","parts","value","part","index","formatList","items","type","stringItems","separator","last","interpolate","template","vars","options","missingVar","match","actualKey","isLength","getPluralForm","count","n","I18n","config","messages","loader","existing","promise","error","targetLocale","opts","shouldEscape","message","date","ns","handler","locales","loc","chain","lang","fallback","fallbackLang","result","d","v","pluralMsg","form","createI18n"],"mappings":"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"}
|
package/dist/i18nit.js
CHANGED
|
@@ -12,21 +12,11 @@ const b = {
|
|
|
12
12
|
"&": "&",
|
|
13
13
|
"<": "<",
|
|
14
14
|
">": ">"
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
} catch {
|
|
21
|
-
if (s.length === 1) return s[0];
|
|
22
|
-
if (s.length === 2) {
|
|
23
|
-
const o = e === "conjunction" ? "and" : "or";
|
|
24
|
-
return `${s[0]} ${o} ${s[1]}`;
|
|
25
|
-
}
|
|
26
|
-
const r = e === "conjunction" ? "and" : "or", n = s[s.length - 1];
|
|
27
|
-
return `${s.slice(0, -1).join(", ")} ${r} ${n}`;
|
|
28
|
-
}
|
|
29
|
-
}, m = (i, t) => {
|
|
15
|
+
};
|
|
16
|
+
function h(i) {
|
|
17
|
+
return i.replace(/[&<>"']/g, (t) => b[t]);
|
|
18
|
+
}
|
|
19
|
+
function m(i, t) {
|
|
30
20
|
if (t in i) return i[t];
|
|
31
21
|
const e = t.match(/[^.[\]]+/g) || [];
|
|
32
22
|
let s = i;
|
|
@@ -34,20 +24,35 @@ const b = {
|
|
|
34
24
|
if (s == null || typeof s != "object") return;
|
|
35
25
|
if (Array.isArray(s)) {
|
|
36
26
|
const n = Number(r);
|
|
37
|
-
if (
|
|
38
|
-
s = s[n];
|
|
39
|
-
else
|
|
27
|
+
if (Number.isNaN(n) || n < 0 || n >= s.length)
|
|
40
28
|
return;
|
|
29
|
+
s = s[n];
|
|
41
30
|
} else
|
|
42
31
|
s = s[r];
|
|
43
32
|
}
|
|
44
33
|
return s;
|
|
45
|
-
}
|
|
34
|
+
}
|
|
35
|
+
function f(i, t, e) {
|
|
36
|
+
if (i.length === 0) return "";
|
|
37
|
+
const s = i.map(String);
|
|
38
|
+
try {
|
|
39
|
+
return new Intl.ListFormat(t, { style: "long", type: e }).format(s);
|
|
40
|
+
} catch {
|
|
41
|
+
if (s.length === 1) return s[0];
|
|
42
|
+
if (s.length === 2) {
|
|
43
|
+
const a = e === "conjunction" ? "and" : "or";
|
|
44
|
+
return `${s[0]} ${a} ${s[1]}`;
|
|
45
|
+
}
|
|
46
|
+
const r = e === "conjunction" ? "and" : "or", n = s.pop();
|
|
47
|
+
return `${s.join(", ")} ${r} ${n}`;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
function g(i, t, e = {}) {
|
|
46
51
|
const s = e.missingVar ?? "empty";
|
|
47
52
|
return i.replace(/\{([\w.[\]]+)(?:\|([^}]+))?\}/g, (r, n, a) => {
|
|
48
|
-
let o =
|
|
49
|
-
n.endsWith(".length") && (o =
|
|
50
|
-
const c = m(t,
|
|
53
|
+
let o = n, l = !1;
|
|
54
|
+
n.endsWith(".length") && (o = n.slice(0, -7), l = !0);
|
|
55
|
+
const c = m(t, o);
|
|
51
56
|
if (c == null) {
|
|
52
57
|
if (s === "preserve") return r;
|
|
53
58
|
if (s === "error")
|
|
@@ -55,18 +60,10 @@ const b = {
|
|
|
55
60
|
return "";
|
|
56
61
|
}
|
|
57
62
|
if (Array.isArray(c)) {
|
|
58
|
-
if (
|
|
59
|
-
return String(c.length);
|
|
63
|
+
if (l) return String(c.length);
|
|
60
64
|
if (a !== void 0) {
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
return f(c, h, "conjunction");
|
|
64
|
-
}
|
|
65
|
-
if (a === "or") {
|
|
66
|
-
const h = e.locale || "en";
|
|
67
|
-
return f(c, h, "disjunction");
|
|
68
|
-
}
|
|
69
|
-
return c.map(String).join(a);
|
|
65
|
+
const u = e.locale || "en";
|
|
66
|
+
return a === "and" ? f(c, u, "conjunction") : a === "or" ? f(c, u, "disjunction") : c.map(String).join(a);
|
|
70
67
|
}
|
|
71
68
|
return c.map(String).join(", ");
|
|
72
69
|
}
|
|
@@ -77,14 +74,15 @@ const b = {
|
|
|
77
74
|
}
|
|
78
75
|
return String(c);
|
|
79
76
|
});
|
|
80
|
-
}
|
|
77
|
+
}
|
|
78
|
+
function y(i, t) {
|
|
81
79
|
const e = Math.abs(Math.floor(t));
|
|
82
80
|
try {
|
|
83
81
|
return new Intl.PluralRules(i).select(e);
|
|
84
82
|
} catch {
|
|
85
83
|
return e === 1 ? "one" : "other";
|
|
86
84
|
}
|
|
87
|
-
}
|
|
85
|
+
}
|
|
88
86
|
class p {
|
|
89
87
|
locale;
|
|
90
88
|
fallbacks;
|
|
@@ -103,18 +101,24 @@ class p {
|
|
|
103
101
|
for (const [e, s] of Object.entries(t.loaders))
|
|
104
102
|
this.loaders.set(e, s);
|
|
105
103
|
}
|
|
106
|
-
|
|
104
|
+
/* -------------------- Locale Management -------------------- */
|
|
107
105
|
setLocale(t) {
|
|
108
106
|
this.locale !== t && (this.locale = t, this.notifySubscribers());
|
|
109
107
|
}
|
|
110
108
|
getLocale() {
|
|
111
109
|
return this.locale;
|
|
112
110
|
}
|
|
113
|
-
|
|
111
|
+
/* -------------------- Message Management -------------------- */
|
|
112
|
+
/**
|
|
113
|
+
* Adds messages to a locale (merges with existing).
|
|
114
|
+
*/
|
|
114
115
|
add(t, e) {
|
|
115
116
|
const s = this.catalogs.get(t) ?? {};
|
|
116
117
|
this.catalogs.set(t, { ...s, ...e }), this.notifySubscribers();
|
|
117
118
|
}
|
|
119
|
+
/**
|
|
120
|
+
* Sets messages for a locale (replaces existing).
|
|
121
|
+
*/
|
|
118
122
|
set(t, e) {
|
|
119
123
|
this.catalogs.set(t, e), this.notifySubscribers();
|
|
120
124
|
}
|
|
@@ -125,9 +129,9 @@ class p {
|
|
|
125
129
|
return this.catalogs.has(t);
|
|
126
130
|
}
|
|
127
131
|
has(t, e) {
|
|
128
|
-
return this.findMessage(t, e) !== void 0;
|
|
132
|
+
return this.findMessage(t, e ?? this.locale) !== void 0;
|
|
129
133
|
}
|
|
130
|
-
|
|
134
|
+
/* -------------------- Async Loaders -------------------- */
|
|
131
135
|
async load(t) {
|
|
132
136
|
if (this.loading.has(t)) return this.loading.get(t);
|
|
133
137
|
if (this.catalogs.has(t)) return;
|
|
@@ -152,11 +156,17 @@ class p {
|
|
|
152
156
|
const s = e ?? this.locale;
|
|
153
157
|
return !this.catalogs.has(s) && this.loaders.has(s) && await this.load(s), this.has(t, s);
|
|
154
158
|
}
|
|
155
|
-
|
|
159
|
+
/* -------------------- Translation -------------------- */
|
|
160
|
+
/**
|
|
161
|
+
* Translates a key with optional variables.
|
|
162
|
+
*/
|
|
156
163
|
t(t, e, s) {
|
|
157
164
|
const r = s ?? {}, n = r.locale ?? this.locale, a = r.escape ?? this.escape, o = this.findMessage(t, n);
|
|
158
165
|
return o === void 0 ? r.fallback ?? this.missingKey(t, n) : this.formatMessage(o, e ?? {}, n, a, t);
|
|
159
166
|
}
|
|
167
|
+
/**
|
|
168
|
+
* Translates a key with automatic async loading.
|
|
169
|
+
*/
|
|
160
170
|
async tl(t, e, s) {
|
|
161
171
|
const r = s?.locale ?? this.locale;
|
|
162
172
|
if (!this.catalogs.has(r) && this.loaders.has(r))
|
|
@@ -166,7 +176,7 @@ class p {
|
|
|
166
176
|
}
|
|
167
177
|
return this.t(t, e, s);
|
|
168
178
|
}
|
|
169
|
-
|
|
179
|
+
/* -------------------- Formatting Helpers -------------------- */
|
|
170
180
|
number(t, e, s) {
|
|
171
181
|
try {
|
|
172
182
|
return new Intl.NumberFormat(s ?? this.locale, e).format(t);
|
|
@@ -182,14 +192,14 @@ class p {
|
|
|
182
192
|
return r.toString();
|
|
183
193
|
}
|
|
184
194
|
}
|
|
185
|
-
|
|
195
|
+
/* -------------------- Namespaced Translator -------------------- */
|
|
186
196
|
namespace(t) {
|
|
187
197
|
return {
|
|
188
198
|
t: (e, s, r) => this.t(`${t}.${e}`, s, r),
|
|
189
199
|
tl: (e, s, r) => this.tl(`${t}.${e}`, s, r)
|
|
190
200
|
};
|
|
191
201
|
}
|
|
192
|
-
|
|
202
|
+
/* -------------------- Subscriptions -------------------- */
|
|
193
203
|
subscribe(t) {
|
|
194
204
|
this.subscribers.add(t);
|
|
195
205
|
try {
|
|
@@ -205,9 +215,9 @@ class p {
|
|
|
205
215
|
} catch {
|
|
206
216
|
}
|
|
207
217
|
}
|
|
208
|
-
|
|
218
|
+
/* -------------------- Internal Helpers -------------------- */
|
|
209
219
|
findMessage(t, e) {
|
|
210
|
-
const s = this.getLocaleChain(e
|
|
220
|
+
const s = this.getLocaleChain(e);
|
|
211
221
|
for (const r of s) {
|
|
212
222
|
const n = this.catalogs.get(r);
|
|
213
223
|
if (!n) continue;
|
|
@@ -225,7 +235,7 @@ class p {
|
|
|
225
235
|
}
|
|
226
236
|
return e;
|
|
227
237
|
}
|
|
228
|
-
// biome-ignore lint/complexity/noExcessiveCognitiveComplexity:
|
|
238
|
+
// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: Legitimate complexity for message formatting
|
|
229
239
|
formatMessage(t, e, s, r, n) {
|
|
230
240
|
if (typeof t == "function")
|
|
231
241
|
try {
|
|
@@ -233,7 +243,7 @@ class p {
|
|
|
233
243
|
date: (o, l) => this.date(o, l, s),
|
|
234
244
|
number: (o, l) => this.number(o, l, s)
|
|
235
245
|
});
|
|
236
|
-
return r ?
|
|
246
|
+
return r ? h(a) : a;
|
|
237
247
|
} catch {
|
|
238
248
|
return "";
|
|
239
249
|
}
|
|
@@ -241,12 +251,12 @@ class p {
|
|
|
241
251
|
const a = Number(e.count ?? 0), o = t;
|
|
242
252
|
let l;
|
|
243
253
|
a === 0 && o.zero !== void 0 ? l = "zero" : l = y(s, a);
|
|
244
|
-
const c = o[l] ?? o.other,
|
|
245
|
-
return r ? u
|
|
254
|
+
const c = o[l] ?? o.other, u = g(c, e, { key: n, locale: s, missingVar: this.missingVar });
|
|
255
|
+
return r ? h(u) : u;
|
|
246
256
|
}
|
|
247
257
|
if (typeof t == "string") {
|
|
248
258
|
const a = g(t, e, { key: n, locale: s, missingVar: this.missingVar });
|
|
249
|
-
return r ?
|
|
259
|
+
return r ? h(a) : a;
|
|
250
260
|
}
|
|
251
261
|
return "";
|
|
252
262
|
}
|
package/dist/i18nit.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"i18nit.js","sources":["../src/i18nit.ts"],"sourcesContent":["export 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 MessageFunction = (\n vars: Record<string, unknown>,\n helpers: {\n number: (value: number, options?: Intl.NumberFormatOptions) => string;\n date: (value: Date | number, options?: Intl.DateTimeFormatOptions) => string;\n },\n) => string;\n\nexport type MessageValue = string | PluralMessages | MessageFunction;\n\nexport type Messages = Record<string, MessageValue>;\n\nexport type TranslateParams = {\n locale?: Locale;\n fallback?: string;\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 missingKey?: (key: string, locale: Locale) => string;\n missingVar?: 'preserve' | 'empty' | 'error';\n};\n\n/**\n * Error thrown when a required variable is missing during interpolation.\n */\nexport class MissingVariableError extends Error {\n readonly key: string;\n readonly variable: string;\n readonly locale: Locale;\n\n constructor(key: string, variable: string, locale: Locale) {\n super(`Missing variable '${variable}' for key '${key}' in locale '${locale}'`);\n this.name = 'MissingVariableError';\n this.key = key;\n this.variable = variable;\n this.locale = locale;\n }\n}\n\n/* Helpers */\n\nconst HTML_ENTITIES: Record<string, string> = {\n \"'\": ''',\n '\"': '"',\n '&': '&',\n '<': '<',\n '>': '>',\n};\n\nconst escapeHtml = (str: string): string => str.replace(/[&<>\"']/g, (char) => HTML_ENTITIES[char]);\n\n/**\n * Join array elements with natural language formatting using Intl.ListFormat.\n * Automatically supports 100+ languages with proper grammar and conjunctions.\n *\n * Uses the browser/Node.js built-in Intl.ListFormat API which handles:\n * - Locale-specific conjunctions (and/or/etc)\n * - Proper grammar for each language\n * - Oxford comma rules\n * - Right-to-left languages\n *\n * @param items - Array items to join\n * @param locale - Target locale\n * @param type - List type ('conjunction' for \"and\", 'disjunction' for \"or\")\n * @returns Formatted list string\n */\nconst 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 // Use Intl.ListFormat for automatic locale-aware formatting\n const formatter = new Intl.ListFormat(locale, { style: 'long', type });\n return formatter.format(stringItems);\n } catch {\n // Fallback for environments without Intl.ListFormat support (very rare)\n if (stringItems.length === 1) return stringItems[0];\n if (stringItems.length === 2) {\n const conjunction = type === 'conjunction' ? 'and' : 'or';\n return `${stringItems[0]} ${conjunction} ${stringItems[1]}`;\n }\n const conjunction = type === 'conjunction' ? 'and' : 'or';\n const last = stringItems[stringItems.length - 1];\n const rest = stringItems.slice(0, -1);\n return `${rest.join(', ')} ${conjunction} ${last}`;\n }\n};\n\n/**\n * Resolve nested properties using dot notation and numeric bracket notation.\n * Safely handles array access - returns undefined for out-of-bounds indices.\n *\n * @param obj - Object to traverse\n * @param path - Path string to resolve\n * @returns Value at a path or undefined if not found\n */\nconst resolvePath = (obj: Record<string, unknown>, path: string): unknown => {\n // Try direct access first (supports literal keys with dots)\n if (path in obj) return obj[path];\n\n // Parse and traverse path - matches: word characters, numbers\n // Regex: /[^.[\\]]+/g matches segments between dots and brackets\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 // Safe array access - check bounds\n if (Array.isArray(value)) {\n const index = Number(part);\n if (!Number.isNaN(index) && index >= 0 && index < value.length) {\n value = value[index];\n } else {\n return undefined;\n }\n } else {\n value = (value as Record<string, unknown>)[part];\n }\n }\n\n return value;\n};\n\n/**\n * Interpolate variables into a template string.\n *\n * Template format:\n * - {variableName} - Simple variable\n * - {nested.path} - Nested object access\n * - {array[0]} - Array index (safe - returns empty if out of bounds)\n * - {array} - Array join with default separator (', ')\n * - {array|and} - Array join with locale-aware 'and' (automatically supports 100+ languages via Intl.ListFormat)\n * - {array|or} - Array join with locale-aware 'or' (automatically supports 100+ languages via Intl.ListFormat)\n * - {array| - } - Array join with custom separator\n * - {array.length} - Array length\n *\n * Uses Intl.ListFormat for locale-aware list formatting, which automatically handles:\n * - All languages supported by the browser/runtime (100+ languages)\n * - Proper conjunctions for each language\n * - Oxford comma rules\n * - Right-to-left languages\n * - No manual language configuration needed\n *\n * @param template - Template string with {variable} placeholders\n * @param vars - Variables object\n * @param options - Interpolation options\n * @returns Interpolated string\n * @throws {MissingVariableError} When missingVar is 'error' and a variable is not found\n */\nconst interpolate = (\n template: string,\n vars: Record<string, unknown>,\n options: {\n locale?: Locale;\n missingVar?: 'preserve' | 'empty' | 'error';\n key?: string; // For better error messages\n } = {},\n): string => {\n const missingVar = options.missingVar ?? 'empty';\n\n // biome-ignore lint/complexity/noExcessiveCognitiveComplexity: Variable interpolation requires conditional logic\n return template.replace(/\\{([\\w.[\\]]+)(?:\\|([^}]+))?\\}/g, (match, key, separator) => {\n // Handle array.length special case\n let isLengthAccess = false;\n let actualKey = key;\n if (key.endsWith('.length')) {\n isLengthAccess = true;\n actualKey = key.slice(0, -7); // Remove '.length'\n }\n\n const value = resolvePath(vars, actualKey);\n\n if (value == null) {\n if (missingVar === 'preserve') return match;\n if (missingVar === 'error') {\n throw new MissingVariableError(options.key ?? 'unknown', key, options.locale ?? 'unknown');\n }\n return '';\n }\n\n // Handle arrays\n if (Array.isArray(value)) {\n // Array length\n if (isLengthAccess) {\n return String(value.length);\n }\n\n // Array joining with separator\n if (separator !== undefined) {\n // Locale-aware special separators using Intl.ListFormat\n if (separator === 'and') {\n const locale = options.locale || 'en';\n return formatList(value, locale, 'conjunction');\n }\n if (separator === 'or') {\n const locale = options.locale || 'en';\n return formatList(value, locale, 'disjunction');\n }\n // Custom separator\n return value.map(String).join(separator);\n }\n\n // Default array join with comma and space\n return value.map(String).join(', ');\n }\n\n // Format numbers with locale\n if (typeof value === 'number' && options.locale) {\n try {\n return new Intl.NumberFormat(options.locale).format(value);\n } catch {\n // Fall through to string conversion\n }\n }\n\n return String(value);\n });\n};\n\n/* Pluralization */\n\ntype PluralCategory = 'zero' | 'one' | 'two' | 'few' | 'many' | 'other';\n\n/**\n * Get the plural form for a number in a given locale using Intl.PluralRules API.\n *\n * Automatically handles all locale-specific plural rules including:\n * - English: one/other\n * - Arabic: zero/one/two/few/many/other\n * - Russian/Polish: one/few/many/other\n * - And 100+ other languages\n *\n * @param locale - Locale string (e.g., 'en-US', 'fr')\n * @param count - Number to pluralize\n * @returns Plural category\n */\nconst getPluralForm = (locale: Locale, count: number): PluralCategory => {\n const n = Math.abs(Math.floor(count));\n\n try {\n const pluralRules = new Intl.PluralRules(locale);\n return pluralRules.select(n) as PluralCategory;\n } catch {\n // Fallback to English-like behavior if locale is invalid\n return n === 1 ? 'one' : 'other';\n }\n};\n\ntype LocaleChangeHandler = (locale: Locale) => void;\n\nclass I18n {\n private locale: Locale;\n private fallbacks: Locale[];\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<LocaleChangeHandler>();\n\n private escape: boolean;\n private missingKey: (key: string, locale: Locale) => string;\n private missingVar: 'preserve' | 'empty' | 'error';\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\n this.escape = config.escape ?? false;\n this.missingKey = config.missingKey ?? ((key) => key);\n this.missingVar = config.missingVar ?? 'empty';\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 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 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) !== undefined;\n }\n\n // Async Loaders\n\n async load(locale: Locale): Promise<void> {\n if (this.loading.has(locale)) return this.loading.get(locale);\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 // Log loader failures for visibility\n console.warn(`[I18n] Failed to load locale '${locale}':`, error);\n // Re-throw so callers can handle errors\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 if (!this.catalogs.has(targetLocale) && this.loaders.has(targetLocale)) {\n await this.load(targetLocale);\n }\n return this.has(key, targetLocale);\n }\n\n // Translation\n\n t(key: string, vars?: Record<string, unknown>, options?: TranslateParams): string {\n const opts = options ?? {};\n const targetLocale = opts.locale ?? this.locale;\n const shouldEscape = opts.escape ?? this.escape;\n\n const message = this.findMessage(key, targetLocale);\n if (message === undefined) {\n return opts.fallback ?? this.missingKey(key, targetLocale);\n }\n\n return this.formatMessage(message, vars ?? {}, targetLocale, shouldEscape, key);\n }\n\n async tl(key: string, vars?: Record<string, unknown>, options?: TranslateParams): Promise<string> {\n const targetLocale = options?.locale ?? this.locale;\n\n if (!this.catalogs.has(targetLocale) && this.loaders.has(targetLocale)) {\n try {\n await this.load(targetLocale);\n } catch {\n // Loader errors are already logged in load(), continue with fallback\n // This catch prevents the error from propagating to the caller\n }\n }\n\n return this.t(key, vars, options);\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?: TranslateParams) =>\n this.t(`${ns}.${key}`, vars, options),\n tl: (key: string, vars?: Record<string, unknown>, options?: TranslateParams) =>\n this.tl(`${ns}.${key}`, vars, options),\n };\n }\n\n // Subscriptions\n\n subscribe(handler: LocaleChangeHandler): () => void {\n this.subscribers.add(handler);\n try {\n handler(this.locale);\n } catch {\n // Ignore handler errors\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 ?? this.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 // biome-ignore lint/complexity/noExcessiveCognitiveComplexity: -\n private formatMessage(\n message: MessageValue,\n vars: Record<string, unknown>,\n locale: Locale,\n shouldEscape: boolean,\n key?: string,\n ): string {\n // Handle function messages\n if (typeof message === 'function') {\n try {\n const result = message(vars, {\n date: (d, opts) => this.date(d, opts, locale),\n number: (v, opts) => this.number(v, opts, locale),\n });\n return shouldEscape ? escapeHtml(result) : result;\n } catch {\n return '';\n }\n }\n\n // Handle 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 when the count is 0\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 const result = interpolate(template, vars, { key, locale, missingVar: this.missingVar });\n return shouldEscape ? escapeHtml(result) : result;\n }\n\n // Handle string messages\n if (typeof message === 'string') {\n const result = interpolate(message, vars, { key, locale, missingVar: this.missingVar });\n return shouldEscape ? escapeHtml(result) : result;\n }\n\n return '';\n }\n}\n\nexport function createI18n(config?: I18nConfig): I18n {\n return new I18n(config);\n}\n"],"names":["MissingVariableError","key","variable","locale","HTML_ENTITIES","escapeHtml","str","char","formatList","items","type","stringItems","conjunction","last","resolvePath","obj","path","parts","value","part","index","interpolate","template","vars","options","missingVar","match","separator","isLengthAccess","actualKey","getPluralForm","count","n","I18n","config","messages","loader","existing","promise","error","targetLocale","opts","shouldEscape","message","date","ns","handler","locales","loc","chain","lang","fallback","fallbackLang","result","d","v","pluralMsg","form","createI18n"],"mappings":"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"}
|
|
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 MessageFunction = (\n vars: Record<string, unknown>,\n helpers: {\n number: (value: number, options?: Intl.NumberFormatOptions) => string;\n date: (value: Date | number, options?: Intl.DateTimeFormatOptions) => string;\n },\n) => string;\n\nexport type MessageValue = string | PluralMessages | MessageFunction;\n\nexport type Messages = Record<string, MessageValue>;\n\nexport type TranslateOptions = {\n locale?: Locale;\n fallback?: string;\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 missingKey?: (key: string, locale: Locale) => string;\n missingVar?: 'preserve' | 'empty' | 'error';\n};\n\nexport type LocaleChangeHandler = (locale: Locale) => void;\n\n/* -------------------- Errors -------------------- */\n\nexport class MissingVariableError extends Error {\n readonly key: string;\n readonly variable: string;\n readonly locale: Locale;\n\n constructor(key: string, variable: string, locale: Locale) {\n super(`Missing variable '${variable}' for key '${key}' in locale '${locale}'`);\n this.name = 'MissingVariableError';\n this.key = key;\n this.variable = variable;\n this.locale = locale;\n }\n}\n\n/* -------------------- HTML Escaping -------------------- */\n\nconst HTML_ENTITIES: Record<string, string> = {\n \"'\": ''',\n '\"': '"',\n '&': '&',\n '<': '<',\n '>': '>',\n};\n\nfunction escapeHtml(str: string): string {\n return str.replace(/[&<>\"']/g, (char) => HTML_ENTITIES[char]);\n}\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 */\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\ntype InterpolateOptions = {\n locale?: Locale;\n missingVar?: 'preserve' | 'empty' | 'error';\n key?: string;\n};\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>, options: InterpolateOptions = {}): string {\n const missingVar = options.missingVar ?? 'empty';\n\n // biome-ignore lint/complexity/noExcessiveCognitiveComplexity: Legitimate complexity for variable interpolation\n return template.replace(/\\{([\\w.[\\]]+)(?:\\|([^}]+))?\\}/g, (match, key: string, separator?: string) => {\n // Handle .length property\n let actualKey = key;\n let isLength = false;\n\n if (key.endsWith('.length')) {\n actualKey = key.slice(0, -7);\n isLength = true;\n }\n\n const value = resolvePath(vars, actualKey);\n\n // Handle missing variables\n if (value == null) {\n if (missingVar === 'preserve') return match;\n if (missingVar === 'error') {\n throw new MissingVariableError(options.key ?? 'unknown', key, options.locale ?? 'unknown');\n }\n return '';\n }\n\n // Handle arrays\n if (Array.isArray(value)) {\n if (isLength) return String(value.length);\n\n if (separator !== undefined) {\n const locale = options.locale || 'en';\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' && options.locale) {\n try {\n return new Intl.NumberFormat(options.locale).format(value);\n } catch {\n // Fall through\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 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<LocaleChangeHandler>();\n\n private escape: boolean;\n private missingKey: (key: string, locale: Locale) => string;\n private missingVar: 'preserve' | 'empty' | 'error';\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 this.missingKey = config.missingKey ?? ((key) => key);\n this.missingVar = config.missingVar ?? 'empty';\n\n // Load initial messages\n if (config.messages) {\n for (const [locale, messages] of Object.entries(config.messages)) {\n this.catalogs.set(locale, messages);\n }\n }\n\n // Register loaders\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 /* -------------------- Translation -------------------- */\n\n /**\n * Translates a key with optional variables.\n */\n t(key: string, vars?: Record<string, unknown>, options?: TranslateOptions): string {\n const opts = options ?? {};\n const targetLocale = opts.locale ?? this.locale;\n const shouldEscape = opts.escape ?? this.escape;\n\n const message = this.findMessage(key, targetLocale);\n\n if (message === undefined) {\n return opts.fallback ?? this.missingKey(key, targetLocale);\n }\n\n return this.formatMessage(message, vars ?? {}, targetLocale, shouldEscape, key);\n }\n\n /**\n * Translates a key with automatic async loading.\n */\n async tl(key: string, vars?: Record<string, unknown>, options?: TranslateOptions): Promise<string> {\n const targetLocale = options?.locale ?? this.locale;\n\n if (!this.catalogs.has(targetLocale) && this.loaders.has(targetLocale)) {\n try {\n await this.load(targetLocale);\n } catch {\n // Error already logged in load()\n }\n }\n\n return this.t(key, vars, options);\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 tl: (key: string, vars?: Record<string, unknown>, options?: TranslateOptions) =>\n this.tl(`${ns}.${key}`, vars, options),\n };\n }\n\n /* -------------------- Subscriptions -------------------- */\n\n subscribe(handler: LocaleChangeHandler): () => 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 // biome-ignore lint/complexity/noExcessiveCognitiveComplexity: Legitimate complexity for message formatting\n private formatMessage(\n message: MessageValue,\n vars: Record<string, unknown>,\n locale: Locale,\n shouldEscape: boolean,\n key?: string,\n ): string {\n // Function messages\n if (typeof message === 'function') {\n try {\n const result = message(vars, {\n date: (d, opts) => this.date(d, opts, locale),\n number: (v, opts) => this.number(v, opts, locale),\n });\n return shouldEscape ? escapeHtml(result) : result;\n } catch {\n return '';\n }\n }\n\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 const result = interpolate(template, vars, { key, locale, missingVar: this.missingVar });\n return shouldEscape ? escapeHtml(result) : result;\n }\n\n // String messages\n if (typeof message === 'string') {\n const result = interpolate(message, vars, { key, locale, missingVar: this.missingVar });\n return shouldEscape ? escapeHtml(result) : result;\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":["MissingVariableError","key","variable","locale","HTML_ENTITIES","escapeHtml","str","char","resolvePath","obj","path","parts","value","part","index","formatList","items","type","stringItems","separator","last","interpolate","template","vars","options","missingVar","match","actualKey","isLength","getPluralForm","count","n","I18n","config","messages","loader","existing","promise","error","targetLocale","opts","shouldEscape","message","date","ns","handler","locales","loc","chain","lang","fallback","fallbackLang","result","d","v","pluralMsg","form","createI18n"],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -13,7 +13,13 @@ declare class I18n {
|
|
|
13
13
|
constructor(config?: I18nConfig);
|
|
14
14
|
setLocale(locale: Locale): void;
|
|
15
15
|
getLocale(): Locale;
|
|
16
|
+
/**
|
|
17
|
+
* Adds messages to a locale (merges with existing).
|
|
18
|
+
*/
|
|
16
19
|
add(locale: Locale, messages: Messages): void;
|
|
20
|
+
/**
|
|
21
|
+
* Sets messages for a locale (replaces existing).
|
|
22
|
+
*/
|
|
17
23
|
set(locale: Locale, messages: Messages): void;
|
|
18
24
|
getMessages(locale: Locale): Messages | undefined;
|
|
19
25
|
hasLocale(locale: Locale): boolean;
|
|
@@ -21,13 +27,19 @@ declare class I18n {
|
|
|
21
27
|
load(locale: Locale): Promise<void>;
|
|
22
28
|
register(locale: Locale, loader: () => Promise<Messages>): void;
|
|
23
29
|
hasAsync(key: string, locale?: Locale): Promise<boolean>;
|
|
24
|
-
|
|
25
|
-
|
|
30
|
+
/**
|
|
31
|
+
* Translates a key with optional variables.
|
|
32
|
+
*/
|
|
33
|
+
t(key: string, vars?: Record<string, unknown>, options?: TranslateOptions): string;
|
|
34
|
+
/**
|
|
35
|
+
* Translates a key with automatic async loading.
|
|
36
|
+
*/
|
|
37
|
+
tl(key: string, vars?: Record<string, unknown>, options?: TranslateOptions): Promise<string>;
|
|
26
38
|
number(value: number, options?: Intl.NumberFormatOptions, locale?: Locale): string;
|
|
27
39
|
date(value: Date | number, options?: Intl.DateTimeFormatOptions, locale?: Locale): string;
|
|
28
40
|
namespace(ns: string): {
|
|
29
|
-
t: (key: string, vars?: Record<string, unknown>, options?:
|
|
30
|
-
tl: (key: string, vars?: Record<string, unknown>, options?:
|
|
41
|
+
t: (key: string, vars?: Record<string, unknown>, options?: TranslateOptions) => string;
|
|
42
|
+
tl: (key: string, vars?: Record<string, unknown>, options?: TranslateOptions) => Promise<string>;
|
|
31
43
|
};
|
|
32
44
|
subscribe(handler: LocaleChangeHandler): () => void;
|
|
33
45
|
private notifySubscribers;
|
|
@@ -48,7 +60,7 @@ export declare type I18nConfig = {
|
|
|
48
60
|
|
|
49
61
|
export declare type Locale = string;
|
|
50
62
|
|
|
51
|
-
declare type LocaleChangeHandler = (locale: Locale) => void;
|
|
63
|
+
export declare type LocaleChangeHandler = (locale: Locale) => void;
|
|
52
64
|
|
|
53
65
|
export declare type MessageFunction = (vars: Record<string, unknown>, helpers: {
|
|
54
66
|
number: (value: number, options?: Intl.NumberFormatOptions) => string;
|
|
@@ -59,9 +71,6 @@ export declare type Messages = Record<string, MessageValue>;
|
|
|
59
71
|
|
|
60
72
|
export declare type MessageValue = string | PluralMessages | MessageFunction;
|
|
61
73
|
|
|
62
|
-
/**
|
|
63
|
-
* Error thrown when a required variable is missing during interpolation.
|
|
64
|
-
*/
|
|
65
74
|
export declare class MissingVariableError extends Error {
|
|
66
75
|
readonly key: string;
|
|
67
76
|
readonly variable: string;
|
|
@@ -75,7 +84,7 @@ export declare type PluralMessages = Partial<Record<PluralForm, string>> & {
|
|
|
75
84
|
other: string;
|
|
76
85
|
};
|
|
77
86
|
|
|
78
|
-
export declare type
|
|
87
|
+
export declare type TranslateOptions = {
|
|
79
88
|
locale?: Locale;
|
|
80
89
|
fallback?: string;
|
|
81
90
|
escape?: boolean;
|