@vielzeug/lingua 1.1.3 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (110) hide show
  1. package/README.md +15 -54
  2. package/dist/_bounded-cache.cjs +2 -0
  3. package/dist/_bounded-cache.cjs.map +1 -0
  4. package/dist/_bounded-cache.d.ts +2 -0
  5. package/dist/_bounded-cache.d.ts.map +1 -0
  6. package/dist/_bounded-cache.js +10 -0
  7. package/dist/_bounded-cache.js.map +1 -0
  8. package/dist/_catalog.cjs +1 -1
  9. package/dist/_catalog.cjs.map +1 -1
  10. package/dist/_catalog.d.ts +13 -16
  11. package/dist/_catalog.d.ts.map +1 -1
  12. package/dist/_catalog.js +43 -36
  13. package/dist/_catalog.js.map +1 -1
  14. package/dist/_dev.cjs +1 -1
  15. package/dist/_dev.cjs.map +1 -1
  16. package/dist/_dev.js +3 -8
  17. package/dist/_dev.js.map +1 -1
  18. package/dist/_locale.cjs +2 -0
  19. package/dist/_locale.cjs.map +1 -0
  20. package/dist/_locale.d.ts +5 -0
  21. package/dist/_locale.d.ts.map +1 -0
  22. package/dist/_locale.js +26 -0
  23. package/dist/_locale.js.map +1 -0
  24. package/dist/_resources.cjs +2 -0
  25. package/dist/_resources.cjs.map +1 -0
  26. package/dist/_resources.d.ts +10 -0
  27. package/dist/_resources.d.ts.map +1 -0
  28. package/dist/_resources.js +51 -0
  29. package/dist/_resources.js.map +1 -0
  30. package/dist/_template.cjs +2 -0
  31. package/dist/_template.cjs.map +1 -0
  32. package/dist/_template.d.ts +8 -0
  33. package/dist/_template.d.ts.map +1 -0
  34. package/dist/_template.js +32 -0
  35. package/dist/_template.js.map +1 -0
  36. package/dist/errors.cjs +1 -1
  37. package/dist/errors.cjs.map +1 -1
  38. package/dist/errors.d.ts +6 -18
  39. package/dist/errors.d.ts.map +1 -1
  40. package/dist/errors.js +3 -9
  41. package/dist/errors.js.map +1 -1
  42. package/dist/format.cjs +1 -1
  43. package/dist/format.cjs.map +1 -1
  44. package/dist/format.d.ts +0 -9
  45. package/dist/format.d.ts.map +1 -1
  46. package/dist/format.js +40 -46
  47. package/dist/format.js.map +1 -1
  48. package/dist/i18n.cjs +1 -1
  49. package/dist/i18n.cjs.map +1 -1
  50. package/dist/i18n.d.ts +25 -39
  51. package/dist/i18n.d.ts.map +1 -1
  52. package/dist/i18n.js +65 -218
  53. package/dist/i18n.js.map +1 -1
  54. package/dist/index.cjs +1 -1
  55. package/dist/index.d.ts +4 -12
  56. package/dist/index.d.ts.map +1 -1
  57. package/dist/index.js +4 -4
  58. package/dist/lingua.cjs +1 -1
  59. package/dist/lingua.cjs.map +1 -1
  60. package/dist/lingua.iife.js +1 -1
  61. package/dist/lingua.iife.js.map +1 -1
  62. package/dist/lingua.js +1 -1
  63. package/dist/lingua.js.map +1 -1
  64. package/dist/translator.cjs +2 -0
  65. package/dist/translator.cjs.map +1 -0
  66. package/dist/translator.d.ts +20 -0
  67. package/dist/translator.d.ts.map +1 -0
  68. package/dist/translator.js +61 -0
  69. package/dist/translator.js.map +1 -0
  70. package/dist/types.d.ts +53 -0
  71. package/dist/types.d.ts.map +1 -0
  72. package/dist/validate.cjs +1 -1
  73. package/dist/validate.cjs.map +1 -1
  74. package/dist/validate.d.ts +5 -36
  75. package/dist/validate.d.ts.map +1 -1
  76. package/dist/validate.js +21 -52
  77. package/dist/validate.js.map +1 -1
  78. package/package.json +4 -4
  79. package/dist/_catalog-store.cjs +0 -2
  80. package/dist/_catalog-store.cjs.map +0 -1
  81. package/dist/_catalog-store.d.ts +0 -33
  82. package/dist/_catalog-store.d.ts.map +0 -1
  83. package/dist/_catalog-store.js +0 -74
  84. package/dist/_catalog-store.js.map +0 -1
  85. package/dist/_chain.cjs +0 -2
  86. package/dist/_chain.cjs.map +0 -1
  87. package/dist/_chain.d.ts +0 -21
  88. package/dist/_chain.d.ts.map +0 -1
  89. package/dist/_chain.js +0 -45
  90. package/dist/_chain.js.map +0 -1
  91. package/dist/_constants.cjs +0 -2
  92. package/dist/_constants.cjs.map +0 -1
  93. package/dist/_constants.d.ts +0 -3
  94. package/dist/_constants.d.ts.map +0 -1
  95. package/dist/_constants.js +0 -17
  96. package/dist/_constants.js.map +0 -1
  97. package/dist/_namespace-store.cjs +0 -2
  98. package/dist/_namespace-store.cjs.map +0 -1
  99. package/dist/_namespace-store.d.ts +0 -31
  100. package/dist/_namespace-store.d.ts.map +0 -1
  101. package/dist/_namespace-store.js +0 -54
  102. package/dist/_namespace-store.js.map +0 -1
  103. package/dist/i18n-types.d.ts +0 -250
  104. package/dist/i18n-types.d.ts.map +0 -1
  105. package/dist/template.cjs +0 -2
  106. package/dist/template.cjs.map +0 -1
  107. package/dist/template.d.ts +0 -15
  108. package/dist/template.d.ts.map +0 -1
  109. package/dist/template.js +0 -49
  110. package/dist/template.js.map +0 -1
package/README.md CHANGED
@@ -1,86 +1,47 @@
1
1
  # @vielzeug/lingua
2
2
 
3
- > Minimal i18n runtime with typed keys, deterministic locale fallback, and framework-agnostic reactive subscriptions.
3
+ > Explicit, framework-neutral localization for TypeScript.
4
4
 
