@vielzeug/lingua 1.1.4 → 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 (118) hide show
  1. package/README.md +15 -54
  2. package/dist/_catalog.cjs +1 -1
  3. package/dist/_catalog.cjs.map +1 -1
  4. package/dist/_catalog.d.ts +13 -16
  5. package/dist/_catalog.d.ts.map +1 -1
  6. package/dist/_catalog.js +43 -36
  7. package/dist/_catalog.js.map +1 -1
  8. package/dist/_dev.cjs +1 -1
  9. package/dist/_dev.cjs.map +1 -1
  10. package/dist/_dev.js +3 -11
  11. package/dist/_dev.js.map +1 -1
  12. package/dist/_locale.cjs +2 -0
  13. package/dist/_locale.cjs.map +1 -0
  14. package/dist/_locale.d.ts +5 -0
  15. package/dist/_locale.d.ts.map +1 -0
  16. package/dist/_locale.js +26 -0
  17. package/dist/_locale.js.map +1 -0
  18. package/dist/_resources.cjs +2 -0
  19. package/dist/_resources.cjs.map +1 -0
  20. package/dist/_resources.d.ts +10 -0
  21. package/dist/_resources.d.ts.map +1 -0
  22. package/dist/_resources.js +51 -0
  23. package/dist/_resources.js.map +1 -0
  24. package/dist/_template.cjs +2 -0
  25. package/dist/_template.cjs.map +1 -0
  26. package/dist/_template.d.ts +8 -0
  27. package/dist/_template.d.ts.map +1 -0
  28. package/dist/_template.js +32 -0
  29. package/dist/_template.js.map +1 -0
  30. package/dist/errors.cjs +1 -1
  31. package/dist/errors.cjs.map +1 -1
  32. package/dist/errors.d.ts +6 -18
  33. package/dist/errors.d.ts.map +1 -1
  34. package/dist/errors.js +3 -9
  35. package/dist/errors.js.map +1 -1
  36. package/dist/format.cjs +1 -1
  37. package/dist/format.cjs.map +1 -1
  38. package/dist/format.d.ts +0 -9
  39. package/dist/format.d.ts.map +1 -1
  40. package/dist/format.js +10 -12
  41. package/dist/format.js.map +1 -1
  42. package/dist/i18n.cjs +1 -1
  43. package/dist/i18n.cjs.map +1 -1
  44. package/dist/i18n.d.ts +25 -11
  45. package/dist/i18n.d.ts.map +1 -1
  46. package/dist/i18n.js +66 -183
  47. package/dist/i18n.js.map +1 -1
  48. package/dist/index.cjs +1 -1
  49. package/dist/index.d.ts +4 -12
  50. package/dist/index.d.ts.map +1 -1
  51. package/dist/index.js +4 -4
  52. package/dist/lingua.cjs +1 -1
  53. package/dist/lingua.cjs.map +1 -1
  54. package/dist/lingua.iife.js +1 -1
  55. package/dist/lingua.iife.js.map +1 -1
  56. package/dist/lingua.js +1 -1
  57. package/dist/lingua.js.map +1 -1
  58. package/dist/translator.cjs +2 -0
  59. package/dist/translator.cjs.map +1 -0
  60. package/dist/translator.d.ts +20 -0
  61. package/dist/translator.d.ts.map +1 -0
  62. package/dist/translator.js +61 -0
  63. package/dist/translator.js.map +1 -0
  64. package/dist/types.d.ts +53 -0
  65. package/dist/types.d.ts.map +1 -0
  66. package/dist/validate.cjs +1 -1
  67. package/dist/validate.cjs.map +1 -1
  68. package/dist/validate.d.ts +5 -37
  69. package/dist/validate.d.ts.map +1 -1
  70. package/dist/validate.js +21 -52
  71. package/dist/validate.js.map +1 -1
  72. package/package.json +4 -4
  73. package/dist/_catalog-store.cjs +0 -2
  74. package/dist/_catalog-store.cjs.map +0 -1
  75. package/dist/_catalog-store.d.ts +0 -34
  76. package/dist/_catalog-store.d.ts.map +0 -1
  77. package/dist/_catalog-store.js +0 -81
  78. package/dist/_catalog-store.js.map +0 -1
  79. package/dist/_chain.cjs +0 -2
  80. package/dist/_chain.cjs.map +0 -1
  81. package/dist/_chain.d.ts +0 -21
  82. package/dist/_chain.d.ts.map +0 -1
  83. package/dist/_chain.js +0 -45
  84. package/dist/_chain.js.map +0 -1
  85. package/dist/_constants.cjs +0 -2
  86. package/dist/_constants.cjs.map +0 -1
  87. package/dist/_constants.d.ts +0 -3
  88. package/dist/_constants.d.ts.map +0 -1
  89. package/dist/_constants.js +0 -17
  90. package/dist/_constants.js.map +0 -1
  91. package/dist/_namespace-store.cjs +0 -2
  92. package/dist/_namespace-store.cjs.map +0 -1
  93. package/dist/_namespace-store.d.ts +0 -31
  94. package/dist/_namespace-store.d.ts.map +0 -1
  95. package/dist/_namespace-store.js +0 -54
  96. package/dist/_namespace-store.js.map +0 -1
  97. package/dist/_translate.cjs +0 -2
  98. package/dist/_translate.cjs.map +0 -1
  99. package/dist/_translate.d.ts +0 -21
  100. package/dist/_translate.d.ts.map +0 -1
  101. package/dist/_translate.js +0 -54
  102. package/dist/_translate.js.map +0 -1
  103. package/dist/i18n-types.d.ts +0 -261
  104. package/dist/i18n-types.d.ts.map +0 -1
  105. package/dist/template-BZRFdmaQ.cjs +0 -2
  106. package/dist/template-BZRFdmaQ.cjs.map +0 -1
  107. package/dist/template-BihTfBYb.js +0 -2
  108. package/dist/template-BihTfBYb.js.map +0 -1
  109. package/dist/template.cjs +0 -2
  110. package/dist/template.cjs.map +0 -1
  111. package/dist/template.d.ts +0 -15
  112. package/dist/template.d.ts.map +0 -1
  113. package/dist/template.js +0 -49
  114. package/dist/template.js.map +0 -1
  115. package/dist/validate-D1U8TTeT.js +0 -2
  116. package/dist/validate-D1U8TTeT.js.map +0 -1
  117. package/dist/validate-x7lFFyV2.cjs +0 -2
  118. package/dist/validate-x7lFFyV2.cjs.map +0 -1
