@vielzeug/i18nit 1.2.0 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,2 @@
1
+ function e(e,t){if(Object.hasOwn(e,t))return e[t];let n=t.match(/[^.[\]]+/gu)??[],r=e;for(let e of n){if(typeof r!=`object`||!r||!Object.hasOwn(r,e))return;r=r[e]}return r}var t=new Set([`zero`,`one`,`two`,`few`,`many`,`other`]);function n(e){if(typeof e==`string`)return!0;if(typeof e!=`object`||!e||Array.isArray(e))return!1;let n=e;if(!(`other`in n))return!1;let r=Object.keys(n);return r.length>t.size?!1:r.every(e=>t.has(e))&&Object.values(n).every(e=>typeof e==`string`)}function r(e,t){let i={...e};for(let[e,a]of Object.entries(t)){let t=i[e];!n(a)&&!n(t)&&typeof t==`object`&&t?i[e]=r(t,a):i[e]=typeof a==`object`&&a?{...a}:a}return i}var i=class extends Map{#e;constructor(e){super(),this.#e=e}set(e,t){return!this.has(e)&&this.size>=this.#e&&this.delete(this.keys().next().value),super.set(e,t)}};exports.BoundedMap=i,exports.deepMerge=r,exports.isMessageValue=n,exports.resolvePath=e;
2
+ //# sourceMappingURL=helpers.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"helpers.cjs","names":["#cap"],"sources":["../src/helpers.ts"],"sourcesContent":["import type { MessageValue, Messages } from './types';\n\n/* -------------------- Path Resolution -------------------- */\n\n/**\n * Resolves nested properties using dot notation and bracket notation.\n * Supports: 'user.name', 'items[0]', 'user.items[0].name'\n */\nexport function resolvePath(obj: Record<string, unknown>, path: string): unknown {\n // Try direct access first (handles keys with literal dots)\n if (Object.hasOwn(obj, path)) return obj[path];\n\n const parts = path.match(/[^.[\\]]+/gu) ?? [];\n let value: unknown = obj;\n\n for (const part of parts) {\n if (value == null || typeof value !== 'object') return undefined;\n\n if (!Object.hasOwn(value as object, part)) return undefined;\n\n value = (value as Record<string, unknown>)[part];\n }\n\n return value;\n}\n\n/* -------------------- Message Value Guard -------------------- */\n\nexport const PLURAL_FORMS = new Set<string>(['zero', 'one', 'two', 'few', 'many', 'other']);\n\nexport function isMessageValue(value: unknown): value is MessageValue {\n if (typeof value === 'string') return true;\n\n if (typeof value !== 'object' || value === null || Array.isArray(value)) return false;\n\n const obj = value as Record<string, unknown>;\n\n if (!('other' in obj)) return false;\n\n const keys = Object.keys(obj);\n\n if (keys.length > PLURAL_FORMS.size) return false;\n\n return keys.every((k) => PLURAL_FORMS.has(k)) && Object.values(obj).every((v) => typeof v === 'string');\n}\n\n/* -------------------- Deep Merge -------------------- */\n\nexport function deepMerge(target: Messages, source: Messages): Messages {\n const result = { ...target };\n\n for (const [key, val] of Object.entries(source)) {\n const existing = result[key];\n\n if (!isMessageValue(val) && !isMessageValue(existing) && typeof existing === 'object' && existing !== null) {\n result[key] = deepMerge(existing as Messages, val as Messages);\n } else {\n // Clone PluralMessages objects to prevent external mutations from corrupting the catalog.\n result[key] = typeof val === 'object' && val !== null ? ({ ...(val as object) } as MessageValue) : val;\n }\n }\n\n return result;\n}\n\n/* -------------------- BoundedMap -------------------- */\n\n/**\n * Size-bounded Map that evicts the oldest entry (insertion order) when the cap is reached.\n * Used by I18n's chain cache to prevent unbounded growth in long-lived SSR singletons when\n * locale tags are derived from arbitrary user input (e.g. Accept-Language headers).\n */\nexport class BoundedMap<K, V> extends Map<K, V> {\n readonly #cap: number;\n\n constructor(cap: number) {\n super();\n this.#cap = cap;\n }\n\n override set(key: K, value: V): this {\n if (!this.has(key) && this.size >= this.#cap) {\n this.delete(this.keys().next().value as K);\n }\n\n return super.set(key, value);\n }\n}\n"],"mappings":"AAQA,SAAgB,EAAY,EAA8B,EAAuB,CAE/E,GAAI,OAAO,OAAO,EAAK,EAAK,CAAE,OAAO,EAAI,GAEzC,IAAM,EAAQ,EAAK,MAAM,aAAa,EAAI,EAAE,CACxC,EAAiB,EAErB,IAAK,IAAM,KAAQ,EAAO,CAGxB,GAFqB,OAAO,GAAU,WAAlC,GAEA,CAAC,OAAO,OAAO,EAAiB,EAAK,CAAE,OAE3C,EAAS,EAAkC,GAG7C,OAAO,EAKT,IAAa,EAAe,IAAI,IAAY,CAAC,OAAQ,MAAO,MAAO,MAAO,OAAQ,QAAQ,CAAC,CAE3F,SAAgB,EAAe,EAAuC,CACpE,GAAI,OAAO,GAAU,SAAU,MAAO,GAEtC,GAAI,OAAO,GAAU,WAAY,GAAkB,MAAM,QAAQ,EAAM,CAAE,MAAO,GAEhF,IAAM,EAAM,EAEZ,GAAI,EAAE,UAAW,GAAM,MAAO,GAE9B,IAAM,EAAO,OAAO,KAAK,EAAI,CAI7B,OAFI,EAAK,OAAS,EAAa,KAAa,GAErC,EAAK,MAAO,GAAM,EAAa,IAAI,EAAE,CAAC,EAAI,OAAO,OAAO,EAAI,CAAC,MAAO,GAAM,OAAO,GAAM,SAAS,CAKzG,SAAgB,EAAU,EAAkB,EAA4B,CACtE,IAAM,EAAS,CAAE,GAAG,EAAQ,CAE5B,IAAK,GAAM,CAAC,EAAK,KAAQ,OAAO,QAAQ,EAAO,CAAE,CAC/C,IAAM,EAAW,EAAO,GAEpB,CAAC,EAAe,EAAI,EAAI,CAAC,EAAe,EAAS,EAAI,OAAO,GAAa,UAAY,EACvF,EAAO,GAAO,EAAU,EAAsB,EAAgB,CAG9D,EAAO,GAAO,OAAO,GAAQ,UAAY,EAAgB,CAAE,GAAI,EAAgB,CAAoB,EAIvG,OAAO,EAUT,IAAa,EAAb,cAAsC,GAAU,CAC9C,GAEA,YAAY,EAAa,CACvB,OAAO,CACP,MAAA,EAAY,EAGd,IAAa,EAAQ,EAAgB,CAKnC,MAJI,CAAC,KAAK,IAAI,EAAI,EAAI,KAAK,MAAQ,MAAA,GACjC,KAAK,OAAO,KAAK,MAAM,CAAC,MAAM,CAAC,MAAW,CAGrC,MAAM,IAAI,EAAK,EAAM"}
@@ -0,0 +1,20 @@
1
+ import type { MessageValue, Messages } from './types';
2
+ /**
3
+ * Resolves nested properties using dot notation and bracket notation.
4
+ * Supports: 'user.name', 'items[0]', 'user.items[0].name'
5
+ */
6
+ export declare function resolvePath(obj: Record<string, unknown>, path: string): unknown;
7
+ export declare const PLURAL_FORMS: Set<string>;
8
+ export declare function isMessageValue(value: unknown): value is MessageValue;
9
+ export declare function deepMerge(target: Messages, source: Messages): Messages;
10
+ /**
11
+ * Size-bounded Map that evicts the oldest entry (insertion order) when the cap is reached.
12
+ * Used by I18n's chain cache to prevent unbounded growth in long-lived SSR singletons when
13
+ * locale tags are derived from arbitrary user input (e.g. Accept-Language headers).
14
+ */
15
+ export declare class BoundedMap<K, V> extends Map<K, V> {
16
+ #private;
17
+ constructor(cap: number);
18
+ set(key: K, value: V): this;
19
+ }
20
+ //# sourceMappingURL=helpers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"helpers.d.ts","sourceRoot":"","sources":["../src/helpers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAItD;;;GAGG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAgB/E;AAID,eAAO,MAAM,YAAY,aAAkE,CAAC;AAE5F,wBAAgB,cAAc,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,YAAY,CAcpE;AAID,wBAAgB,SAAS,CAAC,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,QAAQ,GAAG,QAAQ,CAetE;AAID;;;;GAIG;AACH,qBAAa,UAAU,CAAC,CAAC,EAAE,CAAC,CAAE,SAAQ,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC;;gBAGjC,GAAG,EAAE,MAAM;IAKd,GAAG,CAAC,GAAG,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,GAAG,IAAI;CAOrC"}
@@ -0,0 +1,47 @@
1
+ //#region src/helpers.ts
2
+ function e(e, t) {
3
+ if (Object.hasOwn(e, t)) return e[t];
4
+ let n = t.match(/[^.[\]]+/gu) ?? [], r = e;
5
+ for (let e of n) {
6
+ if (typeof r != "object" || !r || !Object.hasOwn(r, e)) return;
7
+ r = r[e];
8
+ }
9
+ return r;
10
+ }
11
+ var t = new Set([
12
+ "zero",
13
+ "one",
14
+ "two",
15
+ "few",
16
+ "many",
17
+ "other"
18
+ ]);
19
+ function n(e) {
20
+ if (typeof e == "string") return !0;
21
+ if (typeof e != "object" || !e || Array.isArray(e)) return !1;
22
+ let n = e;
23
+ if (!("other" in n)) return !1;
24
+ let r = Object.keys(n);
25
+ return r.length > t.size ? !1 : r.every((e) => t.has(e)) && Object.values(n).every((e) => typeof e == "string");
26
+ }
27
+ function r(e, t) {
28
+ let i = { ...e };
29
+ for (let [e, a] of Object.entries(t)) {
30
+ let t = i[e];
31
+ !n(a) && !n(t) && typeof t == "object" && t ? i[e] = r(t, a) : i[e] = typeof a == "object" && a ? { ...a } : a;
32
+ }
33
+ return i;
34
+ }
35
+ var i = class extends Map {
36
+ #e;
37
+ constructor(e) {
38
+ super(), this.#e = e;
39
+ }
40
+ set(e, t) {
41
+ return !this.has(e) && this.size >= this.#e && this.delete(this.keys().next().value), super.set(e, t);
42
+ }
43
+ };
44
+ //#endregion
45
+ export { i as BoundedMap, r as deepMerge, n as isMessageValue, e as resolvePath };
46
+
47
+ //# sourceMappingURL=helpers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"helpers.js","names":["#cap"],"sources":["../src/helpers.ts"],"sourcesContent":["import type { MessageValue, Messages } from './types';\n\n/* -------------------- Path Resolution -------------------- */\n\n/**\n * Resolves nested properties using dot notation and bracket notation.\n * Supports: 'user.name', 'items[0]', 'user.items[0].name'\n */\nexport function resolvePath(obj: Record<string, unknown>, path: string): unknown {\n // Try direct access first (handles keys with literal dots)\n if (Object.hasOwn(obj, path)) return obj[path];\n\n const parts = path.match(/[^.[\\]]+/gu) ?? [];\n let value: unknown = obj;\n\n for (const part of parts) {\n if (value == null || typeof value !== 'object') return undefined;\n\n if (!Object.hasOwn(value as object, part)) return undefined;\n\n value = (value as Record<string, unknown>)[part];\n }\n\n return value;\n}\n\n/* -------------------- Message Value Guard -------------------- */\n\nexport const PLURAL_FORMS = new Set<string>(['zero', 'one', 'two', 'few', 'many', 'other']);\n\nexport function isMessageValue(value: unknown): value is MessageValue {\n if (typeof value === 'string') return true;\n\n if (typeof value !== 'object' || value === null || Array.isArray(value)) return false;\n\n const obj = value as Record<string, unknown>;\n\n if (!('other' in obj)) return false;\n\n const keys = Object.keys(obj);\n\n if (keys.length > PLURAL_FORMS.size) return false;\n\n return keys.every((k) => PLURAL_FORMS.has(k)) && Object.values(obj).every((v) => typeof v === 'string');\n}\n\n/* -------------------- Deep Merge -------------------- */\n\nexport function deepMerge(target: Messages, source: Messages): Messages {\n const result = { ...target };\n\n for (const [key, val] of Object.entries(source)) {\n const existing = result[key];\n\n if (!isMessageValue(val) && !isMessageValue(existing) && typeof existing === 'object' && existing !== null) {\n result[key] = deepMerge(existing as Messages, val as Messages);\n } else {\n // Clone PluralMessages objects to prevent external mutations from corrupting the catalog.\n result[key] = typeof val === 'object' && val !== null ? ({ ...(val as object) } as MessageValue) : val;\n }\n }\n\n return result;\n}\n\n/* -------------------- BoundedMap -------------------- */\n\n/**\n * Size-bounded Map that evicts the oldest entry (insertion order) when the cap is reached.\n * Used by I18n's chain cache to prevent unbounded growth in long-lived SSR singletons when\n * locale tags are derived from arbitrary user input (e.g. Accept-Language headers).\n */\nexport class BoundedMap<K, V> extends Map<K, V> {\n readonly #cap: number;\n\n constructor(cap: number) {\n super();\n this.#cap = cap;\n }\n\n override set(key: K, value: V): this {\n if (!this.has(key) && this.size >= this.#cap) {\n this.delete(this.keys().next().value as K);\n }\n\n return super.set(key, value);\n }\n}\n"],"mappings":";AAQA,SAAgB,EAAY,GAA8B,GAAuB;AAE/E,KAAI,OAAO,OAAO,GAAK,EAAK,CAAE,QAAO,EAAI;CAEzC,IAAM,IAAQ,EAAK,MAAM,aAAa,IAAI,EAAE,EACxC,IAAiB;AAErB,MAAK,IAAM,KAAQ,GAAO;AAGxB,MAFqB,OAAO,KAAU,aAAlC,KAEA,CAAC,OAAO,OAAO,GAAiB,EAAK,CAAE;AAE3C,MAAS,EAAkC;;AAG7C,QAAO;;AAKT,IAAa,IAAe,IAAI,IAAY;CAAC;CAAQ;CAAO;CAAO;CAAO;CAAQ;CAAQ,CAAC;AAE3F,SAAgB,EAAe,GAAuC;AACpE,KAAI,OAAO,KAAU,SAAU,QAAO;AAEtC,KAAI,OAAO,KAAU,aAAY,KAAkB,MAAM,QAAQ,EAAM,CAAE,QAAO;CAEhF,IAAM,IAAM;AAEZ,KAAI,EAAE,WAAW,GAAM,QAAO;CAE9B,IAAM,IAAO,OAAO,KAAK,EAAI;AAI7B,QAFI,EAAK,SAAS,EAAa,OAAa,KAErC,EAAK,OAAO,MAAM,EAAa,IAAI,EAAE,CAAC,IAAI,OAAO,OAAO,EAAI,CAAC,OAAO,MAAM,OAAO,KAAM,SAAS;;AAKzG,SAAgB,EAAU,GAAkB,GAA4B;CACtE,IAAM,IAAS,EAAE,GAAG,GAAQ;AAE5B,MAAK,IAAM,CAAC,GAAK,MAAQ,OAAO,QAAQ,EAAO,EAAE;EAC/C,IAAM,IAAW,EAAO;AAExB,EAAI,CAAC,EAAe,EAAI,IAAI,CAAC,EAAe,EAAS,IAAI,OAAO,KAAa,YAAY,IACvF,EAAO,KAAO,EAAU,GAAsB,EAAgB,GAG9D,EAAO,KAAO,OAAO,KAAQ,YAAY,IAAgB,EAAE,GAAI,GAAgB,GAAoB;;AAIvG,QAAO;;AAUT,IAAa,IAAb,cAAsC,IAAU;CAC9C;CAEA,YAAY,GAAa;AAEvB,EADA,OAAO,EACP,MAAA,IAAY;;CAGd,IAAa,GAAQ,GAAgB;AAKnC,SAJI,CAAC,KAAK,IAAI,EAAI,IAAI,KAAK,QAAQ,MAAA,KACjC,KAAK,OAAO,KAAK,MAAM,CAAC,MAAM,CAAC,MAAW,EAGrC,MAAM,IAAI,GAAK,EAAM"}
package/dist/i18n.cjs ADDED
@@ -0,0 +1,2 @@
1
+ const e=require(`./helpers.cjs`),t=require(`./intl.cjs`),n=require(`./interpolate.cjs`);function r(r={}){let i=r.locale??`en`,a=r.switchMode??`strict`,o=Array.isArray(r.fallback)?r.fallback:r.fallback?[r.fallback]:[],s=new Map,c=new Map,l=new Map,u=new Set,d=new e.BoundedMap(128),f=t.makeIntlCaches(),p=null,m=null,h=!1,g=0,_=null,v=r.onMissing,y=r.onDiagnostic;if(r.messages)for(let[e,t]of Object.entries(r.messages))s.set(e,structuredClone(t));if(r.loaders)for(let[e,t]of Object.entries(r.loaders))c.set(e,t);function b(e){y?y({error:e,kind:`subscriber-error`}):console.error(`[i18nit] Subscriber threw:`,e)}function x(e,t){y?y({error:e,kind:`loader-error`,locale:t}):console.warn(`[i18nit] Loader error:`,e)}function S(e){if(g>0){_!==`locale-change`&&(_=e);return}let t={locale:i,reason:e};for(let e of u)try{e(t)}catch(e){b(e)}}function C(e){let t=d.get(e);if(t)return t;let n=new Set,r=e=>{n.add(e);let t=e.split(`-`);for(let e=t.length-1;e>0;e--)n.add(t.slice(0,e).join(`-`))};r(e);for(let e of o)r(e);let i=[...n];return d.set(e,i),i}function w(t,n){let r=s.get(n);if(!r)return!1;let i=e.resolvePath(r,t);return i!==void 0&&e.isMessageValue(i)}function T(t,n){for(let r of C(n)){let n=s.get(r);if(!n)continue;let i=e.resolvePath(n,t);if(i!==void 0&&e.isMessageValue(i))return i}}function E(e,r,i){let a=T(e,i);if(a===void 0)return v?.(e,i)??e;if(typeof a==`string`)return n.interpolate(a,r??{},i,f);let o=r??{},s=Number(o.count??0);return n.interpolate(a[s===0&&a.zero!==void 0?`zero`:t.getPluralForm(f,i,s)]??a.other,o,i,f)}function D(e,t){if(l.has(e))return l.get(e);if(s.has(e))return Promise.resolve();let n=c.get(e);if(!n)return t===`strict`?Promise.reject(Error(`[i18nit] Missing loader for locale "${e}".`)):Promise.resolve();let r=(async()=>{try{let t=await n(e);h||A.replace(e,t)}catch(t){throw x(t,e),t}finally{l.delete(e)}})();return l.set(e,r),r}function O(e,n){let r=()=>e??i,a=e=>n?`${n}.${e}`:e;return{currency(e,n,i){return t.formatNumber(f,e,{...i,currency:n,style:`currency`},r())},date(e,n){return t.formatDate(f,e,n,r())},has(e){return T(a(e),r())!==void 0},hasOwn(e){return w(a(e),r())},list(e,n=`and`){return t.formatList(f,e,r(),n)},get locale(){return r()},number(e,n){return t.formatNumber(f,e,n,r())},relative(e,n,i){return t.formatRelative(f,e,n,i,r())},scope(t){return O(e,n?`${n}.${String(t)}`:String(t))},t:(e,t)=>E(a(e),t,r()),withLocale(e){return O(e,n)}}}let k=O(null),A=Object.create(k);return A.add=(t,n)=>{let r=s.get(t)??{};s.set(t,e.deepMerge(r,n)),p=null,C(i).includes(t)&&S(`catalog-update`)},A.batch=e=>{g++;try{e()}finally{if(g--,g===0&&_!==null){let e=_;_=null,S(e)}}},A.dispose=()=>{h=!0,u.clear(),s.clear(),c.clear(),l.clear(),d.clear(),p=null,m=null},A.ensureLocale=async(e,t=a)=>{await D(e,t)},A.hasLocale=e=>s.has(e),A.isReady=e=>s.has(e),Object.defineProperties(A,{loadableLocales:{get(){return m??=[...c.keys()],m}},locales:{get(){return p??=[...s.keys()],p}}}),A.registerLoader=(e,t)=>{c.set(e,t),m=null},A.reload=async e=>{if(!c.has(e))throw Error(`[i18nit] Cannot reload locale "${e}" without a registered loader.`);s.delete(e),p=null,await D(e,`strict`)},A.replace=(e,t)=>{s.set(e,structuredClone(t)),p=null,C(i).includes(e)&&S(`catalog-update`)},A.subscribe=(e,t)=>{if(u.add(e),t)try{e({locale:i,reason:`locale-change`})}catch(e){b(e)}return()=>u.delete(e)},A.switchLocale=async(e,t=a)=>{e!==i&&(await D(e,t),i=e,S(`locale-change`))},A[Symbol.asyncDispose]=async()=>{await Promise.allSettled([...l.values()]),A.dispose()},A[Symbol.dispose]=()=>{A.dispose()},A}exports.createI18n=r;
2
+ //# sourceMappingURL=i18n.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"i18n.cjs","names":[],"sources":["../src/i18n.ts"],"sourcesContent":["import type {\n BoundI18n,\n I18n,\n I18nOptions,\n Loader,\n Locale,\n LocaleChangeEvent,\n LocaleChangeReason,\n Messages,\n MessageValue,\n NamespaceKeys,\n SwitchMode,\n Unsubscribe,\n Vars,\n} from './types';\n\nimport { BoundedMap, deepMerge, isMessageValue, resolvePath } from './helpers';\nimport { interpolate } from './interpolate';\nimport {\n formatDate,\n formatList,\n formatNumber,\n formatRelative,\n getPluralForm,\n type IntlCaches,\n makeIntlCaches,\n} from './intl';\n\nexport function createI18n<T extends Messages = Messages>(config: I18nOptions<T> = {}): I18n<T> {\n let locale = config.locale ?? 'en';\n const defaultSwitchMode: SwitchMode = config.switchMode ?? 'strict';\n const fallbacks = Array.isArray(config.fallback) ? config.fallback : config.fallback ? [config.fallback] : [];\n const catalogs = new Map<Locale, Messages>();\n const loaders = new Map<Locale, Loader>();\n const loading = new Map<Locale, Promise<void>>();\n const subscribers = new Set<(event: LocaleChangeEvent) => void>();\n const chainCache = new BoundedMap<Locale, Locale[]>(128);\n const caches: IntlCaches = makeIntlCaches();\n\n let localesCache: Locale[] | null = null;\n let loadersCache: Locale[] | null = null;\n let disposed = false;\n let batchDepth = 0;\n let pendingNotify: LocaleChangeReason | null = null;\n\n const onMissing = config.onMissing;\n const onDiagnostic = config.onDiagnostic;\n\n if (config.messages) {\n for (const [loc, messages] of Object.entries(config.messages)) {\n catalogs.set(loc, structuredClone(messages) as Messages);\n }\n }\n\n if (config.loaders) {\n for (const [loc, loader] of Object.entries(config.loaders)) {\n loaders.set(loc, loader);\n }\n }\n\n function diagnoseSubscriber(error: unknown): void {\n if (onDiagnostic) {\n onDiagnostic({ error, kind: 'subscriber-error' });\n } else {\n console.error('[i18nit] Subscriber threw:', error);\n }\n }\n\n function diagnoseLoader(error: unknown, loc: Locale): void {\n if (onDiagnostic) {\n onDiagnostic({ error, kind: 'loader-error', locale: loc });\n } else {\n console.warn('[i18nit] Loader error:', error);\n }\n }\n\n function notify(reason: LocaleChangeReason): void {\n if (batchDepth > 0) {\n if (pendingNotify !== 'locale-change') pendingNotify = reason;\n\n return;\n }\n\n const event: LocaleChangeEvent = { locale, reason };\n\n for (const listener of subscribers) {\n try {\n listener(event);\n } catch (error) {\n diagnoseSubscriber(error);\n }\n }\n }\n\n function getLocaleChain(loc: Locale): Locale[] {\n const cached = chainCache.get(loc);\n\n if (cached) return cached;\n\n const seen = new Set<Locale>();\n\n const push = (value: Locale) => {\n seen.add(value);\n\n const parts = value.split('-');\n\n for (let i = parts.length - 1; i > 0; i--) {\n seen.add(parts.slice(0, i).join('-'));\n }\n };\n\n push(loc);\n for (const fallback of fallbacks) push(fallback);\n\n const chain = [...seen];\n\n chainCache.set(loc, chain);\n\n return chain;\n }\n\n function checkOwn(key: string, loc: Locale): boolean {\n const catalog = catalogs.get(loc);\n\n if (!catalog) return false;\n\n const value = resolvePath(catalog, key);\n\n return value !== undefined && isMessageValue(value);\n }\n\n function findMessage(key: string, loc: Locale): MessageValue | undefined {\n for (const localeInChain of getLocaleChain(loc)) {\n const messages = catalogs.get(localeInChain);\n\n if (!messages) continue;\n\n const value = resolvePath(messages, key);\n\n if (value !== undefined && isMessageValue(value)) return value;\n }\n\n return undefined;\n }\n\n function translate(key: string, vars: Vars | undefined, loc: Locale): string {\n const message = findMessage(key, loc);\n\n if (message === undefined) return onMissing?.(key, loc) ?? key;\n\n if (typeof message === 'string') {\n return interpolate(message, vars ?? {}, loc, caches);\n }\n\n const context = vars ?? {};\n\n if (import.meta.env?.DEV && context.count === undefined) {\n console.warn(`[i18nit] Key \"${key}\" is a plural message but vars.count is missing. Defaulting to 0.`);\n }\n\n const count = Number(context.count ?? 0);\n const form = count === 0 && message.zero !== undefined ? 'zero' : getPluralForm(caches, loc, count);\n\n return interpolate(message[form] ?? message.other, context, loc, caches);\n }\n\n function loadOne(loc: Locale, mode: SwitchMode): Promise<void> {\n if (loading.has(loc)) return loading.get(loc)!;\n\n if (catalogs.has(loc)) return Promise.resolve();\n\n const loader = loaders.get(loc);\n\n if (!loader) {\n if (mode === 'strict') {\n return Promise.reject(new Error(`[i18nit] Missing loader for locale \"${loc}\".`));\n }\n\n return Promise.resolve();\n }\n\n const promise = (async () => {\n try {\n const messages = await loader(loc);\n\n if (!disposed) api.replace(loc, messages);\n } catch (error) {\n diagnoseLoader(error, loc);\n throw error;\n } finally {\n loading.delete(loc);\n }\n })();\n\n loading.set(loc, promise);\n\n return promise;\n }\n\n function createView<U extends Messages = Messages>(fixedLocale: Locale | null, prefix?: string): BoundI18n<U> {\n const activeLocale = (): Locale => fixedLocale ?? locale;\n const keyWithPrefix = (key: string): string => (prefix ? `${prefix}.${key}` : key);\n const t: BoundI18n<U>['t'] = (key: NamespaceKeys<U>, vars?: Record<string, unknown>) =>\n translate(keyWithPrefix(key as string), vars, activeLocale());\n\n const view = {\n currency(\n value: number,\n currency: string,\n options?: Omit<Intl.NumberFormatOptions, 'style' | 'currency'>,\n ): string {\n return formatNumber(caches, value, { ...options, currency, style: 'currency' }, activeLocale());\n },\n date(value: Date | number, options?: Intl.DateTimeFormatOptions): string {\n return formatDate(caches, value, options, activeLocale());\n },\n has(key: string): boolean {\n return findMessage(keyWithPrefix(key), activeLocale()) !== undefined;\n },\n hasOwn(key: string): boolean {\n return checkOwn(keyWithPrefix(key), activeLocale());\n },\n list(items: unknown[], type: 'and' | 'or' = 'and'): string {\n return formatList(caches, items, activeLocale(), type);\n },\n get locale(): Locale {\n return activeLocale();\n },\n number(value: number, options?: Intl.NumberFormatOptions): string {\n return formatNumber(caches, value, options, activeLocale());\n },\n relative(value: number, unit: Intl.RelativeTimeFormatUnit, options?: Intl.RelativeTimeFormatOptions): string {\n return formatRelative(caches, value, unit, options, activeLocale());\n },\n scope<K extends NamespaceKeys<U>>(ns: K): BoundI18n<U[K] & Messages> {\n const nextPrefix = prefix ? `${prefix}.${String(ns)}` : String(ns);\n\n return createView<U[K] & Messages>(fixedLocale, nextPrefix);\n },\n t,\n withLocale(nextLocale: Locale): BoundI18n<U> {\n return createView<U>(nextLocale, prefix);\n },\n } satisfies BoundI18n<U>;\n\n return view;\n }\n\n const rootView = createView<T>(null);\n\n const api = Object.create(rootView) as I18n<T>;\n\n api.add = (loc: Locale, messages: Messages): void => {\n const existing = catalogs.get(loc) ?? {};\n\n catalogs.set(loc, deepMerge(existing, messages));\n localesCache = null;\n\n if (getLocaleChain(locale).includes(loc)) notify('catalog-update');\n };\n\n api.batch = (fn: () => void): void => {\n batchDepth++;\n\n try {\n fn();\n } finally {\n batchDepth--;\n\n if (batchDepth === 0 && pendingNotify !== null) {\n const reason = pendingNotify;\n\n pendingNotify = null;\n notify(reason);\n }\n }\n };\n\n api.dispose = (): void => {\n disposed = true;\n subscribers.clear();\n catalogs.clear();\n loaders.clear();\n loading.clear();\n chainCache.clear();\n localesCache = null;\n loadersCache = null;\n };\n\n api.ensureLocale = async (loc: Locale, mode: SwitchMode = defaultSwitchMode): Promise<void> => {\n await loadOne(loc, mode);\n };\n\n api.hasLocale = (loc: Locale): boolean => catalogs.has(loc);\n api.isReady = (loc: Locale): boolean => catalogs.has(loc);\n\n Object.defineProperties(api, {\n loadableLocales: {\n get(): Locale[] {\n loadersCache ??= [...loaders.keys()];\n\n return loadersCache;\n },\n },\n locales: {\n get(): Locale[] {\n localesCache ??= [...catalogs.keys()];\n\n return localesCache;\n },\n },\n });\n\n api.registerLoader = (loc: Locale, loader: Loader): void => {\n loaders.set(loc, loader);\n loadersCache = null;\n };\n\n api.reload = async (loc: Locale): Promise<void> => {\n if (!loaders.has(loc)) {\n throw new Error(`[i18nit] Cannot reload locale \"${loc}\" without a registered loader.`);\n }\n\n catalogs.delete(loc);\n localesCache = null;\n await loadOne(loc, 'strict');\n };\n\n api.replace = (loc: Locale, messages: Messages): void => {\n catalogs.set(loc, structuredClone(messages));\n localesCache = null;\n\n if (getLocaleChain(locale).includes(loc)) notify('catalog-update');\n };\n\n api.subscribe = (listener: (event: LocaleChangeEvent) => void, immediate?: boolean): Unsubscribe => {\n subscribers.add(listener);\n\n if (immediate) {\n try {\n listener({ locale, reason: 'locale-change' });\n } catch (error) {\n diagnoseSubscriber(error);\n }\n }\n\n return () => subscribers.delete(listener);\n };\n\n api.switchLocale = async (nextLocale: Locale, mode: SwitchMode = defaultSwitchMode): Promise<void> => {\n if (nextLocale === locale) return;\n\n await loadOne(nextLocale, mode);\n locale = nextLocale;\n notify('locale-change');\n };\n\n api[Symbol.asyncDispose] = async (): Promise<void> => {\n await Promise.allSettled([...loading.values()]);\n api.dispose();\n };\n\n api[Symbol.dispose] = (): void => {\n api.dispose();\n };\n\n return api;\n}\n\nexport type { I18n };\n"],"mappings":"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"}
package/dist/i18n.d.ts ADDED
@@ -0,0 +1,4 @@
1
+ import type { I18n, I18nOptions, Messages } from './types';
2
+ export declare function createI18n<T extends Messages = Messages>(config?: I18nOptions<T>): I18n<T>;
3
+ export type { I18n };
4
+ //# sourceMappingURL=i18n.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"i18n.d.ts","sourceRoot":"","sources":["../src/i18n.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAEV,IAAI,EACJ,WAAW,EAKX,QAAQ,EAMT,MAAM,SAAS,CAAC;AAcjB,wBAAgB,UAAU,CAAC,CAAC,SAAS,QAAQ,GAAG,QAAQ,EAAE,MAAM,GAAE,WAAW,CAAC,CAAC,CAAM,GAAG,IAAI,CAAC,CAAC,CAAC,CAmV9F;AAED,YAAY,EAAE,IAAI,EAAE,CAAC"}
package/dist/i18n.js ADDED
@@ -0,0 +1,181 @@
1
+ import { BoundedMap as e, deepMerge as t, isMessageValue as n, resolvePath as r } from "./helpers.js";
2
+ import { formatDate as i, formatList as a, formatNumber as o, formatRelative as s, getPluralForm as c, makeIntlCaches as l } from "./intl.js";
3
+ import { interpolate as u } from "./interpolate.js";
4
+ //#region src/i18n.ts
5
+ function d(d = {}) {
6
+ let f = d.locale ?? "en", p = d.switchMode ?? "strict", m = Array.isArray(d.fallback) ? d.fallback : d.fallback ? [d.fallback] : [], h = /* @__PURE__ */ new Map(), g = /* @__PURE__ */ new Map(), _ = /* @__PURE__ */ new Map(), v = /* @__PURE__ */ new Set(), y = new e(128), b = l(), x = null, S = null, C = !1, w = 0, T = null, E = d.onMissing, D = d.onDiagnostic;
7
+ if (d.messages) for (let [e, t] of Object.entries(d.messages)) h.set(e, structuredClone(t));
8
+ if (d.loaders) for (let [e, t] of Object.entries(d.loaders)) g.set(e, t);
9
+ function O(e) {
10
+ D ? D({
11
+ error: e,
12
+ kind: "subscriber-error"
13
+ }) : console.error("[i18nit] Subscriber threw:", e);
14
+ }
15
+ function k(e, t) {
16
+ D ? D({
17
+ error: e,
18
+ kind: "loader-error",
19
+ locale: t
20
+ }) : console.warn("[i18nit] Loader error:", e);
21
+ }
22
+ function A(e) {
23
+ if (w > 0) {
24
+ T !== "locale-change" && (T = e);
25
+ return;
26
+ }
27
+ let t = {
28
+ locale: f,
29
+ reason: e
30
+ };
31
+ for (let e of v) try {
32
+ e(t);
33
+ } catch (e) {
34
+ O(e);
35
+ }
36
+ }
37
+ function j(e) {
38
+ let t = y.get(e);
39
+ if (t) return t;
40
+ let n = /* @__PURE__ */ new Set(), r = (e) => {
41
+ n.add(e);
42
+ let t = e.split("-");
43
+ for (let e = t.length - 1; e > 0; e--) n.add(t.slice(0, e).join("-"));
44
+ };
45
+ r(e);
46
+ for (let e of m) r(e);
47
+ let i = [...n];
48
+ return y.set(e, i), i;
49
+ }
50
+ function M(e, t) {
51
+ let i = h.get(t);
52
+ if (!i) return !1;
53
+ let a = r(i, e);
54
+ return a !== void 0 && n(a);
55
+ }
56
+ function N(e, t) {
57
+ for (let i of j(t)) {
58
+ let t = h.get(i);
59
+ if (!t) continue;
60
+ let a = r(t, e);
61
+ if (a !== void 0 && n(a)) return a;
62
+ }
63
+ }
64
+ function P(e, t, n) {
65
+ let r = N(e, n);
66
+ if (r === void 0) return E?.(e, n) ?? e;
67
+ if (typeof r == "string") return u(r, t ?? {}, n, b);
68
+ let i = t ?? {}, a = Number(i.count ?? 0);
69
+ return u(r[a === 0 && r.zero !== void 0 ? "zero" : c(b, n, a)] ?? r.other, i, n, b);
70
+ }
71
+ function F(e, t) {
72
+ if (_.has(e)) return _.get(e);
73
+ if (h.has(e)) return Promise.resolve();
74
+ let n = g.get(e);
75
+ if (!n) return t === "strict" ? Promise.reject(/* @__PURE__ */ Error(`[i18nit] Missing loader for locale "${e}".`)) : Promise.resolve();
76
+ let r = (async () => {
77
+ try {
78
+ let t = await n(e);
79
+ C || R.replace(e, t);
80
+ } catch (t) {
81
+ throw k(t, e), t;
82
+ } finally {
83
+ _.delete(e);
84
+ }
85
+ })();
86
+ return _.set(e, r), r;
87
+ }
88
+ function I(e, t) {
89
+ let n = () => e ?? f, r = (e) => t ? `${t}.${e}` : e;
90
+ return {
91
+ currency(e, t, r) {
92
+ return o(b, e, {
93
+ ...r,
94
+ currency: t,
95
+ style: "currency"
96
+ }, n());
97
+ },
98
+ date(e, t) {
99
+ return i(b, e, t, n());
100
+ },
101
+ has(e) {
102
+ return N(r(e), n()) !== void 0;
103
+ },
104
+ hasOwn(e) {
105
+ return M(r(e), n());
106
+ },
107
+ list(e, t = "and") {
108
+ return a(b, e, n(), t);
109
+ },
110
+ get locale() {
111
+ return n();
112
+ },
113
+ number(e, t) {
114
+ return o(b, e, t, n());
115
+ },
116
+ relative(e, t, r) {
117
+ return s(b, e, t, r, n());
118
+ },
119
+ scope(n) {
120
+ return I(e, t ? `${t}.${String(n)}` : String(n));
121
+ },
122
+ t: (e, t) => P(r(e), t, n()),
123
+ withLocale(e) {
124
+ return I(e, t);
125
+ }
126
+ };
127
+ }
128
+ let L = I(null), R = Object.create(L);
129
+ return R.add = (e, n) => {
130
+ let r = h.get(e) ?? {};
131
+ h.set(e, t(r, n)), x = null, j(f).includes(e) && A("catalog-update");
132
+ }, R.batch = (e) => {
133
+ w++;
134
+ try {
135
+ e();
136
+ } finally {
137
+ if (w--, w === 0 && T !== null) {
138
+ let e = T;
139
+ T = null, A(e);
140
+ }
141
+ }
142
+ }, R.dispose = () => {
143
+ C = !0, v.clear(), h.clear(), g.clear(), _.clear(), y.clear(), x = null, S = null;
144
+ }, R.ensureLocale = async (e, t = p) => {
145
+ await F(e, t);
146
+ }, R.hasLocale = (e) => h.has(e), R.isReady = (e) => h.has(e), Object.defineProperties(R, {
147
+ loadableLocales: { get() {
148
+ return S ??= [...g.keys()], S;
149
+ } },
150
+ locales: { get() {
151
+ return x ??= [...h.keys()], x;
152
+ } }
153
+ }), R.registerLoader = (e, t) => {
154
+ g.set(e, t), S = null;
155
+ }, R.reload = async (e) => {
156
+ if (!g.has(e)) throw Error(`[i18nit] Cannot reload locale "${e}" without a registered loader.`);
157
+ h.delete(e), x = null, await F(e, "strict");
158
+ }, R.replace = (e, t) => {
159
+ h.set(e, structuredClone(t)), x = null, j(f).includes(e) && A("catalog-update");
160
+ }, R.subscribe = (e, t) => {
161
+ if (v.add(e), t) try {
162
+ e({
163
+ locale: f,
164
+ reason: "locale-change"
165
+ });
166
+ } catch (e) {
167
+ O(e);
168
+ }
169
+ return () => v.delete(e);
170
+ }, R.switchLocale = async (e, t = p) => {
171
+ e !== f && (await F(e, t), f = e, A("locale-change"));
172
+ }, R[Symbol.asyncDispose] = async () => {
173
+ await Promise.allSettled([..._.values()]), R.dispose();
174
+ }, R[Symbol.dispose] = () => {
175
+ R.dispose();
176
+ }, R;
177
+ }
178
+ //#endregion
179
+ export { d as createI18n };
180
+
181
+ //# sourceMappingURL=i18n.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"i18n.js","names":[],"sources":["../src/i18n.ts"],"sourcesContent":["import type {\n BoundI18n,\n I18n,\n I18nOptions,\n Loader,\n Locale,\n LocaleChangeEvent,\n LocaleChangeReason,\n Messages,\n MessageValue,\n NamespaceKeys,\n SwitchMode,\n Unsubscribe,\n Vars,\n} from './types';\n\nimport { BoundedMap, deepMerge, isMessageValue, resolvePath } from './helpers';\nimport { interpolate } from './interpolate';\nimport {\n formatDate,\n formatList,\n formatNumber,\n formatRelative,\n getPluralForm,\n type IntlCaches,\n makeIntlCaches,\n} from './intl';\n\nexport function createI18n<T extends Messages = Messages>(config: I18nOptions<T> = {}): I18n<T> {\n let locale = config.locale ?? 'en';\n const defaultSwitchMode: SwitchMode = config.switchMode ?? 'strict';\n const fallbacks = Array.isArray(config.fallback) ? config.fallback : config.fallback ? [config.fallback] : [];\n const catalogs = new Map<Locale, Messages>();\n const loaders = new Map<Locale, Loader>();\n const loading = new Map<Locale, Promise<void>>();\n const subscribers = new Set<(event: LocaleChangeEvent) => void>();\n const chainCache = new BoundedMap<Locale, Locale[]>(128);\n const caches: IntlCaches = makeIntlCaches();\n\n let localesCache: Locale[] | null = null;\n let loadersCache: Locale[] | null = null;\n let disposed = false;\n let batchDepth = 0;\n let pendingNotify: LocaleChangeReason | null = null;\n\n const onMissing = config.onMissing;\n const onDiagnostic = config.onDiagnostic;\n\n if (config.messages) {\n for (const [loc, messages] of Object.entries(config.messages)) {\n catalogs.set(loc, structuredClone(messages) as Messages);\n }\n }\n\n if (config.loaders) {\n for (const [loc, loader] of Object.entries(config.loaders)) {\n loaders.set(loc, loader);\n }\n }\n\n function diagnoseSubscriber(error: unknown): void {\n if (onDiagnostic) {\n onDiagnostic({ error, kind: 'subscriber-error' });\n } else {\n console.error('[i18nit] Subscriber threw:', error);\n }\n }\n\n function diagnoseLoader(error: unknown, loc: Locale): void {\n if (onDiagnostic) {\n onDiagnostic({ error, kind: 'loader-error', locale: loc });\n } else {\n console.warn('[i18nit] Loader error:', error);\n }\n }\n\n function notify(reason: LocaleChangeReason): void {\n if (batchDepth > 0) {\n if (pendingNotify !== 'locale-change') pendingNotify = reason;\n\n return;\n }\n\n const event: LocaleChangeEvent = { locale, reason };\n\n for (const listener of subscribers) {\n try {\n listener(event);\n } catch (error) {\n diagnoseSubscriber(error);\n }\n }\n }\n\n function getLocaleChain(loc: Locale): Locale[] {\n const cached = chainCache.get(loc);\n\n if (cached) return cached;\n\n const seen = new Set<Locale>();\n\n const push = (value: Locale) => {\n seen.add(value);\n\n const parts = value.split('-');\n\n for (let i = parts.length - 1; i > 0; i--) {\n seen.add(parts.slice(0, i).join('-'));\n }\n };\n\n push(loc);\n for (const fallback of fallbacks) push(fallback);\n\n const chain = [...seen];\n\n chainCache.set(loc, chain);\n\n return chain;\n }\n\n function checkOwn(key: string, loc: Locale): boolean {\n const catalog = catalogs.get(loc);\n\n if (!catalog) return false;\n\n const value = resolvePath(catalog, key);\n\n return value !== undefined && isMessageValue(value);\n }\n\n function findMessage(key: string, loc: Locale): MessageValue | undefined {\n for (const localeInChain of getLocaleChain(loc)) {\n const messages = catalogs.get(localeInChain);\n\n if (!messages) continue;\n\n const value = resolvePath(messages, key);\n\n if (value !== undefined && isMessageValue(value)) return value;\n }\n\n return undefined;\n }\n\n function translate(key: string, vars: Vars | undefined, loc: Locale): string {\n const message = findMessage(key, loc);\n\n if (message === undefined) return onMissing?.(key, loc) ?? key;\n\n if (typeof message === 'string') {\n return interpolate(message, vars ?? {}, loc, caches);\n }\n\n const context = vars ?? {};\n\n if (import.meta.env?.DEV && context.count === undefined) {\n console.warn(`[i18nit] Key \"${key}\" is a plural message but vars.count is missing. Defaulting to 0.`);\n }\n\n const count = Number(context.count ?? 0);\n const form = count === 0 && message.zero !== undefined ? 'zero' : getPluralForm(caches, loc, count);\n\n return interpolate(message[form] ?? message.other, context, loc, caches);\n }\n\n function loadOne(loc: Locale, mode: SwitchMode): Promise<void> {\n if (loading.has(loc)) return loading.get(loc)!;\n\n if (catalogs.has(loc)) return Promise.resolve();\n\n const loader = loaders.get(loc);\n\n if (!loader) {\n if (mode === 'strict') {\n return Promise.reject(new Error(`[i18nit] Missing loader for locale \"${loc}\".`));\n }\n\n return Promise.resolve();\n }\n\n const promise = (async () => {\n try {\n const messages = await loader(loc);\n\n if (!disposed) api.replace(loc, messages);\n } catch (error) {\n diagnoseLoader(error, loc);\n throw error;\n } finally {\n loading.delete(loc);\n }\n })();\n\n loading.set(loc, promise);\n\n return promise;\n }\n\n function createView<U extends Messages = Messages>(fixedLocale: Locale | null, prefix?: string): BoundI18n<U> {\n const activeLocale = (): Locale => fixedLocale ?? locale;\n const keyWithPrefix = (key: string): string => (prefix ? `${prefix}.${key}` : key);\n const t: BoundI18n<U>['t'] = (key: NamespaceKeys<U>, vars?: Record<string, unknown>) =>\n translate(keyWithPrefix(key as string), vars, activeLocale());\n\n const view = {\n currency(\n value: number,\n currency: string,\n options?: Omit<Intl.NumberFormatOptions, 'style' | 'currency'>,\n ): string {\n return formatNumber(caches, value, { ...options, currency, style: 'currency' }, activeLocale());\n },\n date(value: Date | number, options?: Intl.DateTimeFormatOptions): string {\n return formatDate(caches, value, options, activeLocale());\n },\n has(key: string): boolean {\n return findMessage(keyWithPrefix(key), activeLocale()) !== undefined;\n },\n hasOwn(key: string): boolean {\n return checkOwn(keyWithPrefix(key), activeLocale());\n },\n list(items: unknown[], type: 'and' | 'or' = 'and'): string {\n return formatList(caches, items, activeLocale(), type);\n },\n get locale(): Locale {\n return activeLocale();\n },\n number(value: number, options?: Intl.NumberFormatOptions): string {\n return formatNumber(caches, value, options, activeLocale());\n },\n relative(value: number, unit: Intl.RelativeTimeFormatUnit, options?: Intl.RelativeTimeFormatOptions): string {\n return formatRelative(caches, value, unit, options, activeLocale());\n },\n scope<K extends NamespaceKeys<U>>(ns: K): BoundI18n<U[K] & Messages> {\n const nextPrefix = prefix ? `${prefix}.${String(ns)}` : String(ns);\n\n return createView<U[K] & Messages>(fixedLocale, nextPrefix);\n },\n t,\n withLocale(nextLocale: Locale): BoundI18n<U> {\n return createView<U>(nextLocale, prefix);\n },\n } satisfies BoundI18n<U>;\n\n return view;\n }\n\n const rootView = createView<T>(null);\n\n const api = Object.create(rootView) as I18n<T>;\n\n api.add = (loc: Locale, messages: Messages): void => {\n const existing = catalogs.get(loc) ?? {};\n\n catalogs.set(loc, deepMerge(existing, messages));\n localesCache = null;\n\n if (getLocaleChain(locale).includes(loc)) notify('catalog-update');\n };\n\n api.batch = (fn: () => void): void => {\n batchDepth++;\n\n try {\n fn();\n } finally {\n batchDepth--;\n\n if (batchDepth === 0 && pendingNotify !== null) {\n const reason = pendingNotify;\n\n pendingNotify = null;\n notify(reason);\n }\n }\n };\n\n api.dispose = (): void => {\n disposed = true;\n subscribers.clear();\n catalogs.clear();\n loaders.clear();\n loading.clear();\n chainCache.clear();\n localesCache = null;\n loadersCache = null;\n };\n\n api.ensureLocale = async (loc: Locale, mode: SwitchMode = defaultSwitchMode): Promise<void> => {\n await loadOne(loc, mode);\n };\n\n api.hasLocale = (loc: Locale): boolean => catalogs.has(loc);\n api.isReady = (loc: Locale): boolean => catalogs.has(loc);\n\n Object.defineProperties(api, {\n loadableLocales: {\n get(): Locale[] {\n loadersCache ??= [...loaders.keys()];\n\n return loadersCache;\n },\n },\n locales: {\n get(): Locale[] {\n localesCache ??= [...catalogs.keys()];\n\n return localesCache;\n },\n },\n });\n\n api.registerLoader = (loc: Locale, loader: Loader): void => {\n loaders.set(loc, loader);\n loadersCache = null;\n };\n\n api.reload = async (loc: Locale): Promise<void> => {\n if (!loaders.has(loc)) {\n throw new Error(`[i18nit] Cannot reload locale \"${loc}\" without a registered loader.`);\n }\n\n catalogs.delete(loc);\n localesCache = null;\n await loadOne(loc, 'strict');\n };\n\n api.replace = (loc: Locale, messages: Messages): void => {\n catalogs.set(loc, structuredClone(messages));\n localesCache = null;\n\n if (getLocaleChain(locale).includes(loc)) notify('catalog-update');\n };\n\n api.subscribe = (listener: (event: LocaleChangeEvent) => void, immediate?: boolean): Unsubscribe => {\n subscribers.add(listener);\n\n if (immediate) {\n try {\n listener({ locale, reason: 'locale-change' });\n } catch (error) {\n diagnoseSubscriber(error);\n }\n }\n\n return () => subscribers.delete(listener);\n };\n\n api.switchLocale = async (nextLocale: Locale, mode: SwitchMode = defaultSwitchMode): Promise<void> => {\n if (nextLocale === locale) return;\n\n await loadOne(nextLocale, mode);\n locale = nextLocale;\n notify('locale-change');\n };\n\n api[Symbol.asyncDispose] = async (): Promise<void> => {\n await Promise.allSettled([...loading.values()]);\n api.dispose();\n };\n\n api[Symbol.dispose] = (): void => {\n api.dispose();\n };\n\n return api;\n}\n\nexport type { I18n };\n"],"mappings":"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"}
package/dist/i18nit.cjs CHANGED
@@ -1,2 +1,2 @@
1
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});function g(a){return a.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;").replace(/'/g,"&#39;")}function h(a,t){if(t in a)return a[t];const e=t.match(/[^.[\]]+/g)||[];let r=a;for(const s of e){if(r==null||typeof r!="object")return;if(Array.isArray(r)){const n=Number(s);if(Number.isNaN(n)||n<0||n>=r.length)return;r=r[n]}else r=r[s]}return r}function l(a,t,e){if(a.length===0)return"";const r=a.map(String);try{return new Intl.ListFormat(t,{style:"long",type:e}).format(r)}catch{if(r.length===1)return r[0];if(r.length===2){const o=e==="conjunction"?"and":"or";return`${r[0]} ${o} ${r[1]}`}const s=e==="conjunction"?"and":"or",n=r.pop();return`${r.join(", ")} ${s} ${n}`}}function u(a,t,e){return a.replace(/\{([\w.[\]]+)(?:\|([^}]+))?\}/g,(r,s,n)=>{const o=s.endsWith(".length"),c=o?s.slice(0,-7):s,i=h(t,c);if(i==null)return"";if(Array.isArray(i))return o?String(i.length):n!==void 0?n==="and"?l(i,e,"conjunction"):n==="or"?l(i,e,"disjunction"):i.map(String).join(n):i.map(String).join(", ");if(typeof i=="number")try{return new Intl.NumberFormat(e).format(i)}catch{return String(i)}return String(i)})}function d(a,t){const e=Math.abs(Math.floor(t));try{return new Intl.PluralRules(a).select(e)}catch{return e===1?"one":"other"}}class f{locale;fallbacks;escape;catalogs=new Map;loaders=new Map;loading=new Map;subscribers=new Set;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,t.messages)for(const[e,r]of Object.entries(t.messages))this.catalogs.set(e,r);if(t.loaders)for(const[e,r]of Object.entries(t.loaders))this.loaders.set(e,r)}setLocale(t){this.locale!==t&&(this.locale=t,this.notifySubscribers())}getLocale(){return this.locale}add(t,e){const r=this.catalogs.get(t)??{};this.catalogs.set(t,{...r,...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 r=(async()=>{try{const s=await e(t);this.add(t,s)}catch(s){throw console.warn(`[I18n] Failed to load locale '${t}':`,s),s}finally{this.loading.delete(t)}})();return this.loading.set(t,r),r}register(t,e){this.loaders.set(t,e)}async hasAsync(t,e){const r=e??this.locale;return!this.catalogs.has(r)&&this.loaders.has(r)&&await this.load(r),this.has(t,r)}async loadAll(t){await Promise.all(t.map(e=>this.load(e)))}t(t,e,r){const s=r?.locale??this.locale,n=r?.escape??this.escape,o=this.findMessage(t,s);if(o===void 0)return t;const c=this.formatMessage(o,e??{},s);return n?g(c):c}number(t,e,r){try{return new Intl.NumberFormat(r??this.locale,e).format(t)}catch{return String(t)}}date(t,e,r){const s=typeof t=="number"?new Date(t):t;try{return new Intl.DateTimeFormat(r??this.locale,e).format(s)}catch{return s.toString()}}namespace(t){return{t:(e,r,s)=>this.t(`${t}.${e}`,r,s)}}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 r=this.getLocaleChain(e);for(const s of r){const n=this.catalogs.get(s);if(!n)continue;const o=h(n,t);if(o!==void 0&&this.isMessageValue(o))return o}}isMessageValue(t){if(typeof t=="string")return!0;if(typeof t=="object"&&t!==null&&!Array.isArray(t)){const e=t;return"other"in e&&typeof e.other=="string"?Object.values(e).every(r=>typeof r=="string"):!1}return!1}getLocaleChain(t){const e=[t],r=t.split("-")[0];r!==t&&e.push(r);for(const s of this.fallbacks){e.push(s);const n=s.split("-")[0];n!==s&&e.push(n)}return e}formatMessage(t,e,r){if(typeof t=="object"&&"other"in t){const s=Number(e.count??0),n=t;let o;s===0&&n.zero!==void 0?o="zero":o=d(r,s);const c=n[o]??n.other;return u(c,e,r)}return typeof t=="string"?u(t,e,r):""}}function b(a){return new f(a)}exports.I18n=f;exports.createI18n=b;
2
- //# sourceMappingURL=i18nit.cjs.map
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});function e(e,t){if(Object.hasOwn(e,t))return e[t];let n=t.match(/[^.[\]]+/gu)??[],r=e;for(let e of n){if(typeof r!=`object`||!r||!Object.hasOwn(r,e))return;r=r[e]}return r}var t=new Set([`zero`,`one`,`two`,`few`,`many`,`other`]);function n(e){if(typeof e==`string`)return!0;if(typeof e!=`object`||!e||Array.isArray(e))return!1;let n=e;if(!(`other`in n))return!1;let r=Object.keys(n);return r.length>t.size?!1:r.every(e=>t.has(e))&&Object.values(n).every(e=>typeof e==`string`)}function r(e,t){let i={...e};for(let[e,a]of Object.entries(t)){let t=i[e];!n(a)&&!n(t)&&typeof t==`object`&&t?i[e]=r(t,a):i[e]=typeof a==`object`&&a?{...a}:a}return i}var i=class extends Map{#e;constructor(e){super(),this.#e=e}set(e,t){return!this.has(e)&&this.size>=this.#e&&this.delete(this.keys().next().value),super.set(e,t)}};function a(){return{dateFormat:new Map,listFormat:new Map,numberFormat:new Map,pluralRules:new Map,relativeTimeFormat:new Map}}function o(e,t,n){let r=e.get(t);return r||(r=n(),e.set(t,r)),r}function s(e,t){return t?`${e}:${JSON.stringify(t,Object.keys(t).sort())}`:e}function c(e,t,n,r){let i=s(r,n);try{return o(e.numberFormat,i,()=>new Intl.NumberFormat(r,n)).format(t)}catch{return String(t)}}function l(e,t,n,r){let i=typeof t==`number`?new Date(t):t,a=s(r,n);try{return o(e.dateFormat,a,()=>new Intl.DateTimeFormat(r,n)).format(i)}catch{return i.toString()}}function u(e,t,n,r,i){let a=s(i,r);try{return o(e.relativeTimeFormat,a,()=>new Intl.RelativeTimeFormat(i,r)).format(t,n)}catch{return String(t)}}function d(e,t,n,r){if(t.length===0)return``;let i=t.map(String),a=r===`and`?`conjunction`:`disjunction`;try{return o(e.listFormat,`${n}:${a}`,()=>new Intl.ListFormat(n,{style:`long`,type:a})).format(i)}catch{return i.length===1?i[0]:i.length===2?`${i[0]} ${r} ${i[1]}`:`${i.slice(0,-1).join(`, `)} ${r} ${i.at(-1)}`}}function f(e,t,n){let r=Math.floor(Math.abs(n));try{return o(e.pluralRules,t,()=>new Intl.PluralRules(t)).select(r)}catch{return r===1?`one`:`other`}}function p(e,t,n,r){return t==null?``:Array.isArray(t)?n===`and`?d(e,t,r,`and`):n===`or`?d(e,t,r,`or`):n===void 0?t.map(String).join(`, `):t.map(String).join(n):typeof t==`number`?c(e,t,void 0,r):String(t)}function m(t,n,r,i){return t.includes(`{`)?t.replace(/\{([\p{ID_Continue}\-.[\]]+)(?:\|([^}]+))?\}/gu,(t,a,o)=>p(i,e(n,a),o,r)):t}function h(t={}){let o=t.locale??`en`,s=t.switchMode??`strict`,p=Array.isArray(t.fallback)?t.fallback:t.fallback?[t.fallback]:[],h=new Map,g=new Map,_=new Map,v=new Set,y=new i(128),b=a(),x=null,S=null,C=!1,w=0,T=null,E=t.onMissing,D=t.onDiagnostic;if(t.messages)for(let[e,n]of Object.entries(t.messages))h.set(e,structuredClone(n));if(t.loaders)for(let[e,n]of Object.entries(t.loaders))g.set(e,n);function O(e){D?D({error:e,kind:`subscriber-error`}):console.error(`[i18nit] Subscriber threw:`,e)}function k(e,t){D?D({error:e,kind:`loader-error`,locale:t}):console.warn(`[i18nit] Loader error:`,e)}function A(e){if(w>0){T!==`locale-change`&&(T=e);return}let t={locale:o,reason:e};for(let e of v)try{e(t)}catch(e){O(e)}}function j(e){let t=y.get(e);if(t)return t;let n=new Set,r=e=>{n.add(e);let t=e.split(`-`);for(let e=t.length-1;e>0;e--)n.add(t.slice(0,e).join(`-`))};r(e);for(let e of p)r(e);let i=[...n];return y.set(e,i),i}function M(t,r){let i=h.get(r);if(!i)return!1;let a=e(i,t);return a!==void 0&&n(a)}function N(t,r){for(let i of j(r)){let r=h.get(i);if(!r)continue;let a=e(r,t);if(a!==void 0&&n(a))return a}}function P(e,t,n){let r=N(e,n);if(r===void 0)return E?.(e,n)??e;if(typeof r==`string`)return m(r,t??{},n,b);let i=t??{},a=Number(i.count??0);return m(r[a===0&&r.zero!==void 0?`zero`:f(b,n,a)]??r.other,i,n,b)}function F(e,t){if(_.has(e))return _.get(e);if(h.has(e))return Promise.resolve();let n=g.get(e);if(!n)return t===`strict`?Promise.reject(Error(`[i18nit] Missing loader for locale "${e}".`)):Promise.resolve();let r=(async()=>{try{let t=await n(e);C||R.replace(e,t)}catch(t){throw k(t,e),t}finally{_.delete(e)}})();return _.set(e,r),r}function I(e,t){let n=()=>e??o,r=e=>t?`${t}.${e}`:e;return{currency(e,t,r){return c(b,e,{...r,currency:t,style:`currency`},n())},date(e,t){return l(b,e,t,n())},has(e){return N(r(e),n())!==void 0},hasOwn(e){return M(r(e),n())},list(e,t=`and`){return d(b,e,n(),t)},get locale(){return n()},number(e,t){return c(b,e,t,n())},relative(e,t,r){return u(b,e,t,r,n())},scope(n){return I(e,t?`${t}.${String(n)}`:String(n))},t:(e,t)=>P(r(e),t,n()),withLocale(e){return I(e,t)}}}let L=I(null),R=Object.create(L);return R.add=(e,t)=>{let n=h.get(e)??{};h.set(e,r(n,t)),x=null,j(o).includes(e)&&A(`catalog-update`)},R.batch=e=>{w++;try{e()}finally{if(w--,w===0&&T!==null){let e=T;T=null,A(e)}}},R.dispose=()=>{C=!0,v.clear(),h.clear(),g.clear(),_.clear(),y.clear(),x=null,S=null},R.ensureLocale=async(e,t=s)=>{await F(e,t)},R.hasLocale=e=>h.has(e),R.isReady=e=>h.has(e),Object.defineProperties(R,{loadableLocales:{get(){return S??=[...g.keys()],S}},locales:{get(){return x??=[...h.keys()],x}}}),R.registerLoader=(e,t)=>{g.set(e,t),S=null},R.reload=async e=>{if(!g.has(e))throw Error(`[i18nit] Cannot reload locale "${e}" without a registered loader.`);h.delete(e),x=null,await F(e,`strict`)},R.replace=(e,t)=>{h.set(e,structuredClone(t)),x=null,j(o).includes(e)&&A(`catalog-update`)},R.subscribe=(e,t)=>{if(v.add(e),t)try{e({locale:o,reason:`locale-change`})}catch(e){O(e)}return()=>v.delete(e)},R.switchLocale=async(e,t=s)=>{e!==o&&(await F(e,t),o=e,A(`locale-change`))},R[Symbol.asyncDispose]=async()=>{await Promise.allSettled([..._.values()]),R.dispose()},R[Symbol.dispose]=()=>{R.dispose()},R}exports.createI18n=h;
2
+ //# sourceMappingURL=i18nit.cjs.map
@@ -1 +1 @@
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 MessageValue = string | PluralMessages;\n\n// Support nested message objects with dot notation access\nexport type Messages = {\n [key: string]: MessageValue | Messages;\n};\n\nexport type TranslateOptions = {\n locale?: Locale;\n escape?: boolean;\n};\n\nexport type I18nConfig = {\n locale?: Locale;\n fallback?: Locale | Locale[];\n messages?: Record<Locale, Messages>;\n loaders?: Record<Locale, (locale: Locale) => Promise<Messages>>;\n escape?: boolean;\n};\n\n/* -------------------- Path Resolution -------------------- */\n\nfunction escapeHtml(str: string): string {\n return str\n .replace(/&/g, '&amp;')\n .replace(/</g, '&lt;')\n .replace(/>/g, '&gt;')\n .replace(/\"/g, '&quot;')\n .replace(/'/g, '&#39;');\n}\n\n/**\n * Resolves nested properties using dot notation and bracket notation.\n * Supports: 'user.name', 'items[0]', 'user.items[0].name'\n */\nfunction resolvePath(obj: Record<string, unknown>, path: string): unknown {\n // Try direct access first (handles keys with dots)\n if (path in obj) return obj[path];\n\n // Parse path segments\n const parts = path.match(/[^.[\\]]+/g) || [];\n let value: unknown = obj;\n\n for (const part of parts) {\n if (value == null || typeof value !== 'object') return undefined;\n\n if (Array.isArray(value)) {\n const index = Number(part);\n if (Number.isNaN(index) || index < 0 || index >= value.length) {\n return undefined;\n }\n value = value[index];\n } else {\n value = (value as Record<string, unknown>)[part];\n }\n }\n\n return value;\n}\n\n/* -------------------- List Formatting -------------------- */\n\n/**\n * Formats an array as a natural language list using Intl.ListFormat.\n * Automatically handles locale-specific conjunctions and grammar.\n */\nfunction formatList(items: unknown[], locale: string, type: 'conjunction' | 'disjunction'): string {\n if (items.length === 0) return '';\n\n const stringItems = items.map(String);\n\n try {\n const formatter = new Intl.ListFormat(locale, { style: 'long', type });\n return formatter.format(stringItems);\n } catch {\n // Fallback for environments without Intl.ListFormat\n if (stringItems.length === 1) return stringItems[0];\n if (stringItems.length === 2) {\n const separator = type === 'conjunction' ? 'and' : 'or';\n return `${stringItems[0]} ${separator} ${stringItems[1]}`;\n }\n const separator = type === 'conjunction' ? 'and' : 'or';\n const last = stringItems.pop()!;\n return `${stringItems.join(', ')} ${separator} ${last}`;\n }\n}\n\n/* -------------------- Variable Interpolation -------------------- */\n\n/**\n * Interpolates variables into a template string.\n *\n * Supported formats:\n * - {name} - Simple variable\n * - {user.name} - Nested property\n * - {items[0]} - Array index\n * - {items} - Array (comma-separated)\n * - {items|and} - Array with locale-aware \"and\"\n * - {items|or} - Array with locale-aware \"or\"\n * - {items| - } - Array with custom separator\n * - {items.length} - Array length\n */\nfunction interpolate(template: string, vars: Record<string, unknown>, locale: string): string {\n // biome-ignore lint/complexity/noExcessiveCognitiveComplexity: -\n return template.replace(/\\{([\\w.[\\]]+)(?:\\|([^}]+))?\\}/g, (_match, key: string, separator?: string) => {\n // Handle .length property\n const isLength = key.endsWith('.length');\n const actualKey = isLength ? key.slice(0, -7) : key;\n\n const value = resolvePath(vars, actualKey);\n\n // Missing variables are replaced with empty string\n if (value == null) return '';\n\n // Handle arrays\n if (Array.isArray(value)) {\n if (isLength) return String(value.length);\n\n if (separator !== undefined) {\n if (separator === 'and') return formatList(value, locale, 'conjunction');\n if (separator === 'or') return formatList(value, locale, 'disjunction');\n return value.map(String).join(separator);\n }\n\n return value.map(String).join(', ');\n }\n\n // Format numbers with locale\n if (typeof value === 'number') {\n try {\n return new Intl.NumberFormat(locale).format(value);\n } catch {\n return String(value);\n }\n }\n\n return String(value);\n });\n}\n\n/* -------------------- Pluralization -------------------- */\n\n/**\n * Gets the plural form for a number using Intl.PluralRules.\n * Automatically handles all locale-specific plural rules.\n */\nfunction getPluralForm(locale: Locale, count: number): PluralForm {\n const n = Math.abs(Math.floor(count));\n\n try {\n const rules = new Intl.PluralRules(locale);\n return rules.select(n) as PluralForm;\n } catch {\n // Fallback to English-like behavior\n return n === 1 ? 'one' : 'other';\n }\n}\n\n/* -------------------- I18n Class -------------------- */\n\nexport class I18n {\n private locale: Locale;\n private fallbacks: Locale[];\n private escape: boolean;\n private catalogs = new Map<Locale, Messages>();\n private loaders = new Map<Locale, (locale: Locale) => Promise<Messages>>();\n private loading = new Map<Locale, Promise<void>>();\n private subscribers = new Set<(locale: Locale) => void>();\n\n constructor(config: I18nConfig = {}) {\n this.locale = config.locale ?? 'en';\n this.fallbacks = Array.isArray(config.fallback) ? config.fallback : config.fallback ? [config.fallback] : [];\n this.escape = config.escape ?? false;\n\n if (config.messages) {\n for (const [locale, messages] of Object.entries(config.messages)) {\n this.catalogs.set(locale, messages);\n }\n }\n\n if (config.loaders) {\n for (const [locale, loader] of Object.entries(config.loaders)) {\n this.loaders.set(locale, loader);\n }\n }\n }\n\n /* -------------------- Locale Management -------------------- */\n\n setLocale(locale: Locale): void {\n if (this.locale === locale) return;\n this.locale = locale;\n this.notifySubscribers();\n }\n\n getLocale(): Locale {\n return this.locale;\n }\n\n /* -------------------- Message Management -------------------- */\n\n /**\n * Adds messages to a locale (merges with existing).\n */\n add(locale: Locale, messages: Messages): void {\n const existing = this.catalogs.get(locale) ?? {};\n this.catalogs.set(locale, { ...existing, ...messages });\n this.notifySubscribers();\n }\n\n /**\n * Sets messages for a locale (replaces existing).\n */\n set(locale: Locale, messages: Messages): void {\n this.catalogs.set(locale, messages);\n this.notifySubscribers();\n }\n\n getMessages(locale: Locale): Messages | undefined {\n return this.catalogs.get(locale);\n }\n\n hasLocale(locale: Locale): boolean {\n return this.catalogs.has(locale);\n }\n\n has(key: string, locale?: Locale): boolean {\n return this.findMessage(key, locale ?? this.locale) !== undefined;\n }\n\n /* -------------------- Async Loaders -------------------- */\n\n async load(locale: Locale): Promise<void> {\n // Return existing loading promise\n if (this.loading.has(locale)) return this.loading.get(locale);\n\n // Already loaded\n if (this.catalogs.has(locale)) return;\n\n const loader = this.loaders.get(locale);\n if (!loader) return;\n\n const promise = (async () => {\n try {\n const messages = await loader(locale);\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: (locale: Locale) => Promise<Messages>): void {\n this.loaders.set(locale, loader);\n }\n\n async hasAsync(key: string, locale?: Locale): Promise<boolean> {\n const targetLocale = locale ?? this.locale;\n\n if (!this.catalogs.has(targetLocale) && this.loaders.has(targetLocale)) {\n await this.load(targetLocale);\n }\n\n return this.has(key, targetLocale);\n }\n\n /**\n * Load multiple locales in parallel.\n * Useful for preloading all needed locales at app startup.\n */\n async loadAll(locales: Locale[]): Promise<void> {\n await Promise.all(locales.map((locale) => this.load(locale)));\n }\n\n /* -------------------- Translation -------------------- */\n\n /**\n * Translates a key with optional variables and options.\n * Synchronous - locale must be loaded first via load() or provided in config.\n */\n t(key: string, vars?: Record<string, unknown>, options?: TranslateOptions): string {\n const targetLocale = options?.locale ?? this.locale;\n const shouldEscape = options?.escape ?? this.escape;\n\n const message = this.findMessage(key, targetLocale);\n if (message === undefined) return key;\n\n const result = this.formatMessage(message, vars ?? {}, targetLocale);\n return shouldEscape ? escapeHtml(result) : result;\n }\n\n /* -------------------- Formatting Helpers -------------------- */\n\n number(value: number, options?: Intl.NumberFormatOptions, locale?: Locale): string {\n try {\n return new Intl.NumberFormat(locale ?? this.locale, options).format(value);\n } catch {\n return String(value);\n }\n }\n\n date(value: Date | number, options?: Intl.DateTimeFormatOptions, locale?: Locale): string {\n const date = typeof value === 'number' ? new Date(value) : value;\n try {\n return new Intl.DateTimeFormat(locale ?? this.locale, options).format(date);\n } catch {\n return date.toString();\n }\n }\n\n /* -------------------- Namespaced Translator -------------------- */\n\n namespace(ns: string) {\n return {\n t: (key: string, vars?: Record<string, unknown>, options?: TranslateOptions) =>\n this.t(`${ns}.${key}`, vars, options),\n };\n }\n\n /* -------------------- Subscriptions -------------------- */\n\n subscribe(handler: (locale: Locale) => void): () => void {\n this.subscribers.add(handler);\n\n // Call handler immediately with the current locale\n try {\n handler(this.locale);\n } catch {\n // Ignore handler errors\n }\n\n return () => this.subscribers.delete(handler);\n }\n\n private notifySubscribers(): void {\n for (const handler of this.subscribers) {\n try {\n handler(this.locale);\n } catch {\n // Ignore handler errors\n }\n }\n }\n\n /* -------------------- Internal Helpers -------------------- */\n\n private findMessage(key: string, locale: Locale): MessageValue | undefined {\n const locales = this.getLocaleChain(locale);\n\n for (const loc of locales) {\n const messages = this.catalogs.get(loc);\n if (!messages) continue;\n\n const value = resolvePath(messages, key);\n\n // If the value is a nested Messages object (not a MessageValue), return undefined\n // This ensures we only return actual translatable strings, not object containers\n if (value !== undefined && this.isMessageValue(value)) {\n return value as MessageValue;\n }\n }\n\n return undefined;\n }\n\n /**\n * Check if a value is a MessageValue (string or PluralMessages) rather than a nested Messages object\n */\n private isMessageValue(value: unknown): boolean {\n if (typeof value === 'string') return true;\n\n // Check if it's a PluralMessages object (has 'other' key and all values are strings)\n if (typeof value === 'object' && value !== null && !Array.isArray(value)) {\n const obj = value as Record<string, unknown>;\n\n // If it has 'other' key and values are strings, it's a PluralMessages\n if ('other' in obj && typeof obj.other === 'string') {\n return Object.values(obj).every((v) => typeof v === 'string');\n }\n\n // Otherwise it's a nested Messages object\n return false;\n }\n\n return false;\n }\n\n private getLocaleChain(locale: Locale): Locale[] {\n const chain: Locale[] = [locale];\n\n // Add base language (e.g., 'en' from 'en-US')\n const lang = locale.split('-')[0];\n if (lang !== locale) chain.push(lang);\n\n // Add fallback locales\n for (const fallback of this.fallbacks) {\n chain.push(fallback);\n const fallbackLang = fallback.split('-')[0];\n if (fallbackLang !== fallback) chain.push(fallbackLang);\n }\n\n return chain;\n }\n\n private formatMessage(message: MessageValue, vars: Record<string, unknown>, locale: Locale): string {\n // Plural messages\n if (typeof message === 'object' && 'other' in message) {\n const count = Number(vars.count ?? 0);\n const pluralMsg = message as PluralMessages;\n\n // Prefer an explicit 'zero' form\n let form: PluralForm;\n if (count === 0 && pluralMsg.zero !== undefined) {\n form = 'zero';\n } else {\n form = getPluralForm(locale, count);\n }\n\n const template = pluralMsg[form] ?? pluralMsg.other;\n return interpolate(template, vars, locale);\n }\n\n // String messages\n if (typeof message === 'string') {\n return interpolate(message, vars, locale);\n }\n\n return '';\n }\n}\n\n/* -------------------- Factory Function -------------------- */\n\nexport function createI18n(config?: I18nConfig): I18n {\n return new I18n(config);\n}\n"],"names":["escapeHtml","str","resolvePath","obj","path","parts","value","part","index","formatList","items","locale","type","stringItems","separator","last","interpolate","template","vars","_match","key","isLength","actualKey","getPluralForm","count","n","I18n","config","messages","loader","existing","promise","error","targetLocale","locales","options","shouldEscape","message","result","date","ns","handler","loc","v","chain","lang","fallback","fallbackLang","pluralMsg","form","createI18n"],"mappings":"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"}
1
+ {"version":3,"file":"i18nit.cjs","names":["#cap"],"sources":["../src/helpers.ts","../src/intl.ts","../src/interpolate.ts","../src/i18n.ts"],"sourcesContent":["import type { MessageValue, Messages } from './types';\n\n/* -------------------- Path Resolution -------------------- */\n\n/**\n * Resolves nested properties using dot notation and bracket notation.\n * Supports: 'user.name', 'items[0]', 'user.items[0].name'\n */\nexport function resolvePath(obj: Record<string, unknown>, path: string): unknown {\n // Try direct access first (handles keys with literal dots)\n if (Object.hasOwn(obj, path)) return obj[path];\n\n const parts = path.match(/[^.[\\]]+/gu) ?? [];\n let value: unknown = obj;\n\n for (const part of parts) {\n if (value == null || typeof value !== 'object') return undefined;\n\n if (!Object.hasOwn(value as object, part)) return undefined;\n\n value = (value as Record<string, unknown>)[part];\n }\n\n return value;\n}\n\n/* -------------------- Message Value Guard -------------------- */\n\nexport const PLURAL_FORMS = new Set<string>(['zero', 'one', 'two', 'few', 'many', 'other']);\n\nexport function isMessageValue(value: unknown): value is MessageValue {\n if (typeof value === 'string') return true;\n\n if (typeof value !== 'object' || value === null || Array.isArray(value)) return false;\n\n const obj = value as Record<string, unknown>;\n\n if (!('other' in obj)) return false;\n\n const keys = Object.keys(obj);\n\n if (keys.length > PLURAL_FORMS.size) return false;\n\n return keys.every((k) => PLURAL_FORMS.has(k)) && Object.values(obj).every((v) => typeof v === 'string');\n}\n\n/* -------------------- Deep Merge -------------------- */\n\nexport function deepMerge(target: Messages, source: Messages): Messages {\n const result = { ...target };\n\n for (const [key, val] of Object.entries(source)) {\n const existing = result[key];\n\n if (!isMessageValue(val) && !isMessageValue(existing) && typeof existing === 'object' && existing !== null) {\n result[key] = deepMerge(existing as Messages, val as Messages);\n } else {\n // Clone PluralMessages objects to prevent external mutations from corrupting the catalog.\n result[key] = typeof val === 'object' && val !== null ? ({ ...(val as object) } as MessageValue) : val;\n }\n }\n\n return result;\n}\n\n/* -------------------- BoundedMap -------------------- */\n\n/**\n * Size-bounded Map that evicts the oldest entry (insertion order) when the cap is reached.\n * Used by I18n's chain cache to prevent unbounded growth in long-lived SSR singletons when\n * locale tags are derived from arbitrary user input (e.g. Accept-Language headers).\n */\nexport class BoundedMap<K, V> extends Map<K, V> {\n readonly #cap: number;\n\n constructor(cap: number) {\n super();\n this.#cap = cap;\n }\n\n override set(key: K, value: V): this {\n if (!this.has(key) && this.size >= this.#cap) {\n this.delete(this.keys().next().value as K);\n }\n\n return super.set(key, value);\n }\n}\n","import type { Locale, PluralForm } from './types';\n\n/* -------------------- Cache Container -------------------- */\n\n/** Holds all Intl formatter caches for one I18n instance — GC'd with the instance. */\nexport type IntlCaches = {\n dateFormat: Map<string, Intl.DateTimeFormat>;\n listFormat: Map<string, Intl.ListFormat>;\n numberFormat: Map<string, Intl.NumberFormat>;\n pluralRules: Map<string, Intl.PluralRules>;\n relativeTimeFormat: Map<string, Intl.RelativeTimeFormat>;\n};\n\nexport function makeIntlCaches(): IntlCaches {\n return {\n dateFormat: new Map(),\n listFormat: new Map(),\n numberFormat: new Map(),\n pluralRules: new Map(),\n relativeTimeFormat: new Map(),\n };\n}\n\n/* -------------------- Cache Helpers -------------------- */\n\nfunction intlFmt<F extends object>(cache: Map<string, F>, key: string, build: () => F): F {\n let fmt = cache.get(key);\n\n if (!fmt) {\n fmt = build();\n cache.set(key, fmt);\n }\n\n return fmt;\n}\n\n/**\n * Builds a stable string key for an Intl formatter cache.\n * Call this once per formatter construction path — not on every format call — so key\n * serialization cost is paid only on cache misses.\n */\nfunction intlKey(locale: string, options?: object): string {\n return options ? `${locale}:${JSON.stringify(options, Object.keys(options).sort())}` : locale;\n}\n\n/* -------------------- Format Functions -------------------- */\n\nexport function formatNumber(\n caches: IntlCaches,\n value: number,\n options: Intl.NumberFormatOptions | undefined,\n locale: Locale,\n): string {\n const key = intlKey(locale, options);\n\n try {\n return intlFmt(caches.numberFormat, key, () => new Intl.NumberFormat(locale, options)).format(value);\n } catch {\n return String(value);\n }\n}\n\nexport function formatDate(\n caches: IntlCaches,\n value: Date | number,\n options: Intl.DateTimeFormatOptions | undefined,\n locale: Locale,\n): string {\n const d = typeof value === 'number' ? new Date(value) : value;\n const key = intlKey(locale, options);\n\n try {\n return intlFmt(caches.dateFormat, key, () => new Intl.DateTimeFormat(locale, options)).format(d);\n } catch {\n return d.toString();\n }\n}\n\nexport function formatRelative(\n caches: IntlCaches,\n value: number,\n unit: Intl.RelativeTimeFormatUnit,\n options: Intl.RelativeTimeFormatOptions | undefined,\n locale: Locale,\n): string {\n const key = intlKey(locale, options);\n\n try {\n return intlFmt(caches.relativeTimeFormat, key, () => new Intl.RelativeTimeFormat(locale, options)).format(\n value,\n unit,\n );\n } catch {\n return String(value);\n }\n}\n\nexport function formatList(caches: IntlCaches, items: unknown[], locale: string, type: 'and' | 'or'): string {\n if (items.length === 0) return '';\n\n const stringItems = items.map(String);\n const intlType = type === 'and' ? 'conjunction' : 'disjunction';\n\n try {\n return intlFmt(\n caches.listFormat,\n `${locale}:${intlType}`,\n () => new Intl.ListFormat(locale, { style: 'long', type: intlType }),\n ).format(stringItems);\n } catch {\n // Fallback for environments without Intl.ListFormat\n if (stringItems.length === 1) return stringItems[0];\n\n if (stringItems.length === 2) return `${stringItems[0]} ${type} ${stringItems[1]}`;\n\n return `${stringItems.slice(0, -1).join(', ')} ${type} ${stringItems.at(-1)}`;\n }\n}\n\nexport function getPluralForm(caches: IntlCaches, locale: Locale, count: number): PluralForm {\n const n = Math.floor(Math.abs(count));\n\n try {\n return intlFmt(caches.pluralRules, locale, () => new Intl.PluralRules(locale)).select(n) as PluralForm;\n } catch {\n return n === 1 ? 'one' : 'other';\n }\n}\n","import type { Vars } from './types';\n\nimport { resolvePath } from './helpers';\nimport { type IntlCaches, formatList, formatNumber } from './intl';\n\n/* -------------------- Token Resolution -------------------- */\n\nfunction resolveToken(caches: IntlCaches, value: unknown, separator: string | undefined, locale: string): string {\n if (value == null) return '';\n\n if (Array.isArray(value)) {\n if (separator === 'and') return formatList(caches, value, locale, 'and');\n\n if (separator === 'or') return formatList(caches, value, locale, 'or');\n\n if (separator !== undefined) return value.map(String).join(separator);\n\n return value.map(String).join(', ');\n }\n\n if (typeof value === 'number') {\n return formatNumber(caches, value, undefined, locale);\n }\n\n return String(value);\n}\n\n/* -------------------- Interpolation -------------------- */\n\n/**\n * Interpolates variables into a template string. Supports Unicode variable names\n * via `\\p{ID_Continue}` so non-ASCII identifiers like `{prénom}` or `{名前}` work correctly.\n *\n * Supported formats: `{name}` · `{user.name}` · `{items[0]}` · `{items}` ·\n * `{items|and}` · `{items|or}` · `{items| - }` · `{items.length}`\n */\nexport function interpolate(template: string, vars: Vars, locale: string, caches: IntlCaches): string {\n if (!template.includes('{')) return template;\n\n return template.replace(/\\{([\\p{ID_Continue}\\-.[\\]]+)(?:\\|([^}]+))?\\}/gu, (_match, key: string, separator?: string) =>\n resolveToken(caches, resolvePath(vars, key), separator, locale),\n );\n}\n","import type {\n BoundI18n,\n I18n,\n I18nOptions,\n Loader,\n Locale,\n LocaleChangeEvent,\n LocaleChangeReason,\n Messages,\n MessageValue,\n NamespaceKeys,\n SwitchMode,\n Unsubscribe,\n Vars,\n} from './types';\n\nimport { BoundedMap, deepMerge, isMessageValue, resolvePath } from './helpers';\nimport { interpolate } from './interpolate';\nimport {\n formatDate,\n formatList,\n formatNumber,\n formatRelative,\n getPluralForm,\n type IntlCaches,\n makeIntlCaches,\n} from './intl';\n\nexport function createI18n<T extends Messages = Messages>(config: I18nOptions<T> = {}): I18n<T> {\n let locale = config.locale ?? 'en';\n const defaultSwitchMode: SwitchMode = config.switchMode ?? 'strict';\n const fallbacks = Array.isArray(config.fallback) ? config.fallback : config.fallback ? [config.fallback] : [];\n const catalogs = new Map<Locale, Messages>();\n const loaders = new Map<Locale, Loader>();\n const loading = new Map<Locale, Promise<void>>();\n const subscribers = new Set<(event: LocaleChangeEvent) => void>();\n const chainCache = new BoundedMap<Locale, Locale[]>(128);\n const caches: IntlCaches = makeIntlCaches();\n\n let localesCache: Locale[] | null = null;\n let loadersCache: Locale[] | null = null;\n let disposed = false;\n let batchDepth = 0;\n let pendingNotify: LocaleChangeReason | null = null;\n\n const onMissing = config.onMissing;\n const onDiagnostic = config.onDiagnostic;\n\n if (config.messages) {\n for (const [loc, messages] of Object.entries(config.messages)) {\n catalogs.set(loc, structuredClone(messages) as Messages);\n }\n }\n\n if (config.loaders) {\n for (const [loc, loader] of Object.entries(config.loaders)) {\n loaders.set(loc, loader);\n }\n }\n\n function diagnoseSubscriber(error: unknown): void {\n if (onDiagnostic) {\n onDiagnostic({ error, kind: 'subscriber-error' });\n } else {\n console.error('[i18nit] Subscriber threw:', error);\n }\n }\n\n function diagnoseLoader(error: unknown, loc: Locale): void {\n if (onDiagnostic) {\n onDiagnostic({ error, kind: 'loader-error', locale: loc });\n } else {\n console.warn('[i18nit] Loader error:', error);\n }\n }\n\n function notify(reason: LocaleChangeReason): void {\n if (batchDepth > 0) {\n if (pendingNotify !== 'locale-change') pendingNotify = reason;\n\n return;\n }\n\n const event: LocaleChangeEvent = { locale, reason };\n\n for (const listener of subscribers) {\n try {\n listener(event);\n } catch (error) {\n diagnoseSubscriber(error);\n }\n }\n }\n\n function getLocaleChain(loc: Locale): Locale[] {\n const cached = chainCache.get(loc);\n\n if (cached) return cached;\n\n const seen = new Set<Locale>();\n\n const push = (value: Locale) => {\n seen.add(value);\n\n const parts = value.split('-');\n\n for (let i = parts.length - 1; i > 0; i--) {\n seen.add(parts.slice(0, i).join('-'));\n }\n };\n\n push(loc);\n for (const fallback of fallbacks) push(fallback);\n\n const chain = [...seen];\n\n chainCache.set(loc, chain);\n\n return chain;\n }\n\n function checkOwn(key: string, loc: Locale): boolean {\n const catalog = catalogs.get(loc);\n\n if (!catalog) return false;\n\n const value = resolvePath(catalog, key);\n\n return value !== undefined && isMessageValue(value);\n }\n\n function findMessage(key: string, loc: Locale): MessageValue | undefined {\n for (const localeInChain of getLocaleChain(loc)) {\n const messages = catalogs.get(localeInChain);\n\n if (!messages) continue;\n\n const value = resolvePath(messages, key);\n\n if (value !== undefined && isMessageValue(value)) return value;\n }\n\n return undefined;\n }\n\n function translate(key: string, vars: Vars | undefined, loc: Locale): string {\n const message = findMessage(key, loc);\n\n if (message === undefined) return onMissing?.(key, loc) ?? key;\n\n if (typeof message === 'string') {\n return interpolate(message, vars ?? {}, loc, caches);\n }\n\n const context = vars ?? {};\n\n if (import.meta.env?.DEV && context.count === undefined) {\n console.warn(`[i18nit] Key \"${key}\" is a plural message but vars.count is missing. Defaulting to 0.`);\n }\n\n const count = Number(context.count ?? 0);\n const form = count === 0 && message.zero !== undefined ? 'zero' : getPluralForm(caches, loc, count);\n\n return interpolate(message[form] ?? message.other, context, loc, caches);\n }\n\n function loadOne(loc: Locale, mode: SwitchMode): Promise<void> {\n if (loading.has(loc)) return loading.get(loc)!;\n\n if (catalogs.has(loc)) return Promise.resolve();\n\n const loader = loaders.get(loc);\n\n if (!loader) {\n if (mode === 'strict') {\n return Promise.reject(new Error(`[i18nit] Missing loader for locale \"${loc}\".`));\n }\n\n return Promise.resolve();\n }\n\n const promise = (async () => {\n try {\n const messages = await loader(loc);\n\n if (!disposed) api.replace(loc, messages);\n } catch (error) {\n diagnoseLoader(error, loc);\n throw error;\n } finally {\n loading.delete(loc);\n }\n })();\n\n loading.set(loc, promise);\n\n return promise;\n }\n\n function createView<U extends Messages = Messages>(fixedLocale: Locale | null, prefix?: string): BoundI18n<U> {\n const activeLocale = (): Locale => fixedLocale ?? locale;\n const keyWithPrefix = (key: string): string => (prefix ? `${prefix}.${key}` : key);\n const t: BoundI18n<U>['t'] = (key: NamespaceKeys<U>, vars?: Record<string, unknown>) =>\n translate(keyWithPrefix(key as string), vars, activeLocale());\n\n const view = {\n currency(\n value: number,\n currency: string,\n options?: Omit<Intl.NumberFormatOptions, 'style' | 'currency'>,\n ): string {\n return formatNumber(caches, value, { ...options, currency, style: 'currency' }, activeLocale());\n },\n date(value: Date | number, options?: Intl.DateTimeFormatOptions): string {\n return formatDate(caches, value, options, activeLocale());\n },\n has(key: string): boolean {\n return findMessage(keyWithPrefix(key), activeLocale()) !== undefined;\n },\n hasOwn(key: string): boolean {\n return checkOwn(keyWithPrefix(key), activeLocale());\n },\n list(items: unknown[], type: 'and' | 'or' = 'and'): string {\n return formatList(caches, items, activeLocale(), type);\n },\n get locale(): Locale {\n return activeLocale();\n },\n number(value: number, options?: Intl.NumberFormatOptions): string {\n return formatNumber(caches, value, options, activeLocale());\n },\n relative(value: number, unit: Intl.RelativeTimeFormatUnit, options?: Intl.RelativeTimeFormatOptions): string {\n return formatRelative(caches, value, unit, options, activeLocale());\n },\n scope<K extends NamespaceKeys<U>>(ns: K): BoundI18n<U[K] & Messages> {\n const nextPrefix = prefix ? `${prefix}.${String(ns)}` : String(ns);\n\n return createView<U[K] & Messages>(fixedLocale, nextPrefix);\n },\n t,\n withLocale(nextLocale: Locale): BoundI18n<U> {\n return createView<U>(nextLocale, prefix);\n },\n } satisfies BoundI18n<U>;\n\n return view;\n }\n\n const rootView = createView<T>(null);\n\n const api = Object.create(rootView) as I18n<T>;\n\n api.add = (loc: Locale, messages: Messages): void => {\n const existing = catalogs.get(loc) ?? {};\n\n catalogs.set(loc, deepMerge(existing, messages));\n localesCache = null;\n\n if (getLocaleChain(locale).includes(loc)) notify('catalog-update');\n };\n\n api.batch = (fn: () => void): void => {\n batchDepth++;\n\n try {\n fn();\n } finally {\n batchDepth--;\n\n if (batchDepth === 0 && pendingNotify !== null) {\n const reason = pendingNotify;\n\n pendingNotify = null;\n notify(reason);\n }\n }\n };\n\n api.dispose = (): void => {\n disposed = true;\n subscribers.clear();\n catalogs.clear();\n loaders.clear();\n loading.clear();\n chainCache.clear();\n localesCache = null;\n loadersCache = null;\n };\n\n api.ensureLocale = async (loc: Locale, mode: SwitchMode = defaultSwitchMode): Promise<void> => {\n await loadOne(loc, mode);\n };\n\n api.hasLocale = (loc: Locale): boolean => catalogs.has(loc);\n api.isReady = (loc: Locale): boolean => catalogs.has(loc);\n\n Object.defineProperties(api, {\n loadableLocales: {\n get(): Locale[] {\n loadersCache ??= [...loaders.keys()];\n\n return loadersCache;\n },\n },\n locales: {\n get(): Locale[] {\n localesCache ??= [...catalogs.keys()];\n\n return localesCache;\n },\n },\n });\n\n api.registerLoader = (loc: Locale, loader: Loader): void => {\n loaders.set(loc, loader);\n loadersCache = null;\n };\n\n api.reload = async (loc: Locale): Promise<void> => {\n if (!loaders.has(loc)) {\n throw new Error(`[i18nit] Cannot reload locale \"${loc}\" without a registered loader.`);\n }\n\n catalogs.delete(loc);\n localesCache = null;\n await loadOne(loc, 'strict');\n };\n\n api.replace = (loc: Locale, messages: Messages): void => {\n catalogs.set(loc, structuredClone(messages));\n localesCache = null;\n\n if (getLocaleChain(locale).includes(loc)) notify('catalog-update');\n };\n\n api.subscribe = (listener: (event: LocaleChangeEvent) => void, immediate?: boolean): Unsubscribe => {\n subscribers.add(listener);\n\n if (immediate) {\n try {\n listener({ locale, reason: 'locale-change' });\n } catch (error) {\n diagnoseSubscriber(error);\n }\n }\n\n return () => subscribers.delete(listener);\n };\n\n api.switchLocale = async (nextLocale: Locale, mode: SwitchMode = defaultSwitchMode): Promise<void> => {\n if (nextLocale === locale) return;\n\n await loadOne(nextLocale, mode);\n locale = nextLocale;\n notify('locale-change');\n };\n\n api[Symbol.asyncDispose] = async (): Promise<void> => {\n await Promise.allSettled([...loading.values()]);\n api.dispose();\n };\n\n api[Symbol.dispose] = (): void => {\n api.dispose();\n };\n\n return api;\n}\n\nexport type { I18n };\n"],"mappings":"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"}