5
- [![npm version](https://img.shields.io/npm/v/@vielzeug/lingua)](https://www.npmjs.com/package/@vielzeug/lingua) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
6
-
7
- <details>
8
- <summary>Quick Reference</summary>
9
-
10
- **Package:** `@vielzeug/lingua` &nbsp;·&nbsp; **Category:** i18n
11
-
12
- **Key exports:** `createI18n` · `createFormatter` (from `@vielzeug/lingua/format`) · `validateCatalog` (from `@vielzeug/lingua/validate`)
13
-
14
- **When to use:** Minimal i18n runtime with typed keys, deterministic locale fallback, and framework-agnostic reactive subscriptions.
15
-
16
- **Related:** [@vielzeug/ripple](https://vielzeug.dev/ripple/) · [@vielzeug/wayfinder](https://vielzeug.dev/wayfinder/) · [@vielzeug/courier](https://vielzeug.dev/courier/)
17
-
18
- </details>
19
-
20
- `@vielzeug/lingua` is part of Vielzeug and ships as a zero-dependency TypeScript package with ESM+CJS output.
5
+ `@vielzeug/lingua` provides an immutable translator for fixed locales and a stateful i18n store for locale changes and optional lazy catalogs.
21
6
 
22
7
  ## Installation
23
8
 
24
9
  ```sh
25
10
  pnpm add @vielzeug/lingua
26
- npm install @vielzeug/lingua
27
- yarn add @vielzeug/lingua
28
11
  ```
29
12
 
30
13
  ## Quick Start
31
14
 
32
15
  ```ts
33
- import { createI18n } from '@vielzeug/lingua';
34
- import { createFormatter } from '@vielzeug/lingua/format';
16
+ import { createTranslationStore } from '@vielzeug/lingua';
35
17
 
36
- const i18n = createI18n({
37
- locale: 'en',
38
- fallback: 'en',
18
+ const translations = createTranslationStore({
39
19
  catalogs: {
40
20
  en: {
41
21
  greeting: 'Hello, {name}!',
42
- inbox: {
43
- zero: 'No messages',
44
- one: 'One message',
45
- other: '{count} messages',
46
- },
22
+ inbox: { plural: { one: 'One message', other: '{count} messages' } },
47
23
  },
48
- de: () => import('./locales/de.json').then((m) => m.default),
49
24
  },
25
+ locale: 'en',
50
26
  });
51
27
 
52
- await i18n.setLocale('de');
53
-
54
- i18n.t('greeting', { name: 'Alice' });
55
- i18n.tp('inbox', 3);
56
-
57
- // Scope reduces key repetition inside a namespace
58
- const nav = i18n.scope('nav');
59
- nav.t('home'); // resolves 'nav.home'
60
-
61
- // Bind a cached per-key function for hot-path rendering
62
- const greet = i18n.bind('greeting');
63
- const users = [{ name: 'Alice' }, { name: 'Bob' }];
64
- users.forEach((u) => greet({ name: u.name }));
65
-
66
- // Merge route-specific keys on top of the base catalog
67
- await i18n.merge('en', () => import('./routes/settings.i18n.json').then((m) => m.default));
28
+ translations.translate('greeting', { values: { name: 'Ada' } });
29
+ translations.translate('inbox', { count: 3 });
30
+ ```
68
31
 
69
- // Fork the shared instance for SSR per-request isolation
70
- const reqI18n = i18n.fork({ locale: req.locale });
32
+ ## Exports
71
33
 
72
- // Formatter bound to the current locale — follows locale changes automatically
73
- const fmt = createFormatter(() => i18n.locale);
74
- fmt.currency(19.99, 'EUR');
75
- ```
34
+ - `createTranslator` immutable translation engine for fixed-locale catalogs.
35
+ - `createTranslationStore` / `hydrateTranslationStore` mutable locale state, lazy catalog loading, and SSR state.
36
+ - `createFormatter` from `@vielzeug/lingua/format`.
37
+ - `validateCatalog` from `@vielzeug/lingua/validate`.
76
38
 
77
39
  ## Documentation
78
40
 
79
41
  - [Overview](https://vielzeug.dev/lingua/)
80
42
  - [Usage Guide](https://vielzeug.dev/lingua/usage)
81
43
  - [API Reference](https://vielzeug.dev/lingua/api)
82
- - [Examples](https://vielzeug.dev/lingua/examples)
83
44
 
84
45
  ## License
85
46
 
86
- MIT © [Helmuth Saatkamp](https://github.com/helmuthdu) — part of the [Vielzeug](https://github.com/helmuthdu/vielzeug) monorepo.
47
+ MIT © Helmuth Saatkamp
@@ -0,0 +1,2 @@
1
+ function e(e,t,n,r){if(e.has(t))return e.get(t);let i=r();return e.size>=n&&e.delete(e.keys().next().value),e.set(t,i),i}exports.getOrCreate=e;
2
+ //# sourceMappingURL=_bounded-cache.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"_bounded-cache.cjs","names":[],"sources":["../src/_bounded-cache.ts"],"sourcesContent":["// Internal — not part of the public API.\n// Minimal bounded cache: evicts the oldest entry (insertion order) once `maxSize` is exceeded.\n// Not LRU — no access-time reordering, only insertion order matters. These caches exist purely\n// to avoid rebuilding cheap-but-not-free values (compiled `Intl` formatters, scoped translator\n// objects) for a bounded number of frequently-reused keys, not to model real recency.\n\nexport function getOrCreate<K, V>(cache: Map<K, V>, key: K, maxSize: number, build: () => V): V {\n if (cache.has(key)) return cache.get(key)!;\n\n const value = build();\n\n if (cache.size >= maxSize) cache.delete(cache.keys().next().value as K);\n\n cache.set(key, value);\n\n return value;\n}\n"],"mappings":"AAMA,SAAgB,EAAkB,EAAkB,EAAQ,EAAiB,EAAmB,CAC9F,GAAI,EAAM,IAAI,CAAG,EAAG,OAAO,EAAM,IAAI,CAAG,EAExC,IAAM,EAAQ,EAAM,EAMpB,OAJI,EAAM,MAAQ,GAAS,EAAM,OAAO,EAAM,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,KAAU,EAEtE,EAAM,IAAI,EAAK,CAAK,EAEb,CACT"}
@@ -0,0 +1,2 @@
1
+ export declare function getOrCreate<K, V>(cache: Map<K, V>, key: K, maxSize: number, build: () => V): V;
2
+ //# sourceMappingURL=_bounded-cache.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"_bounded-cache.d.ts","sourceRoot":"","sources":["../src/_bounded-cache.ts"],"names":[],"mappings":"AAMA,wBAAgB,WAAW,CAAC,CAAC,EAAE,CAAC,EAAE,KAAK,EAAE,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,CAAC,GAAG,CAAC,CAU9F"}
@@ -0,0 +1,10 @@
1
+ //#region src/_bounded-cache.ts
2
+ function e(e, t, n, r) {
3
+ if (e.has(t)) return e.get(t);
4
+ let i = r();
5
+ return e.size >= n && e.delete(e.keys().next().value), e.set(t, i), i;
6
+ }
7
+ //#endregion
8
+ export { e as getOrCreate };
9
+
10
+ //# sourceMappingURL=_bounded-cache.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"_bounded-cache.js","names":[],"sources":["../src/_bounded-cache.ts"],"sourcesContent":["// Internal — not part of the public API.\n// Minimal bounded cache: evicts the oldest entry (insertion order) once `maxSize` is exceeded.\n// Not LRU — no access-time reordering, only insertion order matters. These caches exist purely\n// to avoid rebuilding cheap-but-not-free values (compiled `Intl` formatters, scoped translator\n// objects) for a bounded number of frequently-reused keys, not to model real recency.\n\nexport function getOrCreate<K, V>(cache: Map<K, V>, key: K, maxSize: number, build: () => V): V {\n if (cache.has(key)) return cache.get(key)!;\n\n const value = build();\n\n if (cache.size >= maxSize) cache.delete(cache.keys().next().value as K);\n\n cache.set(key, value);\n\n return value;\n}\n"],"mappings":";AAMA,SAAgB,EAAkB,GAAkB,GAAQ,GAAiB,GAAmB;CAC9F,IAAI,EAAM,IAAI,CAAG,GAAG,OAAO,EAAM,IAAI,CAAG;CAExC,IAAM,IAAQ,EAAM;CAMpB,OAJI,EAAM,QAAQ,KAAS,EAAM,OAAO,EAAM,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,KAAU,GAEtE,EAAM,IAAI,GAAK,CAAK,GAEb;AACT"}
package/dist/_catalog.cjs CHANGED
@@ -1,2 +1,2 @@
1
- const e=require("./_constants.cjs"),t=require("./template.cjs");var n=class e{entries=new Map;prefixes=new Set;get(e){return this.entries.get(e)}set(e,n){this.entries.set(e,{compiled:t.compileTemplate(n),message:n});let r=e.indexOf(`.`);for(;r!==-1;)this.prefixes.add(e.slice(0,r)),r=e.indexOf(`.`,r+1)}setAll(e){for(let[t,n]of e)this.set(t,n)}clone(){let t=new e;for(let[e,n]of this.entries)t.entries.set(e,n);for(let e of this.prefixes)t.prefixes.add(e);return t}};function r(n,i=new Map,a){for(let[o,s]of Object.entries(n)){if(e.UNSAFE_KEYS.has(o))continue;let n=a?`${a}.${o}`:o;if(typeof s==`string`){let e=t.parsePipePlural(s);e?r(e,i,n):i.set(n,s)}else r(s,i,n)}return i}exports.CatalogEntry=n,exports.flattenStrings=r;
1
+ const e=require("./errors.cjs"),t=require("./_template.cjs");var n=new Set([`__proto__`,`constructor`,`prototype`]);function r(e){return typeof e==`object`&&!!e&&Object.hasOwn(e,`plural`)}function i(i){let a=new Map,o=(i,s)=>{if(typeof i==`string`){a.set(s,{kind:`text`,template:t.compileTemplate(i)});return}if(typeof i!=`object`||!i||Array.isArray(i))throw new e.LinguaInvalidCatalogError(`Catalog node "${s}" must be a string, plural message, or object.`);if(r(i)){if(typeof i.plural!=`object`||i.plural===null||Array.isArray(i.plural))throw new e.LinguaInvalidCatalogError(`Plural message "${s}" must provide an object of string forms.`);let n=new Map;for(let[r,a]of Object.entries(i.plural)){if(typeof a!=`string`)throw new e.LinguaInvalidCatalogError(`Plural form "${s}.${r}" must be a string.`);n.set(r,t.compileTemplate(a))}a.set(s,{forms:n,kind:`plural`});return}for(let[t,r]of Object.entries(i)){if(n.has(t))throw new e.LinguaInvalidCatalogError(`Catalog key "${t}" is reserved.`);o(r,s?`${s}.${t}`:t)}};for(let[t,r]of Object.entries(i)){if(n.has(t))throw new e.LinguaInvalidCatalogError(`Catalog key "${t}" is reserved.`);o(r,t)}return a}exports.compileCatalog=i;
2
2
  //# sourceMappingURL=_catalog.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"_catalog.cjs","names":[],"sources":["../src/_catalog.ts"],"sourcesContent":["// Internal not part of the public API.\n// Encapsulates the CatalogEntry class, flattenStrings, and related utilities.\n\nimport { UNSAFE_KEYS } from './_constants';\nimport { compileTemplate, type CompiledTemplate, parsePipePlural } from './template';\n\nexport interface Messages {\n [key: string]: string | Messages;\n}\n\nexport type CatalogEntryData = {\n compiled: CompiledTemplate;\n message: string;\n};\n\nexport class CatalogEntry {\n readonly entries = new Map<string, CatalogEntryData>();\n readonly prefixes = new Set<string>();\n\n get(key: string): CatalogEntryData | undefined {\n return this.entries.get(key);\n }\n\n set(key: string, value: string): void {\n this.entries.set(key, { compiled: compileTemplate(value), message: value });\n\n // Populate prefix set for O(1) branch detection in has().\n let dot = key.indexOf('.');\n\n while (dot !== -1) {\n this.prefixes.add(key.slice(0, dot));\n dot = key.indexOf('.', dot + 1);\n }\n }\n\n setAll(flat: Iterable<[string, string]>): void {\n for (const [k, v] of flat) this.set(k, v);\n }\n\n clone(): CatalogEntry {\n const copy = new CatalogEntry();\n\n for (const [k, v] of this.entries) copy.entries.set(k, v);\n for (const p of this.prefixes) copy.prefixes.add(p);\n\n return copy;\n }\n}\n\nexport function flattenStrings(\n messages: Messages,\n result = new Map<string, string>(),\n prefix?: string,\n): Map<string, string> {\n for (const [key, value] of Object.entries(messages)) {\n if (UNSAFE_KEYS.has(key)) continue;\n\n const fullKey = prefix ? `${prefix}.${key}` : key;\n\n if (typeof value === 'string') {\n const plural = parsePipePlural(value);\n\n if (plural) {\n flattenStrings(plural, result, fullKey);\n } else {\n result.set(fullKey, value);\n }\n } else {\n flattenStrings(value as Messages, result, fullKey);\n }\n }\n\n return result;\n}\n"],"mappings":"gEAeA,IAAa,EAAb,MAAa,CAAa,CACxB,QAAmB,IAAI,IACvB,SAAoB,IAAI,IAExB,IAAI,EAA2C,CAC7C,OAAO,KAAK,QAAQ,IAAI,CAAG,CAC7B,CAEA,IAAI,EAAa,EAAqB,CACpC,KAAK,QAAQ,IAAI,EAAK,CAAE,SAAU,EAAA,gBAAgB,CAAK,EAAG,QAAS,CAAM,CAAC,EAG1E,IAAI,EAAM,EAAI,QAAQ,GAAG,EAEzB,KAAO,IAAQ,IACb,KAAK,SAAS,IAAI,EAAI,MAAM,EAAG,CAAG,CAAC,EACnC,EAAM,EAAI,QAAQ,IAAK,EAAM,CAAC,CAElC,CAEA,OAAO,EAAwC,CAC7C,IAAK,GAAM,CAAC,EAAG,KAAM,EAAM,KAAK,IAAI,EAAG,CAAC,CAC1C,CAEA,OAAsB,CACpB,IAAM,EAAO,IAAI,EAEjB,IAAK,GAAM,CAAC,EAAG,KAAM,KAAK,QAAS,EAAK,QAAQ,IAAI,EAAG,CAAC,EACxD,IAAK,IAAM,KAAK,KAAK,SAAU,EAAK,SAAS,IAAI,CAAC,EAElD,OAAO,CACT,CACF,EAEA,SAAgB,EACd,EACA,EAAS,IAAI,IACb,EACqB,CACrB,IAAK,GAAM,CAAC,EAAK,KAAU,OAAO,QAAQ,CAAQ,EAAG,CACnD,GAAI,EAAA,YAAY,IAAI,CAAG,EAAG,SAE1B,IAAM,EAAU,EAAS,GAAG,EAAO,GAAG,IAAQ,EAE9C,GAAI,OAAO,GAAU,SAAU,CAC7B,IAAM,EAAS,EAAA,gBAAgB,CAAK,EAEhC,EACF,EAAe,EAAQ,EAAQ,CAAO,EAEtC,EAAO,IAAI,EAAS,CAAK,CAE7B,MACE,EAAe,EAAmB,EAAQ,CAAO,CAErD,CAEA,OAAO,CACT"}
1
+ {"version":3,"file":"_catalog.cjs","names":[],"sources":["../src/_catalog.ts"],"sourcesContent":["import type { Catalog, CatalogNode, PluralMessage } from './types';\n\nimport { compileTemplate, type Template } from './_template';\nimport { LinguaInvalidCatalogError } from './errors';\n\nconst unsafeKeys = new Set(['__proto__', 'constructor', 'prototype']);\n\nexport type CompiledText = { readonly kind: 'text'; readonly template: Template };\nexport type CompiledPlural = { readonly forms: ReadonlyMap<string, Template>; readonly kind: 'plural' };\nexport type CompiledMessage = CompiledPlural | CompiledText;\nexport type CompiledCatalog = ReadonlyMap<string, CompiledMessage>;\n\nfunction isPluralMessage(node: CatalogNode): node is PluralMessage {\n return typeof node === 'object' && node !== null && Object.hasOwn(node, 'plural');\n}\n\n/** Compile explicit catalog nodes once. Nested objects only group keys; plural intent is never inferred. */\nexport function compileCatalog(catalog: Catalog): CompiledCatalog {\n const messages = new Map<string, CompiledMessage>();\n\n const visit = (node: CatalogNode, prefix: string): void => {\n if (typeof node === 'string') {\n messages.set(prefix, { kind: 'text', template: compileTemplate(node) });\n\n return;\n }\n\n if (typeof node !== 'object' || node === null || Array.isArray(node)) {\n throw new LinguaInvalidCatalogError(`Catalog node \"${prefix}\" must be a string, plural message, or object.`);\n }\n\n if (isPluralMessage(node)) {\n if (typeof node.plural !== 'object' || node.plural === null || Array.isArray(node.plural)) {\n throw new LinguaInvalidCatalogError(`Plural message \"${prefix}\" must provide an object of string forms.`);\n }\n\n const forms = new Map<string, Template>();\n\n for (const [form, template] of Object.entries(node.plural)) {\n if (typeof template !== 'string') {\n throw new LinguaInvalidCatalogError(`Plural form \"${prefix}.${form}\" must be a string.`);\n }\n\n forms.set(form, compileTemplate(template));\n }\n\n messages.set(prefix, { forms, kind: 'plural' });\n\n return;\n }\n\n for (const [key, value] of Object.entries(node)) {\n if (unsafeKeys.has(key)) {\n throw new LinguaInvalidCatalogError(`Catalog key \"${key}\" is reserved.`);\n }\n\n visit(value, prefix ? `${prefix}.${key}` : key);\n }\n };\n\n for (const [key, value] of Object.entries(catalog)) {\n if (unsafeKeys.has(key)) throw new LinguaInvalidCatalogError(`Catalog key \"${key}\" is reserved.`);\n\n visit(value, key);\n }\n\n return messages;\n}\n"],"mappings":"6DAKA,IAAM,EAAa,IAAI,IAAI,CAAC,YAAa,cAAe,WAAW,CAAC,EAOpE,SAAS,EAAgB,EAA0C,CACjE,OAAO,OAAO,GAAS,YAAY,GAAiB,OAAO,OAAO,EAAM,QAAQ,CAClF,CAGA,SAAgB,EAAe,EAAmC,CAChE,IAAM,EAAW,IAAI,IAEf,GAAS,EAAmB,IAAyB,CACzD,GAAI,OAAO,GAAS,SAAU,CAC5B,EAAS,IAAI,EAAQ,CAAE,KAAM,OAAQ,SAAU,EAAA,gBAAgB,CAAI,CAAE,CAAC,EAEtE,MACF,CAEA,GAAI,OAAO,GAAS,WAAY,GAAiB,MAAM,QAAQ,CAAI,EACjE,MAAM,IAAI,EAAA,0BAA0B,iBAAiB,EAAO,+CAA+C,EAG7G,GAAI,EAAgB,CAAI,EAAG,CACzB,GAAI,OAAO,EAAK,QAAW,UAAY,EAAK,SAAW,MAAQ,MAAM,QAAQ,EAAK,MAAM,EACtF,MAAM,IAAI,EAAA,0BAA0B,mBAAmB,EAAO,0CAA0C,EAG1G,IAAM,EAAQ,IAAI,IAElB,IAAK,GAAM,CAAC,EAAM,KAAa,OAAO,QAAQ,EAAK,MAAM,EAAG,CAC1D,GAAI,OAAO,GAAa,SACtB,MAAM,IAAI,EAAA,0BAA0B,gBAAgB,EAAO,GAAG,EAAK,oBAAoB,EAGzF,EAAM,IAAI,EAAM,EAAA,gBAAgB,CAAQ,CAAC,CAC3C,CAEA,EAAS,IAAI,EAAQ,CAAE,QAAO,KAAM,QAAS,CAAC,EAE9C,MACF,CAEA,IAAK,GAAM,CAAC,EAAK,KAAU,OAAO,QAAQ,CAAI,EAAG,CAC/C,GAAI,EAAW,IAAI,CAAG,EACpB,MAAM,IAAI,EAAA,0BAA0B,gBAAgB,EAAI,eAAe,EAGzE,EAAM,EAAO,EAAS,GAAG,EAAO,GAAG,IAAQ,CAAG,CAChD,CACF,EAEA,IAAK,GAAM,CAAC,EAAK,KAAU,OAAO,QAAQ,CAAO,EAAG,CAClD,GAAI,EAAW,IAAI,CAAG,EAAG,MAAM,IAAI,EAAA,0BAA0B,gBAAgB,EAAI,eAAe,EAEhG,EAAM,EAAO,CAAG,CAClB,CAEA,OAAO,CACT"}
@@ -1,18 +1,15 @@
1
- import { type CompiledTemplate } from './template';
2
- export interface Messages {
3
- [key: string]: string | Messages;
4
- }
5
- export type CatalogEntryData = {
6
- compiled: CompiledTemplate;
7
- message: string;
1
+ import type { Catalog } from './types';
2
+ import { type Template } from './_template';
3
+ export type CompiledText = {
4
+ readonly kind: 'text';
5
+ readonly template: Template;
8
6
  };
9
- export declare class CatalogEntry {
10
- readonly entries: Map<string, CatalogEntryData>;
11
- readonly prefixes: Set<string>;
12
- get(key: string): CatalogEntryData | undefined;
13
- set(key: string, value: string): void;
14
- setAll(flat: Iterable<[string, string]>): void;
15
- clone(): CatalogEntry;
16
- }
17
- export declare function flattenStrings(messages: Messages, result?: Map<string, string>, prefix?: string): Map<string, string>;
7
+ export type CompiledPlural = {
8
+ readonly forms: ReadonlyMap<string, Template>;
9
+ readonly kind: 'plural';
10
+ };
11
+ export type CompiledMessage = CompiledPlural | CompiledText;
12
+ export type CompiledCatalog = ReadonlyMap<string, CompiledMessage>;
13
+ /** Compile explicit catalog nodes once. Nested objects only group keys; plural intent is never inferred. */
14
+ export declare function compileCatalog(catalog: Catalog): CompiledCatalog;
18
15
  //# sourceMappingURL=_catalog.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"_catalog.d.ts","sourceRoot":"","sources":["../src/_catalog.ts"],"names":[],"mappings":"AAIA,OAAO,EAAmB,KAAK,gBAAgB,EAAmB,MAAM,YAAY,CAAC;AAErF,MAAM,WAAW,QAAQ;IACvB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,QAAQ,CAAC;CAClC;AAED,MAAM,MAAM,gBAAgB,GAAG;IAC7B,QAAQ,EAAE,gBAAgB,CAAC;IAC3B,OAAO,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF,qBAAa,YAAY;IACvB,QAAQ,CAAC,OAAO,gCAAuC;IACvD,QAAQ,CAAC,QAAQ,cAAqB;IAEtC,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,gBAAgB,GAAG,SAAS;IAI9C,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI;IAYrC,MAAM,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,GAAG,IAAI;IAI9C,KAAK,IAAI,YAAY;CAQtB;AAED,wBAAgB,cAAc,CAC5B,QAAQ,EAAE,QAAQ,EAClB,MAAM,sBAA4B,EAClC,MAAM,CAAC,EAAE,MAAM,GACd,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAoBrB"}
1
+ {"version":3,"file":"_catalog.d.ts","sourceRoot":"","sources":["../src/_catalog.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAA8B,MAAM,SAAS,CAAC;AAEnE,OAAO,EAAmB,KAAK,QAAQ,EAAE,MAAM,aAAa,CAAC;AAK7D,MAAM,MAAM,YAAY,GAAG;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAA;CAAE,CAAC;AAClF,MAAM,MAAM,cAAc,GAAG;IAAE,QAAQ,CAAC,KAAK,EAAE,WAAW,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAA;CAAE,CAAC;AACxG,MAAM,MAAM,eAAe,GAAG,cAAc,GAAG,YAAY,CAAC;AAC5D,MAAM,MAAM,eAAe,GAAG,WAAW,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC;AAMnE,4GAA4G;AAC5G,wBAAgB,cAAc,CAAC,OAAO,EAAE,OAAO,GAAG,eAAe,CAkDhE"}
package/dist/_catalog.js CHANGED
@@ -1,42 +1,49 @@
1
- import { UNSAFE_KEYS as e } from "./_constants.js";
2
- import { compileTemplate as t, parsePipePlural as n } from "./template.js";
1
+ import { LinguaInvalidCatalogError as e } from "./errors.js";
2
+ import { compileTemplate as t } from "./_template.js";
3
3
  //#region src/_catalog.ts
4
- var r = class e {
5
- entries = /* @__PURE__ */ new Map();
6
- prefixes = /* @__PURE__ */ new Set();
7
- get(e) {
8
- return this.entries.get(e);
9
- }
10
- set(e, n) {
11
- this.entries.set(e, {
12
- compiled: t(n),
13
- message: n
14
- });
15
- let r = e.indexOf(".");
16
- for (; r !== -1;) this.prefixes.add(e.slice(0, r)), r = e.indexOf(".", r + 1);
17
- }
18
- setAll(e) {
19
- for (let [t, n] of e) this.set(t, n);
20
- }
21
- clone() {
22
- let t = new e();
23
- for (let [e, n] of this.entries) t.entries.set(e, n);
24
- for (let e of this.prefixes) t.prefixes.add(e);
25
- return t;
26
- }
27
- };
28
- function i(t, r = /* @__PURE__ */ new Map(), a) {
29
- for (let [o, s] of Object.entries(t)) {
30
- if (e.has(o)) continue;
31
- let t = a ? `${a}.${o}` : o;
32
- if (typeof s == "string") {
33
- let e = n(s);
34
- e ? i(e, r, t) : r.set(t, s);
35
- } else i(s, r, t);
4
+ var n = /* @__PURE__ */ new Set([
5
+ "__proto__",
6
+ "constructor",
7
+ "prototype"
8
+ ]);
9
+ function r(e) {
10
+ return typeof e == "object" && !!e && Object.hasOwn(e, "plural");
11
+ }
12
+ function i(i) {
13
+ let a = /* @__PURE__ */ new Map(), o = (i, s) => {
14
+ if (typeof i == "string") {
15
+ a.set(s, {
16
+ kind: "text",
17
+ template: t(i)
18
+ });
19
+ return;
20
+ }
21
+ if (typeof i != "object" || !i || Array.isArray(i)) throw new e(`Catalog node "${s}" must be a string, plural message, or object.`);
22
+ if (r(i)) {
23
+ if (typeof i.plural != "object" || i.plural === null || Array.isArray(i.plural)) throw new e(`Plural message "${s}" must provide an object of string forms.`);
24
+ let n = /* @__PURE__ */ new Map();
25
+ for (let [r, a] of Object.entries(i.plural)) {
26
+ if (typeof a != "string") throw new e(`Plural form "${s}.${r}" must be a string.`);
27
+ n.set(r, t(a));
28
+ }
29
+ a.set(s, {
30
+ forms: n,
31
+ kind: "plural"
32
+ });
33
+ return;
34
+ }
35
+ for (let [t, r] of Object.entries(i)) {
36
+ if (n.has(t)) throw new e(`Catalog key "${t}" is reserved.`);
37
+ o(r, s ? `${s}.${t}` : t);
38
+ }
39
+ };
40
+ for (let [t, r] of Object.entries(i)) {
41
+ if (n.has(t)) throw new e(`Catalog key "${t}" is reserved.`);
42
+ o(r, t);
36
43
  }
37
- return r;
44
+ return a;
38
45
  }
39
46
  //#endregion
40
- export { r as CatalogEntry, i as flattenStrings };
47
+ export { i as compileCatalog };
41
48
 
42
49
  //# sourceMappingURL=_catalog.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"_catalog.js","names":[],"sources":["../src/_catalog.ts"],"sourcesContent":["// Internal not part of the public API.\n// Encapsulates the CatalogEntry class, flattenStrings, and related utilities.\n\nimport { UNSAFE_KEYS } from './_constants';\nimport { compileTemplate, type CompiledTemplate, parsePipePlural } from './template';\n\nexport interface Messages {\n [key: string]: string | Messages;\n}\n\nexport type CatalogEntryData = {\n compiled: CompiledTemplate;\n message: string;\n};\n\nexport class CatalogEntry {\n readonly entries = new Map<string, CatalogEntryData>();\n readonly prefixes = new Set<string>();\n\n get(key: string): CatalogEntryData | undefined {\n return this.entries.get(key);\n }\n\n set(key: string, value: string): void {\n this.entries.set(key, { compiled: compileTemplate(value), message: value });\n\n // Populate prefix set for O(1) branch detection in has().\n let dot = key.indexOf('.');\n\n while (dot !== -1) {\n this.prefixes.add(key.slice(0, dot));\n dot = key.indexOf('.', dot + 1);\n }\n }\n\n setAll(flat: Iterable<[string, string]>): void {\n for (const [k, v] of flat) this.set(k, v);\n }\n\n clone(): CatalogEntry {\n const copy = new CatalogEntry();\n\n for (const [k, v] of this.entries) copy.entries.set(k, v);\n for (const p of this.prefixes) copy.prefixes.add(p);\n\n return copy;\n }\n}\n\nexport function flattenStrings(\n messages: Messages,\n result = new Map<string, string>(),\n prefix?: string,\n): Map<string, string> {\n for (const [key, value] of Object.entries(messages)) {\n if (UNSAFE_KEYS.has(key)) continue;\n\n const fullKey = prefix ? `${prefix}.${key}` : key;\n\n if (typeof value === 'string') {\n const plural = parsePipePlural(value);\n\n if (plural) {\n flattenStrings(plural, result, fullKey);\n } else {\n result.set(fullKey, value);\n }\n } else {\n flattenStrings(value as Messages, result, fullKey);\n }\n }\n\n return result;\n}\n"],"mappings":";;;AAeA,IAAa,IAAb,MAAa,EAAa;CACxB,0BAAmB,IAAI,IAA8B;CACrD,2BAAoB,IAAI,IAAY;CAEpC,IAAI,GAA2C;EAC7C,OAAO,KAAK,QAAQ,IAAI,CAAG;CAC7B;CAEA,IAAI,GAAa,GAAqB;EACpC,KAAK,QAAQ,IAAI,GAAK;GAAE,UAAU,EAAgB,CAAK;GAAG,SAAS;EAAM,CAAC;EAG1E,IAAI,IAAM,EAAI,QAAQ,GAAG;EAEzB,OAAO,MAAQ,KAEb,AADA,KAAK,SAAS,IAAI,EAAI,MAAM,GAAG,CAAG,CAAC,GACnC,IAAM,EAAI,QAAQ,KAAK,IAAM,CAAC;CAElC;CAEA,OAAO,GAAwC;EAC7C,KAAK,IAAM,CAAC,GAAG,MAAM,GAAM,KAAK,IAAI,GAAG,CAAC;CAC1C;CAEA,QAAsB;EACpB,IAAM,IAAO,IAAI,EAAa;EAE9B,KAAK,IAAM,CAAC,GAAG,MAAM,KAAK,SAAS,EAAK,QAAQ,IAAI,GAAG,CAAC;EACxD,KAAK,IAAM,KAAK,KAAK,UAAU,EAAK,SAAS,IAAI,CAAC;EAElD,OAAO;CACT;AACF;AAEA,SAAgB,EACd,GACA,oBAAS,IAAI,IAAoB,GACjC,GACqB;CACrB,KAAK,IAAM,CAAC,GAAK,MAAU,OAAO,QAAQ,CAAQ,GAAG;EACnD,IAAI,EAAY,IAAI,CAAG,GAAG;EAE1B,IAAM,IAAU,IAAS,GAAG,EAAO,GAAG,MAAQ;EAE9C,IAAI,OAAO,KAAU,UAAU;GAC7B,IAAM,IAAS,EAAgB,CAAK;GAEpC,AAAI,IACF,EAAe,GAAQ,GAAQ,CAAO,IAEtC,EAAO,IAAI,GAAS,CAAK;EAE7B,OACE,EAAe,GAAmB,GAAQ,CAAO;CAErD;CAEA,OAAO;AACT"}
1
+ {"version":3,"file":"_catalog.js","names":[],"sources":["../src/_catalog.ts"],"sourcesContent":["import type { Catalog, CatalogNode, PluralMessage } from './types';\n\nimport { compileTemplate, type Template } from './_template';\nimport { LinguaInvalidCatalogError } from './errors';\n\nconst unsafeKeys = new Set(['__proto__', 'constructor', 'prototype']);\n\nexport type CompiledText = { readonly kind: 'text'; readonly template: Template };\nexport type CompiledPlural = { readonly forms: ReadonlyMap<string, Template>; readonly kind: 'plural' };\nexport type CompiledMessage = CompiledPlural | CompiledText;\nexport type CompiledCatalog = ReadonlyMap<string, CompiledMessage>;\n\nfunction isPluralMessage(node: CatalogNode): node is PluralMessage {\n return typeof node === 'object' && node !== null && Object.hasOwn(node, 'plural');\n}\n\n/** Compile explicit catalog nodes once. Nested objects only group keys; plural intent is never inferred. */\nexport function compileCatalog(catalog: Catalog): CompiledCatalog {\n const messages = new Map<string, CompiledMessage>();\n\n const visit = (node: CatalogNode, prefix: string): void => {\n if (typeof node === 'string') {\n messages.set(prefix, { kind: 'text', template: compileTemplate(node) });\n\n return;\n }\n\n if (typeof node !== 'object' || node === null || Array.isArray(node)) {\n throw new LinguaInvalidCatalogError(`Catalog node \"${prefix}\" must be a string, plural message, or object.`);\n }\n\n if (isPluralMessage(node)) {\n if (typeof node.plural !== 'object' || node.plural === null || Array.isArray(node.plural)) {\n throw new LinguaInvalidCatalogError(`Plural message \"${prefix}\" must provide an object of string forms.`);\n }\n\n const forms = new Map<string, Template>();\n\n for (const [form, template] of Object.entries(node.plural)) {\n if (typeof template !== 'string') {\n throw new LinguaInvalidCatalogError(`Plural form \"${prefix}.${form}\" must be a string.`);\n }\n\n forms.set(form, compileTemplate(template));\n }\n\n messages.set(prefix, { forms, kind: 'plural' });\n\n return;\n }\n\n for (const [key, value] of Object.entries(node)) {\n if (unsafeKeys.has(key)) {\n throw new LinguaInvalidCatalogError(`Catalog key \"${key}\" is reserved.`);\n }\n\n visit(value, prefix ? `${prefix}.${key}` : key);\n }\n };\n\n for (const [key, value] of Object.entries(catalog)) {\n if (unsafeKeys.has(key)) throw new LinguaInvalidCatalogError(`Catalog key \"${key}\" is reserved.`);\n\n visit(value, key);\n }\n\n return messages;\n}\n"],"mappings":";;;AAKA,IAAM,oBAAa,IAAI,IAAI;CAAC;CAAa;CAAe;AAAW,CAAC;AAOpE,SAAS,EAAgB,GAA0C;CACjE,OAAO,OAAO,KAAS,cAAY,KAAiB,OAAO,OAAO,GAAM,QAAQ;AAClF;AAGA,SAAgB,EAAe,GAAmC;CAChE,IAAM,oBAAW,IAAI,IAA6B,GAE5C,KAAS,GAAmB,MAAyB;EACzD,IAAI,OAAO,KAAS,UAAU;GAC5B,EAAS,IAAI,GAAQ;IAAE,MAAM;IAAQ,UAAU,EAAgB,CAAI;GAAE,CAAC;GAEtE;EACF;EAEA,IAAI,OAAO,KAAS,aAAY,KAAiB,MAAM,QAAQ,CAAI,GACjE,MAAM,IAAI,EAA0B,iBAAiB,EAAO,+CAA+C;EAG7G,IAAI,EAAgB,CAAI,GAAG;GACzB,IAAI,OAAO,EAAK,UAAW,YAAY,EAAK,WAAW,QAAQ,MAAM,QAAQ,EAAK,MAAM,GACtF,MAAM,IAAI,EAA0B,mBAAmB,EAAO,0CAA0C;GAG1G,IAAM,oBAAQ,IAAI,IAAsB;GAExC,KAAK,IAAM,CAAC,GAAM,MAAa,OAAO,QAAQ,EAAK,MAAM,GAAG;IAC1D,IAAI,OAAO,KAAa,UACtB,MAAM,IAAI,EAA0B,gBAAgB,EAAO,GAAG,EAAK,oBAAoB;IAGzF,EAAM,IAAI,GAAM,EAAgB,CAAQ,CAAC;GAC3C;GAEA,EAAS,IAAI,GAAQ;IAAE;IAAO,MAAM;GAAS,CAAC;GAE9C;EACF;EAEA,KAAK,IAAM,CAAC,GAAK,MAAU,OAAO,QAAQ,CAAI,GAAG;GAC/C,IAAI,EAAW,IAAI,CAAG,GACpB,MAAM,IAAI,EAA0B,gBAAgB,EAAI,eAAe;GAGzE,EAAM,GAAO,IAAS,GAAG,EAAO,GAAG,MAAQ,CAAG;EAChD;CACF;CAEA,KAAK,IAAM,CAAC,GAAK,MAAU,OAAO,QAAQ,CAAO,GAAG;EAClD,IAAI,EAAW,IAAI,CAAG,GAAG,MAAM,IAAI,EAA0B,gBAAgB,EAAI,eAAe;EAEhG,EAAM,GAAO,CAAG;CAClB;CAEA,OAAO;AACT"}
package/dist/_dev.cjs CHANGED
@@ -1,2 +1,2 @@
1
- var e=!globalThis.__LINGUA_PROD__;function t(t){e&&console.warn(`[@vielzeug/lingua] ${t}`)}function n(t,...n){e&&console.error(`[@vielzeug/lingua] ${t}`,...n)}exports.error=n,exports.warn=t;
1
+ function e(e){}function t(e,...t){}exports.error=t,exports.warn=e;
2
2
  //# sourceMappingURL=_dev.cjs.map
package/dist/_dev.cjs.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"_dev.cjs","names":[],"sources":["../src/_dev.ts"],"sourcesContent":["const isDev = !(globalThis as { __LINGUA_PROD__?: boolean }).__LINGUA_PROD__;\n\n/** @internal @security Messages may include user-supplied data. */\nexport function warn(msg: string): void {\n if (isDev) console.warn(`[@vielzeug/lingua] ${msg}`);\n}\n\n/** @internal */\nexport function error(msg: string, ...args: unknown[]): void {\n if (isDev) console.error(`[@vielzeug/lingua] ${msg}`, ...args);\n}\n\n/** @internal — Run fn only in dev builds. Use when dev-only logic goes beyond a single warn() / error() call. */\nexport function devOnly(fn: () => void): void {\n if (isDev) fn();\n}\n"],"mappings":"AAAA,IAAM,EAAQ,CAAE,WAA6C,gBAG7D,SAAgB,EAAK,EAAmB,CAClC,GAAO,QAAQ,KAAK,sBAAsB,GAAK,CACrD,CAGA,SAAgB,EAAM,EAAa,GAAG,EAAuB,CACvD,GAAO,QAAQ,MAAM,sBAAsB,IAAO,GAAG,CAAI,CAC/D"}
1
+ {"version":3,"file":"_dev.cjs","names":[],"sources":["../src/_dev.ts"],"sourcesContent":["const isDev = !(globalThis as { __LINGUA_PROD__?: boolean }).__LINGUA_PROD__;\n\n/** @internal @security Messages may include user-supplied data. */\nexport function warn(msg: string): void {\n if (isDev) console.warn(`[@vielzeug/lingua] ${msg}`);\n}\n\n/** @internal */\nexport function error(msg: string, ...args: unknown[]): void {\n if (isDev) console.error(`[@vielzeug/lingua] ${msg}`, ...args);\n}\n\n/** @internal — Run fn only in dev builds. Use when dev-only logic goes beyond a single warn() / error() call. */\nexport function devOnly(fn: () => void): void {\n if (isDev) fn();\n}\n"],"mappings":"AAGA,SAAgB,EAAK,EAAmB,CAExC,CAGA,SAAgB,EAAM,EAAa,GAAG,EAAuB,CAE7D"}
package/dist/_dev.js CHANGED
@@ -1,12 +1,7 @@
1
1
  //#region src/_dev.ts
2
- var e = !globalThis.__LINGUA_PROD__;
3
- function t(t) {
4
- e && console.warn(`[@vielzeug/lingua] ${t}`);
5
- }
6
- function n(t, ...n) {
7
- e && console.error(`[@vielzeug/lingua] ${t}`, ...n);
8
- }
2
+ function e(e) {}
3
+ function t(e, ...t) {}
9
4
  //#endregion
10
- export { n as error, t as warn };
5
+ export { t as error, e as warn };
11
6
 
12
7
  //# sourceMappingURL=_dev.js.map
package/dist/_dev.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"_dev.js","names":[],"sources":["../src/_dev.ts"],"sourcesContent":["const isDev = !(globalThis as { __LINGUA_PROD__?: boolean }).__LINGUA_PROD__;\n\n/** @internal @security Messages may include user-supplied data. */\nexport function warn(msg: string): void {\n if (isDev) console.warn(`[@vielzeug/lingua] ${msg}`);\n}\n\n/** @internal */\nexport function error(msg: string, ...args: unknown[]): void {\n if (isDev) console.error(`[@vielzeug/lingua] ${msg}`, ...args);\n}\n\n/** @internal — Run fn only in dev builds. Use when dev-only logic goes beyond a single warn() / error() call. */\nexport function devOnly(fn: () => void): void {\n if (isDev) fn();\n}\n"],"mappings":";AAAA,IAAM,IAAQ,CAAE,WAA6C;AAG7D,SAAgB,EAAK,GAAmB;CACtC,AAAI,KAAO,QAAQ,KAAK,sBAAsB,GAAK;AACrD;AAGA,SAAgB,EAAM,GAAa,GAAG,GAAuB;CAC3D,AAAI,KAAO,QAAQ,MAAM,sBAAsB,KAAO,GAAG,CAAI;AAC/D"}
1
+ {"version":3,"file":"_dev.js","names":[],"sources":["../src/_dev.ts"],"sourcesContent":["const isDev = !(globalThis as { __LINGUA_PROD__?: boolean }).__LINGUA_PROD__;\n\n/** @internal @security Messages may include user-supplied data. */\nexport function warn(msg: string): void {\n if (isDev) console.warn(`[@vielzeug/lingua] ${msg}`);\n}\n\n/** @internal */\nexport function error(msg: string, ...args: unknown[]): void {\n if (isDev) console.error(`[@vielzeug/lingua] ${msg}`, ...args);\n}\n\n/** @internal — Run fn only in dev builds. Use when dev-only logic goes beyond a single warn() / error() call. */\nexport function devOnly(fn: () => void): void {\n if (isDev) fn();\n}\n"],"mappings":";AAGA,SAAgB,EAAK,GAAmB,CAExC;AAGA,SAAgB,EAAM,GAAa,GAAG,GAAuB,CAE7D"}
@@ -0,0 +1,2 @@
1
+ const e=require("./errors.cjs");var t=new Map;function n(t){try{let[e]=Intl.getCanonicalLocales(t);if(e)return e}catch{}throw new e.LinguaInvalidLocaleError(`Invalid BCP 47 locale tag: "${t}".`)}function r(e,t){let n=new Set;for(let r of[e,...t]){let e=r.split(`-`);for(let t=e.length;t>0;t--)n.add(e.slice(0,t).join(`-`))}return[...n]}function i(e,n,r){let i=`${e}:${r?`ordinal`:`cardinal`}`,a=t.get(i);return a||(a=new Intl.PluralRules(e,{type:r?`ordinal`:`cardinal`}),t.set(i,a)),a.select(n)}exports.canonicalLocale=n,exports.localeChain=r,exports.pluralCategory=i;
2
+ //# sourceMappingURL=_locale.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"_locale.cjs","names":[],"sources":["../src/_locale.ts"],"sourcesContent":["import type { Locale } from './types';\n\nimport { LinguaInvalidLocaleError } from './errors';\n\nconst pluralRules = new Map<string, Intl.PluralRules>();\n\nexport function canonicalLocale(locale: string): Locale {\n try {\n const [canonical] = Intl.getCanonicalLocales(locale);\n\n if (canonical) return canonical;\n } catch {\n // Error below names the failed public input.\n }\n\n throw new LinguaInvalidLocaleError(`Invalid BCP 47 locale tag: \"${locale}\".`);\n}\n\nexport function localeChain(locale: Locale, fallback: readonly Locale[]): readonly Locale[] {\n const chain = new Set<Locale>();\n\n for (const candidate of [locale, ...fallback]) {\n const parts = candidate.split('-');\n\n for (let length = parts.length; length > 0; length--) {\n chain.add(parts.slice(0, length).join('-'));\n }\n }\n\n return [...chain];\n}\n\nexport function pluralCategory(locale: Locale, count: number, ordinal: boolean): Intl.LDMLPluralRule {\n const key = `${locale}:${ordinal ? 'ordinal' : 'cardinal'}`;\n let rules = pluralRules.get(key);\n\n if (!rules) {\n rules = new Intl.PluralRules(locale, { type: ordinal ? 'ordinal' : 'cardinal' });\n pluralRules.set(key, rules);\n }\n\n return rules.select(count);\n}\n"],"mappings":"gCAIA,IAAM,EAAc,IAAI,IAExB,SAAgB,EAAgB,EAAwB,CACtD,GAAI,CACF,GAAM,CAAC,GAAa,KAAK,oBAAoB,CAAM,EAEnD,GAAI,EAAW,OAAO,CACxB,MAAQ,CAER,CAEA,MAAM,IAAI,EAAA,yBAAyB,+BAA+B,EAAO,GAAG,CAC9E,CAEA,SAAgB,EAAY,EAAgB,EAAgD,CAC1F,IAAM,EAAQ,IAAI,IAElB,IAAK,IAAM,IAAa,CAAC,EAAQ,GAAG,CAAQ,EAAG,CAC7C,IAAM,EAAQ,EAAU,MAAM,GAAG,EAEjC,IAAK,IAAI,EAAS,EAAM,OAAQ,EAAS,EAAG,IAC1C,EAAM,IAAI,EAAM,MAAM,EAAG,CAAM,CAAC,CAAC,KAAK,GAAG,CAAC,CAE9C,CAEA,MAAO,CAAC,GAAG,CAAK,CAClB,CAEA,SAAgB,EAAe,EAAgB,EAAe,EAAuC,CACnG,IAAM,EAAM,GAAG,EAAO,GAAG,EAAU,UAAY,aAC3C,EAAQ,EAAY,IAAI,CAAG,EAO/B,OALK,IACH,EAAQ,IAAI,KAAK,YAAY,EAAQ,CAAE,KAAM,EAAU,UAAY,UAAW,CAAC,EAC/E,EAAY,IAAI,EAAK,CAAK,GAGrB,EAAM,OAAO,CAAK,CAC3B"}
@@ -0,0 +1,5 @@
1
+ import type { Locale } from './types';
2
+ export declare function canonicalLocale(locale: string): Locale;
3
+ export declare function localeChain(locale: Locale, fallback: readonly Locale[]): readonly Locale[];
4
+ export declare function pluralCategory(locale: Locale, count: number, ordinal: boolean): Intl.LDMLPluralRule;
5
+ //# sourceMappingURL=_locale.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"_locale.d.ts","sourceRoot":"","sources":["../src/_locale.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AAMtC,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAUtD;AAED,wBAAgB,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,SAAS,MAAM,EAAE,GAAG,SAAS,MAAM,EAAE,CAY1F;AAED,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,IAAI,CAAC,cAAc,CAUnG"}
@@ -0,0 +1,26 @@
1
+ import { LinguaInvalidLocaleError as e } from "./errors.js";
2
+ //#region src/_locale.ts
3
+ var t = /* @__PURE__ */ new Map();
4
+ function n(t) {
5
+ try {
6
+ let [e] = Intl.getCanonicalLocales(t);
7
+ if (e) return e;
8
+ } catch {}
9
+ throw new e(`Invalid BCP 47 locale tag: "${t}".`);
10
+ }
11
+ function r(e, t) {
12
+ let n = /* @__PURE__ */ new Set();
13
+ for (let r of [e, ...t]) {
14
+ let e = r.split("-");
15
+ for (let t = e.length; t > 0; t--) n.add(e.slice(0, t).join("-"));
16
+ }
17
+ return [...n];
18
+ }
19
+ function i(e, n, r) {
20
+ let i = `${e}:${r ? "ordinal" : "cardinal"}`, a = t.get(i);
21
+ return a || (a = new Intl.PluralRules(e, { type: r ? "ordinal" : "cardinal" }), t.set(i, a)), a.select(n);
22
+ }
23
+ //#endregion
24
+ export { n as canonicalLocale, r as localeChain, i as pluralCategory };
25
+
26
+ //# sourceMappingURL=_locale.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"_locale.js","names":[],"sources":["../src/_locale.ts"],"sourcesContent":["import type { Locale } from './types';\n\nimport { LinguaInvalidLocaleError } from './errors';\n\nconst pluralRules = new Map<string, Intl.PluralRules>();\n\nexport function canonicalLocale(locale: string): Locale {\n try {\n const [canonical] = Intl.getCanonicalLocales(locale);\n\n if (canonical) return canonical;\n } catch {\n // Error below names the failed public input.\n }\n\n throw new LinguaInvalidLocaleError(`Invalid BCP 47 locale tag: \"${locale}\".`);\n}\n\nexport function localeChain(locale: Locale, fallback: readonly Locale[]): readonly Locale[] {\n const chain = new Set<Locale>();\n\n for (const candidate of [locale, ...fallback]) {\n const parts = candidate.split('-');\n\n for (let length = parts.length; length > 0; length--) {\n chain.add(parts.slice(0, length).join('-'));\n }\n }\n\n return [...chain];\n}\n\nexport function pluralCategory(locale: Locale, count: number, ordinal: boolean): Intl.LDMLPluralRule {\n const key = `${locale}:${ordinal ? 'ordinal' : 'cardinal'}`;\n let rules = pluralRules.get(key);\n\n if (!rules) {\n rules = new Intl.PluralRules(locale, { type: ordinal ? 'ordinal' : 'cardinal' });\n pluralRules.set(key, rules);\n }\n\n return rules.select(count);\n}\n"],"mappings":";;AAIA,IAAM,oBAAc,IAAI,IAA8B;AAEtD,SAAgB,EAAgB,GAAwB;CACtD,IAAI;EACF,IAAM,CAAC,KAAa,KAAK,oBAAoB,CAAM;EAEnD,IAAI,GAAW,OAAO;CACxB,QAAQ,CAER;CAEA,MAAM,IAAI,EAAyB,+BAA+B,EAAO,GAAG;AAC9E;AAEA,SAAgB,EAAY,GAAgB,GAAgD;CAC1F,IAAM,oBAAQ,IAAI,IAAY;CAE9B,KAAK,IAAM,KAAa,CAAC,GAAQ,GAAG,CAAQ,GAAG;EAC7C,IAAM,IAAQ,EAAU,MAAM,GAAG;EAEjC,KAAK,IAAI,IAAS,EAAM,QAAQ,IAAS,GAAG,KAC1C,EAAM,IAAI,EAAM,MAAM,GAAG,CAAM,CAAC,CAAC,KAAK,GAAG,CAAC;CAE9C;CAEA,OAAO,CAAC,GAAG,CAAK;AAClB;AAEA,SAAgB,EAAe,GAAgB,GAAe,GAAuC;CACnG,IAAM,IAAM,GAAG,EAAO,GAAG,IAAU,YAAY,cAC3C,IAAQ,EAAY,IAAI,CAAG;CAO/B,OALK,MACH,IAAQ,IAAI,KAAK,YAAY,GAAQ,EAAE,MAAM,IAAU,YAAY,WAAW,CAAC,GAC/E,EAAY,IAAI,GAAK,CAAK,IAGrB,EAAM,OAAO,CAAK;AAC3B"}
@@ -0,0 +1,2 @@
1
+ const e=require("./errors.cjs"),t=require("./_locale.cjs"),n=require("./_catalog.cjs");function r(r){let i=new Map,a=new Map,o=new Map;for(let[e,n]of Object.entries(r))i.set(t.canonicalLocale(e),n);let s=(e,t)=>{a.set(e,{catalog:t,compiled:n.compileCatalog(t)})};for(let[e,t]of i)typeof t!=`function`&&s(e,t);return{catalogMap(){return new Map([...a].map(([e,{compiled:t}])=>[e,t]))},isLoaded(e){return a.has(t.canonicalLocale(e))},load:async n=>{let r=t.canonicalLocale(n);if(a.has(r))return!1;let c=i.get(r);if(!c)throw new e.LinguaMissingCatalogError(`Catalog has no source for locale "${r}".`);if(typeof c!=`function`)return s(r,c),!0;let l=o.get(r);if(l)return await l,!1;let u=c().then(e=>{s(r,e),o.delete(r)},e=>{throw o.delete(r),e});return o.set(r,u),await u,!0},state(e){let t={};for(let[e,{catalog:n}]of a)t[e]=n;return{catalogs:t,locale:e,version:3}}}}exports.createCatalogStore=r;
2
+ //# sourceMappingURL=_resources.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"_resources.cjs","names":[],"sources":["../src/_resources.ts"],"sourcesContent":["import type { Catalog, CatalogSources, TranslationState, LoadedCatalogs, Locale } from './types';\n\nimport { type CompiledCatalog, compileCatalog } from './_catalog';\nimport { canonicalLocale } from './_locale';\nimport { LinguaMissingCatalogError } from './errors';\n\n/** One catalog state machine owns static sources, lazy sources, and in-flight work. */\nexport function createCatalogStore<C extends Catalog>(sources: CatalogSources<C>) {\n const definitions = new Map<Locale, C | (() => Promise<C>)>();\n const loaded = new Map<Locale, { readonly catalog: C; readonly compiled: CompiledCatalog }>();\n const tasks = new Map<Locale, Promise<void>>();\n\n for (const [locale, source] of Object.entries(sources)) definitions.set(canonicalLocale(locale), source);\n\n const add = (locale: Locale, catalog: C): void => {\n loaded.set(locale, { catalog, compiled: compileCatalog(catalog) });\n };\n\n for (const [locale, source] of definitions) {\n if (typeof source !== 'function') add(locale, source);\n }\n\n const load = async (requestedLocale: Locale): Promise<boolean> => {\n const locale = canonicalLocale(requestedLocale);\n\n if (loaded.has(locale)) return false;\n\n const source = definitions.get(locale);\n\n if (!source) throw new LinguaMissingCatalogError(`Catalog has no source for locale \"${locale}\".`);\n\n if (typeof source !== 'function') {\n add(locale, source);\n\n return true;\n }\n\n const existing = tasks.get(locale);\n\n if (existing) {\n await existing;\n\n return false;\n }\n\n const task = source().then(\n (catalog) => {\n add(locale, catalog);\n tasks.delete(locale);\n },\n (error: unknown) => {\n tasks.delete(locale);\n throw error;\n },\n );\n\n tasks.set(locale, task);\n await task;\n\n return true;\n };\n\n return {\n catalogMap(): ReadonlyMap<Locale, CompiledCatalog> {\n return new Map([...loaded].map(([locale, { compiled }]) => [locale, compiled]));\n },\n isLoaded(locale: Locale): boolean {\n return loaded.has(canonicalLocale(locale));\n },\n load,\n state(locale: Locale): TranslationState<C> {\n const catalogs: LoadedCatalogs<C> = {};\n\n for (const [loadedLocale, { catalog }] of loaded) catalogs[loadedLocale] = catalog;\n\n return { catalogs, locale, version: 3 };\n },\n };\n}\n"],"mappings":"uFAOA,SAAgB,EAAsC,EAA4B,CAChF,IAAM,EAAc,IAAI,IAClB,EAAS,IAAI,IACb,EAAQ,IAAI,IAElB,IAAK,GAAM,CAAC,EAAQ,KAAW,OAAO,QAAQ,CAAO,EAAG,EAAY,IAAI,EAAA,gBAAgB,CAAM,EAAG,CAAM,EAEvG,IAAM,GAAO,EAAgB,IAAqB,CAChD,EAAO,IAAI,EAAQ,CAAE,UAAS,SAAU,EAAA,eAAe,CAAO,CAAE,CAAC,CACnE,EAEA,IAAK,GAAM,CAAC,EAAQ,KAAW,EACzB,OAAO,GAAW,YAAY,EAAI,EAAQ,CAAM,EA2CtD,MAAO,CACL,YAAmD,CACjD,OAAO,IAAI,IAAI,CAAC,GAAG,CAAM,CAAC,CAAC,KAAK,CAAC,EAAQ,CAAE,eAAgB,CAAC,EAAQ,CAAQ,CAAC,CAAC,CAChF,EACA,SAAS,EAAyB,CAChC,OAAO,EAAO,IAAI,EAAA,gBAAgB,CAAM,CAAC,CAC3C,EACA,UA/CkB,IAA8C,CAChE,IAAM,EAAS,EAAA,gBAAgB,CAAe,EAE9C,GAAI,EAAO,IAAI,CAAM,EAAG,MAAO,GAE/B,IAAM,EAAS,EAAY,IAAI,CAAM,EAErC,GAAI,CAAC,EAAQ,MAAM,IAAI,EAAA,0BAA0B,qCAAqC,EAAO,GAAG,EAEhG,GAAI,OAAO,GAAW,WAGpB,OAFA,EAAI,EAAQ,CAAM,EAEX,GAGT,IAAM,EAAW,EAAM,IAAI,CAAM,EAEjC,GAAI,EAGF,OAFA,MAAM,EAEC,GAGT,IAAM,EAAO,EAAO,CAAC,CAAC,KACnB,GAAY,CACX,EAAI,EAAQ,CAAO,EACnB,EAAM,OAAO,CAAM,CACrB,EACC,GAAmB,CAElB,MADA,EAAM,OAAO,CAAM,EACb,CACR,CACF,EAKA,OAHA,EAAM,IAAI,EAAQ,CAAI,EACtB,MAAM,EAEC,EACT,EAUE,MAAM,EAAqC,CACzC,IAAM,EAA8B,CAAC,EAErC,IAAK,GAAM,CAAC,EAAc,CAAE,cAAc,EAAQ,EAAS,GAAgB,EAE3E,MAAO,CAAE,WAAU,SAAQ,QAAS,CAAE,CACxC,CACF,CACF"}
@@ -0,0 +1,10 @@
1
+ import type { Catalog, CatalogSources, TranslationState, Locale } from './types';
2
+ import { type CompiledCatalog } from './_catalog';
3
+ /** One catalog state machine owns static sources, lazy sources, and in-flight work. */
4
+ export declare function createCatalogStore<C extends Catalog>(sources: CatalogSources<C>): {
5
+ catalogMap(): ReadonlyMap<Locale, CompiledCatalog>;
6
+ isLoaded(locale: Locale): boolean;
7
+ load: (requestedLocale: Locale) => Promise<boolean>;
8
+ state(locale: Locale): TranslationState<C>;
9
+ };
10
+ //# sourceMappingURL=_resources.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"_resources.d.ts","sourceRoot":"","sources":["../src/_resources.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,cAAc,EAAE,gBAAgB,EAAkB,MAAM,EAAE,MAAM,SAAS,CAAC;AAEjG,OAAO,EAAE,KAAK,eAAe,EAAkB,MAAM,YAAY,CAAC;AAIlE,uFAAuF;AACvF,wBAAgB,kBAAkB,CAAC,CAAC,SAAS,OAAO,EAAE,OAAO,EAAE,cAAc,CAAC,CAAC,CAAC;kBAwD9D,WAAW,CAAC,MAAM,EAAE,eAAe,CAAC;qBAGjC,MAAM,GAAG,OAAO;4BA5CE,MAAM,KAAG,OAAO,CAAC,OAAO,CAAC;kBAgD9C,MAAM,GAAG,gBAAgB,CAAC,CAAC,CAAC;EAQ7C"}
@@ -0,0 +1,51 @@
1
+ import { LinguaMissingCatalogError as e } from "./errors.js";
2
+ import { canonicalLocale as t } from "./_locale.js";
3
+ import { compileCatalog as n } from "./_catalog.js";
4
+ //#region src/_resources.ts
5
+ function r(r) {
6
+ let i = /* @__PURE__ */ new Map(), a = /* @__PURE__ */ new Map(), o = /* @__PURE__ */ new Map();
7
+ for (let [e, n] of Object.entries(r)) i.set(t(e), n);
8
+ let s = (e, t) => {
9
+ a.set(e, {
10
+ catalog: t,
11
+ compiled: n(t)
12
+ });
13
+ };
14
+ for (let [e, t] of i) typeof t != "function" && s(e, t);
15
+ return {
16
+ catalogMap() {
17
+ return new Map([...a].map(([e, { compiled: t }]) => [e, t]));
18
+ },
19
+ isLoaded(e) {
20
+ return a.has(t(e));
21
+ },
22
+ load: async (n) => {
23
+ let r = t(n);
24
+ if (a.has(r)) return !1;
25
+ let c = i.get(r);
26
+ if (!c) throw new e(`Catalog has no source for locale "${r}".`);
27
+ if (typeof c != "function") return s(r, c), !0;
28
+ let l = o.get(r);
29
+ if (l) return await l, !1;
30
+ let u = c().then((e) => {
31
+ s(r, e), o.delete(r);
32
+ }, (e) => {
33
+ throw o.delete(r), e;
34
+ });
35
+ return o.set(r, u), await u, !0;
36
+ },
37
+ state(e) {
38
+ let t = {};
39
+ for (let [e, { catalog: n }] of a) t[e] = n;
40
+ return {
41
+ catalogs: t,
42
+ locale: e,
43
+ version: 3
44
+ };
45
+ }
46
+ };
47
+ }
48
+ //#endregion
49
+ export { r as createCatalogStore };
50
+
51
+ //# sourceMappingURL=_resources.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"_resources.js","names":[],"sources":["../src/_resources.ts"],"sourcesContent":["import type { Catalog, CatalogSources, TranslationState, LoadedCatalogs, Locale } from './types';\n\nimport { type CompiledCatalog, compileCatalog } from './_catalog';\nimport { canonicalLocale } from './_locale';\nimport { LinguaMissingCatalogError } from './errors';\n\n/** One catalog state machine owns static sources, lazy sources, and in-flight work. */\nexport function createCatalogStore<C extends Catalog>(sources: CatalogSources<C>) {\n const definitions = new Map<Locale, C | (() => Promise<C>)>();\n const loaded = new Map<Locale, { readonly catalog: C; readonly compiled: CompiledCatalog }>();\n const tasks = new Map<Locale, Promise<void>>();\n\n for (const [locale, source] of Object.entries(sources)) definitions.set(canonicalLocale(locale), source);\n\n const add = (locale: Locale, catalog: C): void => {\n loaded.set(locale, { catalog, compiled: compileCatalog(catalog) });\n };\n\n for (const [locale, source] of definitions) {\n if (typeof source !== 'function') add(locale, source);\n }\n\n const load = async (requestedLocale: Locale): Promise<boolean> => {\n const locale = canonicalLocale(requestedLocale);\n\n if (loaded.has(locale)) return false;\n\n const source = definitions.get(locale);\n\n if (!source) throw new LinguaMissingCatalogError(`Catalog has no source for locale \"${locale}\".`);\n\n if (typeof source !== 'function') {\n add(locale, source);\n\n return true;\n }\n\n const existing = tasks.get(locale);\n\n if (existing) {\n await existing;\n\n return false;\n }\n\n const task = source().then(\n (catalog) => {\n add(locale, catalog);\n tasks.delete(locale);\n },\n (error: unknown) => {\n tasks.delete(locale);\n throw error;\n },\n );\n\n tasks.set(locale, task);\n await task;\n\n return true;\n };\n\n return {\n catalogMap(): ReadonlyMap<Locale, CompiledCatalog> {\n return new Map([...loaded].map(([locale, { compiled }]) => [locale, compiled]));\n },\n isLoaded(locale: Locale): boolean {\n return loaded.has(canonicalLocale(locale));\n },\n load,\n state(locale: Locale): TranslationState<C> {\n const catalogs: LoadedCatalogs<C> = {};\n\n for (const [loadedLocale, { catalog }] of loaded) catalogs[loadedLocale] = catalog;\n\n return { catalogs, locale, version: 3 };\n },\n };\n}\n"],"mappings":";;;;AAOA,SAAgB,EAAsC,GAA4B;CAChF,IAAM,oBAAc,IAAI,IAAoC,GACtD,oBAAS,IAAI,IAAyE,GACtF,oBAAQ,IAAI,IAA2B;CAE7C,KAAK,IAAM,CAAC,GAAQ,MAAW,OAAO,QAAQ,CAAO,GAAG,EAAY,IAAI,EAAgB,CAAM,GAAG,CAAM;CAEvG,IAAM,KAAO,GAAgB,MAAqB;EAChD,EAAO,IAAI,GAAQ;GAAE;GAAS,UAAU,EAAe,CAAO;EAAE,CAAC;CACnE;CAEA,KAAK,IAAM,CAAC,GAAQ,MAAW,GAC7B,AAAI,OAAO,KAAW,cAAY,EAAI,GAAQ,CAAM;CA2CtD,OAAO;EACL,aAAmD;GACjD,OAAO,IAAI,IAAI,CAAC,GAAG,CAAM,CAAC,CAAC,KAAK,CAAC,GAAQ,EAAE,mBAAgB,CAAC,GAAQ,CAAQ,CAAC,CAAC;EAChF;EACA,SAAS,GAAyB;GAChC,OAAO,EAAO,IAAI,EAAgB,CAAM,CAAC;EAC3C;EACA,aA/CkB,MAA8C;GAChE,IAAM,IAAS,EAAgB,CAAe;GAE9C,IAAI,EAAO,IAAI,CAAM,GAAG,OAAO;GAE/B,IAAM,IAAS,EAAY,IAAI,CAAM;GAErC,IAAI,CAAC,GAAQ,MAAM,IAAI,EAA0B,qCAAqC,EAAO,GAAG;GAEhG,IAAI,OAAO,KAAW,YAGpB,OAFA,EAAI,GAAQ,CAAM,GAEX;GAGT,IAAM,IAAW,EAAM,IAAI,CAAM;GAEjC,IAAI,GAGF,OAFA,MAAM,GAEC;GAGT,IAAM,IAAO,EAAO,CAAC,CAAC,MACnB,MAAY;IAEX,AADA,EAAI,GAAQ,CAAO,GACnB,EAAM,OAAO,CAAM;GACrB,IACC,MAAmB;IAElB,MADA,EAAM,OAAO,CAAM,GACb;GACR,CACF;GAKA,OAHA,EAAM,IAAI,GAAQ,CAAI,GACtB,MAAM,GAEC;EACT;EAUE,MAAM,GAAqC;GACzC,IAAM,IAA8B,CAAC;GAErC,KAAK,IAAM,CAAC,GAAc,EAAE,iBAAc,GAAQ,EAAS,KAAgB;GAE3E,OAAO;IAAE;IAAU;IAAQ,SAAS;GAAE;EACxC;CACF;AACF"}
@@ -0,0 +1,2 @@
1
+ var e=/\{([\p{ID_Continue}-]+)\}/gu;function t(t){let n=[],r=0;for(let i of t.matchAll(e)){let e=i.index??0;e>r&&n.push(t.slice(r,e)),n.push({value:i[1]}),r=e+i[0].length}return r<t.length&&n.push(t.slice(r)),n}function n(e,t,n){return e.map(e=>{if(typeof e==`string`)return e;let r=Object.hasOwn(t,e.value)?t[e.value]:void 0;return r==null?n(e.value):String(r)}).join(``)}function r(e,t,n){let r=[];for(let i of e){if(typeof i==`string`){i!==``&&r.push(i);continue}r.push(Object.hasOwn(t,i.value)?t[i.value]:n(i.value))}return r}exports.compileTemplate=t,exports.renderSegments=r,exports.renderText=n;
2
+ //# sourceMappingURL=_template.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"_template.cjs","names":[],"sources":["../src/_template.ts"],"sourcesContent":["export type TemplatePart = string | { readonly value: string };\nexport type Template = readonly TemplatePart[];\n\nconst interpolation = /\\{([\\p{ID_Continue}-]+)\\}/gu;\n\nexport function compileTemplate(value: string): Template {\n const parts: TemplatePart[] = [];\n let offset = 0;\n\n for (const match of value.matchAll(interpolation)) {\n const index = match.index ?? 0;\n\n if (index > offset) parts.push(value.slice(offset, index));\n\n parts.push({ value: match[1] });\n offset = index + match[0].length;\n }\n\n if (offset < value.length) parts.push(value.slice(offset));\n\n return parts;\n}\n\nexport function renderText(\n parts: Template,\n values: Record<string, unknown>,\n missing: (name: string) => string,\n): string {\n return parts\n .map((part) => {\n if (typeof part === 'string') return part;\n\n const value = Object.hasOwn(values, part.value) ? values[part.value] : undefined;\n\n return value == null ? missing(part.value) : String(value);\n })\n .join('');\n}\n\nexport function renderSegments<V>(\n parts: Template,\n values: Record<string, V | number>,\n missing: (name: string) => string,\n): Array<string | number | V> {\n const result: Array<string | number | V> = [];\n\n for (const part of parts) {\n if (typeof part === 'string') {\n if (part !== '') result.push(part);\n\n continue;\n }\n\n result.push(Object.hasOwn(values, part.value) ? values[part.value]! : missing(part.value));\n }\n\n return result;\n}\n"],"mappings":"AAGA,IAAM,EAAgB,8BAEtB,SAAgB,EAAgB,EAAyB,CACvD,IAAM,EAAwB,CAAC,EAC3B,EAAS,EAEb,IAAK,IAAM,KAAS,EAAM,SAAS,CAAa,EAAG,CACjD,IAAM,EAAQ,EAAM,OAAS,EAEzB,EAAQ,GAAQ,EAAM,KAAK,EAAM,MAAM,EAAQ,CAAK,CAAC,EAEzD,EAAM,KAAK,CAAE,MAAO,EAAM,EAAG,CAAC,EAC9B,EAAS,EAAQ,EAAM,EAAE,CAAC,MAC5B,CAIA,OAFI,EAAS,EAAM,QAAQ,EAAM,KAAK,EAAM,MAAM,CAAM,CAAC,EAElD,CACT,CAEA,SAAgB,EACd,EACA,EACA,EACQ,CACR,OAAO,EACJ,IAAK,GAAS,CACb,GAAI,OAAO,GAAS,SAAU,OAAO,EAErC,IAAM,EAAQ,OAAO,OAAO,EAAQ,EAAK,KAAK,EAAI,EAAO,EAAK,OAAS,IAAA,GAEvE,OAAO,GAAS,KAAO,EAAQ,EAAK,KAAK,EAAI,OAAO,CAAK,CAC3D,CAAC,CAAC,CACD,KAAK,EAAE,CACZ,CAEA,SAAgB,EACd,EACA,EACA,EAC4B,CAC5B,IAAM,EAAqC,CAAC,EAE5C,IAAK,IAAM,KAAQ,EAAO,CACxB,GAAI,OAAO,GAAS,SAAU,CACxB,IAAS,IAAI,EAAO,KAAK,CAAI,EAEjC,QACF,CAEA,EAAO,KAAK,OAAO,OAAO,EAAQ,EAAK,KAAK,EAAI,EAAO,EAAK,OAAU,EAAQ,EAAK,KAAK,CAAC,CAC3F,CAEA,OAAO,CACT"}
@@ -0,0 +1,8 @@
1
+ export type TemplatePart = string | {
2
+ readonly value: string;
3
+ };
4
+ export type Template = readonly TemplatePart[];
5
+ export declare function compileTemplate(value: string): Template;
6
+ export declare function renderText(parts: Template, values: Record<string, unknown>, missing: (name: string) => string): string;
7
+ export declare function renderSegments<V>(parts: Template, values: Record<string, V | number>, missing: (name: string) => string): Array<string | number | V>;
8
+ //# sourceMappingURL=_template.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"_template.d.ts","sourceRoot":"","sources":["../src/_template.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,YAAY,GAAG,MAAM,GAAG;IAAE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AAC/D,MAAM,MAAM,QAAQ,GAAG,SAAS,YAAY,EAAE,CAAC;AAI/C,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,QAAQ,CAgBvD;AAED,wBAAgB,UAAU,CACxB,KAAK,EAAE,QAAQ,EACf,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/B,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,GAChC,MAAM,CAUR;AAED,wBAAgB,cAAc,CAAC,CAAC,EAC9B,KAAK,EAAE,QAAQ,EACf,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,GAAG,MAAM,CAAC,EAClC,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,GAChC,KAAK,CAAC,MAAM,GAAG,MAAM,GAAG,CAAC,CAAC,CAc5B"}
@@ -0,0 +1,32 @@
1
+ //#region src/_template.ts
2
+ var e = /\{([\p{ID_Continue}-]+)\}/gu;
3
+ function t(t) {
4
+ let n = [], r = 0;
5
+ for (let i of t.matchAll(e)) {
6
+ let e = i.index ?? 0;
7
+ e > r && n.push(t.slice(r, e)), n.push({ value: i[1] }), r = e + i[0].length;
8
+ }
9
+ return r < t.length && n.push(t.slice(r)), n;
10
+ }
11
+ function n(e, t, n) {
12
+ return e.map((e) => {
13
+ if (typeof e == "string") return e;
14
+ let r = Object.hasOwn(t, e.value) ? t[e.value] : void 0;
15
+ return r == null ? n(e.value) : String(r);
16
+ }).join("");
17
+ }
18
+ function r(e, t, n) {
19
+ let r = [];
20
+ for (let i of e) {
21
+ if (typeof i == "string") {
22
+ i !== "" && r.push(i);
23
+ continue;
24
+ }
25
+ r.push(Object.hasOwn(t, i.value) ? t[i.value] : n(i.value));
26
+ }
27
+ return r;
28
+ }
29
+ //#endregion
30
+ export { t as compileTemplate, r as renderSegments, n as renderText };
31
+
32
+ //# sourceMappingURL=_template.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"_template.js","names":[],"sources":["../src/_template.ts"],"sourcesContent":["export type TemplatePart = string | { readonly value: string };\nexport type Template = readonly TemplatePart[];\n\nconst interpolation = /\\{([\\p{ID_Continue}-]+)\\}/gu;\n\nexport function compileTemplate(value: string): Template {\n const parts: TemplatePart[] = [];\n let offset = 0;\n\n for (const match of value.matchAll(interpolation)) {\n const index = match.index ?? 0;\n\n if (index > offset) parts.push(value.slice(offset, index));\n\n parts.push({ value: match[1] });\n offset = index + match[0].length;\n }\n\n if (offset < value.length) parts.push(value.slice(offset));\n\n return parts;\n}\n\nexport function renderText(\n parts: Template,\n values: Record<string, unknown>,\n missing: (name: string) => string,\n): string {\n return parts\n .map((part) => {\n if (typeof part === 'string') return part;\n\n const value = Object.hasOwn(values, part.value) ? values[part.value] : undefined;\n\n return value == null ? missing(part.value) : String(value);\n })\n .join('');\n}\n\nexport function renderSegments<V>(\n parts: Template,\n values: Record<string, V | number>,\n missing: (name: string) => string,\n): Array<string | number | V> {\n const result: Array<string | number | V> = [];\n\n for (const part of parts) {\n if (typeof part === 'string') {\n if (part !== '') result.push(part);\n\n continue;\n }\n\n result.push(Object.hasOwn(values, part.value) ? values[part.value]! : missing(part.value));\n }\n\n return result;\n}\n"],"mappings":";AAGA,IAAM,IAAgB;AAEtB,SAAgB,EAAgB,GAAyB;CACvD,IAAM,IAAwB,CAAC,GAC3B,IAAS;CAEb,KAAK,IAAM,KAAS,EAAM,SAAS,CAAa,GAAG;EACjD,IAAM,IAAQ,EAAM,SAAS;EAK7B,AAHI,IAAQ,KAAQ,EAAM,KAAK,EAAM,MAAM,GAAQ,CAAK,CAAC,GAEzD,EAAM,KAAK,EAAE,OAAO,EAAM,GAAG,CAAC,GAC9B,IAAS,IAAQ,EAAM,EAAE,CAAC;CAC5B;CAIA,OAFI,IAAS,EAAM,UAAQ,EAAM,KAAK,EAAM,MAAM,CAAM,CAAC,GAElD;AACT;AAEA,SAAgB,EACd,GACA,GACA,GACQ;CACR,OAAO,EACJ,KAAK,MAAS;EACb,IAAI,OAAO,KAAS,UAAU,OAAO;EAErC,IAAM,IAAQ,OAAO,OAAO,GAAQ,EAAK,KAAK,IAAI,EAAO,EAAK,SAAS,KAAA;EAEvE,OAAO,KAAS,OAAO,EAAQ,EAAK,KAAK,IAAI,OAAO,CAAK;CAC3D,CAAC,CAAC,CACD,KAAK,EAAE;AACZ;AAEA,SAAgB,EACd,GACA,GACA,GAC4B;CAC5B,IAAM,IAAqC,CAAC;CAE5C,KAAK,IAAM,KAAQ,GAAO;EACxB,IAAI,OAAO,KAAS,UAAU;GAC5B,AAAI,MAAS,MAAI,EAAO,KAAK,CAAI;GAEjC;EACF;EAEA,EAAO,KAAK,OAAO,OAAO,GAAQ,EAAK,KAAK,IAAI,EAAO,EAAK,SAAU,EAAQ,EAAK,KAAK,CAAC;CAC3F;CAEA,OAAO;AACT"}