@@ -1,54 +0,0 @@
1
- import { devOnly as e, warn as t } from "./_dev.js";
2
- import { renderTemplate as n } from "./template.js";
3
- import { LinguaCountInVarsError as r, LinguaInvalidCountError as i } from "./errors.js";
4
- import { selectPluralForm as a } from "./_chain.js";
5
- //#region src/_translate.ts
6
- function o(e, t) {
7
- for (let n of e.chain) {
8
- let r = e.catalogStore.resolve(n)?.get(t);
9
- if (r !== void 0) return r;
10
- }
11
- }
12
- function s(e, t) {
13
- for (let n of e.chain) if (e.catalogStore.resolve(n)?.prefixes.has(t)) return !0;
14
- return !1;
15
- }
16
- function c(e, t) {
17
- return o(e, t) !== void 0 || s(e, t);
18
- }
19
- function l(e, t, r, i) {
20
- return n(r.compiled, i, t, e.locale, e.onMissingVar);
21
- }
22
- function u(n, r, i) {
23
- let a = o(n, r);
24
- return a ? l(n, r, a, i) : (e(() => {
25
- s(n, r) && t(`t('${r}') resolved to the missing-key fallback, but '${r}' exists as a plural branch — use tp('${r}', count) instead.`);
26
- }), n.onMissingKey(r, n.locale));
27
- }
28
- function d(e, t, n, r) {
29
- let i = [];
30
- return !r && n === 0 && i.push(`${e}.zero`), (t !== "zero" || r) && i.push(`${e}.${t}`), t !== "other" && i.push(`${e}.other`), i;
31
- }
32
- function f(e, t, n, o) {
33
- if (!Number.isFinite(n)) throw new i("`count` must be a finite number.");
34
- let s = o?.vars, c = o?.ordinal ?? !1;
35
- if (s && Object.hasOwn(s, "count")) throw new r("`tp` does not allow `vars.count`; `count` is injected automatically.");
36
- let u = s ? {
37
- count: n,
38
- ...s
39
- } : { count: n };
40
- for (let r of e.chain) {
41
- let i = e.catalogStore.resolve(r);
42
- if (!i) continue;
43
- let o = d(t, a(r, n, c, e.caches), n, c);
44
- for (let t of o) {
45
- let n = i.get(t);
46
- if (n !== void 0) return l(e, t, n, u);
47
- }
48
- }
49
- return e.onMissingKey(t, e.locale);
50
- }
51
- //#endregion
52
- export { c as hasKey, u as translate, f as translatePlural };
53
-
54
- //# sourceMappingURL=_translate.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"_translate.js","names":[],"sources":["../src/_translate.ts"],"sourcesContent":["// Internal — not part of the public API.\n// The translation algorithm: fallback-chain key lookup, plural-form selection, and\n// interpolation. Extracted out of i18n.ts's factory closure so it has its own unit-test seam\n// independent of the full `createI18n()` setup — mirrors `_catalog-store.ts`/`_namespace-store.ts`,\n// which already got this treatment; this was the one piece of comparable complexity left inline.\n\nimport type { CatalogEntry, CatalogEntryData } from './_catalog';\nimport type { LocaleCaches } from './_chain';\nimport type { Locale, TpOptions, TranslateVars } from './i18n-types';\n\nimport { selectPluralForm } from './_chain';\nimport { devOnly, warn } from './_dev';\nimport { LinguaCountInVarsError, LinguaInvalidCountError } from './errors';\nimport { renderTemplate } from './template';\n\n/** Only the read-side of `CatalogStore` this module needs — avoids importing its `M` generic. */\nexport type CatalogResolver = { resolve(loc: Locale): CatalogEntry | undefined };\n\nexport type TranslateContext = {\n readonly caches: LocaleCaches;\n readonly catalogStore: CatalogResolver;\n /** Active fallback chain, most-specific locale first (e.g. `['en-US', 'en']`). */\n readonly chain: readonly Locale[];\n readonly locale: Locale;\n readonly onMissingKey: (key: string, locale: Locale) => string;\n readonly onMissingVar: (varName: string, key: string, locale: Locale) => string;\n};\n\n// Single-pass entry lookup across the active fallback chain.\nfunction findEntry(ctx: TranslateContext, key: string): CatalogEntryData | undefined {\n for (const candidate of ctx.chain) {\n const found = ctx.catalogStore.resolve(candidate)?.get(key);\n\n if (found !== undefined) return found;\n }\n\n return undefined;\n}\n\n// True if `base` exists as a plural branch prefix (e.g. `items` when only `items.one`/\n// `items.other` are registered) anywhere in the fallback chain. Shared by `hasKey()` and\n// `translate()`'s dev-mode miss diagnostic below — both need to distinguish \"key genuinely\n// doesn't exist\" from \"key exists, but only as a plural branch — you want tp(), not t()\".\nfunction isPluralBranch(ctx: TranslateContext, base: string): boolean {\n for (const candidate of ctx.chain) {\n const catalog = ctx.catalogStore.resolve(candidate);\n\n if (catalog?.prefixes.has(base)) return true;\n }\n\n return false;\n}\n\n/** True if `base` exists as a leaf key or a plural branch prefix, anywhere in the fallback chain. */\nexport function hasKey(ctx: TranslateContext, base: string): boolean {\n return findEntry(ctx, base) !== undefined || isPluralBranch(ctx, base);\n}\n\nfunction interpolate(\n ctx: TranslateContext,\n key: string,\n found: CatalogEntryData,\n vars: TranslateVars | undefined,\n): string {\n return renderTemplate(found.compiled, vars, key, ctx.locale, ctx.onMissingVar);\n}\n\nexport function translate(ctx: TranslateContext, key: string, vars?: TranslateVars): string {\n const found = findEntry(ctx, key);\n\n if (!found) {\n // has(key) returns true for a plural-branch-only key (no leaf entry), but t() can't resolve\n // one — the common `if (i18n.has(key)) i18n.t(key)` pattern silently returns the raw key in\n // that case, indistinguishable from a genuinely missing key without this warning.\n devOnly(() => {\n if (isPluralBranch(ctx, key)) {\n warn(\n `t('${key}') resolved to the missing-key fallback, but '${key}' exists as a plural branch — use tp('${key}', count) instead.`,\n );\n }\n });\n\n return ctx.onMissingKey(key, ctx.locale);\n }\n\n return interpolate(ctx, key, found, vars);\n}\n\n// Plural key priority: cardinal zero tries `.zero` override first, then the CLDR form, then\n// `.other` as the final fallback. Ordinal / non-zero skips the `.zero` special case.\nfunction pluralKeyPriority(base: string, form: string, count: number, ordinal: boolean): string[] {\n const keys: string[] = [];\n\n if (!ordinal && count === 0) keys.push(`${base}.zero`);\n\n if (form !== 'zero' || ordinal) keys.push(`${base}.${form}`);\n\n if (form !== 'other') keys.push(`${base}.other`);\n\n return keys;\n}\n\nexport function translatePlural(ctx: TranslateContext, key: string, count: number, options?: TpOptions): string {\n if (!Number.isFinite(count)) {\n throw new LinguaInvalidCountError('`count` must be a finite number.');\n }\n\n const vars = options?.vars;\n const ordinal = options?.ordinal ?? false;\n\n if (vars && Object.hasOwn(vars, 'count')) {\n throw new LinguaCountInVarsError('`tp` does not allow `vars.count`; `count` is injected automatically.');\n }\n\n const mergedVars = vars ? { count, ...vars } : { count };\n\n // Walk the fallback chain locale-by-locale, selecting CLDR plural form using each locale's\n // own rules. This ensures cross-locale fallbacks produce grammatically correct forms.\n for (const candidate of ctx.chain) {\n const catalog = ctx.catalogStore.resolve(candidate);\n\n if (!catalog) continue;\n\n const form = selectPluralForm(candidate, count, ordinal, ctx.caches);\n const keys = pluralKeyPriority(key, form, count, ordinal);\n\n for (const k of keys) {\n const found = catalog.get(k);\n\n if (found !== undefined) return interpolate(ctx, k, found, mergedVars);\n }\n }\n\n return ctx.onMissingKey(key, ctx.locale);\n}\n"],"mappings":";;;;;AA6BA,SAAS,EAAU,GAAuB,GAA2C;CACnF,KAAK,IAAM,KAAa,EAAI,OAAO;EACjC,IAAM,IAAQ,EAAI,aAAa,QAAQ,CAAS,CAAC,EAAE,IAAI,CAAG;EAE1D,IAAI,MAAU,KAAA,GAAW,OAAO;CAClC;AAGF;AAMA,SAAS,EAAe,GAAuB,GAAuB;CACpE,KAAK,IAAM,KAAa,EAAI,OAG1B,IAFgB,EAAI,aAAa,QAAQ,CAErC,CAAA,EAAS,SAAS,IAAI,CAAI,GAAG,OAAO;CAG1C,OAAO;AACT;AAGA,SAAgB,EAAO,GAAuB,GAAuB;CACnE,OAAO,EAAU,GAAK,CAAI,MAAM,KAAA,KAAa,EAAe,GAAK,CAAI;AACvE;AAEA,SAAS,EACP,GACA,GACA,GACA,GACQ;CACR,OAAO,EAAe,EAAM,UAAU,GAAM,GAAK,EAAI,QAAQ,EAAI,YAAY;AAC/E;AAEA,SAAgB,EAAU,GAAuB,GAAa,GAA8B;CAC1F,IAAM,IAAQ,EAAU,GAAK,CAAG;CAiBhC,OAfK,IAeE,EAAY,GAAK,GAAK,GAAO,CAAI,KAXtC,QAAc;EACZ,AAAI,EAAe,GAAK,CAAG,KACzB,EACE,MAAM,EAAI,gDAAgD,EAAI,wCAAwC,EAAI,mBAC5G;CAEJ,CAAC,GAEM,EAAI,aAAa,GAAK,EAAI,MAAM;AAI3C;AAIA,SAAS,EAAkB,GAAc,GAAc,GAAe,GAA4B;CAChG,IAAM,IAAiB,CAAC;CAQxB,OANI,CAAC,KAAW,MAAU,KAAG,EAAK,KAAK,GAAG,EAAK,MAAM,IAEjD,MAAS,UAAU,MAAS,EAAK,KAAK,GAAG,EAAK,GAAG,GAAM,GAEvD,MAAS,WAAS,EAAK,KAAK,GAAG,EAAK,OAAO,GAExC;AACT;AAEA,SAAgB,EAAgB,GAAuB,GAAa,GAAe,GAA6B;CAC9G,IAAI,CAAC,OAAO,SAAS,CAAK,GACxB,MAAM,IAAI,EAAwB,kCAAkC;CAGtE,IAAM,IAAO,GAAS,MAChB,IAAU,GAAS,WAAW;CAEpC,IAAI,KAAQ,OAAO,OAAO,GAAM,OAAO,GACrC,MAAM,IAAI,EAAuB,sEAAsE;CAGzG,IAAM,IAAa,IAAO;EAAE;EAAO,GAAG;CAAK,IAAI,EAAE,SAAM;CAIvD,KAAK,IAAM,KAAa,EAAI,OAAO;EACjC,IAAM,IAAU,EAAI,aAAa,QAAQ,CAAS;EAElD,IAAI,CAAC,GAAS;EAGd,IAAM,IAAO,EAAkB,GADlB,EAAiB,GAAW,GAAO,GAAS,EAAI,MACzB,GAAM,GAAO,CAAO;EAExD,KAAK,IAAM,KAAK,GAAM;GACpB,IAAM,IAAQ,EAAQ,IAAI,CAAC;GAE3B,IAAI,MAAU,KAAA,GAAW,OAAO,EAAY,GAAK,GAAG,GAAO,CAAU;EACvE;CACF;CAEA,OAAO,EAAI,aAAa,GAAK,EAAI,MAAM;AACzC"}
@@ -1,261 +0,0 @@
1
- import type { Messages } from './_catalog';
2
- import type { LocaleSource } from './_catalog-store';
3
- import type { NamespaceFactory } from './_namespace-store';
4
- import type { Formatter } from './format';
5
- export type Locale = string;
6
- export type Unsubscribe = () => void;
7
- export type { Messages } from './_catalog';
8
- export type { Loader, LocaleSource } from './_catalog-store';
9
- export type { NamespaceFactory } from './_namespace-store';
10
- export type TranslateVars = Record<string, unknown>;
11
- /**
12
- * A snapshot of the i18n instance state at a point in time.
13
- * Object identity changes on every observable change (locale switch, catalog load).
14
- * `t` and `tp` are bound to the locale captured in this snapshot.
15
- */
16
- export type I18nSnapshot = {
17
- readonly locale: Locale;
18
- /** @security Returns raw, unsanitized strings. Sanitize before `innerHTML` insertion. */
19
- readonly t: (key: string, vars?: TranslateVars) => string;
20
- readonly tp: (key: string, count: number, options?: TpOptions) => string;
21
- };
22
- /** Shape of the serialised state produced by `serializeI18n()`. Pass to `hydrateI18n()` on the client. */
23
- export type I18nState = {
24
- readonly catalogs: Record<Locale, Record<string, string>>;
25
- readonly locale: Locale;
26
- };
27
- export type SubscribeOptions = {
28
- immediate?: boolean;
29
- /** AbortSignal — automatically unsubscribes when the signal is aborted. */
30
- signal?: AbortSignal;
31
- };
32
- export type TpOptions = {
33
- /** Use ordinal plural rules (1st, 2nd, 3rd) instead of cardinal (default: `false`). */
34
- ordinal?: boolean;
35
- /** Inject additional interpolation variables alongside the automatically injected `count`. */
36
- vars?: TranslateVars;
37
- };
38
- export type ScopedI18n = {
39
- /** Intl formatter inherited from the parent instance. Follows locale changes automatically. */
40
- readonly fmt: Formatter;
41
- has(key: string): boolean;
42
- t(key: string, vars?: TranslateVars): string;
43
- tp(key: string, count: number, options?: TpOptions): string;
44
- };
45
- type Depth = [never, 0, 1, 2, 3, 4, 5, 6];
46
- export type MessageLeafKeys<T, P extends string = '', D extends number = 7> = [D] extends [0] ? never : T extends string ? P : T extends Record<string, unknown> ? {
47
- [K in string & keyof T]: MessageLeafKeys<T[K], P extends '' ? K : `${P}.${K}`, Depth[D]>;
48
- }[string & keyof T] : never;
49
- export type MessageBranchKeys<T, P extends string = '', D extends number = 7> = [D] extends [0] ? never : T extends Record<string, unknown> ? {
50
- [K in string & keyof T]: T[K] extends string ? never : (P extends '' ? K : `${P}.${K}`) | MessageBranchKeys<T[K], P extends '' ? K : `${P}.${K}`, Depth[D]>;
51
- }[string & keyof T] : never;
52
- export type I18nOptions<M extends Messages = Messages> = {
53
- /** Locale registry. Values can be static message objects or async loaders. */
54
- catalogs?: Record<Locale, LocaleSource<M>>;
55
- /** Locale(s) to search when the active locale is missing a key. Subtags are expanded automatically (e.g. `en-US` → `en`). */
56
- fallback?: Locale | Locale[];
57
- /** Initial active locale. Defaults to `"en"`. Canonicalized via `Intl.getCanonicalLocales`. */
58
- locale?: Locale;
59
- /**
60
- * Called when a translation key is missing.
61
- * Defaults to returning the key string.
62
- *
63
- * @security The default handler returns `key` verbatim. Do not render the return value as HTML
64
- * if keys are constructed from untrusted user input.
65
- */
66
- onMissingKey?: (key: string, locale: Locale) => string;
67
- /**
68
- * Called when an interpolation variable is missing.
69
- * Defaults to returning `{varName}`.
70
- */
71
- onMissingVar?: (varName: string, key: string, locale: Locale) => string;
72
- /**
73
- * Called when a subscriber callback throws. Defaults to `console.error`.
74
- * Override in production to route errors to a structured logger rather than the browser console.
75
- */
76
- onSubscriberError?: (error: unknown) => void;
77
- };
78
- export type I18n<M extends Messages = Messages> = {
79
- /** Delegates to `dispose()`. Enables `using` declarations. */
80
- [Symbol.dispose](): void;
81
- /** `AbortSignal` aborted when `dispose()` is called. Use to tie external lifetimes to this instance. */
82
- readonly disposalSignal: AbortSignal;
83
- /**
84
- * Disposes this i18n instance: removes all subscribers and clears catalog, loader, and namespace state.
85
- * After disposal, all mutation methods throw `LinguaDisposedError` and translation methods
86
- * fall back to `onMissingKey` for every key. Idempotent.
87
- */
88
- dispose(): void;
89
- /** `true` after `dispose()` has been called. */
90
- readonly disposed: boolean;
91
- /**
92
- * Registers a namespace factory and immediately starts loading it for the given locale
93
- * (defaults to the active locale). Deduplicates concurrent and repeated calls.
94
- *
95
- * @remarks
96
- * Call `registerNamespace()` first if you only want to register without loading.
97
- * `extend()` is a convenience that does both in one call.
98
- *
99
- * @throws `LinguaDisposedError` if called on a disposed instance.
100
- *
101
- * @example
102
- * await i18n.extend('settings', (locale) =>
103
- * import(`./locales/${locale}/settings.json`).then((m) => m.default),
104
- * );
105
- */
106
- extend(ns: string, factory: NamespaceFactory, locale?: Locale): Promise<void>;
107
- /** Intl formatter bound to this instance's locale. Follows locale changes automatically. */
108
- readonly fmt: Formatter;
109
- /**
110
- * Creates a derived instance that inherits the current catalog snapshot, loaders,
111
- * namespace registry, and loaded-namespace markers, but has its own locale, fallback chain,
112
- * and subscribers. Catalog mutations on the fork do not affect the parent.
113
- *
114
- * Resolved catalog entries are shared by reference (no template re-compilation), making
115
- * `fork()` cheap for SSR fork-per-request patterns with large catalogs.
116
- *
117
- * @example
118
- * // SSR: per-request locale without touching the shared instance
119
- * const reqI18n = i18n.fork({ locale: req.locale });
120
- */
121
- fork(overrides?: Omit<I18nOptions<M>, 'catalogs'>): I18n<M>;
122
- /** Returns the current snapshot. Object identity changes on every observable change. */
123
- getSnapshot(): I18nSnapshot;
124
- /**
125
- * Extracts a serializable snapshot of all loaded catalogs and the active locale.
126
- * Pass the result to `hydrateI18n()` on the client.
127
- *
128
- * **Warning:** Only fully resolved catalogs are included. Loader-only locales not yet
129
- * preloaded are omitted. Use `i18n.isLoaded(locale)` to verify before calling.
130
- *
131
- * **Warning:** The namespace registry is **not** serialized — factory functions cannot
132
- * be converted to JSON. After `hydrateI18n()`, call `extend()` again for each namespace
133
- * before relying on namespace-patched keys.
134
- */
135
- getState(): I18nState;
136
- /**
137
- * Returns all registered locales.
138
- * - Default (no argument): locales in registration order.
139
- * - `getSupportedLocales(true)`: sorted in ascending code-point order.
140
- */
141
- getSupportedLocales(sorted?: boolean): Locale[];
142
- /**
143
- * Returns `true` if the given key exists in the active fallback chain — either as a leaf
144
- * string key or as a plural branch (any key under `key.` prefix).
145
- *
146
- * **Note:** a branch-only key (e.g. `items` when only `items.one`/`items.other` exist) makes
147
- * `has()` return `true`, but `t()` can't resolve it — use `tp(key, count)` for those. Calling
148
- * `t()` on a branch-only key logs a dev-mode warning pointing at this.
149
- *
150
- * @example
151
- * i18n.has('inbox') // true for leaf or pipe-plural expanded branch
152
- * i18n.has('inbox.one') // true for explicit sub-key
153
- */
154
- has(key: MessageLeafKeys<M> | MessageBranchKeys<M> | (string & {})): boolean;
155
- /**
156
- * Returns `true` if the catalog for `locale` is fully resolved.
157
- * Returns `false` for locales registered as async loaders not yet preloaded, and for unknown locales.
158
- */
159
- isLoaded(locale: Locale): boolean;
160
- /**
161
- * Returns `true` if the namespace has been fully loaded for the given locale.
162
- * Returns `false` if it is not registered or not yet loaded for this locale.
163
- */
164
- isNamespaceLoaded(ns: string, locale?: Locale): boolean;
165
- /**
166
- * Returns `true` if a namespace factory is registered under the given name.
167
- */
168
- isNamespaceRegistered(ns: string): boolean;
169
- /**
170
- * Returns `true` if `locale` is in the known locale registry — either resolved or pending loader.
171
- * Returns `false` for locales that have never been registered.
172
- */
173
- isRegistered(locale: Locale): boolean;
174
- /**
175
- * Loads a previously registered namespace for the given locale (defaults to the active locale).
176
- * Deduplicates concurrent and repeated calls.
177
- *
178
- * @throws `LinguaNamespaceMissingError` if the namespace has not been registered with `registerNamespace()` first.
179
- * @throws `LinguaDisposedError` if called on a disposed instance.
180
- */
181
- loadNamespace(ns: string, locale?: Locale): Promise<void>;
182
- readonly locale: Locale;
183
- preload(locale: Locale): Promise<void>;
184
- /**
185
- * Registers (or replaces) a locale source. If the source is an async loader, it is loaded
186
- * immediately and this method returns a Promise that resolves when the load is complete.
187
- * If the source is a static message object, it is synchronously registered and the returned
188
- * Promise resolves immediately.
189
- *
190
- * @throws `LinguaDisposedError` if called on a disposed instance.
191
- */
192
- register(locale: Locale, source: LocaleSource<M>): Promise<void>;
193
- /**
194
- * Registers a namespace factory without loading it. Use `loadNamespace()` to trigger loading,
195
- * or use `extend()` to register and load in one call.
196
- *
197
- * @remarks
198
- * Re-registering a namespace updates the factory for future loads but does **not** reload
199
- * the namespace if it is already loaded. The new factory takes effect the next time the
200
- * namespace marker is cleared (by a `register()` or `restoreState()` call).
201
- *
202
- * @throws `LinguaDisposedError` if called on a disposed instance.
203
- */
204
- registerNamespace(ns: string, factory: NamespaceFactory): void;
205
- /**
206
- * Hydrates this instance with pre-loaded state (e.g. from a server-rendered payload).
207
- *
208
- * @remarks The namespace registry is **not** included in `I18nState`. After restoring,
209
- * call `extend()` for each namespace before relying on namespace-patched keys.
210
- *
211
- * @remarks Unlike `register()` and construction, this does **not** run the automatic
212
- * dev-mode plural-form check — the assumption is that `state` was already registered (and
213
- * therefore already checked) once on whatever system produced it.
214
- *
215
- * @throws `LinguaDisposedError` if called on a disposed instance.
216
- * @throws `LinguaRestoreError` if the state's locale has no catalog.
217
- */
218
- restoreState(state: I18nState): void;
219
- /**
220
- * Returns a scoped translator. All `t()` / `tp()` calls are automatically prefixed with `${prefix}.`.
221
- * The returned object is memoized — calling `scope(prefix)` with the same string always returns the
222
- * same reference, as long as it's still in the cache (bounded at 128 distinct prefixes; oldest
223
- * evicts first). Intended for a static, finite set of prefixes (nav sections, form names, ...) —
224
- * don't pass a dynamic per-item prefix (e.g. `scope(`item.${id}`)` in a list render), or the
225
- * memoization guarantee stops holding once you exceed the bound.
226
- *
227
- * @example
228
- * const nav = i18n.scope('nav');
229
- * nav.t('home'); // i18n.t('nav.home')
230
- * nav.tp('items', 3); // i18n.tp('nav.items', 3)
231
- */
232
- scope(prefix: MessageBranchKeys<M> | (string & {})): ScopedI18n;
233
- /**
234
- * Switches the active locale. Loads the locale if it is registered as an async loader.
235
- * Last concurrent call wins; stale responses are discarded.
236
- * If loading fails, the active locale is unchanged.
237
- *
238
- * @throws `LinguaMissingLocaleError` if the locale is not registered.
239
- * @throws `LinguaDisposedError` if called on a disposed instance.
240
- */
241
- setLocale(locale: Locale): Promise<void>;
242
- /**
243
- * Subscribes to locale/catalog changes.
244
- * - `{ immediate: true }`: fires immediately and on every change.
245
- * - `{ signal }`: unsubscribes when the AbortSignal fires.
246
- *
247
- * @throws `LinguaDisposedError` if called on a disposed instance.
248
- */
249
- subscribe(callback: (snapshot: I18nSnapshot) => void, options?: SubscribeOptions): Unsubscribe;
250
- /** @security Returns raw, unsanitized strings. Sanitize before `innerHTML` insertion. */
251
- t(key: MessageLeafKeys<M> | (string & {}), vars?: TranslateVars): string;
252
- /**
253
- * Translates a plural branch key. `count` is injected automatically.
254
- *
255
- * @throws `LinguaInvalidCountError` if `count` is not finite.
256
- * @throws `LinguaCountInVarsError` if `options.vars.count` is set.
257
- * @security Returns raw, unsanitized strings.
258
- */
259
- tp(key: MessageBranchKeys<M> | (string & {}), count: number, options?: TpOptions): string;
260
- };
261
- //# sourceMappingURL=i18n-types.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"i18n-types.d.ts","sourceRoot":"","sources":["../src/i18n-types.ts"],"names":[],"mappings":"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"}
@@ -1,2 +0,0 @@
1
- var e=new Set([`__proto__`,`constructor`,`prototype`]),t=new Set([`zero`,`one`,`two`,`few`,`many`,`other`]),n=!globalThis.__LINGUA_PROD__;function r(e){n&&console.warn(`[@vielzeug/lingua] ${e}`)}function i(e,...t){n&&console.error(`[@vielzeug/lingua] ${e}`,...t)}function a(e){n&&e()}var o={2:[`one`,`other`],3:[`zero`,`one`,`other`],6:[`zero`,`one`,`two`,`few`,`many`,`other`]};function s(e){if(e.indexOf(`|`)===-1)return null;let t=e.split(`|`),n=o[t.length];if(!n)return r(`Pipe-plural shorthand with ${t.length} parts is not recognized (valid counts: 2, 3, 6). Treating as a plain string: "${e.slice(0,60)}${e.length>60?`…`:``}"`),null;if(t.some(e=>e.trim()===``))return null;let i={};for(let e=0;e<t.length;e++)i[n[e]]=t[e];return i}var c=/\{([\p{ID_Continue}-]+)\}/gu;function l(e){let t=[],n=0;for(let r of e.matchAll(c)){let{index:i}=r,a=i??0;a>n&&t.push(e.slice(n,a)),t.push({var:r[1]}),n=a+r[0].length}return n<e.length&&t.push(e.slice(n)),t}function u(e,t,n,r,i){let a=``;for(let o of e)if(typeof o==`string`)a+=o;else{let e=t?.[o.var];a+=e==null?i(o.var,n,r):String(e)}return a}Object.defineProperty(exports,"a",{enumerable:!0,get:function(){return i}}),Object.defineProperty(exports,"c",{enumerable:!0,get:function(){return e}}),Object.defineProperty(exports,"i",{enumerable:!0,get:function(){return a}}),Object.defineProperty(exports,"n",{enumerable:!0,get:function(){return s}}),Object.defineProperty(exports,"o",{enumerable:!0,get:function(){return r}}),Object.defineProperty(exports,"r",{enumerable:!0,get:function(){return u}}),Object.defineProperty(exports,"s",{enumerable:!0,get:function(){return t}}),Object.defineProperty(exports,"t",{enumerable:!0,get:function(){return l}});
2
- //# sourceMappingURL=template-BZRFdmaQ.cjs.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"template-BZRFdmaQ.cjs","names":[],"sources":["../src/_constants.ts","../src/_dev.ts","../src/template.ts"],"sourcesContent":["// Shared constants used by both i18n.ts and validate.ts.\n// Not part of the public API — internal module only.\n\nexport const UNSAFE_KEYS = new Set(['__proto__', 'constructor', 'prototype']);\n\nexport const CLDR_FORMS = new Set(['zero', 'one', 'two', 'few', 'many', 'other']);\n","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","// ─── Template helpers ─────────────────────────────────────────────────────────\n// Shared by i18n.ts — extracted for navigability. Not part of the public API.\n\nimport { warn } from './_dev';\n\ntype Locale = string;\ntype TranslateVars = Record<string, unknown>;\n\ninterface Messages {\n [key: string]: string | Messages;\n}\n\n// ─── Pipe-plural shorthand (F1) ───────────────────────────────────────────────\n// A leaf string containing '|' is parsed as a pipe-delimited plural shorthand.\n// Supported part counts and their form mappings (positional, left-to-right):\n// 2 parts → one | other\n// 3 parts → zero | one | other\n// 6 parts → zero | one | two | few | many | other\n// Any other part count is treated as a plain string with no expansion.\n\nconst PIPE_FORM_MAPS: Readonly<Partial<Record<number, readonly string[]>>> = {\n 2: ['one', 'other'],\n 3: ['zero', 'one', 'other'],\n 6: ['zero', 'one', 'two', 'few', 'many', 'other'],\n};\n\nexport function parsePipePlural(value: string): Messages | null {\n const pipeIdx = value.indexOf('|');\n\n if (pipeIdx === -1) return null;\n\n const parts = value.split('|');\n const forms = PIPE_FORM_MAPS[parts.length];\n\n if (!forms) {\n warn(\n `Pipe-plural shorthand with ${parts.length} parts is not recognized (valid counts: 2, 3, 6). Treating as a plain string: \"${value.slice(0, 60)}${value.length > 60 ? '…' : ''}\"`,\n );\n\n return null;\n }\n\n // An empty part is almost certainly a catalog authoring error.\n // Treat the whole value as a plain string rather than silently producing an empty form.\n if (parts.some((p) => p.trim() === '')) return null;\n\n const result: Messages = {};\n\n for (let i = 0; i < parts.length; i++) {\n result[forms[i]] = parts[i];\n }\n\n return result;\n}\n\n// ─── Template compilation ─────────────────────────────────────────────────────\n// Message strings are parsed once at registration time into a pre-compiled form:\n// an array of static strings and variable names. Rendering a compiled template\n// skips the regex entirely.\n\ntype TemplatePart = string | { var: string };\nexport type CompiledTemplate = TemplatePart[];\n\nexport const INTERPOLATION_PATTERN = /\\{([\\p{ID_Continue}-]+)\\}/gu;\n\nexport function compileTemplate(template: string): CompiledTemplate {\n const parts: CompiledTemplate = [];\n let lastIndex = 0;\n\n for (const match of template.matchAll(INTERPOLATION_PATTERN)) {\n const { index } = match;\n const start = index ?? 0;\n\n if (start > lastIndex) {\n parts.push(template.slice(lastIndex, start));\n }\n\n parts.push({ var: match[1] });\n lastIndex = start + match[0].length;\n }\n\n if (lastIndex < template.length) {\n parts.push(template.slice(lastIndex));\n }\n\n return parts;\n}\n\nexport function renderTemplate(\n parts: CompiledTemplate,\n vars: TranslateVars | undefined,\n key: string,\n locale: Locale,\n onMissingVar: (varName: string, key: string, locale: Locale) => string,\n): string {\n let result = '';\n\n for (const part of parts) {\n if (typeof part === 'string') {\n result += part;\n } else {\n const value = vars?.[part.var];\n\n result += value == null ? onMissingVar(part.var, key, locale) : String(value);\n }\n }\n\n return result;\n}\n"],"mappings":"AAGA,IAAa,EAAc,IAAI,IAAI,CAAC,YAAa,cAAe,WAAW,CAAC,EAE/D,EAAa,IAAI,IAAI,CAAC,OAAQ,MAAO,MAAO,MAAO,OAAQ,OAAO,CAAC,ECL1E,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,CAGA,SAAgB,EAAQ,EAAsB,CACxC,GAAO,EAAG,CAChB,CCKA,IAAM,EAAuE,CAC3E,EAAG,CAAC,MAAO,OAAO,EAClB,EAAG,CAAC,OAAQ,MAAO,OAAO,EAC1B,EAAG,CAAC,OAAQ,MAAO,MAAO,MAAO,OAAQ,OAAO,CAClD,EAEA,SAAgB,EAAgB,EAAgC,CAG9D,GAFgB,EAAM,QAAQ,GAE1B,IAAY,GAAI,OAAO,KAE3B,IAAM,EAAQ,EAAM,MAAM,GAAG,EACvB,EAAQ,EAAe,EAAM,QAEnC,GAAI,CAAC,EAKH,OAJA,EACE,8BAA8B,EAAM,OAAO,iFAAiF,EAAM,MAAM,EAAG,EAAE,IAAI,EAAM,OAAS,GAAK,IAAM,GAAG,EAChL,EAEO,KAKT,GAAI,EAAM,KAAM,GAAM,EAAE,KAAK,IAAM,EAAE,EAAG,OAAO,KAE/C,IAAM,EAAmB,CAAC,EAE1B,IAAK,IAAI,EAAI,EAAG,EAAI,EAAM,OAAQ,IAChC,EAAO,EAAM,IAAM,EAAM,GAG3B,OAAO,CACT,CAUA,IAAa,EAAwB,8BAErC,SAAgB,EAAgB,EAAoC,CAClE,IAAM,EAA0B,CAAC,EAC7B,EAAY,EAEhB,IAAK,IAAM,KAAS,EAAS,SAAS,CAAqB,EAAG,CAC5D,GAAM,CAAE,SAAU,EACZ,EAAQ,GAAS,EAEnB,EAAQ,GACV,EAAM,KAAK,EAAS,MAAM,EAAW,CAAK,CAAC,EAG7C,EAAM,KAAK,CAAE,IAAK,EAAM,EAAG,CAAC,EAC5B,EAAY,EAAQ,EAAM,EAAE,CAAC,MAC/B,CAMA,OAJI,EAAY,EAAS,QACvB,EAAM,KAAK,EAAS,MAAM,CAAS,CAAC,EAG/B,CACT,CAEA,SAAgB,EACd,EACA,EACA,EACA,EACA,EACQ,CACR,IAAI,EAAS,GAEb,IAAK,IAAM,KAAQ,EACjB,GAAI,OAAO,GAAS,SAClB,GAAU,MACL,CACL,IAAM,EAAQ,IAAO,EAAK,KAE1B,GAAU,GAAS,KAAO,EAAa,EAAK,IAAK,EAAK,CAAM,EAAI,OAAO,CAAK,CAC9E,CAGF,OAAO,CACT"}
@@ -1,2 +0,0 @@
1
- var e=new Set([`__proto__`,`constructor`,`prototype`]),t=new Set([`zero`,`one`,`two`,`few`,`many`,`other`]),n=!globalThis.__LINGUA_PROD__;function r(e){n&&console.warn(`[@vielzeug/lingua] ${e}`)}function i(e,...t){n&&console.error(`[@vielzeug/lingua] ${e}`,...t)}function a(e){n&&e()}var o={2:[`one`,`other`],3:[`zero`,`one`,`other`],6:[`zero`,`one`,`two`,`few`,`many`,`other`]};function s(e){if(e.indexOf(`|`)===-1)return null;let t=e.split(`|`),n=o[t.length];if(!n)return r(`Pipe-plural shorthand with ${t.length} parts is not recognized (valid counts: 2, 3, 6). Treating as a plain string: "${e.slice(0,60)}${e.length>60?`…`:``}"`),null;if(t.some(e=>e.trim()===``))return null;let i={};for(let e=0;e<t.length;e++)i[n[e]]=t[e];return i}var c=/\{([\p{ID_Continue}-]+)\}/gu;function l(e){let t=[],n=0;for(let r of e.matchAll(c)){let{index:i}=r,a=i??0;a>n&&t.push(e.slice(n,a)),t.push({var:r[1]}),n=a+r[0].length}return n<e.length&&t.push(e.slice(n)),t}function u(e,t,n,r,i){let a=``;for(let o of e)if(typeof o==`string`)a+=o;else{let e=t?.[o.var];a+=e==null?i(o.var,n,r):String(e)}return a}export{i as a,e as c,a as i,s as n,r as o,u as r,t as s,l as t};
2
- //# sourceMappingURL=template-BihTfBYb.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"template-BihTfBYb.js","names":[],"sources":["../src/_constants.ts","../src/_dev.ts","../src/template.ts"],"sourcesContent":["// Shared constants used by both i18n.ts and validate.ts.\n// Not part of the public API — internal module only.\n\nexport const UNSAFE_KEYS = new Set(['__proto__', 'constructor', 'prototype']);\n\nexport const CLDR_FORMS = new Set(['zero', 'one', 'two', 'few', 'many', 'other']);\n","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","// ─── Template helpers ─────────────────────────────────────────────────────────\n// Shared by i18n.ts — extracted for navigability. Not part of the public API.\n\nimport { warn } from './_dev';\n\ntype Locale = string;\ntype TranslateVars = Record<string, unknown>;\n\ninterface Messages {\n [key: string]: string | Messages;\n}\n\n// ─── Pipe-plural shorthand (F1) ───────────────────────────────────────────────\n// A leaf string containing '|' is parsed as a pipe-delimited plural shorthand.\n// Supported part counts and their form mappings (positional, left-to-right):\n// 2 parts → one | other\n// 3 parts → zero | one | other\n// 6 parts → zero | one | two | few | many | other\n// Any other part count is treated as a plain string with no expansion.\n\nconst PIPE_FORM_MAPS: Readonly<Partial<Record<number, readonly string[]>>> = {\n 2: ['one', 'other'],\n 3: ['zero', 'one', 'other'],\n 6: ['zero', 'one', 'two', 'few', 'many', 'other'],\n};\n\nexport function parsePipePlural(value: string): Messages | null {\n const pipeIdx = value.indexOf('|');\n\n if (pipeIdx === -1) return null;\n\n const parts = value.split('|');\n const forms = PIPE_FORM_MAPS[parts.length];\n\n if (!forms) {\n warn(\n `Pipe-plural shorthand with ${parts.length} parts is not recognized (valid counts: 2, 3, 6). Treating as a plain string: \"${value.slice(0, 60)}${value.length > 60 ? '…' : ''}\"`,\n );\n\n return null;\n }\n\n // An empty part is almost certainly a catalog authoring error.\n // Treat the whole value as a plain string rather than silently producing an empty form.\n if (parts.some((p) => p.trim() === '')) return null;\n\n const result: Messages = {};\n\n for (let i = 0; i < parts.length; i++) {\n result[forms[i]] = parts[i];\n }\n\n return result;\n}\n\n// ─── Template compilation ─────────────────────────────────────────────────────\n// Message strings are parsed once at registration time into a pre-compiled form:\n// an array of static strings and variable names. Rendering a compiled template\n// skips the regex entirely.\n\ntype TemplatePart = string | { var: string };\nexport type CompiledTemplate = TemplatePart[];\n\nexport const INTERPOLATION_PATTERN = /\\{([\\p{ID_Continue}-]+)\\}/gu;\n\nexport function compileTemplate(template: string): CompiledTemplate {\n const parts: CompiledTemplate = [];\n let lastIndex = 0;\n\n for (const match of template.matchAll(INTERPOLATION_PATTERN)) {\n const { index } = match;\n const start = index ?? 0;\n\n if (start > lastIndex) {\n parts.push(template.slice(lastIndex, start));\n }\n\n parts.push({ var: match[1] });\n lastIndex = start + match[0].length;\n }\n\n if (lastIndex < template.length) {\n parts.push(template.slice(lastIndex));\n }\n\n return parts;\n}\n\nexport function renderTemplate(\n parts: CompiledTemplate,\n vars: TranslateVars | undefined,\n key: string,\n locale: Locale,\n onMissingVar: (varName: string, key: string, locale: Locale) => string,\n): string {\n let result = '';\n\n for (const part of parts) {\n if (typeof part === 'string') {\n result += part;\n } else {\n const value = vars?.[part.var];\n\n result += value == null ? onMissingVar(part.var, key, locale) : String(value);\n }\n }\n\n return result;\n}\n"],"mappings":"AAGA,IAAa,EAAc,IAAI,IAAI,CAAC,YAAa,cAAe,WAAW,CAAC,EAE/D,EAAa,IAAI,IAAI,CAAC,OAAQ,MAAO,MAAO,MAAO,OAAQ,OAAO,CAAC,ECL1E,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,CAGA,SAAgB,EAAQ,EAAsB,CACxC,GAAO,EAAG,CAChB,CCKA,IAAM,EAAuE,CAC3E,EAAG,CAAC,MAAO,OAAO,EAClB,EAAG,CAAC,OAAQ,MAAO,OAAO,EAC1B,EAAG,CAAC,OAAQ,MAAO,MAAO,MAAO,OAAQ,OAAO,CAClD,EAEA,SAAgB,EAAgB,EAAgC,CAG9D,GAFgB,EAAM,QAAQ,GAE1B,IAAY,GAAI,OAAO,KAE3B,IAAM,EAAQ,EAAM,MAAM,GAAG,EACvB,EAAQ,EAAe,EAAM,QAEnC,GAAI,CAAC,EAKH,OAJA,EACE,8BAA8B,EAAM,OAAO,iFAAiF,EAAM,MAAM,EAAG,EAAE,IAAI,EAAM,OAAS,GAAK,IAAM,GAAG,EAChL,EAEO,KAKT,GAAI,EAAM,KAAM,GAAM,EAAE,KAAK,IAAM,EAAE,EAAG,OAAO,KAE/C,IAAM,EAAmB,CAAC,EAE1B,IAAK,IAAI,EAAI,EAAG,EAAI,EAAM,OAAQ,IAChC,EAAO,EAAM,IAAM,EAAM,GAG3B,OAAO,CACT,CAUA,IAAa,EAAwB,8BAErC,SAAgB,EAAgB,EAAoC,CAClE,IAAM,EAA0B,CAAC,EAC7B,EAAY,EAEhB,IAAK,IAAM,KAAS,EAAS,SAAS,CAAqB,EAAG,CAC5D,GAAM,CAAE,SAAU,EACZ,EAAQ,GAAS,EAEnB,EAAQ,GACV,EAAM,KAAK,EAAS,MAAM,EAAW,CAAK,CAAC,EAG7C,EAAM,KAAK,CAAE,IAAK,EAAM,EAAG,CAAC,EAC5B,EAAY,EAAQ,EAAM,EAAE,CAAC,MAC/B,CAMA,OAJI,EAAY,EAAS,QACvB,EAAM,KAAK,EAAS,MAAM,CAAS,CAAC,EAG/B,CACT,CAEA,SAAgB,EACd,EACA,EACA,EACA,EACA,EACQ,CACR,IAAI,EAAS,GAEb,IAAK,IAAM,KAAQ,EACjB,GAAI,OAAO,GAAS,SAClB,GAAU,MACL,CACL,IAAM,EAAQ,IAAO,EAAK,KAE1B,GAAU,GAAS,KAAO,EAAa,EAAK,IAAK,EAAK,CAAM,EAAI,OAAO,CAAK,CAC9E,CAGF,OAAO,CACT"}
package/dist/template.cjs DELETED
@@ -1,2 +0,0 @@
1
- const e=require("./_dev.cjs");var t={2:[`one`,`other`],3:[`zero`,`one`,`other`],6:[`zero`,`one`,`two`,`few`,`many`,`other`]};function n(n){if(n.indexOf(`|`)===-1)return null;let r=n.split(`|`),i=t[r.length];if(!i)return e.warn(`Pipe-plural shorthand with ${r.length} parts is not recognized (valid counts: 2, 3, 6). Treating as a plain string: "${n.slice(0,60)}${n.length>60?`…`:``}"`),null;if(r.some(e=>e.trim()===``))return null;let a={};for(let e=0;e<r.length;e++)a[i[e]]=r[e];return a}var r=/\{([\p{ID_Continue}-]+)\}/gu;function i(e){let t=[],n=0;for(let i of e.matchAll(r)){let{index:r}=i,a=r??0;a>n&&t.push(e.slice(n,a)),t.push({var:i[1]}),n=a+i[0].length}return n<e.length&&t.push(e.slice(n)),t}function a(e,t,n,r,i){let a=``;for(let o of e)if(typeof o==`string`)a+=o;else{let e=t?.[o.var];a+=e==null?i(o.var,n,r):String(e)}return a}exports.INTERPOLATION_PATTERN=r,exports.compileTemplate=i,exports.parsePipePlural=n,exports.renderTemplate=a;
2
- //# sourceMappingURL=template.cjs.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"template.cjs","names":[],"sources":["../src/template.ts"],"sourcesContent":["// ─── Template helpers ─────────────────────────────────────────────────────────\n// Shared by i18n.ts — extracted for navigability. Not part of the public API.\n\nimport { warn } from './_dev';\n\ntype Locale = string;\ntype TranslateVars = Record<string, unknown>;\n\ninterface Messages {\n [key: string]: string | Messages;\n}\n\n// ─── Pipe-plural shorthand (F1) ───────────────────────────────────────────────\n// A leaf string containing '|' is parsed as a pipe-delimited plural shorthand.\n// Supported part counts and their form mappings (positional, left-to-right):\n// 2 parts → one | other\n// 3 parts → zero | one | other\n// 6 parts → zero | one | two | few | many | other\n// Any other part count is treated as a plain string with no expansion.\n\nconst PIPE_FORM_MAPS: Readonly<Partial<Record<number, readonly string[]>>> = {\n 2: ['one', 'other'],\n 3: ['zero', 'one', 'other'],\n 6: ['zero', 'one', 'two', 'few', 'many', 'other'],\n};\n\nexport function parsePipePlural(value: string): Messages | null {\n const pipeIdx = value.indexOf('|');\n\n if (pipeIdx === -1) return null;\n\n const parts = value.split('|');\n const forms = PIPE_FORM_MAPS[parts.length];\n\n if (!forms) {\n warn(\n `Pipe-plural shorthand with ${parts.length} parts is not recognized (valid counts: 2, 3, 6). Treating as a plain string: \"${value.slice(0, 60)}${value.length > 60 ? '…' : ''}\"`,\n );\n\n return null;\n }\n\n // An empty part is almost certainly a catalog authoring error.\n // Treat the whole value as a plain string rather than silently producing an empty form.\n if (parts.some((p) => p.trim() === '')) return null;\n\n const result: Messages = {};\n\n for (let i = 0; i < parts.length; i++) {\n result[forms[i]] = parts[i];\n }\n\n return result;\n}\n\n// ─── Template compilation ─────────────────────────────────────────────────────\n// Message strings are parsed once at registration time into a pre-compiled form:\n// an array of static strings and variable names. Rendering a compiled template\n// skips the regex entirely.\n\ntype TemplatePart = string | { var: string };\nexport type CompiledTemplate = TemplatePart[];\n\nexport const INTERPOLATION_PATTERN = /\\{([\\p{ID_Continue}-]+)\\}/gu;\n\nexport function compileTemplate(template: string): CompiledTemplate {\n const parts: CompiledTemplate = [];\n let lastIndex = 0;\n\n for (const match of template.matchAll(INTERPOLATION_PATTERN)) {\n const { index } = match;\n const start = index ?? 0;\n\n if (start > lastIndex) {\n parts.push(template.slice(lastIndex, start));\n }\n\n parts.push({ var: match[1] });\n lastIndex = start + match[0].length;\n }\n\n if (lastIndex < template.length) {\n parts.push(template.slice(lastIndex));\n }\n\n return parts;\n}\n\nexport function renderTemplate(\n parts: CompiledTemplate,\n vars: TranslateVars | undefined,\n key: string,\n locale: Locale,\n onMissingVar: (varName: string, key: string, locale: Locale) => string,\n): string {\n let result = '';\n\n for (const part of parts) {\n if (typeof part === 'string') {\n result += part;\n } else {\n const value = vars?.[part.var];\n\n result += value == null ? onMissingVar(part.var, key, locale) : String(value);\n }\n }\n\n return result;\n}\n"],"mappings":"8BAoBA,IAAM,EAAuE,CAC3E,EAAG,CAAC,MAAO,OAAO,EAClB,EAAG,CAAC,OAAQ,MAAO,OAAO,EAC1B,EAAG,CAAC,OAAQ,MAAO,MAAO,MAAO,OAAQ,OAAO,CAClD,EAEA,SAAgB,EAAgB,EAAgC,CAG9D,GAFgB,EAAM,QAAQ,GAE1B,IAAY,GAAI,OAAO,KAE3B,IAAM,EAAQ,EAAM,MAAM,GAAG,EACvB,EAAQ,EAAe,EAAM,QAEnC,GAAI,CAAC,EAKH,OAJA,EAAA,KACE,8BAA8B,EAAM,OAAO,iFAAiF,EAAM,MAAM,EAAG,EAAE,IAAI,EAAM,OAAS,GAAK,IAAM,GAAG,EAChL,EAEO,KAKT,GAAI,EAAM,KAAM,GAAM,EAAE,KAAK,IAAM,EAAE,EAAG,OAAO,KAE/C,IAAM,EAAmB,CAAC,EAE1B,IAAK,IAAI,EAAI,EAAG,EAAI,EAAM,OAAQ,IAChC,EAAO,EAAM,IAAM,EAAM,GAG3B,OAAO,CACT,CAUA,IAAa,EAAwB,8BAErC,SAAgB,EAAgB,EAAoC,CAClE,IAAM,EAA0B,CAAC,EAC7B,EAAY,EAEhB,IAAK,IAAM,KAAS,EAAS,SAAS,CAAqB,EAAG,CAC5D,GAAM,CAAE,SAAU,EACZ,EAAQ,GAAS,EAEnB,EAAQ,GACV,EAAM,KAAK,EAAS,MAAM,EAAW,CAAK,CAAC,EAG7C,EAAM,KAAK,CAAE,IAAK,EAAM,EAAG,CAAC,EAC5B,EAAY,EAAQ,EAAM,EAAE,CAAC,MAC/B,CAMA,OAJI,EAAY,EAAS,QACvB,EAAM,KAAK,EAAS,MAAM,CAAS,CAAC,EAG/B,CACT,CAEA,SAAgB,EACd,EACA,EACA,EACA,EACA,EACQ,CACR,IAAI,EAAS,GAEb,IAAK,IAAM,KAAQ,EACjB,GAAI,OAAO,GAAS,SAClB,GAAU,MACL,CACL,IAAM,EAAQ,IAAO,EAAK,KAE1B,GAAU,GAAS,KAAO,EAAa,EAAK,IAAK,EAAK,CAAM,EAAI,OAAO,CAAK,CAC9E,CAGF,OAAO,CACT"}
@@ -1,15 +0,0 @@
1
- type Locale = string;
2
- type TranslateVars = Record<string, unknown>;
3
- interface Messages {
4
- [key: string]: string | Messages;
5
- }
6
- export declare function parsePipePlural(value: string): Messages | null;
7
- type TemplatePart = string | {
8
- var: string;
9
- };
10
- export type CompiledTemplate = TemplatePart[];
11
- export declare const INTERPOLATION_PATTERN: RegExp;
12
- export declare function compileTemplate(template: string): CompiledTemplate;
13
- export declare function renderTemplate(parts: CompiledTemplate, vars: TranslateVars | undefined, key: string, locale: Locale, onMissingVar: (varName: string, key: string, locale: Locale) => string): string;
14
- export {};
15
- //# sourceMappingURL=template.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"template.d.ts","sourceRoot":"","sources":["../src/template.ts"],"names":[],"mappings":"AAKA,KAAK,MAAM,GAAG,MAAM,CAAC;AACrB,KAAK,aAAa,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAE7C,UAAU,QAAQ;IAChB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,QAAQ,CAAC;CAClC;AAgBD,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,QAAQ,GAAG,IAAI,CA2B9D;AAOD,KAAK,YAAY,GAAG,MAAM,GAAG;IAAE,GAAG,EAAE,MAAM,CAAA;CAAE,CAAC;AAC7C,MAAM,MAAM,gBAAgB,GAAG,YAAY,EAAE,CAAC;AAE9C,eAAO,MAAM,qBAAqB,QAAgC,CAAC;AAEnE,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,gBAAgB,CAqBlE;AAED,wBAAgB,cAAc,CAC5B,KAAK,EAAE,gBAAgB,EACvB,IAAI,EAAE,aAAa,GAAG,SAAS,EAC/B,GAAG,EAAE,MAAM,EACX,MAAM,EAAE,MAAM,EACd,YAAY,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,KAAK,MAAM,GACrE,MAAM,CAcR"}
package/dist/template.js DELETED
@@ -1,49 +0,0 @@
1
- import { warn as e } from "./_dev.js";
2
- //#region src/template.ts
3
- var t = {
4
- 2: ["one", "other"],
5
- 3: [
6
- "zero",
7
- "one",
8
- "other"
9
- ],
10
- 6: [
11
- "zero",
12
- "one",
13
- "two",
14
- "few",
15
- "many",
16
- "other"
17
- ]
18
- };
19
- function n(n) {
20
- if (n.indexOf("|") === -1) return null;
21
- let r = n.split("|"), i = t[r.length];
22
- if (!i) return e(`Pipe-plural shorthand with ${r.length} parts is not recognized (valid counts: 2, 3, 6). Treating as a plain string: "${n.slice(0, 60)}${n.length > 60 ? "…" : ""}"`), null;
23
- if (r.some((e) => e.trim() === "")) return null;
24
- let a = {};
25
- for (let e = 0; e < r.length; e++) a[i[e]] = r[e];
26
- return a;
27
- }
28
- var r = /\{([\p{ID_Continue}-]+)\}/gu;
29
- function i(e) {
30
- let t = [], n = 0;
31
- for (let i of e.matchAll(r)) {
32
- let { index: r } = i, a = r ?? 0;
33
- a > n && t.push(e.slice(n, a)), t.push({ var: i[1] }), n = a + i[0].length;
34
- }
35
- return n < e.length && t.push(e.slice(n)), t;
36
- }
37
- function a(e, t, n, r, i) {
38
- let a = "";
39
- for (let o of e) if (typeof o == "string") a += o;
40
- else {
41
- let e = t?.[o.var];
42
- a += e == null ? i(o.var, n, r) : String(e);
43
- }
44
- return a;
45
- }
46
- //#endregion
47
- export { r as INTERPOLATION_PATTERN, i as compileTemplate, n as parsePipePlural, a as renderTemplate };
48
-
49
- //# sourceMappingURL=template.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"template.js","names":[],"sources":["../src/template.ts"],"sourcesContent":["// ─── Template helpers ─────────────────────────────────────────────────────────\n// Shared by i18n.ts — extracted for navigability. Not part of the public API.\n\nimport { warn } from './_dev';\n\ntype Locale = string;\ntype TranslateVars = Record<string, unknown>;\n\ninterface Messages {\n [key: string]: string | Messages;\n}\n\n// ─── Pipe-plural shorthand (F1) ───────────────────────────────────────────────\n// A leaf string containing '|' is parsed as a pipe-delimited plural shorthand.\n// Supported part counts and their form mappings (positional, left-to-right):\n// 2 parts → one | other\n// 3 parts → zero | one | other\n// 6 parts → zero | one | two | few | many | other\n// Any other part count is treated as a plain string with no expansion.\n\nconst PIPE_FORM_MAPS: Readonly<Partial<Record<number, readonly string[]>>> = {\n 2: ['one', 'other'],\n 3: ['zero', 'one', 'other'],\n 6: ['zero', 'one', 'two', 'few', 'many', 'other'],\n};\n\nexport function parsePipePlural(value: string): Messages | null {\n const pipeIdx = value.indexOf('|');\n\n if (pipeIdx === -1) return null;\n\n const parts = value.split('|');\n const forms = PIPE_FORM_MAPS[parts.length];\n\n if (!forms) {\n warn(\n `Pipe-plural shorthand with ${parts.length} parts is not recognized (valid counts: 2, 3, 6). Treating as a plain string: \"${value.slice(0, 60)}${value.length > 60 ? '…' : ''}\"`,\n );\n\n return null;\n }\n\n // An empty part is almost certainly a catalog authoring error.\n // Treat the whole value as a plain string rather than silently producing an empty form.\n if (parts.some((p) => p.trim() === '')) return null;\n\n const result: Messages = {};\n\n for (let i = 0; i < parts.length; i++) {\n result[forms[i]] = parts[i];\n }\n\n return result;\n}\n\n// ─── Template compilation ─────────────────────────────────────────────────────\n// Message strings are parsed once at registration time into a pre-compiled form:\n// an array of static strings and variable names. Rendering a compiled template\n// skips the regex entirely.\n\ntype TemplatePart = string | { var: string };\nexport type CompiledTemplate = TemplatePart[];\n\nexport const INTERPOLATION_PATTERN = /\\{([\\p{ID_Continue}-]+)\\}/gu;\n\nexport function compileTemplate(template: string): CompiledTemplate {\n const parts: CompiledTemplate = [];\n let lastIndex = 0;\n\n for (const match of template.matchAll(INTERPOLATION_PATTERN)) {\n const { index } = match;\n const start = index ?? 0;\n\n if (start > lastIndex) {\n parts.push(template.slice(lastIndex, start));\n }\n\n parts.push({ var: match[1] });\n lastIndex = start + match[0].length;\n }\n\n if (lastIndex < template.length) {\n parts.push(template.slice(lastIndex));\n }\n\n return parts;\n}\n\nexport function renderTemplate(\n parts: CompiledTemplate,\n vars: TranslateVars | undefined,\n key: string,\n locale: Locale,\n onMissingVar: (varName: string, key: string, locale: Locale) => string,\n): string {\n let result = '';\n\n for (const part of parts) {\n if (typeof part === 'string') {\n result += part;\n } else {\n const value = vars?.[part.var];\n\n result += value == null ? onMissingVar(part.var, key, locale) : String(value);\n }\n }\n\n return result;\n}\n"],"mappings":";;AAoBA,IAAM,IAAuE;CAC3E,GAAG,CAAC,OAAO,OAAO;CAClB,GAAG;EAAC;EAAQ;EAAO;CAAO;CAC1B,GAAG;EAAC;EAAQ;EAAO;EAAO;EAAO;EAAQ;CAAO;AAClD;AAEA,SAAgB,EAAgB,GAAgC;CAG9D,IAFgB,EAAM,QAAQ,GAE1B,MAAY,IAAI,OAAO;CAE3B,IAAM,IAAQ,EAAM,MAAM,GAAG,GACvB,IAAQ,EAAe,EAAM;CAEnC,IAAI,CAAC,GAKH,OAJA,EACE,8BAA8B,EAAM,OAAO,iFAAiF,EAAM,MAAM,GAAG,EAAE,IAAI,EAAM,SAAS,KAAK,MAAM,GAAG,EAChL,GAEO;CAKT,IAAI,EAAM,MAAM,MAAM,EAAE,KAAK,MAAM,EAAE,GAAG,OAAO;CAE/C,IAAM,IAAmB,CAAC;CAE1B,KAAK,IAAI,IAAI,GAAG,IAAI,EAAM,QAAQ,KAChC,EAAO,EAAM,MAAM,EAAM;CAG3B,OAAO;AACT;AAUA,IAAa,IAAwB;AAErC,SAAgB,EAAgB,GAAoC;CAClE,IAAM,IAA0B,CAAC,GAC7B,IAAY;CAEhB,KAAK,IAAM,KAAS,EAAS,SAAS,CAAqB,GAAG;EAC5D,IAAM,EAAE,aAAU,GACZ,IAAQ,KAAS;EAOvB,AALI,IAAQ,KACV,EAAM,KAAK,EAAS,MAAM,GAAW,CAAK,CAAC,GAG7C,EAAM,KAAK,EAAE,KAAK,EAAM,GAAG,CAAC,GAC5B,IAAY,IAAQ,EAAM,EAAE,CAAC;CAC/B;CAMA,OAJI,IAAY,EAAS,UACvB,EAAM,KAAK,EAAS,MAAM,CAAS,CAAC,GAG/B;AACT;AAEA,SAAgB,EACd,GACA,GACA,GACA,GACA,GACQ;CACR,IAAI,IAAS;CAEb,KAAK,IAAM,KAAQ,GACjB,IAAI,OAAO,KAAS,UAClB,KAAU;MACL;EACL,IAAM,IAAQ,IAAO,EAAK;EAE1B,KAAU,KAAS,OAAO,EAAa,EAAK,KAAK,GAAK,CAAM,IAAI,OAAO,CAAK;CAC9E;CAGF,OAAO;AACT"}
@@ -1,2 +0,0 @@
1
- import{c as e,n as t,s as n}from"./template-BihTfBYb.js";function r(e){try{let{pluralCategories:t}=new Intl.PluralRules(e).resolvedOptions();return new Set(t)}catch{return new Set([`one`,`other`])}}function i(r,a=``){let o=[];for(let[s,c]of Object.entries(r)){if(e.has(s))continue;let r=a?`${a}.${s}`:s;if(typeof c==`string`){let e=t(c);e&&o.push({key:r,obj:e});continue}let l=c;Object.keys(l).some(e=>n.has(e))?o.push({key:r,obj:l}):o.push(...i(l,r))}return o}var a=new Set([`other`,`two`,`few`,`many`]);function o(e,t){let n=[],o=r(t);for(let{key:r,obj:s}of i(e))for(let e of o)if(!(e in s))n.push({form:e,key:r,locale:t});else if(a.has(e)){let i=s[e];typeof i==`string`&&!i.includes(`{count}`)&&n.push({form:`${e}:missing-count`,key:r,locale:t})}return n}export{o as validateCatalog};
2
- //# sourceMappingURL=validate-D1U8TTeT.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"validate-D1U8TTeT.js","names":[],"sources":["../src/validate.ts"],"sourcesContent":["/**\n * /lingua/validate\n *\n * Build-time and CI catalog validation utilities.\n * Import from `@vielzeug/lingua/validate`.\n *\n * Not intended for production use — keep this entry out of your production bundle.\n *\n * @example\n * ```ts\n * import { validateCatalog } from '@vielzeug/lingua/validate';\n *\n * const warnings = validateCatalog({ inbox: { one: '...', other: '...' } }, 'ar');\n * // → warnings for 'zero', 'two', 'few', 'many' in Arabic\n * if (warnings.length > 0) throw new Error(`Missing plural forms:\\n${JSON.stringify(warnings, null, 2)}`);\n * ```\n */\n\nimport type { Messages } from './_catalog';\nimport type { Locale } from './_chain';\n\nimport { CLDR_FORMS, UNSAFE_KEYS } from './_constants';\nimport { parsePipePlural } from './template';\n\nexport type ValidationWarning = {\n form: string;\n key: string;\n locale: Locale;\n};\n\n/**\n * Returns the canonical CLDR plural forms expected for the given locale.\n * Uses `Intl.PluralRules#resolvedOptions().pluralCategories` — the authoritative source,\n * unlike heuristic sampling.\n */\nfunction getExpectedPluralForms(locale: Locale): Set<string> {\n try {\n const { pluralCategories } = new Intl.PluralRules(locale).resolvedOptions() as {\n pluralCategories: string[];\n };\n\n return new Set(pluralCategories);\n } catch {\n return new Set(['one', 'other']);\n }\n}\n\nfunction findPluralBranches(messages: Messages, prefix = ''): Array<{ key: string; obj: Messages }> {\n const result: Array<{ key: string; obj: Messages }> = [];\n\n for (const [k, v] of Object.entries(messages)) {\n if (UNSAFE_KEYS.has(k)) continue;\n\n const fullKey = prefix ? `${prefix}.${k}` : k;\n\n if (typeof v === 'string') {\n // Expand pipe-plural shorthand so it can be validated like a nested plural branch.\n const expanded = parsePipePlural(v);\n\n if (expanded) result.push({ key: fullKey, obj: expanded });\n\n continue;\n }\n\n const obj = v as Messages;\n const childKeys = Object.keys(obj);\n\n if (childKeys.some((ck) => CLDR_FORMS.has(ck))) {\n result.push({ key: fullKey, obj });\n } else {\n result.push(...findPluralBranches(obj, fullKey));\n }\n }\n\n return result;\n}\n\n// Plural form values that typically reference the count (i.e. non-singleton forms).\n// 'zero' and 'one' often intentionally omit {count} (e.g. 'No messages', 'One message').\nconst FORMS_EXPECTING_COUNT = new Set(['other', 'two', 'few', 'many']);\n\n/**\n * Validates a catalog against the expected CLDR plural forms for the given locale.\n * Returns an array of warnings for:\n * 1. Plural branches missing one or more expected CLDR forms.\n * 2. Plural form templates for `other`, `two`, `few`, or `many` that do not interpolate\n * `{count}` — since `count` is injected automatically by `tp()`, omitting it is almost\n * always a catalog authoring error. Warnings for these use `form: '<form>:missing-count'`.\n *\n * Uses `Intl.PluralRules#resolvedOptions().pluralCategories` — the authoritative CLDR\n * category list, not heuristic sampling.\n *\n * @example\n * const warnings = validateCatalog({ inbox: { one: '...', other: '...' } }, 'ar');\n * // → warnings for missing 'zero', 'two', 'few', 'many' in Arabic\n */\nexport function validateCatalog(messages: Messages, locale: Locale): ValidationWarning[] {\n const warnings: ValidationWarning[] = [];\n const expectedForms = getExpectedPluralForms(locale);\n\n for (const { key, obj } of findPluralBranches(messages)) {\n for (const form of expectedForms) {\n if (!(form in obj)) {\n warnings.push({ form, key, locale });\n } else if (FORMS_EXPECTING_COUNT.has(form)) {\n const template = obj[form];\n\n if (typeof template === 'string' && !template.includes('{count}')) {\n warnings.push({ form: `${form}:missing-count`, key, locale });\n }\n }\n }\n }\n\n return warnings;\n}\n"],"mappings":"yDAmCA,SAAS,EAAuB,EAA6B,CAC3D,GAAI,CACF,GAAM,CAAE,oBAAqB,IAAI,KAAK,YAAY,CAAM,CAAC,CAAC,gBAAgB,EAI1E,OAAO,IAAI,IAAI,CAAgB,CACjC,MAAQ,CACN,OAAO,IAAI,IAAI,CAAC,MAAO,OAAO,CAAC,CACjC,CACF,CAEA,SAAS,EAAmB,EAAoB,EAAS,GAA2C,CAClG,IAAM,EAAgD,CAAC,EAEvD,IAAK,GAAM,CAAC,EAAG,KAAM,OAAO,QAAQ,CAAQ,EAAG,CAC7C,GAAI,EAAY,IAAI,CAAC,EAAG,SAExB,IAAM,EAAU,EAAS,GAAG,EAAO,GAAG,IAAM,EAE5C,GAAI,OAAO,GAAM,SAAU,CAEzB,IAAM,EAAW,EAAgB,CAAC,EAE9B,GAAU,EAAO,KAAK,CAAE,IAAK,EAAS,IAAK,CAAS,CAAC,EAEzD,QACF,CAEA,IAAM,EAAM,EACM,OAAO,KAAK,CAE1B,CAAA,CAAU,KAAM,GAAO,EAAW,IAAI,CAAE,CAAC,EAC3C,EAAO,KAAK,CAAE,IAAK,EAAS,KAAI,CAAC,EAEjC,EAAO,KAAK,GAAG,EAAmB,EAAK,CAAO,CAAC,CAEnD,CAEA,OAAO,CACT,CAIA,IAAM,EAAwB,IAAI,IAAI,CAAC,QAAS,MAAO,MAAO,MAAM,CAAC,EAiBrE,SAAgB,EAAgB,EAAoB,EAAqC,CACvF,IAAM,EAAgC,CAAC,EACjC,EAAgB,EAAuB,CAAM,EAEnD,IAAK,GAAM,CAAE,MAAK,SAAS,EAAmB,CAAQ,EACpD,IAAK,IAAM,KAAQ,EACjB,GAAI,EAAE,KAAQ,GACZ,EAAS,KAAK,CAAE,OAAM,MAAK,QAAO,CAAC,OAC9B,GAAI,EAAsB,IAAI,CAAI,EAAG,CAC1C,IAAM,EAAW,EAAI,GAEjB,OAAO,GAAa,UAAY,CAAC,EAAS,SAAS,SAAS,GAC9D,EAAS,KAAK,CAAE,KAAM,GAAG,EAAK,gBAAiB,MAAK,QAAO,CAAC,CAEhE,CAIJ,OAAO,CACT"}
@@ -1,2 +0,0 @@
1
- const e=require("./template-BZRFdmaQ.cjs");function t(e){try{let{pluralCategories:t}=new Intl.PluralRules(e).resolvedOptions();return new Set(t)}catch{return new Set([`one`,`other`])}}function n(t,r=``){let i=[];for(let[a,o]of Object.entries(t)){if(e.c.has(a))continue;let t=r?`${r}.${a}`:a;if(typeof o==`string`){let n=e.n(o);n&&i.push({key:t,obj:n});continue}let s=o;Object.keys(s).some(t=>e.s.has(t))?i.push({key:t,obj:s}):i.push(...n(s,t))}return i}var r=new Set([`other`,`two`,`few`,`many`]);function i(e,i){let a=[],o=t(i);for(let{key:t,obj:s}of n(e))for(let e of o)if(!(e in s))a.push({form:e,key:t,locale:i});else if(r.has(e)){let n=s[e];typeof n==`string`&&!n.includes(`{count}`)&&a.push({form:`${e}:missing-count`,key:t,locale:i})}return a}exports.validateCatalog=i;
2
- //# sourceMappingURL=validate-x7lFFyV2.cjs.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"validate-x7lFFyV2.cjs","names":[],"sources":["../src/validate.ts"],"sourcesContent":["/**\n * /lingua/validate\n *\n * Build-time and CI catalog validation utilities.\n * Import from `@vielzeug/lingua/validate`.\n *\n * Not intended for production use — keep this entry out of your production bundle.\n *\n * @example\n * ```ts\n * import { validateCatalog } from '@vielzeug/lingua/validate';\n *\n * const warnings = validateCatalog({ inbox: { one: '...', other: '...' } }, 'ar');\n * // → warnings for 'zero', 'two', 'few', 'many' in Arabic\n * if (warnings.length > 0) throw new Error(`Missing plural forms:\\n${JSON.stringify(warnings, null, 2)}`);\n * ```\n */\n\nimport type { Messages } from './_catalog';\nimport type { Locale } from './_chain';\n\nimport { CLDR_FORMS, UNSAFE_KEYS } from './_constants';\nimport { parsePipePlural } from './template';\n\nexport type ValidationWarning = {\n form: string;\n key: string;\n locale: Locale;\n};\n\n/**\n * Returns the canonical CLDR plural forms expected for the given locale.\n * Uses `Intl.PluralRules#resolvedOptions().pluralCategories` — the authoritative source,\n * unlike heuristic sampling.\n */\nfunction getExpectedPluralForms(locale: Locale): Set<string> {\n try {\n const { pluralCategories } = new Intl.PluralRules(locale).resolvedOptions() as {\n pluralCategories: string[];\n };\n\n return new Set(pluralCategories);\n } catch {\n return new Set(['one', 'other']);\n }\n}\n\nfunction findPluralBranches(messages: Messages, prefix = ''): Array<{ key: string; obj: Messages }> {\n const result: Array<{ key: string; obj: Messages }> = [];\n\n for (const [k, v] of Object.entries(messages)) {\n if (UNSAFE_KEYS.has(k)) continue;\n\n const fullKey = prefix ? `${prefix}.${k}` : k;\n\n if (typeof v === 'string') {\n // Expand pipe-plural shorthand so it can be validated like a nested plural branch.\n const expanded = parsePipePlural(v);\n\n if (expanded) result.push({ key: fullKey, obj: expanded });\n\n continue;\n }\n\n const obj = v as Messages;\n const childKeys = Object.keys(obj);\n\n if (childKeys.some((ck) => CLDR_FORMS.has(ck))) {\n result.push({ key: fullKey, obj });\n } else {\n result.push(...findPluralBranches(obj, fullKey));\n }\n }\n\n return result;\n}\n\n// Plural form values that typically reference the count (i.e. non-singleton forms).\n// 'zero' and 'one' often intentionally omit {count} (e.g. 'No messages', 'One message').\nconst FORMS_EXPECTING_COUNT = new Set(['other', 'two', 'few', 'many']);\n\n/**\n * Validates a catalog against the expected CLDR plural forms for the given locale.\n * Returns an array of warnings for:\n * 1. Plural branches missing one or more expected CLDR forms.\n * 2. Plural form templates for `other`, `two`, `few`, or `many` that do not interpolate\n * `{count}` — since `count` is injected automatically by `tp()`, omitting it is almost\n * always a catalog authoring error. Warnings for these use `form: '<form>:missing-count'`.\n *\n * Uses `Intl.PluralRules#resolvedOptions().pluralCategories` — the authoritative CLDR\n * category list, not heuristic sampling.\n *\n * @example\n * const warnings = validateCatalog({ inbox: { one: '...', other: '...' } }, 'ar');\n * // → warnings for missing 'zero', 'two', 'few', 'many' in Arabic\n */\nexport function validateCatalog(messages: Messages, locale: Locale): ValidationWarning[] {\n const warnings: ValidationWarning[] = [];\n const expectedForms = getExpectedPluralForms(locale);\n\n for (const { key, obj } of findPluralBranches(messages)) {\n for (const form of expectedForms) {\n if (!(form in obj)) {\n warnings.push({ form, key, locale });\n } else if (FORMS_EXPECTING_COUNT.has(form)) {\n const template = obj[form];\n\n if (typeof template === 'string' && !template.includes('{count}')) {\n warnings.push({ form: `${form}:missing-count`, key, locale });\n }\n }\n }\n }\n\n return warnings;\n}\n"],"mappings":"2CAmCA,SAAS,EAAuB,EAA6B,CAC3D,GAAI,CACF,GAAM,CAAE,oBAAqB,IAAI,KAAK,YAAY,CAAM,CAAC,CAAC,gBAAgB,EAI1E,OAAO,IAAI,IAAI,CAAgB,CACjC,MAAQ,CACN,OAAO,IAAI,IAAI,CAAC,MAAO,OAAO,CAAC,CACjC,CACF,CAEA,SAAS,EAAmB,EAAoB,EAAS,GAA2C,CAClG,IAAM,EAAgD,CAAC,EAEvD,IAAK,GAAM,CAAC,EAAG,KAAM,OAAO,QAAQ,CAAQ,EAAG,CAC7C,GAAI,EAAA,EAAY,IAAI,CAAC,EAAG,SAExB,IAAM,EAAU,EAAS,GAAG,EAAO,GAAG,IAAM,EAE5C,GAAI,OAAO,GAAM,SAAU,CAEzB,IAAM,EAAW,EAAA,EAAgB,CAAC,EAE9B,GAAU,EAAO,KAAK,CAAE,IAAK,EAAS,IAAK,CAAS,CAAC,EAEzD,QACF,CAEA,IAAM,EAAM,EACM,OAAO,KAAK,CAE1B,CAAA,CAAU,KAAM,GAAO,EAAA,EAAW,IAAI,CAAE,CAAC,EAC3C,EAAO,KAAK,CAAE,IAAK,EAAS,KAAI,CAAC,EAEjC,EAAO,KAAK,GAAG,EAAmB,EAAK,CAAO,CAAC,CAEnD,CAEA,OAAO,CACT,CAIA,IAAM,EAAwB,IAAI,IAAI,CAAC,QAAS,MAAO,MAAO,MAAM,CAAC,EAiBrE,SAAgB,EAAgB,EAAoB,EAAqC,CACvF,IAAM,EAAgC,CAAC,EACjC,EAAgB,EAAuB,CAAM,EAEnD,IAAK,GAAM,CAAE,MAAK,SAAS,EAAmB,CAAQ,EACpD,IAAK,IAAM,KAAQ,EACjB,GAAI,EAAE,KAAQ,GACZ,EAAS,KAAK,CAAE,OAAM,MAAK,QAAO,CAAC,OAC9B,GAAI,EAAsB,IAAI,CAAI,EAAG,CAC1C,IAAM,EAAW,EAAI,GAEjB,OAAO,GAAa,UAAY,CAAC,EAAS,SAAS,SAAS,GAC9D,EAAS,KAAK,CAAE,KAAM,GAAG,EAAK,gBAAiB,MAAK,QAAO,CAAC,CAEhE,CAIJ,OAAO,CACT"}