@vielzeug/i18nit 2.0.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.
package/README.md CHANGED
@@ -18,7 +18,7 @@ pnpm add @vielzeug/i18nit
18
18
 
19
19
  | Entry | Purpose |
20
20
  | --- | --- |
21
- | `@vielzeug/i18nit` | Main API (`createI18n`, `I18n`, exported types) |
21
+ | `@vielzeug/i18nit` | Main API (`createI18n`, exported types) |
22
22
  | `@vielzeug/i18nit/core` | Core bundle entry |
23
23
 
24
24
  ## Quick Start
@@ -46,7 +46,7 @@ i18n.t('greeting', { name: 'Alice' });
46
46
  i18n.t('inbox', { count: 0 });
47
47
  i18n.t('inbox', { count: 3 });
48
48
 
49
- i18n.locale = 'de';
49
+ await i18n.switchLocale('de');
50
50
  i18n.t('nav.home'); // falls back to en
51
51
  ```
52
52
 
@@ -57,7 +57,7 @@ i18n.t('nav.home'); // falls back to en
57
57
  - ICU-style interpolation with object/array path support
58
58
  - Plural messages (`zero/one/two/few/many/other`) via `Intl.PluralRules`
59
59
  - Locale chain fallback (`sr-Latn-RS -> sr-Latn -> sr`) + configured fallback locales
60
- - Async locale loading (`load`, `setLocale`, `registerLoader`, `reload`)
60
+ - Async locale loading (`ensureLocale`, `switchLocale`, `registerLoader`, `reload`)
61
61
  - Catalog updates (`add` deep-merge, `replace` full replace)
62
62
  - Subscription API with batched notifications (`batch`, `subscribe`)
63
63
  - Intl format helpers (`number`, `date`, `list`, `relative`, `currency`)
@@ -67,8 +67,8 @@ i18n.t('nav.home'); // falls back to en
67
67
  ## API At a Glance
68
68
 
69
69
  - `createI18n<T>(options?) => I18n<T>`
70
- - `class I18n<T> implements BoundI18n<T>`
71
70
  - `type BoundI18n<T>`
71
+ - `type I18n<T>`
72
72
  - `type I18nOptions<T>`
73
73
  - `type Messages`, `TranslationKey`, `TranslationKeyParam`, `PluralKeys`, `NamespaceKeys`
74
74
 
package/dist/helpers.cjs CHANGED
@@ -1,2 +1,2 @@
1
- function e(e,t){if(t in e)return e[t];let n=t.match(/[^.[\]]+/gu)??[],r=e;for(let e of n){if(typeof r!=`object`||!r)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;
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
2
  //# sourceMappingURL=helpers.cjs.map
@@ -1 +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 (path in obj) return obj[path];\n\n const parts = path.match(/[^.[\\]]+/gu) ?? [];\n let value: unknown = obj;\n\n for (const part of parts) {\n if (value == null || typeof value !== 'object') return undefined;\n\n value = (value as Record<string, unknown>)[part];\n }\n\n return value;\n}\n\n/* -------------------- Message Value Guard -------------------- */\n\nexport const PLURAL_FORMS = new Set<string>(['zero', 'one', 'two', 'few', 'many', 'other']);\n\nexport function isMessageValue(value: unknown): value is MessageValue {\n if (typeof value === 'string') return true;\n\n if (typeof value !== 'object' || value === null || Array.isArray(value)) return false;\n\n const obj = value as Record<string, unknown>;\n\n if (!('other' in obj)) return false;\n\n const keys = Object.keys(obj);\n\n if (keys.length > PLURAL_FORMS.size) return false;\n\n return keys.every((k) => PLURAL_FORMS.has(k)) && Object.values(obj).every((v) => typeof v === 'string');\n}\n\n/* -------------------- Deep Merge -------------------- */\n\nexport function deepMerge(target: Messages, source: Messages): Messages {\n const result = { ...target };\n\n for (const [key, val] of Object.entries(source)) {\n const existing = result[key];\n\n if (!isMessageValue(val) && !isMessageValue(existing) && typeof existing === 'object' && existing !== null) {\n result[key] = deepMerge(existing as Messages, val as Messages);\n } else {\n // Clone PluralMessages objects to prevent external mutations from corrupting the catalog.\n result[key] = typeof val === 'object' && val !== null ? ({ ...(val as object) } as MessageValue) : val;\n }\n }\n\n return result;\n}\n\n/* -------------------- BoundedMap -------------------- */\n\n/**\n * Size-bounded Map that evicts the oldest entry (insertion order) when the cap is reached.\n * Used by I18n's chain cache to prevent unbounded growth in long-lived SSR singletons when\n * locale tags are derived from arbitrary user input (e.g. Accept-Language headers).\n */\nexport class BoundedMap<K, V> extends Map<K, V> {\n readonly #cap: number;\n\n constructor(cap: number) {\n super();\n this.#cap = cap;\n }\n\n override set(key: K, value: V): this {\n if (!this.has(key) && this.size >= this.#cap) {\n this.delete(this.keys().next().value as K);\n }\n\n return super.set(key, value);\n }\n}\n"],"mappings":"AAQA,SAAgB,EAAY,EAA8B,EAAuB,CAE/E,GAAI,KAAQ,EAAK,OAAO,EAAI,GAE5B,IAAM,EAAQ,EAAK,MAAM,aAAa,EAAI,EAAE,CACxC,EAAiB,EAErB,IAAK,IAAM,KAAQ,EAAO,CACxB,GAAqB,OAAO,GAAU,WAAlC,EAA4C,OAEhD,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"}
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"}
@@ -1 +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,CAc/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"}
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"}
package/dist/helpers.js CHANGED
@@ -1,9 +1,9 @@
1
1
  //#region src/helpers.ts
2
2
  function e(e, t) {
3
- if (t in e) return e[t];
3
+ if (Object.hasOwn(e, t)) return e[t];
4
4
  let n = t.match(/[^.[\]]+/gu) ?? [], r = e;
5
5
  for (let e of n) {
6
- if (typeof r != "object" || !r) return;
6
+ if (typeof r != "object" || !r || !Object.hasOwn(r, e)) return;
7
7
  r = r[e];
8
8
  }
9
9
  return r;
@@ -1 +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 (path in obj) return obj[path];\n\n const parts = path.match(/[^.[\\]]+/gu) ?? [];\n let value: unknown = obj;\n\n for (const part of parts) {\n if (value == null || typeof value !== 'object') return undefined;\n\n value = (value as Record<string, unknown>)[part];\n }\n\n return value;\n}\n\n/* -------------------- Message Value Guard -------------------- */\n\nexport const PLURAL_FORMS = new Set<string>(['zero', 'one', 'two', 'few', 'many', 'other']);\n\nexport function isMessageValue(value: unknown): value is MessageValue {\n if (typeof value === 'string') return true;\n\n if (typeof value !== 'object' || value === null || Array.isArray(value)) return false;\n\n const obj = value as Record<string, unknown>;\n\n if (!('other' in obj)) return false;\n\n const keys = Object.keys(obj);\n\n if (keys.length > PLURAL_FORMS.size) return false;\n\n return keys.every((k) => PLURAL_FORMS.has(k)) && Object.values(obj).every((v) => typeof v === 'string');\n}\n\n/* -------------------- Deep Merge -------------------- */\n\nexport function deepMerge(target: Messages, source: Messages): Messages {\n const result = { ...target };\n\n for (const [key, val] of Object.entries(source)) {\n const existing = result[key];\n\n if (!isMessageValue(val) && !isMessageValue(existing) && typeof existing === 'object' && existing !== null) {\n result[key] = deepMerge(existing as Messages, val as Messages);\n } else {\n // Clone PluralMessages objects to prevent external mutations from corrupting the catalog.\n result[key] = typeof val === 'object' && val !== null ? ({ ...(val as object) } as MessageValue) : val;\n }\n }\n\n return result;\n}\n\n/* -------------------- BoundedMap -------------------- */\n\n/**\n * Size-bounded Map that evicts the oldest entry (insertion order) when the cap is reached.\n * Used by I18n's chain cache to prevent unbounded growth in long-lived SSR singletons when\n * locale tags are derived from arbitrary user input (e.g. Accept-Language headers).\n */\nexport class BoundedMap<K, V> extends Map<K, V> {\n readonly #cap: number;\n\n constructor(cap: number) {\n super();\n this.#cap = cap;\n }\n\n override set(key: K, value: V): this {\n if (!this.has(key) && this.size >= this.#cap) {\n this.delete(this.keys().next().value as K);\n }\n\n return super.set(key, value);\n }\n}\n"],"mappings":";AAQA,SAAgB,EAAY,GAA8B,GAAuB;AAE/E,KAAI,KAAQ,EAAK,QAAO,EAAI;CAE5B,IAAM,IAAQ,EAAK,MAAM,aAAa,IAAI,EAAE,EACxC,IAAiB;AAErB,MAAK,IAAM,KAAQ,GAAO;AACxB,MAAqB,OAAO,KAAU,aAAlC,EAA4C;AAEhD,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"}
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 CHANGED
@@ -1,2 +1,2 @@
1
- const e=require(`./core.cjs`),t=require(`./helpers.cjs`),n=require(`./intl.cjs`),r=require(`./interpolate.cjs`);var i=class{#e;#t;#n=new Map;#r=new Map;#i=new Map;#a=new Set;#o=new t.BoundedMap(128);#s;#c;#l=null;#u=null;#d=!1;#f=0;#p=null;#m;#h=n.makeIntlCaches();#g;constructor({fallback:r,loaders:i,locale:a=`en`,messages:o,onDiagnostic:s,onMissing:c}={}){if(this.#e=a,this.#t=Array.isArray(r)?r:r?[r]:[],this.#s=c,this.#c=s,o)for(let[e,t]of Object.entries(o))this.#n.set(e,structuredClone(t));if(i)for(let[e,t]of Object.entries(i))this.#r.set(e,t);this.#m={checkOwn:(e,n)=>{let r=this.#n.get(n);if(!r)return!1;let i=t.resolvePath(r,e);return i!==void 0&&t.isMessageValue(i)},findMessage:(e,t)=>this.#b(e,t),formatDate:(e,t,r)=>n.formatDate(this.#h,e,t,r),formatList:(e,t,r)=>n.formatList(this.#h,e,t,r),formatNumber:(e,t,r)=>n.formatNumber(this.#h,e,t,r),formatRelative:(e,t,r,i)=>n.formatRelative(this.#h,e,t,r,i),getLocale:()=>this.#e,translate:(e,t,n)=>this.#S(e,t,n)},this.#g=new e.BoundView(this.#m,null)}get locale(){return this.#e}get locales(){return this.#l??=[...this.#n.keys()],this.#l}set locale(e){this.#e!==e&&(this.#e=e,this.#y(`locale-change`))}async setLocale(e){e!==this.#e&&(await this.load(e),this.locale=e)}add(e,n){let r=this.#n.get(e)??{};this.#n.set(e,t.deepMerge(r,n)),this.#l=null,this.#x(this.#e).includes(e)&&this.#y(`catalog-update`)}replace(e,t){this.#n.set(e,structuredClone(t)),this.#l=null,this.#x(this.#e).includes(e)&&this.#y(`catalog-update`)}has(e){return this.#g.has(e)}hasOwn(e){return this.#g.hasOwn(e)}hasLocale(e){return this.#n.has(e)}async load(...e){await Promise.all(e.map(e=>this.#C(e)))}async reload(e){this.#r.has(e)&&(this.#n.delete(e),this.#l=null,await this.#C(e))}registerLoader(e,t){this.#r.set(e,t),this.#u=null}get loadableLocales(){return this.#u??=[...this.#r.keys()],this.#u}t(e,t){return this.#g.t(e,t)}number(e,t){return this.#g.number(e,t)}date(e,t){return this.#g.date(e,t)}list(e,t=`and`){return this.#g.list(e,t)}relative(e,t,n){return this.#g.relative(e,t,n)}currency(e,t,n){return this.#g.currency(e,t,n)}withLocale(e){return this.#g.withLocale(e)}scope(e){return this.#g.scope(e)}batch(e){this.#f++;try{e()}finally{if(this.#f--,this.#f===0&&this.#p!==null){let e=this.#p;this.#p=null,this.#y(e)}}}subscribe(e,t){if(this.#a.add(e),t)try{e({locale:this.#e,reason:`locale-change`})}catch(e){this.#_(e)}return()=>this.#a.delete(e)}dispose(){this.#d=!0,this.#a.clear(),this.#n.clear(),this.#r.clear(),this.#i.clear(),this.#o.clear(),this.#l=null,this.#u=null}[Symbol.dispose](){this.dispose()}async[Symbol.asyncDispose](){await Promise.allSettled([...this.#i.values()]),this.dispose()}#_(e){this.#c?this.#c({error:e,kind:`subscriber-error`}):console.error(`[i18nit] Subscriber threw:`,e)}#v(e,t){this.#c?this.#c({error:e,kind:`loader-error`,locale:t}):console.warn(`[i18nit] Loader error:`,e)}#y(e){if(this.#f>0){this.#p!==`locale-change`&&(this.#p=e);return}let t={locale:this.#e,reason:e};for(let e of this.#a)try{e(t)}catch(e){this.#_(e)}}#b(e,n){for(let r of this.#x(n)){let n=this.#n.get(r);if(!n)continue;let i=t.resolvePath(n,e);if(i!==void 0&&t.isMessageValue(i))return i}}#x(e){let t=this.#o.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 this.#t)r(e);let i=[...n];return this.#o.set(e,i),i}#S(e,t,i){let a=this.#b(e,i);if(a===void 0)return this.#s?.(e,i)??e;if(typeof a==`string`)return r.interpolate(a,t??{},i,this.#h);let o=t??{},s=Number(o.count??0);return r.interpolate(a[s===0&&a.zero!==void 0?`zero`:n.getPluralForm(this.#h,i,s)]??a.other,o,i,this.#h)}#C(e){if(this.#i.has(e))return this.#i.get(e);if(this.#n.has(e))return Promise.resolve();let t=this.#r.get(e);if(!t)return Promise.resolve();let n=(async()=>{try{let n=await t(e);this.#d||this.replace(e,n)}catch(t){throw this.#v(t,e),t}finally{this.#i.delete(e)}})();return this.#i.set(e,n),n}};function a(e){return new i(e)}exports.I18n=i,exports.createI18n=a;
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
2
  //# sourceMappingURL=i18n.cjs.map
package/dist/i18n.cjs.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"i18n.cjs","names":["#caches","#view","#locale","#fallbacks","#onMissing","#onDiagnostic","#catalogs","#loaders","#core","#findMessage","#translate","#localesCache","#notify","#getLocaleChain","#loadOne","#loadersCache","#batchDepth","#pendingNotify","#subscribers","#diagnoseSubscriber","#disposed","#loading","#chainCache","#diagnoseLoader"],"sources":["../src/i18n.ts"],"sourcesContent":["import type {\n BoundI18n,\n DiagnosticEvent,\n I18nOptions,\n Loader,\n Locale,\n LocaleChangeEvent,\n LocaleChangeReason,\n MessageValue,\n Messages,\n NamespaceKeys,\n TranslationKeyParam,\n Unsubscribe,\n Vars,\n} from './types';\n\nimport { BoundView, type I18nCore } from './core';\nimport { BoundedMap, deepMerge, isMessageValue, resolvePath } from './helpers';\nimport { interpolate } from './interpolate';\nimport {\n type IntlCaches,\n formatDate,\n formatList,\n formatNumber,\n formatRelative,\n getPluralForm,\n makeIntlCaches,\n} from './intl';\n\nexport class I18n<T extends Messages = Messages> implements BoundI18n<T> {\n #locale: Locale;\n #fallbacks: Locale[];\n #catalogs = new Map<Locale, Messages>();\n #loaders = new Map<Locale, Loader>();\n #loading = new Map<Locale, Promise<void>>();\n #subscribers = new Set<(event: LocaleChangeEvent) => void>();\n /** Bounded at 128 entries — prevents unbounded growth when locale tags come from user input. */\n #chainCache = new BoundedMap<Locale, Locale[]>(128);\n #onMissing?: (key: string, locale: Locale) => string | undefined;\n #onDiagnostic?: (event: DiagnosticEvent) => void;\n #localesCache: Locale[] | null = null;\n #loadersCache: Locale[] | null = null;\n #disposed = false;\n #batchDepth = 0;\n #pendingNotify: LocaleChangeReason | null = null;\n #core: I18nCore;\n\n // Instance-scoped Intl caches — GC'd with the instance (important for SSR with many locales).\n readonly #caches: IntlCaches = makeIntlCaches();\n\n /** Internal BoundView — I18n delegates its BoundI18n surface here to avoid duplicating every method. */\n readonly #view: BoundView<T>;\n\n constructor({ fallback, loaders, locale = 'en', messages, onDiagnostic, onMissing }: I18nOptions<T> = {}) {\n this.#locale = locale;\n this.#fallbacks = Array.isArray(fallback) ? fallback : fallback ? [fallback] : [];\n this.#onMissing = onMissing;\n this.#onDiagnostic = onDiagnostic;\n\n if (messages) {\n for (const [l, m] of Object.entries(messages)) {\n // Deep-clone at init so external mutations to the source object can't corrupt the catalog.\n this.#catalogs.set(l, structuredClone(m) as Messages);\n }\n }\n\n if (loaders) for (const [l, fn] of Object.entries(loaders)) this.#loaders.set(l, fn);\n\n this.#core = {\n checkOwn: (key: string, locale: Locale) => {\n const catalog = this.#catalogs.get(locale);\n\n if (!catalog) return false;\n\n const value = resolvePath(catalog, key);\n\n return value !== undefined && isMessageValue(value);\n },\n findMessage: (key: string, locale: Locale) => this.#findMessage(key, locale),\n formatDate: (value, options, locale) => formatDate(this.#caches, value, options, locale),\n formatList: (items, locale, type) => formatList(this.#caches, items, locale, type),\n formatNumber: (value, options, locale) => formatNumber(this.#caches, value, options, locale),\n formatRelative: (value, unit, options, locale) => formatRelative(this.#caches, value, unit, options, locale),\n getLocale: () => this.#locale,\n translate: (key, vars, locale) => this.#translate(key, vars, locale),\n };\n\n this.#view = new BoundView<T>(this.#core, null);\n }\n\n /* -------------------- Locale -------------------- */\n\n get locale(): Locale {\n return this.#locale;\n }\n\n get locales(): Locale[] {\n this.#localesCache ??= [...this.#catalogs.keys()];\n\n return this.#localesCache;\n }\n\n set locale(value: Locale) {\n if (this.#locale === value) return;\n\n if (import.meta.env?.DEV && !this.#catalogs.has(value) && this.#loaders.has(value)) {\n console.warn(\n `[i18nit] locale \"${value}\" has a registered loader but is not loaded. ` +\n 'Use setLocale() to load and switch atomically.',\n );\n }\n\n this.#locale = value;\n this.#notify('locale-change');\n }\n\n async setLocale(locale: Locale): Promise<void> {\n if (locale === this.#locale) return;\n\n await this.load(locale);\n this.locale = locale;\n }\n\n /* -------------------- Message Management -------------------- */\n\n /** Deep-merges messages into an existing locale catalog. */\n add(locale: Locale, messages: Messages): void {\n const existing = this.#catalogs.get(locale) ?? {};\n\n this.#catalogs.set(locale, deepMerge(existing, messages));\n this.#localesCache = null;\n\n if (this.#getLocaleChain(this.#locale).includes(locale)) this.#notify('catalog-update');\n }\n\n /** Replaces the entire locale catalog for `locale` with a deep clone of `messages`. */\n replace(locale: Locale, messages: Messages): void {\n this.#catalogs.set(locale, structuredClone(messages));\n this.#localesCache = null;\n\n if (this.#getLocaleChain(this.#locale).includes(locale)) this.#notify('catalog-update');\n }\n\n has(key: string): boolean {\n return this.#view.has(key);\n }\n\n /** Like `has()`, but only checks the exact locale without walking the fallback chain. */\n hasOwn(key: string): boolean {\n return this.#view.hasOwn(key);\n }\n\n hasLocale(locale: Locale): boolean {\n return this.#catalogs.has(locale);\n }\n\n /* -------------------- Async Loaders -------------------- */\n\n async load(...locales: Locale[]): Promise<void> {\n await Promise.all(locales.map((locale) => this.#loadOne(locale)));\n }\n\n /**\n * Force-reloads a locale catalog even if already populated. Useful for hot-reload and forced bundle refresh.\n * No-op (with a dev warning) when no loader is registered for the locale, to prevent silently clearing the catalog.\n */\n async reload(locale: Locale): Promise<void> {\n if (!this.#loaders.has(locale)) {\n if (import.meta.env?.DEV) {\n console.warn(`[i18nit] reload(\"${locale}\") skipped — no loader registered for this locale.`);\n }\n\n return;\n }\n\n this.#catalogs.delete(locale);\n this.#localesCache = null;\n await this.#loadOne(locale);\n }\n\n registerLoader(locale: Locale, loader: Loader): void {\n this.#loaders.set(locale, loader);\n this.#loadersCache = null;\n }\n\n /** Returns the locale keys for which a loader has been registered. */\n get loadableLocales(): Locale[] {\n this.#loadersCache ??= [...this.#loaders.keys()];\n\n return this.#loadersCache;\n }\n\n /* -------------------- BoundI18n surface (delegated to #view) -------------------- */\n\n /**\n * Translates a key with optional interpolation variables.\n * Locale must be loaded first via `load()` or provided via `messages` in config.\n * For a per-call locale override use `withLocale(locale).t(key, vars)`.\n */\n t(key: TranslationKeyParam<T>, vars?: Vars): string {\n return this.#view.t(key, vars);\n }\n\n number(value: number, options?: Intl.NumberFormatOptions): string {\n return this.#view.number(value, options);\n }\n\n date(value: Date | number, options?: Intl.DateTimeFormatOptions): string {\n return this.#view.date(value, options);\n }\n\n list(items: unknown[], type: 'and' | 'or' = 'and'): string {\n return this.#view.list(items, type);\n }\n\n relative(value: number, unit: Intl.RelativeTimeFormatUnit, options?: Intl.RelativeTimeFormatOptions): string {\n return this.#view.relative(value, unit, options);\n }\n\n currency(value: number, currency: string, options?: Omit<Intl.NumberFormatOptions, 'style' | 'currency'>): string {\n return this.#view.currency(value, currency, options);\n }\n\n /**\n * Returns a bound interface that translates in the given locale without\n * changing the active locale on the instance. Useful for SSR and\n * multi-locale rendering in a single pass.\n */\n withLocale(locale: Locale): BoundI18n<T> {\n return this.#view.withLocale(locale);\n }\n\n /**\n * Returns a translator scoped to a key namespace prefix. Reacts to locale changes on the\n * instance. When `T` is a concrete message type, the returned `BoundI18n` is narrowed to\n * the subtree type so `t()` autocomplete works within the scope.\n * Only keys whose values are nested message objects are valid scope targets.\n */\n scope<K extends NamespaceKeys<T>>(ns: K): BoundI18n<T[K] & Messages> {\n return this.#view.scope(ns);\n }\n\n /* -------------------- Subscriptions -------------------- */\n\n /**\n * Executes `fn` while deferring subscriber notifications. A single notification fires\n * after `fn` completes, collapsing any number of `add()` / `replace()` calls made within.\n * Nested `batch()` calls are supported; notification fires when the outermost batch exits.\n * If both a locale change and a catalog update are triggered, `'locale-change'` takes priority.\n *\n * @remarks\n * `batch()` is synchronous. Async operations (e.g. `load()`) started inside `fn` complete\n * after the batch exits and will notify subscribers individually. To batch-load multiple\n * locales and notify once, await `load()` before entering the batch:\n * ```ts\n * await i18n.load('fr', 'de');\n * i18n.batch(() => { i18n.locale = 'fr'; });\n * ```\n */\n batch(fn: () => void): void {\n this.#batchDepth++;\n\n try {\n fn();\n } finally {\n this.#batchDepth--;\n\n if (this.#batchDepth === 0 && this.#pendingNotify !== null) {\n const reason = this.#pendingNotify;\n\n this.#pendingNotify = null;\n this.#notify(reason);\n }\n }\n }\n\n subscribe(listener: (event: LocaleChangeEvent) => void, immediate?: boolean): Unsubscribe {\n this.#subscribers.add(listener);\n\n if (immediate) {\n try {\n listener({ locale: this.#locale, reason: 'locale-change' });\n } catch (err) {\n this.#diagnoseSubscriber(err);\n }\n }\n\n return () => this.#subscribers.delete(listener);\n }\n\n /** Releases all resources held by this instance. */\n dispose(): void {\n this.#disposed = true;\n this.#subscribers.clear();\n this.#catalogs.clear();\n this.#loaders.clear();\n this.#loading.clear();\n this.#chainCache.clear();\n this.#localesCache = null;\n this.#loadersCache = null;\n }\n\n /** Enables `using i18n = createI18n(...)` for deterministic resource release. */\n [Symbol.dispose](): void {\n this.dispose();\n }\n\n /**\n * Awaits any in-flight `load()` calls and then releases all resources.\n * Enables `await using i18n = createI18n(...)` in environments that support `Symbol.asyncDispose`.\n */\n async [Symbol.asyncDispose](): Promise<void> {\n await Promise.allSettled([...this.#loading.values()]);\n this.dispose();\n }\n\n /* -------------------- Private -------------------- */\n\n #diagnoseSubscriber(error: unknown): void {\n if (this.#onDiagnostic) {\n this.#onDiagnostic({ error, kind: 'subscriber-error' });\n } else {\n console.error('[i18nit] Subscriber threw:', error);\n }\n }\n\n #diagnoseLoader(error: unknown, locale: Locale): void {\n if (this.#onDiagnostic) {\n this.#onDiagnostic({ error, kind: 'loader-error', locale });\n } else {\n console.warn('[i18nit] Loader error:', error);\n }\n }\n\n #notify(reason: LocaleChangeReason): void {\n if (this.#batchDepth > 0) {\n // 'locale-change' takes priority over 'catalog-update' if both occur in one batch\n if (this.#pendingNotify !== 'locale-change') this.#pendingNotify = reason;\n\n return;\n }\n\n const event: LocaleChangeEvent = { locale: this.#locale, reason };\n\n for (const listener of this.#subscribers) {\n try {\n listener(event);\n } catch (err) {\n this.#diagnoseSubscriber(err);\n }\n }\n }\n\n #findMessage(key: string, locale: Locale): MessageValue | undefined {\n for (const loc of this.#getLocaleChain(locale)) {\n const messages = this.#catalogs.get(loc);\n\n if (!messages) continue;\n\n const value = resolvePath(messages, key);\n\n if (value !== undefined && isMessageValue(value)) return value;\n }\n\n return undefined;\n }\n\n #getLocaleChain(locale: Locale): Locale[] {\n const cached = this.#chainCache.get(locale);\n\n if (cached) return cached;\n\n const seen = new Set<Locale>();\n const push = (l: Locale) => {\n seen.add(l);\n\n const parts = l.split('-');\n\n for (let i = parts.length - 1; i > 0; i--) {\n seen.add(parts.slice(0, i).join('-'));\n }\n };\n\n push(locale);\n for (const fallback of this.#fallbacks) push(fallback);\n\n const chain = [...seen];\n\n this.#chainCache.set(locale, chain);\n\n return chain;\n }\n\n #translate(key: string, vars: Vars | undefined, locale: Locale): string {\n const message = this.#findMessage(key, locale);\n\n if (message === undefined) return this.#onMissing?.(key, locale) ?? key;\n\n if (typeof message === 'string') return interpolate(message, vars ?? {}, locale, this.#caches);\n\n const v = vars ?? {};\n\n if (import.meta.env?.DEV && v.count === undefined) {\n console.warn(`[i18nit] Key \"${key}\" is a plural message but vars.count is missing. Defaulting to 0.`);\n }\n\n const count = Number(v.count ?? 0);\n const form = count === 0 && message.zero !== undefined ? 'zero' : getPluralForm(this.#caches, locale, count);\n\n return interpolate(message[form] ?? message.other, v, locale, this.#caches);\n }\n\n #loadOne(locale: Locale): Promise<void> {\n if (this.#loading.has(locale)) return this.#loading.get(locale)!;\n\n if (this.#catalogs.has(locale)) return Promise.resolve();\n\n const loader = this.#loaders.get(locale);\n\n if (!loader) return Promise.resolve();\n\n const promise = (async () => {\n try {\n const messages = await loader(locale);\n\n // Use replace() so the loader result is the authoritative catalog for this locale,\n // not merged on top of any pre-seeded static messages.\n if (!this.#disposed) this.replace(locale, messages);\n } catch (error) {\n this.#diagnoseLoader(error, locale);\n throw error;\n } finally {\n this.#loading.delete(locale);\n }\n })();\n\n this.#loading.set(locale, promise);\n\n return promise;\n }\n}\n\nexport function createI18n<T extends Messages = Messages>(config?: I18nOptions<T>): I18n<T> {\n return new I18n<T>(config);\n}\n"],"mappings":"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"}
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 CHANGED
@@ -1,78 +1,4 @@
1
- import type { BoundI18n, I18nOptions, Loader, Locale, LocaleChangeEvent, Messages, NamespaceKeys, TranslationKeyParam, Unsubscribe, Vars } from './types';
2
- export declare class I18n<T extends Messages = Messages> implements BoundI18n<T> {
3
- #private;
4
- constructor({ fallback, loaders, locale, messages, onDiagnostic, onMissing }?: I18nOptions<T>);
5
- get locale(): Locale;
6
- get locales(): Locale[];
7
- set locale(value: Locale);
8
- setLocale(locale: Locale): Promise<void>;
9
- /** Deep-merges messages into an existing locale catalog. */
10
- add(locale: Locale, messages: Messages): void;
11
- /** Replaces the entire locale catalog for `locale` with a deep clone of `messages`. */
12
- replace(locale: Locale, messages: Messages): void;
13
- has(key: string): boolean;
14
- /** Like `has()`, but only checks the exact locale without walking the fallback chain. */
15
- hasOwn(key: string): boolean;
16
- hasLocale(locale: Locale): boolean;
17
- load(...locales: Locale[]): Promise<void>;
18
- /**
19
- * Force-reloads a locale catalog even if already populated. Useful for hot-reload and forced bundle refresh.
20
- * No-op (with a dev warning) when no loader is registered for the locale, to prevent silently clearing the catalog.
21
- */
22
- reload(locale: Locale): Promise<void>;
23
- registerLoader(locale: Locale, loader: Loader): void;
24
- /** Returns the locale keys for which a loader has been registered. */
25
- get loadableLocales(): Locale[];
26
- /**
27
- * Translates a key with optional interpolation variables.
28
- * Locale must be loaded first via `load()` or provided via `messages` in config.
29
- * For a per-call locale override use `withLocale(locale).t(key, vars)`.
30
- */
31
- t(key: TranslationKeyParam<T>, vars?: Vars): string;
32
- number(value: number, options?: Intl.NumberFormatOptions): string;
33
- date(value: Date | number, options?: Intl.DateTimeFormatOptions): string;
34
- list(items: unknown[], type?: 'and' | 'or'): string;
35
- relative(value: number, unit: Intl.RelativeTimeFormatUnit, options?: Intl.RelativeTimeFormatOptions): string;
36
- currency(value: number, currency: string, options?: Omit<Intl.NumberFormatOptions, 'style' | 'currency'>): string;
37
- /**
38
- * Returns a bound interface that translates in the given locale without
39
- * changing the active locale on the instance. Useful for SSR and
40
- * multi-locale rendering in a single pass.
41
- */
42
- withLocale(locale: Locale): BoundI18n<T>;
43
- /**
44
- * Returns a translator scoped to a key namespace prefix. Reacts to locale changes on the
45
- * instance. When `T` is a concrete message type, the returned `BoundI18n` is narrowed to
46
- * the subtree type so `t()` autocomplete works within the scope.
47
- * Only keys whose values are nested message objects are valid scope targets.
48
- */
49
- scope<K extends NamespaceKeys<T>>(ns: K): BoundI18n<T[K] & Messages>;
50
- /**
51
- * Executes `fn` while deferring subscriber notifications. A single notification fires
52
- * after `fn` completes, collapsing any number of `add()` / `replace()` calls made within.
53
- * Nested `batch()` calls are supported; notification fires when the outermost batch exits.
54
- * If both a locale change and a catalog update are triggered, `'locale-change'` takes priority.
55
- *
56
- * @remarks
57
- * `batch()` is synchronous. Async operations (e.g. `load()`) started inside `fn` complete
58
- * after the batch exits and will notify subscribers individually. To batch-load multiple
59
- * locales and notify once, await `load()` before entering the batch:
60
- * ```ts
61
- * await i18n.load('fr', 'de');
62
- * i18n.batch(() => { i18n.locale = 'fr'; });
63
- * ```
64
- */
65
- batch(fn: () => void): void;
66
- subscribe(listener: (event: LocaleChangeEvent) => void, immediate?: boolean): Unsubscribe;
67
- /** Releases all resources held by this instance. */
68
- dispose(): void;
69
- /** Enables `using i18n = createI18n(...)` for deterministic resource release. */
70
- [Symbol.dispose](): void;
71
- /**
72
- * Awaits any in-flight `load()` calls and then releases all resources.
73
- * Enables `await using i18n = createI18n(...)` in environments that support `Symbol.asyncDispose`.
74
- */
75
- [Symbol.asyncDispose](): Promise<void>;
76
- }
1
+ import type { I18n, I18nOptions, Messages } from './types';
77
2
  export declare function createI18n<T extends Messages = Messages>(config?: I18nOptions<T>): I18n<T>;
3
+ export type { I18n };
78
4
  //# sourceMappingURL=i18n.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"i18n.d.ts","sourceRoot":"","sources":["../src/i18n.ts"],"names":[],"mappings":"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"}
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"}