@vielzeug/i18nit 1.1.1 → 1.1.2

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/README.md CHANGED
@@ -5,8 +5,9 @@ Type-safe, lightweight internationalization (i18n) library for TypeScript applic
5
5
  ## Features
6
6
 
7
7
  - ✅ **Type-Safe** - Full TypeScript support with generic types
8
- - ✅ **Lightweight** - ~3KB gzipped with zero dependencies
8
+ - ✅ **Lightweight** - ~2.3KB gzipped with zero dependencies
9
9
  - ✅ **Universal Pluralization** - 100+ languages via Intl.PluralRules API
10
+ - ✅ **Smart Array Handling** - Auto-join with separators, length access, and safe indexing
10
11
  - ✅ **Path Interpolation** - Support for nested objects and array indices
11
12
  - ✅ **Lazy Loading** - Async locale loading with automatic caching
12
13
  - ✅ **Namespaces** - Organize translations by feature or module
@@ -116,11 +117,81 @@ i18n.t('friends', { friends: [{ name: 'Charlie' }, { name: 'Dave' }] });
116
117
  // Result: "First friend: Charlie"
117
118
  ```
118
119
 
120
+ #### Array Handling
121
+
122
+ Arrays can be intelligently formatted with various separators:
123
+
124
+ ```typescript
125
+ const i18n = createI18n({
126
+ messages: {
127
+ en: {
128
+ shopping: 'Shopping list: {items}',
129
+ guests: 'Invited: {names|and}',
130
+ options: 'Choose: {choices|or}',
131
+ path: 'Path: {folders| / }',
132
+ count: 'You have {items.length} items',
133
+ },
134
+ },
135
+ });
136
+
137
+ // Default comma separator
138
+ i18n.t('shopping', { items: ['Apple', 'Banana', 'Orange'] });
139
+ // "Shopping list: Apple, Banana, Orange"
140
+
141
+ // Natural "and" lists (locale-aware via Intl.ListFormat - supports 100+ languages automatically)
142
+ i18n.t('guests', { names: ['Alice'] });
143
+ // "Invited: Alice"
144
+ i18n.t('guests', { names: ['Alice', 'Bob'] });
145
+ // "Invited: Alice and Bob"
146
+ i18n.t('guests', { names: ['Alice', 'Bob', 'Charlie'] });
147
+ // "Invited: Alice, Bob, and Charlie" (Oxford comma in English)
148
+
149
+ // Natural "or" lists (locale-aware via Intl.ListFormat - supports 100+ languages automatically)
150
+ i18n.t('options', { choices: ['Tea', 'Coffee', 'Juice'] });
151
+ // "Choose: Tea, Coffee, or Juice"
152
+
153
+ // Custom separators
154
+ i18n.t('path', { folders: ['home', 'user', 'documents'] });
155
+ // "Path: home / user / documents"
156
+
157
+ // Array length
158
+ i18n.t('count', { items: ['A', 'B', 'C'] });
159
+ // "You have 3 items"
160
+ ```
161
+
162
+ **Array Features:**
163
+ - `{items}` - Join with comma (`, `)
164
+ - `{items|and}` - Natural "and" list with locale-aware conjunction (uses Intl.ListFormat - supports 100+ languages)
165
+ - `{items|or}` - Natural "or" list with locale-aware conjunction (uses Intl.ListFormat - supports 100+ languages)
166
+ - `{items| - }` - Custom separator (e.g., "A - B - C")
167
+ - `{items.length}` - Array length
168
+ - `{items[0]}` - Safe index access (returns empty if out of bounds)
169
+
170
+ **Locale-Aware List Formatting:**
171
+ The `and` and `or` separators use the built-in **Intl.ListFormat API** which automatically handles:
172
+ - **100+ languages** - Supports all languages available in the browser/runtime
173
+ - **Proper grammar** - Oxford comma, locale-specific punctuation
174
+ - **Right-to-left languages** - Arabic, Hebrew, etc.
175
+ - **Unicode CLDR standards** - International standard for list formatting
176
+ - **No manual configuration** - Zero maintenance required
177
+
178
+ Examples across languages:
179
+ - **English**: "A, B, and C" (with Oxford comma)
180
+ - **Spanish**: "A, B y C" (uses "y")
181
+ - **French**: "A, B et C" (uses "et")
182
+ - **German**: "A, B und C" (uses "und")
183
+ - **Japanese**: "A、B、C" (uses Japanese comma)
184
+ - **Arabic**: Proper RTL formatting with "و"
185
+ - And 90+ more languages automatically!
186
+
119
187
  #### Supported Path Formats
120
188
 
121
189
  - `{name}` - Simple variable
122
190
  - `{user.name}` - Nested object property
123
- - `{items[0]}` - Array index
191
+ - `{items[0]}` - Array index (safe - returns empty if out of bounds)
192
+ - `{items}` - Array join with default separator
193
+ - `{items|and}` - Array join with "and"
194
+ - `{items.length}` - Array length
124
195
  - `{data.items[0].value}` - Mixed notation
125
196
 
126
197
  **Limitations:**
@@ -768,7 +839,7 @@ const i18n = createI18n(config);
768
839
 
769
840
  ## License
770
841
 
771
- MIT © [Helmuth Duarte](https://github.com/helmuthdu)
842
+ MIT © [Helmuth Saatkamp](https://github.com/helmuthdu)
772
843
 
773
844
  ## Links
774
845
 
package/dist/i18nit.cjs CHANGED
@@ -1,2 +1,2 @@
1
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});class f extends Error{key;variable;locale;constructor(t,s,e){super(`Missing variable '${s}' for key '${t}' in locale '${e}'`),this.name="MissingVariableError",this.key=t,this.variable=s,this.locale=e}}const d={"'":"&#39;",'"':"&quot;","&":"&amp;","<":"&lt;",">":"&gt;"},c=n=>n.replace(/[&<>"']/g,t=>d[t]),g=(n,t)=>{if(t in n)return n[t];const s=t.match(/[^.[\]]+/g)||[];let e=n;for(const r of s){if(e==null||typeof e!="object")return;e=e[r]}return e},u=(n,t,s={})=>{const e=s.missingVar??"empty";return n.replace(/\{([\w.[\]]+)}/g,(r,i)=>{const a=g(t,i);if(a==null){if(e==="preserve")return r;if(e==="error")throw new f(s.key??"unknown",i,s.locale??"unknown");return""}if(typeof a=="number"&&s.locale)try{return new Intl.NumberFormat(s.locale).format(a)}catch{}return String(a)})},m=(n,t)=>{const s=Math.abs(Math.floor(t));try{return new Intl.PluralRules(n).select(s)}catch{return s===1?"one":"other"}};class y{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??(s=>s),this.missingVar=t.missingVar??"empty",t.messages)for(const[s,e]of Object.entries(t.messages))this.catalogs.set(s,e);if(t.loaders)for(const[s,e]of Object.entries(t.loaders))this.loaders.set(s,e)}setLocale(t){this.locale!==t&&(this.locale=t,this.notifySubscribers())}getLocale(){return this.locale}add(t,s){const e=this.catalogs.get(t)??{};this.catalogs.set(t,{...e,...s}),this.notifySubscribers()}set(t,s){this.catalogs.set(t,s),this.notifySubscribers()}getMessages(t){return this.catalogs.get(t)}hasLocale(t){return this.catalogs.has(t)}has(t,s){return this.findMessage(t,s)!==void 0}async load(t){if(this.loading.has(t))return this.loading.get(t);if(this.catalogs.has(t))return;const s=this.loaders.get(t);if(!s)return;const e=(async()=>{try{const r=await s();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,e),e}register(t,s){this.loaders.set(t,s)}async hasAsync(t,s){const e=s??this.locale;return!this.catalogs.has(e)&&this.loaders.has(e)&&await this.load(e),this.has(t,e)}t(t,s,e){const r=e??{},i=r.locale??this.locale,a=r.escape??this.escape,o=this.findMessage(t,i);return o===void 0?r.fallback??this.missingKey(t,i):this.formatMessage(o,s??{},i,a,t)}async tl(t,s,e){const r=e?.locale??this.locale;if(!this.catalogs.has(r)&&this.loaders.has(r))try{await this.load(r)}catch{}return this.t(t,s,e)}number(t,s,e){try{return new Intl.NumberFormat(e??this.locale,s).format(t)}catch{return String(t)}}date(t,s,e){const r=typeof t=="number"?new Date(t):t;try{return new Intl.DateTimeFormat(e??this.locale,s).format(r)}catch{return r.toString()}}namespace(t){return{t:(s,e,r)=>this.t(`${t}.${s}`,e,r),tl:(s,e,r)=>this.tl(`${t}.${s}`,e,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,s){const e=this.getLocaleChain(s??this.locale);for(const r of e){const i=this.catalogs.get(r);if(!i)continue;const a=g(i,t);if(a!==void 0)return a}}getLocaleChain(t){const s=[t],e=t.split("-")[0];e!==t&&s.push(e);for(const r of this.fallbacks){s.push(r);const i=r.split("-")[0];i!==r&&s.push(i)}return s}formatMessage(t,s,e,r,i){if(typeof t=="function")try{const a=t(s,{date:(o,l)=>this.date(o,l,e),number:(o,l)=>this.number(o,l,e)});return r?c(a):a}catch{return""}if(typeof t=="object"&&"other"in t){const a=Number(s.count??0),o=t;let l;a===0&&o.zero!==void 0?l="zero":l=m(e,a);const b=o[l]??o.other,h=u(b,s,{key:i,locale:e,missingVar:this.missingVar});return r?c(h):h}if(typeof t=="string"){const a=u(t,s,{key:i,locale:e,missingVar:this.missingVar});return r?c(a):a}return""}}function p(n){return new y(n)}exports.MissingVariableError=f;exports.createI18n=p;
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={"'":"&#39;",'"':"&quot;","&":"&amp;","<":"&lt;",">":"&gt;"},h=i=>i.replace(/[&<>"']/g,t=>b[t]),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 o=e==="conjunction"?"and":"or";return`${s[0]} ${o} ${s[1]}`}const r=e==="conjunction"?"and":"or",n=s[s.length-1];return`${s.slice(0,-1).join(", ")} ${r} ${n}`}},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)s=s[n];else return}else s=s[r]}return s},g=(i,t,e={})=>{const s=e.missingVar??"empty";return i.replace(/\{([\w.[\]]+)(?:\|([^}]+))?\}/g,(r,n,a)=>{let o=!1,l=n;n.endsWith(".length")&&(o=!0,l=n.slice(0,-7));const c=d(t,l);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(o)return String(c.length);if(a!==void 0){if(a==="and"){const u=e.locale||"en";return f(c,u,"conjunction")}if(a==="or"){const u=e.locale||"en";return f(c,u,"disjunction")}return 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)})},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)!==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??this.locale);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
@@ -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 \"'\": '&#39;',\n '\"': '&quot;',\n '&': '&amp;',\n '<': '&lt;',\n '>': '&gt;',\n};\n\nconst escapeHtml = (str: string): string => str.replace(/[&<>\"']/g, (char) => HTML_ENTITIES[char]);\n\n/**\n * Resolve nested properties using dot notation and numeric bracket notation.\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 value = (value as Record<string, unknown>)[part];\n }\n\n return value;\n};\n\n/**\n * Interpolate variables into a template string.\n *\n * Template format: {variableName} or {nested.path} or {array[0]}\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) => {\n const value = resolvePath(vars, key);\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 // 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","resolvePath","obj","path","parts","value","part","interpolate","template","vars","options","missingVar","match","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":["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 \"'\": '&#39;',\n '\"': '&quot;',\n '&': '&amp;',\n '<': '&lt;',\n '>': '&gt;',\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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"}
package/dist/i18nit.js CHANGED
@@ -1,52 +1,91 @@
1
- class b extends Error {
1
+ class d extends Error {
2
2
  key;
3
3
  variable;
4
4
  locale;
5
- constructor(t, s, e) {
6
- super(`Missing variable '${s}' for key '${t}' in locale '${e}'`), this.name = "MissingVariableError", this.key = t, this.variable = s, this.locale = e;
5
+ constructor(t, e, s) {
6
+ super(`Missing variable '${e}' for key '${t}' in locale '${s}'`), this.name = "MissingVariableError", this.key = t, this.variable = e, this.locale = s;
7
7
  }
8
8
  }
9
- const m = {
9
+ const b = {
10
10
  "'": "&#39;",
11
11
  '"': "&quot;",
12
12
  "&": "&amp;",
13
13
  "<": "&lt;",
14
14
  ">": "&gt;"
15
- }, c = (n) => n.replace(/[&<>"']/g, (t) => m[t]), f = (n, t) => {
16
- if (t in n) return n[t];
17
- const s = t.match(/[^.[\]]+/g) || [];
18
- let e = n;
19
- for (const r of s) {
20
- if (e == null || typeof e != "object") return;
21
- e = e[r];
22
- }
23
- return e;
24
- }, u = (n, t, s = {}) => {
25
- const e = s.missingVar ?? "empty";
26
- return n.replace(/\{([\w.[\]]+)}/g, (r, i) => {
27
- const a = f(t, i);
28
- if (a == null) {
29
- if (e === "preserve") return r;
30
- if (e === "error")
31
- throw new b(s.key ?? "unknown", i, s.locale ?? "unknown");
15
+ }, u = (i) => i.replace(/[&<>"']/g, (t) => b[t]), f = (i, t, e) => {
16
+ if (i.length === 0) return "";
17
+ const s = i.map(String);
18
+ try {
19
+ return new Intl.ListFormat(t, { style: "long", type: e }).format(s);
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) => {
30
+ if (t in i) return i[t];
31
+ const e = t.match(/[^.[\]]+/g) || [];
32
+ let s = i;
33
+ for (const r of e) {
34
+ if (s == null || typeof s != "object") return;
35
+ if (Array.isArray(s)) {
36
+ const n = Number(r);
37
+ if (!Number.isNaN(n) && n >= 0 && n < s.length)
38
+ s = s[n];
39
+ else
40
+ return;
41
+ } else
42
+ s = s[r];
43
+ }
44
+ return s;
45
+ }, g = (i, t, e = {}) => {
46
+ const s = e.missingVar ?? "empty";
47
+ return i.replace(/\{([\w.[\]]+)(?:\|([^}]+))?\}/g, (r, n, a) => {
48
+ let o = !1, l = n;
49
+ n.endsWith(".length") && (o = !0, l = n.slice(0, -7));
50
+ const c = m(t, l);
51
+ if (c == null) {
52
+ if (s === "preserve") return r;
53
+ if (s === "error")
54
+ throw new d(e.key ?? "unknown", n, e.locale ?? "unknown");
32
55
  return "";
33
56
  }
34
- if (typeof a == "number" && s.locale)
57
+ if (Array.isArray(c)) {
58
+ if (o)
59
+ return String(c.length);
60
+ if (a !== void 0) {
61
+ if (a === "and") {
62
+ const h = e.locale || "en";
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);
70
+ }
71
+ return c.map(String).join(", ");
72
+ }
73
+ if (typeof c == "number" && e.locale)
35
74
  try {
36
- return new Intl.NumberFormat(s.locale).format(a);
75
+ return new Intl.NumberFormat(e.locale).format(c);
37
76
  } catch {
38
77
  }
39
- return String(a);
78
+ return String(c);
40
79
  });
41
- }, d = (n, t) => {
42
- const s = Math.abs(Math.floor(t));
80
+ }, y = (i, t) => {
81
+ const e = Math.abs(Math.floor(t));
43
82
  try {
44
- return new Intl.PluralRules(n).select(s);
83
+ return new Intl.PluralRules(i).select(e);
45
84
  } catch {
46
- return s === 1 ? "one" : "other";
85
+ return e === 1 ? "one" : "other";
47
86
  }
48
87
  };
49
- class y {
88
+ class p {
50
89
  locale;
51
90
  fallbacks;
52
91
  catalogs = /* @__PURE__ */ new Map();
@@ -57,12 +96,12 @@ class y {
57
96
  missingKey;
58
97
  missingVar;
59
98
  constructor(t = {}) {
60
- 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 ?? ((s) => s), this.missingVar = t.missingVar ?? "empty", t.messages)
61
- for (const [s, e] of Object.entries(t.messages))
62
- this.catalogs.set(s, e);
99
+ 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)
100
+ for (const [e, s] of Object.entries(t.messages))
101
+ this.catalogs.set(e, s);
63
102
  if (t.loaders)
64
- for (const [s, e] of Object.entries(t.loaders))
65
- this.loaders.set(s, e);
103
+ for (const [e, s] of Object.entries(t.loaders))
104
+ this.loaders.set(e, s);
66
105
  }
67
106
  // Locale Management
68
107
  setLocale(t) {
@@ -72,12 +111,12 @@ class y {
72
111
  return this.locale;
73
112
  }
74
113
  // Message Management
75
- add(t, s) {
76
- const e = this.catalogs.get(t) ?? {};
77
- this.catalogs.set(t, { ...e, ...s }), this.notifySubscribers();
114
+ add(t, e) {
115
+ const s = this.catalogs.get(t) ?? {};
116
+ this.catalogs.set(t, { ...s, ...e }), this.notifySubscribers();
78
117
  }
79
- set(t, s) {
80
- this.catalogs.set(t, s), this.notifySubscribers();
118
+ set(t, e) {
119
+ this.catalogs.set(t, e), this.notifySubscribers();
81
120
  }
82
121
  getMessages(t) {
83
122
  return this.catalogs.get(t);
@@ -85,18 +124,18 @@ class y {
85
124
  hasLocale(t) {
86
125
  return this.catalogs.has(t);
87
126
  }
88
- has(t, s) {
89
- return this.findMessage(t, s) !== void 0;
127
+ has(t, e) {
128
+ return this.findMessage(t, e) !== void 0;
90
129
  }
91
130
  // Async Loaders
92
131
  async load(t) {
93
132
  if (this.loading.has(t)) return this.loading.get(t);
94
133
  if (this.catalogs.has(t)) return;
95
- const s = this.loaders.get(t);
96
- if (!s) return;
97
- const e = (async () => {
134
+ const e = this.loaders.get(t);
135
+ if (!e) return;
136
+ const s = (async () => {
98
137
  try {
99
- const r = await s();
138
+ const r = await e();
100
139
  this.add(t, r);
101
140
  } catch (r) {
102
141
  throw console.warn(`[I18n] Failed to load locale '${t}':`, r), r;
@@ -104,41 +143,41 @@ class y {
104
143
  this.loading.delete(t);
105
144
  }
106
145
  })();
107
- return this.loading.set(t, e), e;
146
+ return this.loading.set(t, s), s;
108
147
  }
109
- register(t, s) {
110
- this.loaders.set(t, s);
148
+ register(t, e) {
149
+ this.loaders.set(t, e);
111
150
  }
112
- async hasAsync(t, s) {
113
- const e = s ?? this.locale;
114
- return !this.catalogs.has(e) && this.loaders.has(e) && await this.load(e), this.has(t, e);
151
+ async hasAsync(t, e) {
152
+ const s = e ?? this.locale;
153
+ return !this.catalogs.has(s) && this.loaders.has(s) && await this.load(s), this.has(t, s);
115
154
  }
116
155
  // Translation
117
- t(t, s, e) {
118
- const r = e ?? {}, i = r.locale ?? this.locale, a = r.escape ?? this.escape, o = this.findMessage(t, i);
119
- return o === void 0 ? r.fallback ?? this.missingKey(t, i) : this.formatMessage(o, s ?? {}, i, a, t);
156
+ t(t, e, s) {
157
+ const r = s ?? {}, n = r.locale ?? this.locale, a = r.escape ?? this.escape, o = this.findMessage(t, n);
158
+ return o === void 0 ? r.fallback ?? this.missingKey(t, n) : this.formatMessage(o, e ?? {}, n, a, t);
120
159
  }
121
- async tl(t, s, e) {
122
- const r = e?.locale ?? this.locale;
160
+ async tl(t, e, s) {
161
+ const r = s?.locale ?? this.locale;
123
162
  if (!this.catalogs.has(r) && this.loaders.has(r))
124
163
  try {
125
164
  await this.load(r);
126
165
  } catch {
127
166
  }
128
- return this.t(t, s, e);
167
+ return this.t(t, e, s);
129
168
  }
130
169
  // Formatting Helpers
131
- number(t, s, e) {
170
+ number(t, e, s) {
132
171
  try {
133
- return new Intl.NumberFormat(e ?? this.locale, s).format(t);
172
+ return new Intl.NumberFormat(s ?? this.locale, e).format(t);
134
173
  } catch {
135
174
  return String(t);
136
175
  }
137
176
  }
138
- date(t, s, e) {
177
+ date(t, e, s) {
139
178
  const r = typeof t == "number" ? new Date(t) : t;
140
179
  try {
141
- return new Intl.DateTimeFormat(e ?? this.locale, s).format(r);
180
+ return new Intl.DateTimeFormat(s ?? this.locale, e).format(r);
142
181
  } catch {
143
182
  return r.toString();
144
183
  }
@@ -146,8 +185,8 @@ class y {
146
185
  // Namespaced Translator
147
186
  namespace(t) {
148
187
  return {
149
- t: (s, e, r) => this.t(`${t}.${s}`, e, r),
150
- tl: (s, e, r) => this.tl(`${t}.${s}`, e, r)
188
+ t: (e, s, r) => this.t(`${t}.${e}`, s, r),
189
+ tl: (e, s, r) => this.tl(`${t}.${e}`, s, r)
151
190
  };
152
191
  }
153
192
  // Subscriptions
@@ -167,56 +206,56 @@ class y {
167
206
  }
168
207
  }
169
208
  // Internal Helpers
170
- findMessage(t, s) {
171
- const e = this.getLocaleChain(s ?? this.locale);
172
- for (const r of e) {
173
- const i = this.catalogs.get(r);
174
- if (!i) continue;
175
- const a = f(i, t);
209
+ findMessage(t, e) {
210
+ const s = this.getLocaleChain(e ?? this.locale);
211
+ for (const r of s) {
212
+ const n = this.catalogs.get(r);
213
+ if (!n) continue;
214
+ const a = m(n, t);
176
215
  if (a !== void 0) return a;
177
216
  }
178
217
  }
179
218
  getLocaleChain(t) {
180
- const s = [t], e = t.split("-")[0];
181
- e !== t && s.push(e);
219
+ const e = [t], s = t.split("-")[0];
220
+ s !== t && e.push(s);
182
221
  for (const r of this.fallbacks) {
183
- s.push(r);
184
- const i = r.split("-")[0];
185
- i !== r && s.push(i);
222
+ e.push(r);
223
+ const n = r.split("-")[0];
224
+ n !== r && e.push(n);
186
225
  }
187
- return s;
226
+ return e;
188
227
  }
189
228
  // biome-ignore lint/complexity/noExcessiveCognitiveComplexity: -
190
- formatMessage(t, s, e, r, i) {
229
+ formatMessage(t, e, s, r, n) {
191
230
  if (typeof t == "function")
192
231
  try {
193
- const a = t(s, {
194
- date: (o, l) => this.date(o, l, e),
195
- number: (o, l) => this.number(o, l, e)
232
+ const a = t(e, {
233
+ date: (o, l) => this.date(o, l, s),
234
+ number: (o, l) => this.number(o, l, s)
196
235
  });
197
- return r ? c(a) : a;
236
+ return r ? u(a) : a;
198
237
  } catch {
199
238
  return "";
200
239
  }
201
240
  if (typeof t == "object" && "other" in t) {
202
- const a = Number(s.count ?? 0), o = t;
241
+ const a = Number(e.count ?? 0), o = t;
203
242
  let l;
204
- a === 0 && o.zero !== void 0 ? l = "zero" : l = d(e, a);
205
- const g = o[l] ?? o.other, h = u(g, s, { key: i, locale: e, missingVar: this.missingVar });
206
- return r ? c(h) : h;
243
+ a === 0 && o.zero !== void 0 ? l = "zero" : l = y(s, a);
244
+ const c = o[l] ?? o.other, h = g(c, e, { key: n, locale: s, missingVar: this.missingVar });
245
+ return r ? u(h) : h;
207
246
  }
208
247
  if (typeof t == "string") {
209
- const a = u(t, s, { key: i, locale: e, missingVar: this.missingVar });
210
- return r ? c(a) : a;
248
+ const a = g(t, e, { key: n, locale: s, missingVar: this.missingVar });
249
+ return r ? u(a) : a;
211
250
  }
212
251
  return "";
213
252
  }
214
253
  }
215
- function p(n) {
216
- return new y(n);
254
+ function w(i) {
255
+ return new p(i);
217
256
  }
218
257
  export {
219
- b as MissingVariableError,
220
- p as createI18n
258
+ d as MissingVariableError,
259
+ w as createI18n
221
260
  };
222
261
  //# sourceMappingURL=i18nit.js.map
@@ -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 \"'\": '&#39;',\n '\"': '&quot;',\n '&': '&amp;',\n '<': '&lt;',\n '>': '&gt;',\n};\n\nconst escapeHtml = (str: string): string => str.replace(/[&<>\"']/g, (char) => HTML_ENTITIES[char]);\n\n/**\n * Resolve nested properties using dot notation and numeric bracket notation.\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 value = (value as Record<string, unknown>)[part];\n }\n\n return value;\n};\n\n/**\n * Interpolate variables into a template string.\n *\n * Template format: {variableName} or {nested.path} or {array[0]}\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) => {\n const value = resolvePath(vars, key);\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 // 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","resolvePath","obj","path","parts","value","part","interpolate","template","vars","options","missingVar","match","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":"AAqCO,MAAMA,UAA6B,MAAM;AAAA,EACrC;AAAA,EACA;AAAA,EACA;AAAA,EAET,YAAYC,GAAaC,GAAkBC,GAAgB;AACzD,UAAM,qBAAqBD,CAAQ,cAAcD,CAAG,gBAAgBE,CAAM,GAAG,GAC7E,KAAK,OAAO,wBACZ,KAAK,MAAMF,GACX,KAAK,WAAWC,GAChB,KAAK,SAASC;AAAA,EAChB;AACF;AAIA,MAAMC,IAAwC;AAAA,EAC5C,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AACP,GAEMC,IAAa,CAACC,MAAwBA,EAAI,QAAQ,YAAY,CAACC,MAASH,EAAcG,CAAI,CAAC,GAS3FC,IAAc,CAACC,GAA8BC,MAA0B;AAE3E,MAAIA,KAAQD,EAAK,QAAOA,EAAIC,CAAI;AAIhC,QAAMC,IAAQD,EAAK,MAAM,WAAW,KAAK,CAAA;AACzC,MAAIE,IAAiBH;AAErB,aAAWI,KAAQF,GAAO;AACxB,QAAIC,KAAS,QAAQ,OAAOA,KAAU,SAAU;AAChD,IAAAA,IAASA,EAAkCC,CAAI;AAAA,EACjD;AAEA,SAAOD;AACT,GAaME,IAAc,CAClBC,GACAC,GACAC,IAII,CAAA,MACO;AACX,QAAMC,IAAaD,EAAQ,cAAc;AAGzC,SAAOF,EAAS,QAAQ,mBAAmB,CAACI,GAAOlB,MAAQ;AACzD,UAAMW,IAAQJ,EAAYQ,GAAMf,CAAG;AAEnC,QAAIW,KAAS,MAAM;AACjB,UAAIM,MAAe,WAAY,QAAOC;AACtC,UAAID,MAAe;AACjB,cAAM,IAAIlB,EAAqBiB,EAAQ,OAAO,WAAWhB,GAAKgB,EAAQ,UAAU,SAAS;AAE3F,aAAO;AAAA,IACT;AAGA,QAAI,OAAOL,KAAU,YAAYK,EAAQ;AACvC,UAAI;AACF,eAAO,IAAI,KAAK,aAAaA,EAAQ,MAAM,EAAE,OAAOL,CAAK;AAAA,MAC3D,QAAQ;AAAA,MAER;AAGF,WAAO,OAAOA,CAAK;AAAA,EACrB,CAAC;AACH,GAmBMQ,IAAgB,CAACjB,GAAgBkB,MAAkC;AACvE,QAAMC,IAAI,KAAK,IAAI,KAAK,MAAMD,CAAK,CAAC;AAEpC,MAAI;AAEF,WADoB,IAAI,KAAK,YAAYlB,CAAM,EAC5B,OAAOmB,CAAC;AAAA,EAC7B,QAAQ;AAEN,WAAOA,MAAM,IAAI,QAAQ;AAAA,EAC3B;AACF;AAIA,MAAMC,EAAK;AAAA,EACD;AAAA,EACA;AAAA,EACA,+BAAe,IAAA;AAAA,EACf,8BAAc,IAAA;AAAA,EACd,8BAAc,IAAA;AAAA,EACd,kCAAkB,IAAA;AAAA,EAElB;AAAA,EACA;AAAA,EACA;AAAA,EAER,YAAYC,IAAqB,IAAI;AAQnC,QAPA,KAAK,SAASA,EAAO,UAAU,MAC/B,KAAK,YAAY,MAAM,QAAQA,EAAO,QAAQ,IAAIA,EAAO,WAAWA,EAAO,WAAW,CAACA,EAAO,QAAQ,IAAI,CAAA,GAE1G,KAAK,SAASA,EAAO,UAAU,IAC/B,KAAK,aAAaA,EAAO,eAAe,CAACvB,MAAQA,IACjD,KAAK,aAAauB,EAAO,cAAc,SAEnCA,EAAO;AACT,iBAAW,CAACrB,GAAQsB,CAAQ,KAAK,OAAO,QAAQD,EAAO,QAAQ;AAC7D,aAAK,SAAS,IAAIrB,GAAQsB,CAAQ;AAItC,QAAID,EAAO;AACT,iBAAW,CAACrB,GAAQuB,CAAM,KAAK,OAAO,QAAQF,EAAO,OAAO;AAC1D,aAAK,QAAQ,IAAIrB,GAAQuB,CAAM;AAAA,EAGrC;AAAA;AAAA,EAIA,UAAUvB,GAAsB;AAC9B,IAAI,KAAK,WAAWA,MACpB,KAAK,SAASA,GACd,KAAK,kBAAA;AAAA,EACP;AAAA,EAEA,YAAoB;AAClB,WAAO,KAAK;AAAA,EACd;AAAA;AAAA,EAIA,IAAIA,GAAgBsB,GAA0B;AAC5C,UAAME,IAAW,KAAK,SAAS,IAAIxB,CAAM,KAAK,CAAA;AAC9C,SAAK,SAAS,IAAIA,GAAQ,EAAE,GAAGwB,GAAU,GAAGF,GAAU,GACtD,KAAK,kBAAA;AAAA,EACP;AAAA,EAEA,IAAItB,GAAgBsB,GAA0B;AAC5C,SAAK,SAAS,IAAItB,GAAQsB,CAAQ,GAClC,KAAK,kBAAA;AAAA,EACP;AAAA,EAEA,YAAYtB,GAAsC;AAChD,WAAO,KAAK,SAAS,IAAIA,CAAM;AAAA,EACjC;AAAA,EAEA,UAAUA,GAAyB;AACjC,WAAO,KAAK,SAAS,IAAIA,CAAM;AAAA,EACjC;AAAA,EAEA,IAAIF,GAAaE,GAA0B;AACzC,WAAO,KAAK,YAAYF,GAAKE,CAAM,MAAM;AAAA,EAC3C;AAAA;AAAA,EAIA,MAAM,KAAKA,GAA+B;AACxC,QAAI,KAAK,QAAQ,IAAIA,CAAM,EAAG,QAAO,KAAK,QAAQ,IAAIA,CAAM;AAC5D,QAAI,KAAK,SAAS,IAAIA,CAAM,EAAG;AAE/B,UAAMuB,IAAS,KAAK,QAAQ,IAAIvB,CAAM;AACtC,QAAI,CAACuB,EAAQ;AAEb,UAAME,KAAW,YAAY;AAC3B,UAAI;AACF,cAAMH,IAAW,MAAMC,EAAA;AACvB,aAAK,IAAIvB,GAAQsB,CAAQ;AAAA,MAC3B,SAASI,GAAO;AAEd,sBAAQ,KAAK,iCAAiC1B,CAAM,MAAM0B,CAAK,GAEzDA;AAAA,MACR,UAAA;AACE,aAAK,QAAQ,OAAO1B,CAAM;AAAA,MAC5B;AAAA,IACF,GAAA;AAEA,gBAAK,QAAQ,IAAIA,GAAQyB,CAAO,GACzBA;AAAA,EACT;AAAA,EAEA,SAASzB,GAAgBuB,GAAuC;AAC9D,SAAK,QAAQ,IAAIvB,GAAQuB,CAAM;AAAA,EACjC;AAAA,EAEA,MAAM,SAASzB,GAAaE,GAAmC;AAC7D,UAAM2B,IAAe3B,KAAU,KAAK;AACpC,WAAI,CAAC,KAAK,SAAS,IAAI2B,CAAY,KAAK,KAAK,QAAQ,IAAIA,CAAY,KACnE,MAAM,KAAK,KAAKA,CAAY,GAEvB,KAAK,IAAI7B,GAAK6B,CAAY;AAAA,EACnC;AAAA;AAAA,EAIA,EAAE7B,GAAae,GAAgCC,GAAmC;AAChF,UAAMc,IAAOd,KAAW,CAAA,GAClBa,IAAeC,EAAK,UAAU,KAAK,QACnCC,IAAeD,EAAK,UAAU,KAAK,QAEnCE,IAAU,KAAK,YAAYhC,GAAK6B,CAAY;AAClD,WAAIG,MAAY,SACPF,EAAK,YAAY,KAAK,WAAW9B,GAAK6B,CAAY,IAGpD,KAAK,cAAcG,GAASjB,KAAQ,CAAA,GAAIc,GAAcE,GAAc/B,CAAG;AAAA,EAChF;AAAA,EAEA,MAAM,GAAGA,GAAae,GAAgCC,GAA4C;AAChG,UAAMa,IAAeb,GAAS,UAAU,KAAK;AAE7C,QAAI,CAAC,KAAK,SAAS,IAAIa,CAAY,KAAK,KAAK,QAAQ,IAAIA,CAAY;AACnE,UAAI;AACF,cAAM,KAAK,KAAKA,CAAY;AAAA,MAC9B,QAAQ;AAAA,MAGR;AAGF,WAAO,KAAK,EAAE7B,GAAKe,GAAMC,CAAO;AAAA,EAClC;AAAA;AAAA,EAIA,OAAOL,GAAeK,GAAoCd,GAAyB;AACjF,QAAI;AACF,aAAO,IAAI,KAAK,aAAaA,KAAU,KAAK,QAAQc,CAAO,EAAE,OAAOL,CAAK;AAAA,IAC3E,QAAQ;AACN,aAAO,OAAOA,CAAK;AAAA,IACrB;AAAA,EACF;AAAA,EAEA,KAAKA,GAAsBK,GAAsCd,GAAyB;AACxF,UAAM+B,IAAO,OAAOtB,KAAU,WAAW,IAAI,KAAKA,CAAK,IAAIA;AAC3D,QAAI;AACF,aAAO,IAAI,KAAK,eAAeT,KAAU,KAAK,QAAQc,CAAO,EAAE,OAAOiB,CAAI;AAAA,IAC5E,QAAQ;AACN,aAAOA,EAAK,SAAA;AAAA,IACd;AAAA,EACF;AAAA;AAAA,EAIA,UAAUC,GAAY;AACpB,WAAO;AAAA,MACL,GAAG,CAAClC,GAAae,GAAgCC,MAC/C,KAAK,EAAE,GAAGkB,CAAE,IAAIlC,CAAG,IAAIe,GAAMC,CAAO;AAAA,MACtC,IAAI,CAAChB,GAAae,GAAgCC,MAChD,KAAK,GAAG,GAAGkB,CAAE,IAAIlC,CAAG,IAAIe,GAAMC,CAAO;AAAA,IAAA;AAAA,EAE3C;AAAA;AAAA,EAIA,UAAUmB,GAA0C;AAClD,SAAK,YAAY,IAAIA,CAAO;AAC5B,QAAI;AACF,MAAAA,EAAQ,KAAK,MAAM;AAAA,IACrB,QAAQ;AAAA,IAER;AACA,WAAO,MAAM,KAAK,YAAY,OAAOA,CAAO;AAAA,EAC9C;AAAA,EAEQ,oBAA0B;AAChC,eAAWA,KAAW,KAAK;AACzB,UAAI;AACF,QAAAA,EAAQ,KAAK,MAAM;AAAA,MACrB,QAAQ;AAAA,MAER;AAAA,EAEJ;AAAA;AAAA,EAIQ,YAAYnC,GAAaE,GAA2C;AAC1E,UAAMkC,IAAU,KAAK,eAAelC,KAAU,KAAK,MAAM;AAEzD,eAAWmC,KAAOD,GAAS;AACzB,YAAMZ,IAAW,KAAK,SAAS,IAAIa,CAAG;AACtC,UAAI,CAACb,EAAU;AAEf,YAAMb,IAAQJ,EAAYiB,GAAUxB,CAAG;AACvC,UAAIW,MAAU,OAAW,QAAOA;AAAA,IAClC;AAAA,EAGF;AAAA,EAEQ,eAAeT,GAA0B;AAC/C,UAAMoC,IAAkB,CAACpC,CAAM,GAGzBqC,IAAOrC,EAAO,MAAM,GAAG,EAAE,CAAC;AAChC,IAAIqC,MAASrC,KAAQoC,EAAM,KAAKC,CAAI;AAGpC,eAAWC,KAAY,KAAK,WAAW;AACrC,MAAAF,EAAM,KAAKE,CAAQ;AACnB,YAAMC,IAAeD,EAAS,MAAM,GAAG,EAAE,CAAC;AAC1C,MAAIC,MAAiBD,KAAUF,EAAM,KAAKG,CAAY;AAAA,IACxD;AAEA,WAAOH;AAAA,EACT;AAAA;AAAA,EAGQ,cACNN,GACAjB,GACAb,GACA6B,GACA/B,GACQ;AAER,QAAI,OAAOgC,KAAY;AACrB,UAAI;AACF,cAAMU,IAASV,EAAQjB,GAAM;AAAA,UAC3B,MAAM,CAAC4B,GAAGb,MAAS,KAAK,KAAKa,GAAGb,GAAM5B,CAAM;AAAA,UAC5C,QAAQ,CAAC0C,GAAGd,MAAS,KAAK,OAAOc,GAAGd,GAAM5B,CAAM;AAAA,QAAA,CACjD;AACD,eAAO6B,IAAe3B,EAAWsC,CAAM,IAAIA;AAAA,MAC7C,QAAQ;AACN,eAAO;AAAA,MACT;AAIF,QAAI,OAAOV,KAAY,YAAY,WAAWA,GAAS;AACrD,YAAMZ,IAAQ,OAAOL,EAAK,SAAS,CAAC,GAC9B8B,IAAYb;AAGlB,UAAIc;AACJ,MAAI1B,MAAU,KAAKyB,EAAU,SAAS,SACpCC,IAAO,SAEPA,IAAO3B,EAAcjB,GAAQkB,CAAK;AAGpC,YAAMN,IAAW+B,EAAUC,CAAI,KAAKD,EAAU,OACxCH,IAAS7B,EAAYC,GAAUC,GAAM,EAAE,KAAAf,GAAK,QAAAE,GAAQ,YAAY,KAAK,YAAY;AACvF,aAAO6B,IAAe3B,EAAWsC,CAAM,IAAIA;AAAA,IAC7C;AAGA,QAAI,OAAOV,KAAY,UAAU;AAC/B,YAAMU,IAAS7B,EAAYmB,GAASjB,GAAM,EAAE,KAAAf,GAAK,QAAAE,GAAQ,YAAY,KAAK,YAAY;AACtF,aAAO6B,IAAe3B,EAAWsC,CAAM,IAAIA;AAAA,IAC7C;AAEA,WAAO;AAAA,EACT;AACF;AAEO,SAASK,EAAWxB,GAA2B;AACpD,SAAO,IAAID,EAAKC,CAAM;AACxB;"}
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 \"'\": '&#39;',\n '\"': '&quot;',\n '&': '&amp;',\n '<': '&lt;',\n '>': '&gt;',\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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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vielzeug/i18nit",
3
- "version": "1.1.1",
3
+ "version": "1.1.2",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "dist"