@xndrjs/i18n 0.3.0 → 0.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -13,6 +13,7 @@ The core idea: your ICU strings live in local JSON files that act as **type-safe
13
13
  - **Lazy namespace loading** — optional code-splitting via `loadOnInit` and `ensureNamespacesLoaded()` (multi mode).
14
14
  - **Hot compilation cache** — compiled `IntlMessageFormat` instances are cached and invalidated on override.
15
15
  - **Explicit runtime errors** — malformed ICU (e.g. a corrupt remote payload) or missing parameters throw descriptive errors.
16
+ - **Translation audit** — `xndrjs-i18n-audit` reports missing locales per key (direct vs effective after fallback); optional CI gate via `--fail-on`.
16
17
  - **Publishable library** — the runtime and codegen live in a standalone package (`@xndrjs/i18n`) that carries no project-specific types.
17
18
 
18
19
  ## Getting started
@@ -58,15 +59,17 @@ npm install zod
58
59
  ```json
59
60
  {
60
61
  "scripts": {
61
- "i18n:codegen": "xndrjs-i18n-codegen --config i18n/i18n.codegen.json"
62
+ "i18n:codegen": "xndrjs-i18n-codegen --config i18n/i18n.codegen.json",
63
+ "i18n:audit": "xndrjs-i18n-audit --config i18n/i18n.codegen.json"
62
64
  }
63
65
  }
64
66
  ```
65
67
 
66
- **4. Generate and use:**
68
+ **4. Generate and audit:**
67
69
 
68
70
  ```bash
69
71
  npm run i18n:codegen
72
+ npm run i18n:audit
70
73
  ```
71
74
 
72
75
  ```ts
@@ -192,7 +195,7 @@ Variables found across **all locales** of the same key are merged. If parsing fa
192
195
 
193
196
  - **`i18n-types.generated.ts`** — `I18N_MODE`, `MyProjectParams`, `MyProjectSchema`.
194
197
  - **`dictionary.generated.ts`** — imports the JSON files and assembles the initial dictionary.
195
- - **`instance.generated.ts`** — exports `createI18n()`, a typed factory with the default dictionary as fallback.
198
+ - **`instance.generated.ts`** — exports `createI18n()`, typed `projectLocales()` (full schema), and in multi mode `projectNamespaceLocales()` (single namespace); `LOCALE_FALLBACK` wired in when configured.
196
199
  - **`i18n.ts`** (optional, hand-written) — app-owned singleton if desired.
197
200
 
198
201
  Example generated types (multi-namespace):
@@ -299,6 +302,25 @@ const localeFallback = {
299
302
  const i18n = new IcuTranslationProviderMulti(schema, { localeFallback });
300
303
  ```
301
304
 
305
+ #### `projectLocales` / `projectNamespaceLocales`
306
+
307
+ Namespaces split the dictionary by domain; locale projection splits it by **locale**. Use before `setAll()` / `setNamespace()` when you only need one locale (or a small regional group) in memory.
308
+
309
+ ```ts
310
+ import {
311
+ createI18n,
312
+ projectLocales,
313
+ projectNamespaceLocales,
314
+ } from "./i18n/generated/instance.generated.js";
315
+ import billingDictionary from "./i18n/translations/billing.json";
316
+
317
+ const i18n = createI18n();
318
+ i18n.setNamespace("billing", projectNamespaceLocales(billingDictionary, [activeLocale]));
319
+ i18n.setAll(projectLocales(fullDictionary, [activeLocale])); // multi: all namespaces
320
+ ```
321
+
322
+ Codegen emits typed wrappers in `instance.generated.ts` (`projectLocales` for the full schema; `projectNamespaceLocales` in multi mode for one namespace). The low-level `@xndrjs/i18n` exports remain for tooling without codegen.
323
+
302
324
  ### 6. Translation audit (`xndrjs-i18n-audit`)
303
325
 
304
326
  Report missing translations as JSON — useful for translator backlogs and optional CI gates.
@@ -395,7 +417,9 @@ Specify **exactly one** of `dictionary` (single-file) or `namespaces` (multi-fil
395
417
 
396
418
  Dictionary paths may use `.yaml` or `.yml` instead of `.json`. **YAML is an authoring format, not a runtime format** — codegen compiles YAML to JSON under the generated output directory (for example `translations/billing.yaml` → `{dirname(typesOutput)}/translations/billing.json`); generated TypeScript imports the compiled JSON at runtime. Edit only the YAML source — the compiled JSON is overwritten on each codegen run.
397
419
 
398
- YAML block scalars make long ICU messages much easier to read while preserving (or intentionally folding) line breaks.
420
+ Use YAML when ICU is hard to read on one line: multiple parameters, or plural/select branches. It is not aimed at long legal copy or styled pages those usually belong in a separate content template with localized fragments inside.
421
+
422
+ YAML block scalars keep complex ICU readable in source while preserving (or intentionally folding) line breaks.
399
423
 
400
424
  ```yaml
401
425
  # translations/billing.yaml
package/dist/index.d.ts CHANGED
@@ -111,6 +111,17 @@ declare function validateLocaleFallback(map: LocaleFallbackMap): void;
111
111
  declare function resolveLocaleTemplate(localeByKey: Record<string, string | undefined> | undefined, requestedLocale: string, fallbackMap?: LocaleFallbackMap): ResolvedLocaleTemplate | undefined;
112
112
  declare function formatLocaleFallbackChain(requestedLocale: string, fallbackMap?: LocaleFallbackMap): string;
113
113
 
114
+ /**
115
+ * Builds a dictionary that contains only the requested locales.
116
+ * For each key and target locale, copies the direct template when present;
117
+ * otherwise resolves through `localeFallback` (same rules as `.get()` at runtime).
118
+ */
119
+ declare function projectLocales<T extends KeyDictionary>(dictionary: T, locales: readonly string[], localeFallback?: LocaleFallbackMap$1): T;
120
+ /**
121
+ * Projects every namespace in a multi-namespace dictionary.
122
+ */
123
+ declare function projectNamespacesLocales<T extends MultiDictionary>(dictionary: T, locales: readonly string[], localeFallback?: LocaleFallbackMap$1): T;
124
+
114
125
  interface EnsureNamespacesLoadedOptions<Schema extends MultiDictionary, NS extends keyof Schema & string> {
115
126
  provider: Pick<{
116
127
  hasNamespace(namespace: NS): boolean;
@@ -121,4 +132,4 @@ interface EnsureNamespacesLoadedOptions<Schema extends MultiDictionary, NS exten
121
132
  }
122
133
  declare function ensureNamespacesLoadedImpl<Schema extends MultiDictionary, NS extends keyof Schema & string>(options: EnsureNamespacesLoadedOptions<Schema, NS>, namespaces: NS[]): Promise<void>;
123
134
 
124
- export { type EnsureNamespacesLoadedOptions, IcuTranslationProviderMulti, type IcuTranslationProviderOptions, IcuTranslationProviderSingle, type KeyDictionary, type LocaleDictionary, type LocaleFallbackMap$1 as LocaleFallbackMap, type LocaleOfMulti, type LocaleOfSingle, type MultiDictionary, type RequestLocale, type ResolvedLocaleTemplate, type TranslationProviderMulti, type TranslationProviderMultiForLocale, type TranslationProviderSingle, type TranslationProviderSingleForLocale, ensureNamespacesLoadedImpl, formatLocaleFallbackChain, resolveLocaleTemplate, validateLocaleFallback };
135
+ export { type EnsureNamespacesLoadedOptions, IcuTranslationProviderMulti, type IcuTranslationProviderOptions, IcuTranslationProviderSingle, type KeyDictionary, type LocaleDictionary, type LocaleFallbackMap$1 as LocaleFallbackMap, type LocaleOfMulti, type LocaleOfSingle, type MultiDictionary, type RequestLocale, type ResolvedLocaleTemplate, type TranslationProviderMulti, type TranslationProviderMultiForLocale, type TranslationProviderSingle, type TranslationProviderSingleForLocale, ensureNamespacesLoadedImpl, formatLocaleFallbackChain, projectLocales, projectNamespacesLocales, resolveLocaleTemplate, validateLocaleFallback };
package/dist/index.js CHANGED
@@ -243,6 +243,43 @@ var IcuTranslationProviderMulti = class {
243
243
  }
244
244
  };
245
245
 
246
+ // src/project-locales.ts
247
+ function projectKeyDictionary(dictionary, locales, localeFallback) {
248
+ const targetLocales = [...new Set(locales)];
249
+ const result = {};
250
+ for (const [key, localesByKey] of Object.entries(dictionary)) {
251
+ const projected = {};
252
+ for (const locale of targetLocales) {
253
+ const resolved = resolveLocaleTemplate(localesByKey, locale, localeFallback);
254
+ if (resolved !== void 0) {
255
+ projected[locale] = resolved.template;
256
+ }
257
+ }
258
+ if (Object.keys(projected).length > 0) {
259
+ result[key] = projected;
260
+ }
261
+ }
262
+ return result;
263
+ }
264
+ function projectLocales(dictionary, locales, localeFallback) {
265
+ return projectKeyDictionary(dictionary, locales, localeFallback);
266
+ }
267
+ function projectNamespacesLocales(dictionary, locales, localeFallback) {
268
+ const result = {};
269
+ for (const namespace of Object.keys(dictionary)) {
270
+ const namespaceDictionary = dictionary[namespace];
271
+ if (namespaceDictionary === void 0) {
272
+ continue;
273
+ }
274
+ result[namespace] = projectKeyDictionary(
275
+ namespaceDictionary,
276
+ locales,
277
+ localeFallback
278
+ );
279
+ }
280
+ return result;
281
+ }
282
+
246
283
  // src/validation/errors.ts
247
284
  function issueMessage(issue) {
248
285
  switch (issue.kind) {
@@ -315,6 +352,6 @@ async function ensureNamespacesLoadedImpl(options, namespaces) {
315
352
  await Promise.all(namespaces.map((namespace) => ensureOneNamespace(options, namespace)));
316
353
  }
317
354
 
318
- export { IcuTranslationProviderMulti, IcuTranslationProviderSingle, ensureNamespacesLoadedImpl, formatLocaleFallbackChain, resolveLocaleTemplate, validateLocaleFallback };
355
+ export { IcuTranslationProviderMulti, IcuTranslationProviderSingle, ensureNamespacesLoadedImpl, formatLocaleFallbackChain, projectLocales, projectNamespacesLocales, resolveLocaleTemplate, validateLocaleFallback };
319
356
  //# sourceMappingURL=index.js.map
320
357
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/deep-freeze.ts","../src/resolve-locale.ts","../src/IcuTranslationProviderSingle.ts","../src/IcuTranslationProviderMulti.ts","../src/validation/errors.ts","../src/ensure-namespace.ts"],"names":["IntlMessageFormat"],"mappings":"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","file":"index.js","sourcesContent":["export function deepFreeze<T>(value: T): T {\n if (value === null || typeof value !== \"object\") {\n return value;\n }\n\n Object.freeze(value);\n\n for (const child of Object.values(value)) {\n deepFreeze(child);\n }\n\n return value;\n}\n\nexport function cloneAndFreeze<T>(value: T): T {\n return deepFreeze(structuredClone(value));\n}\n","export type LocaleFallbackMap = Record<string, string | null>;\n\nexport interface ResolvedLocaleTemplate {\n template: string;\n resolvedLocale: string;\n fallbackChain: readonly string[];\n}\n\nexport function validateLocaleFallback(map: LocaleFallbackMap): void {\n for (const startLocale of Object.keys(map)) {\n const visited = new Set<string>();\n let current: string | undefined = startLocale;\n\n while (current !== undefined) {\n if (visited.has(current)) {\n throw new Error(`[i18n] Circular locale fallback detected involving \"${current}\"`);\n }\n visited.add(current);\n\n const next: string | null | undefined = map[current];\n if (next === undefined || next === null) {\n break;\n }\n current = next;\n }\n }\n}\n\nexport function resolveLocaleTemplate(\n localeByKey: Record<string, string | undefined> | undefined,\n requestedLocale: string,\n fallbackMap?: LocaleFallbackMap\n): ResolvedLocaleTemplate | undefined {\n const chain: string[] = [requestedLocale];\n let currentLocale = requestedLocale;\n\n while (true) {\n const template = localeByKey?.[currentLocale];\n if (template !== undefined) {\n return { template, resolvedLocale: currentLocale, fallbackChain: chain };\n }\n\n if (!fallbackMap) {\n return undefined;\n }\n\n const fallback = fallbackMap[currentLocale];\n if (fallback === undefined || fallback === null) {\n return undefined;\n }\n\n if (chain.includes(fallback)) {\n throw new Error(\n `[i18n] Circular locale fallback detected: ${[...chain, fallback].join(\" → \")}`\n );\n }\n\n chain.push(fallback);\n currentLocale = fallback;\n }\n}\n\nexport function formatLocaleFallbackChain(\n requestedLocale: string,\n fallbackMap?: LocaleFallbackMap\n): string {\n if (!fallbackMap) {\n return requestedLocale;\n }\n\n const chain: string[] = [requestedLocale];\n let current = requestedLocale;\n\n while (true) {\n const fallback = fallbackMap[current];\n if (fallback === undefined || fallback === null) {\n break;\n }\n if (chain.includes(fallback)) {\n chain.push(fallback);\n break;\n }\n chain.push(fallback);\n current = fallback;\n }\n\n return chain.join(\" → \");\n}\n","import { IntlMessageFormat } from \"intl-messageformat\";\nimport { cloneAndFreeze } from \"./deep-freeze.js\";\nimport {\n formatLocaleFallbackChain,\n resolveLocaleTemplate,\n validateLocaleFallback,\n} from \"./resolve-locale.js\";\nimport type {\n IcuTranslationProviderOptions,\n KeyDictionary,\n LocaleFallbackMap,\n LocaleOfSingle,\n SingleCompiledCache,\n} from \"./types.js\";\n\nexport interface TranslationProviderSingleForLocale<\n Schema extends KeyDictionary,\n Params extends { [K in keyof Schema]: unknown },\n Locale extends string,\n> {\n readonly locale: Locale;\n get<K extends keyof Schema & string>(\n key: K,\n ...params: Params[K] extends never ? [] : [params: Params[K]]\n ): string;\n}\n\nexport interface TranslationProviderSingle<\n Schema extends KeyDictionary,\n Params extends { [K in keyof Schema]: unknown },\n RequestLocales extends string = LocaleOfSingle<Schema>,\n> {\n get<K extends keyof Schema & string>(\n key: K,\n locale: RequestLocales,\n ...params: Params[K] extends never ? [] : [params: Params[K]]\n ): string;\n forLocale<Locale extends RequestLocales>(\n locale: Locale\n ): TranslationProviderSingleForLocale<Schema, Params, Locale>;\n getAll(): Schema;\n setAll(values: Schema): void;\n}\n\nexport class IcuTranslationProviderSingleForLocale<\n Schema extends KeyDictionary,\n Params extends { [K in keyof Schema]: unknown },\n RequestLocales extends string,\n Locale extends RequestLocales,\n Fallback extends LocaleFallbackMap | undefined,\n> implements TranslationProviderSingleForLocale<Schema, Params, Locale> {\n constructor(\n private readonly provider: IcuTranslationProviderSingle<\n Schema,\n Params,\n RequestLocales,\n Fallback\n >,\n readonly locale: Locale\n ) {}\n\n get<K extends keyof Schema & string>(\n key: K,\n ...args: Params[K] extends never ? [] : [params: Params[K]]\n ): string {\n return (\n this.provider.get as (\n key: K,\n locale: Locale,\n ...params: Params[K] extends never ? [] : [params: Params[K]]\n ) => string\n )(key, this.locale, ...args);\n }\n}\n\nexport class IcuTranslationProviderSingle<\n Schema extends KeyDictionary,\n Params extends { [K in keyof Schema]: unknown },\n RequestLocales extends string = LocaleOfSingle<Schema>,\n Fallback extends LocaleFallbackMap | undefined = undefined,\n> implements TranslationProviderSingle<Schema, Params, RequestLocales> {\n private dictionary: Schema;\n private compiledCache: SingleCompiledCache = {};\n private readonly localeFallback?: Fallback;\n\n constructor(dictionary: Schema, options?: IcuTranslationProviderOptions<Fallback>) {\n this.dictionary = structuredClone(dictionary);\n if (options?.localeFallback) {\n validateLocaleFallback(options.localeFallback);\n this.localeFallback = options.localeFallback;\n }\n }\n\n get<K extends keyof Schema & string>(\n key: K,\n locale: RequestLocales,\n ...args: Params[K] extends never ? [] : [params: Params[K]]\n ): string {\n const params = args[0] as Record<string, unknown> | undefined;\n const resolved = resolveLocaleTemplate(this.dictionary[key], locale, this.localeFallback);\n\n if (!resolved) {\n const chain = formatLocaleFallbackChain(locale, this.localeFallback);\n throw new Error(\n `[i18n] Missing key or locale: \"${String(key)}\" [${locale}] (fallback chain: ${chain})`\n );\n }\n\n const { template, resolvedLocale } = resolved;\n\n if (!this.compiledCache[resolvedLocale]) {\n this.compiledCache[resolvedLocale] = {};\n }\n\n const cacheKey = String(key);\n\n if (!this.compiledCache[resolvedLocale][cacheKey]) {\n try {\n this.compiledCache[resolvedLocale][cacheKey] = new IntlMessageFormat(\n template,\n resolvedLocale\n );\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n throw new Error(\n `[i18n ICU Syntax Error] Dictionary error for key \"${String(key)}\" [${resolvedLocale}]: ${message}`\n );\n }\n }\n\n try {\n return this.compiledCache[resolvedLocale][cacheKey].format(params) as string;\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n throw new Error(\n `[i18n Formatting Error] Invalid or missing parameters for key \"${String(key)}\" [${resolvedLocale}]: ${message}`\n );\n }\n }\n\n forLocale<Locale extends RequestLocales>(\n locale: Locale\n ): IcuTranslationProviderSingleForLocale<Schema, Params, RequestLocales, Locale, Fallback> {\n return new IcuTranslationProviderSingleForLocale(this, locale);\n }\n\n getAll(): Schema {\n return cloneAndFreeze(this.dictionary);\n }\n\n setAll(values: Schema): void {\n this.dictionary = structuredClone(values);\n this.compiledCache = {};\n }\n}\n","import { IntlMessageFormat } from \"intl-messageformat\";\nimport { cloneAndFreeze } from \"./deep-freeze.js\";\nimport {\n formatLocaleFallbackChain,\n resolveLocaleTemplate,\n validateLocaleFallback,\n} from \"./resolve-locale.js\";\nimport type {\n IcuTranslationProviderOptions,\n LocaleFallbackMap,\n LocaleOfMulti,\n MultiCompiledCache,\n MultiDictionary,\n} from \"./types.js\";\n\nexport interface TranslationProviderMultiForLocale<\n Schema extends MultiDictionary,\n Params extends { [NS in keyof Schema]: { [K in keyof Schema[NS]]: unknown } },\n Locale extends string,\n> {\n readonly locale: Locale;\n get<NS extends keyof Schema, K extends keyof Schema[NS] & keyof Params[NS] & string>(\n namespace: NS,\n key: K,\n ...params: Params[NS][K] extends never ? [] : [params: Params[NS][K]]\n ): string;\n}\n\nexport interface TranslationProviderMulti<\n Schema extends MultiDictionary,\n Params extends { [NS in keyof Schema]: { [K in keyof Schema[NS]]: unknown } },\n RequestLocales extends string = LocaleOfMulti<Schema>,\n> {\n get<NS extends keyof Schema, K extends keyof Schema[NS] & keyof Params[NS] & string>(\n namespace: NS,\n key: K,\n locale: RequestLocales,\n ...params: Params[NS][K] extends never ? [] : [params: Params[NS][K]]\n ): string;\n forLocale<Locale extends RequestLocales>(\n locale: Locale\n ): TranslationProviderMultiForLocale<Schema, Params, Locale>;\n getAll(): Schema;\n hasNamespace<NS extends keyof Schema & string>(namespace: NS): boolean;\n setAll(values: Schema): void;\n setNamespace<NS extends keyof Schema>(namespace: NS, values: Schema[NS]): void;\n}\n\nexport class IcuTranslationProviderMultiForLocale<\n Schema extends MultiDictionary,\n Params extends { [NS in keyof Schema]: { [K in keyof Schema[NS]]: unknown } },\n RequestLocales extends string,\n Locale extends RequestLocales,\n Fallback extends LocaleFallbackMap | undefined,\n> implements TranslationProviderMultiForLocale<Schema, Params, Locale> {\n constructor(\n private readonly provider: IcuTranslationProviderMulti<\n Schema,\n Params,\n RequestLocales,\n Fallback\n >,\n readonly locale: Locale\n ) {}\n\n get<NS extends keyof Schema, K extends keyof Schema[NS] & keyof Params[NS] & string>(\n namespace: NS,\n key: K,\n ...args: Params[NS][K] extends never ? [] : [params: Params[NS][K]]\n ): string {\n return (\n this.provider.get as <\n NS extends keyof Schema,\n K extends keyof Schema[NS] & keyof Params[NS] & string,\n >(\n namespace: NS,\n key: K,\n locale: Locale,\n ...params: Params[NS][K] extends never ? [] : [params: Params[NS][K]]\n ) => string\n )(namespace, key, this.locale, ...args);\n }\n}\n\nexport class IcuTranslationProviderMulti<\n Schema extends MultiDictionary,\n Params extends { [NS in keyof Schema]: { [K in keyof Schema[NS]]: unknown } },\n RequestLocales extends string = LocaleOfMulti<Schema>,\n Fallback extends LocaleFallbackMap | undefined = undefined,\n> implements TranslationProviderMulti<Schema, Params, RequestLocales> {\n private dictionary: Schema;\n private compiledCache: MultiCompiledCache = {};\n private readonly localeFallback?: Fallback;\n private loadedNamespaces: Set<string>;\n\n constructor(dictionary: Partial<Schema>, options?: IcuTranslationProviderOptions<Fallback>) {\n this.dictionary = structuredClone(dictionary) as Schema;\n this.loadedNamespaces = new Set(Object.keys(dictionary));\n if (options?.localeFallback) {\n validateLocaleFallback(options.localeFallback);\n this.localeFallback = options.localeFallback;\n }\n }\n\n get<NS extends keyof Schema, K extends keyof Schema[NS] & keyof Params[NS] & string>(\n namespace: NS,\n key: K,\n locale: RequestLocales,\n ...args: Params[NS][K] extends never ? [] : [params: Params[NS][K]]\n ): string {\n if (!this.loadedNamespaces.has(namespace as string)) {\n throw new Error(\n `[i18n] Namespace not loaded: \"${String(namespace)}\". Call ensureNamespacesLoaded(i18n, [\"${String(namespace)}\"]) first.`\n );\n }\n\n const params = args[0] as Record<string, unknown> | undefined;\n const localeByKey = (\n this.dictionary[namespace as string] as Record<string, Record<string, string>>\n )?.[key];\n const resolved = resolveLocaleTemplate(localeByKey, locale, this.localeFallback);\n\n if (!resolved) {\n const chain = formatLocaleFallbackChain(locale, this.localeFallback);\n throw new Error(\n `[i18n] Missing key or locale: namespace \"${String(namespace)}\", key \"${String(key)}\" [${locale}] (fallback chain: ${chain})`\n );\n }\n\n const { template, resolvedLocale } = resolved;\n\n if (!this.compiledCache[resolvedLocale]) {\n this.compiledCache[resolvedLocale] = {};\n }\n\n if (!this.compiledCache[resolvedLocale][namespace as string]) {\n this.compiledCache[resolvedLocale][namespace as string] = {};\n }\n\n const namespaceCache = this.compiledCache[resolvedLocale][namespace as string]!;\n const cacheKey = String(key);\n\n if (!namespaceCache[cacheKey]) {\n try {\n namespaceCache[cacheKey] = new IntlMessageFormat(template, resolvedLocale);\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n throw new Error(\n `[i18n ICU Syntax Error] namespace \"${String(namespace)}\", key \"${String(key)}\" [${resolvedLocale}]: ${message}`\n );\n }\n }\n\n try {\n return namespaceCache[cacheKey].format(params) as string;\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n throw new Error(\n `[i18n Formatting Error] namespace \"${String(namespace)}\", key \"${String(key)}\" [${resolvedLocale}]: ${message}`\n );\n }\n }\n\n forLocale<Locale extends RequestLocales>(\n locale: Locale\n ): IcuTranslationProviderMultiForLocale<Schema, Params, RequestLocales, Locale, Fallback> {\n return new IcuTranslationProviderMultiForLocale(this, locale);\n }\n\n getAll(): Schema {\n return cloneAndFreeze(this.dictionary);\n }\n\n hasNamespace<NS extends keyof Schema & string>(namespace: NS): boolean {\n return this.loadedNamespaces.has(namespace);\n }\n\n setAll(values: Schema): void {\n this.dictionary = structuredClone(values);\n this.compiledCache = {};\n this.loadedNamespaces = new Set(Object.keys(values));\n }\n\n setNamespace<NS extends keyof Schema>(namespace: NS, values: Schema[NS]): void {\n this.dictionary = {\n ...this.dictionary,\n [namespace]: structuredClone(values),\n };\n this.loadedNamespaces.add(namespace as string);\n\n for (const locale of Object.keys(this.compiledCache)) {\n delete this.compiledCache[locale]?.[namespace as string];\n }\n }\n}\n","import type { ValidationIssue, ValidationResult } from \"./types.js\";\n\nfunction issueMessage(issue: ValidationIssue): string {\n switch (issue.kind) {\n case \"missing_key\":\n return `Missing required key at ${issue.path.join(\".\")}`;\n case \"invalid_input\":\n case \"invalid_locale_value\":\n case \"icu_syntax_error\":\n case \"locale_args_mismatch\":\n case \"variable_mismatch\":\n case \"variable_type_mismatch\":\n return issue.message;\n }\n}\n\nexport function formatIssues(issues: readonly ValidationIssue[]): string {\n return issues\n .map((issue) => {\n if (\"path\" in issue && issue.path.length > 0) {\n return `[${issue.path.join(\".\")}] ${issue.kind}: ${issueMessage(issue)}`;\n }\n return `${issue.kind}: ${issueMessage(issue)}`;\n })\n .join(\"\\n\");\n}\n\nexport class DictionaryValidationError extends Error {\n constructor(public readonly issues: readonly ValidationIssue[]) {\n super(formatIssues(issues));\n this.name = \"DictionaryValidationError\";\n }\n}\n\nexport function assertValidDictionary<T>(\n result: ValidationResult<T>\n): asserts result is { ok: true; data: T } {\n if (!result.ok) {\n throw new DictionaryValidationError(result.issues);\n }\n}\n","import type { MultiDictionary } from \"./types.js\";\nimport type { ValidationResult } from \"./validation/types.js\";\nimport { DictionaryValidationError } from \"./validation/errors.js\";\n\nexport interface EnsureNamespacesLoadedOptions<\n Schema extends MultiDictionary,\n NS extends keyof Schema & string,\n> {\n provider: Pick<\n { hasNamespace(namespace: NS): boolean; setNamespace(namespace: NS, values: Schema[NS]): void },\n \"hasNamespace\" | \"setNamespace\"\n >;\n resolveLoader: (namespace: NS) => () => Promise<unknown>;\n validate: (namespace: NS, raw: unknown) => ValidationResult<Schema[NS]>;\n}\n\nconst inFlightByProvider = new WeakMap<object, Map<string, Promise<void>>>();\n\nasync function ensureOneNamespace<Schema extends MultiDictionary, NS extends keyof Schema & string>(\n options: EnsureNamespacesLoadedOptions<Schema, NS>,\n namespace: NS\n): Promise<void> {\n if (options.provider.hasNamespace(namespace)) {\n return;\n }\n\n const providerKey = options.provider as object;\n let inFlight = inFlightByProvider.get(providerKey);\n if (!inFlight) {\n inFlight = new Map();\n inFlightByProvider.set(providerKey, inFlight);\n }\n\n const existing = inFlight.get(namespace);\n if (existing) {\n return existing;\n }\n\n const promise = (async () => {\n try {\n if (options.provider.hasNamespace(namespace)) {\n return;\n }\n\n const raw = await options.resolveLoader(namespace)();\n const result = options.validate(namespace, raw);\n if (!result.ok) {\n throw new DictionaryValidationError(result.issues);\n }\n\n options.provider.setNamespace(namespace, result.data);\n } finally {\n inFlight!.delete(namespace);\n }\n })();\n\n inFlight.set(namespace, promise);\n return promise;\n}\n\nexport async function ensureNamespacesLoadedImpl<\n Schema extends MultiDictionary,\n NS extends keyof Schema & string,\n>(options: EnsureNamespacesLoadedOptions<Schema, NS>, namespaces: NS[]): Promise<void> {\n if (namespaces.length === 0) {\n return;\n }\n\n await Promise.all(namespaces.map((namespace) => ensureOneNamespace(options, namespace)));\n}\n"]}
1
+ {"version":3,"sources":["../src/deep-freeze.ts","../src/resolve-locale.ts","../src/IcuTranslationProviderSingle.ts","../src/IcuTranslationProviderMulti.ts","../src/project-locales.ts","../src/validation/errors.ts","../src/ensure-namespace.ts"],"names":["IntlMessageFormat"],"mappings":"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","file":"index.js","sourcesContent":["export function deepFreeze<T>(value: T): T {\n if (value === null || typeof value !== \"object\") {\n return value;\n }\n\n Object.freeze(value);\n\n for (const child of Object.values(value)) {\n deepFreeze(child);\n }\n\n return value;\n}\n\nexport function cloneAndFreeze<T>(value: T): T {\n return deepFreeze(structuredClone(value));\n}\n","export type LocaleFallbackMap = Record<string, string | null>;\n\nexport interface ResolvedLocaleTemplate {\n template: string;\n resolvedLocale: string;\n fallbackChain: readonly string[];\n}\n\nexport function validateLocaleFallback(map: LocaleFallbackMap): void {\n for (const startLocale of Object.keys(map)) {\n const visited = new Set<string>();\n let current: string | undefined = startLocale;\n\n while (current !== undefined) {\n if (visited.has(current)) {\n throw new Error(`[i18n] Circular locale fallback detected involving \"${current}\"`);\n }\n visited.add(current);\n\n const next: string | null | undefined = map[current];\n if (next === undefined || next === null) {\n break;\n }\n current = next;\n }\n }\n}\n\nexport function resolveLocaleTemplate(\n localeByKey: Record<string, string | undefined> | undefined,\n requestedLocale: string,\n fallbackMap?: LocaleFallbackMap\n): ResolvedLocaleTemplate | undefined {\n const chain: string[] = [requestedLocale];\n let currentLocale = requestedLocale;\n\n while (true) {\n const template = localeByKey?.[currentLocale];\n if (template !== undefined) {\n return { template, resolvedLocale: currentLocale, fallbackChain: chain };\n }\n\n if (!fallbackMap) {\n return undefined;\n }\n\n const fallback = fallbackMap[currentLocale];\n if (fallback === undefined || fallback === null) {\n return undefined;\n }\n\n if (chain.includes(fallback)) {\n throw new Error(\n `[i18n] Circular locale fallback detected: ${[...chain, fallback].join(\" → \")}`\n );\n }\n\n chain.push(fallback);\n currentLocale = fallback;\n }\n}\n\nexport function formatLocaleFallbackChain(\n requestedLocale: string,\n fallbackMap?: LocaleFallbackMap\n): string {\n if (!fallbackMap) {\n return requestedLocale;\n }\n\n const chain: string[] = [requestedLocale];\n let current = requestedLocale;\n\n while (true) {\n const fallback = fallbackMap[current];\n if (fallback === undefined || fallback === null) {\n break;\n }\n if (chain.includes(fallback)) {\n chain.push(fallback);\n break;\n }\n chain.push(fallback);\n current = fallback;\n }\n\n return chain.join(\" → \");\n}\n","import { IntlMessageFormat } from \"intl-messageformat\";\nimport { cloneAndFreeze } from \"./deep-freeze.js\";\nimport {\n formatLocaleFallbackChain,\n resolveLocaleTemplate,\n validateLocaleFallback,\n} from \"./resolve-locale.js\";\nimport type {\n IcuTranslationProviderOptions,\n KeyDictionary,\n LocaleFallbackMap,\n LocaleOfSingle,\n SingleCompiledCache,\n} from \"./types.js\";\n\nexport interface TranslationProviderSingleForLocale<\n Schema extends KeyDictionary,\n Params extends { [K in keyof Schema]: unknown },\n Locale extends string,\n> {\n readonly locale: Locale;\n get<K extends keyof Schema & string>(\n key: K,\n ...params: Params[K] extends never ? [] : [params: Params[K]]\n ): string;\n}\n\nexport interface TranslationProviderSingle<\n Schema extends KeyDictionary,\n Params extends { [K in keyof Schema]: unknown },\n RequestLocales extends string = LocaleOfSingle<Schema>,\n> {\n get<K extends keyof Schema & string>(\n key: K,\n locale: RequestLocales,\n ...params: Params[K] extends never ? [] : [params: Params[K]]\n ): string;\n forLocale<Locale extends RequestLocales>(\n locale: Locale\n ): TranslationProviderSingleForLocale<Schema, Params, Locale>;\n getAll(): Schema;\n setAll(values: Schema): void;\n}\n\nexport class IcuTranslationProviderSingleForLocale<\n Schema extends KeyDictionary,\n Params extends { [K in keyof Schema]: unknown },\n RequestLocales extends string,\n Locale extends RequestLocales,\n Fallback extends LocaleFallbackMap | undefined,\n> implements TranslationProviderSingleForLocale<Schema, Params, Locale> {\n constructor(\n private readonly provider: IcuTranslationProviderSingle<\n Schema,\n Params,\n RequestLocales,\n Fallback\n >,\n readonly locale: Locale\n ) {}\n\n get<K extends keyof Schema & string>(\n key: K,\n ...args: Params[K] extends never ? [] : [params: Params[K]]\n ): string {\n return (\n this.provider.get as (\n key: K,\n locale: Locale,\n ...params: Params[K] extends never ? [] : [params: Params[K]]\n ) => string\n )(key, this.locale, ...args);\n }\n}\n\nexport class IcuTranslationProviderSingle<\n Schema extends KeyDictionary,\n Params extends { [K in keyof Schema]: unknown },\n RequestLocales extends string = LocaleOfSingle<Schema>,\n Fallback extends LocaleFallbackMap | undefined = undefined,\n> implements TranslationProviderSingle<Schema, Params, RequestLocales> {\n private dictionary: Schema;\n private compiledCache: SingleCompiledCache = {};\n private readonly localeFallback?: Fallback;\n\n constructor(dictionary: Schema, options?: IcuTranslationProviderOptions<Fallback>) {\n this.dictionary = structuredClone(dictionary);\n if (options?.localeFallback) {\n validateLocaleFallback(options.localeFallback);\n this.localeFallback = options.localeFallback;\n }\n }\n\n get<K extends keyof Schema & string>(\n key: K,\n locale: RequestLocales,\n ...args: Params[K] extends never ? [] : [params: Params[K]]\n ): string {\n const params = args[0] as Record<string, unknown> | undefined;\n const resolved = resolveLocaleTemplate(this.dictionary[key], locale, this.localeFallback);\n\n if (!resolved) {\n const chain = formatLocaleFallbackChain(locale, this.localeFallback);\n throw new Error(\n `[i18n] Missing key or locale: \"${String(key)}\" [${locale}] (fallback chain: ${chain})`\n );\n }\n\n const { template, resolvedLocale } = resolved;\n\n if (!this.compiledCache[resolvedLocale]) {\n this.compiledCache[resolvedLocale] = {};\n }\n\n const cacheKey = String(key);\n\n if (!this.compiledCache[resolvedLocale][cacheKey]) {\n try {\n this.compiledCache[resolvedLocale][cacheKey] = new IntlMessageFormat(\n template,\n resolvedLocale\n );\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n throw new Error(\n `[i18n ICU Syntax Error] Dictionary error for key \"${String(key)}\" [${resolvedLocale}]: ${message}`\n );\n }\n }\n\n try {\n return this.compiledCache[resolvedLocale][cacheKey].format(params) as string;\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n throw new Error(\n `[i18n Formatting Error] Invalid or missing parameters for key \"${String(key)}\" [${resolvedLocale}]: ${message}`\n );\n }\n }\n\n forLocale<Locale extends RequestLocales>(\n locale: Locale\n ): IcuTranslationProviderSingleForLocale<Schema, Params, RequestLocales, Locale, Fallback> {\n return new IcuTranslationProviderSingleForLocale(this, locale);\n }\n\n getAll(): Schema {\n return cloneAndFreeze(this.dictionary);\n }\n\n setAll(values: Schema): void {\n this.dictionary = structuredClone(values);\n this.compiledCache = {};\n }\n}\n","import { IntlMessageFormat } from \"intl-messageformat\";\nimport { cloneAndFreeze } from \"./deep-freeze.js\";\nimport {\n formatLocaleFallbackChain,\n resolveLocaleTemplate,\n validateLocaleFallback,\n} from \"./resolve-locale.js\";\nimport type {\n IcuTranslationProviderOptions,\n LocaleFallbackMap,\n LocaleOfMulti,\n MultiCompiledCache,\n MultiDictionary,\n} from \"./types.js\";\n\nexport interface TranslationProviderMultiForLocale<\n Schema extends MultiDictionary,\n Params extends { [NS in keyof Schema]: { [K in keyof Schema[NS]]: unknown } },\n Locale extends string,\n> {\n readonly locale: Locale;\n get<NS extends keyof Schema, K extends keyof Schema[NS] & keyof Params[NS] & string>(\n namespace: NS,\n key: K,\n ...params: Params[NS][K] extends never ? [] : [params: Params[NS][K]]\n ): string;\n}\n\nexport interface TranslationProviderMulti<\n Schema extends MultiDictionary,\n Params extends { [NS in keyof Schema]: { [K in keyof Schema[NS]]: unknown } },\n RequestLocales extends string = LocaleOfMulti<Schema>,\n> {\n get<NS extends keyof Schema, K extends keyof Schema[NS] & keyof Params[NS] & string>(\n namespace: NS,\n key: K,\n locale: RequestLocales,\n ...params: Params[NS][K] extends never ? [] : [params: Params[NS][K]]\n ): string;\n forLocale<Locale extends RequestLocales>(\n locale: Locale\n ): TranslationProviderMultiForLocale<Schema, Params, Locale>;\n getAll(): Schema;\n hasNamespace<NS extends keyof Schema & string>(namespace: NS): boolean;\n setAll(values: Schema): void;\n setNamespace<NS extends keyof Schema>(namespace: NS, values: Schema[NS]): void;\n}\n\nexport class IcuTranslationProviderMultiForLocale<\n Schema extends MultiDictionary,\n Params extends { [NS in keyof Schema]: { [K in keyof Schema[NS]]: unknown } },\n RequestLocales extends string,\n Locale extends RequestLocales,\n Fallback extends LocaleFallbackMap | undefined,\n> implements TranslationProviderMultiForLocale<Schema, Params, Locale> {\n constructor(\n private readonly provider: IcuTranslationProviderMulti<\n Schema,\n Params,\n RequestLocales,\n Fallback\n >,\n readonly locale: Locale\n ) {}\n\n get<NS extends keyof Schema, K extends keyof Schema[NS] & keyof Params[NS] & string>(\n namespace: NS,\n key: K,\n ...args: Params[NS][K] extends never ? [] : [params: Params[NS][K]]\n ): string {\n return (\n this.provider.get as <\n NS extends keyof Schema,\n K extends keyof Schema[NS] & keyof Params[NS] & string,\n >(\n namespace: NS,\n key: K,\n locale: Locale,\n ...params: Params[NS][K] extends never ? [] : [params: Params[NS][K]]\n ) => string\n )(namespace, key, this.locale, ...args);\n }\n}\n\nexport class IcuTranslationProviderMulti<\n Schema extends MultiDictionary,\n Params extends { [NS in keyof Schema]: { [K in keyof Schema[NS]]: unknown } },\n RequestLocales extends string = LocaleOfMulti<Schema>,\n Fallback extends LocaleFallbackMap | undefined = undefined,\n> implements TranslationProviderMulti<Schema, Params, RequestLocales> {\n private dictionary: Schema;\n private compiledCache: MultiCompiledCache = {};\n private readonly localeFallback?: Fallback;\n private loadedNamespaces: Set<string>;\n\n constructor(dictionary: Partial<Schema>, options?: IcuTranslationProviderOptions<Fallback>) {\n this.dictionary = structuredClone(dictionary) as Schema;\n this.loadedNamespaces = new Set(Object.keys(dictionary));\n if (options?.localeFallback) {\n validateLocaleFallback(options.localeFallback);\n this.localeFallback = options.localeFallback;\n }\n }\n\n get<NS extends keyof Schema, K extends keyof Schema[NS] & keyof Params[NS] & string>(\n namespace: NS,\n key: K,\n locale: RequestLocales,\n ...args: Params[NS][K] extends never ? [] : [params: Params[NS][K]]\n ): string {\n if (!this.loadedNamespaces.has(namespace as string)) {\n throw new Error(\n `[i18n] Namespace not loaded: \"${String(namespace)}\". Call ensureNamespacesLoaded(i18n, [\"${String(namespace)}\"]) first.`\n );\n }\n\n const params = args[0] as Record<string, unknown> | undefined;\n const localeByKey = (\n this.dictionary[namespace as string] as Record<string, Record<string, string>>\n )?.[key];\n const resolved = resolveLocaleTemplate(localeByKey, locale, this.localeFallback);\n\n if (!resolved) {\n const chain = formatLocaleFallbackChain(locale, this.localeFallback);\n throw new Error(\n `[i18n] Missing key or locale: namespace \"${String(namespace)}\", key \"${String(key)}\" [${locale}] (fallback chain: ${chain})`\n );\n }\n\n const { template, resolvedLocale } = resolved;\n\n if (!this.compiledCache[resolvedLocale]) {\n this.compiledCache[resolvedLocale] = {};\n }\n\n if (!this.compiledCache[resolvedLocale][namespace as string]) {\n this.compiledCache[resolvedLocale][namespace as string] = {};\n }\n\n const namespaceCache = this.compiledCache[resolvedLocale][namespace as string]!;\n const cacheKey = String(key);\n\n if (!namespaceCache[cacheKey]) {\n try {\n namespaceCache[cacheKey] = new IntlMessageFormat(template, resolvedLocale);\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n throw new Error(\n `[i18n ICU Syntax Error] namespace \"${String(namespace)}\", key \"${String(key)}\" [${resolvedLocale}]: ${message}`\n );\n }\n }\n\n try {\n return namespaceCache[cacheKey].format(params) as string;\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n throw new Error(\n `[i18n Formatting Error] namespace \"${String(namespace)}\", key \"${String(key)}\" [${resolvedLocale}]: ${message}`\n );\n }\n }\n\n forLocale<Locale extends RequestLocales>(\n locale: Locale\n ): IcuTranslationProviderMultiForLocale<Schema, Params, RequestLocales, Locale, Fallback> {\n return new IcuTranslationProviderMultiForLocale(this, locale);\n }\n\n getAll(): Schema {\n return cloneAndFreeze(this.dictionary);\n }\n\n hasNamespace<NS extends keyof Schema & string>(namespace: NS): boolean {\n return this.loadedNamespaces.has(namespace);\n }\n\n setAll(values: Schema): void {\n this.dictionary = structuredClone(values);\n this.compiledCache = {};\n this.loadedNamespaces = new Set(Object.keys(values));\n }\n\n setNamespace<NS extends keyof Schema>(namespace: NS, values: Schema[NS]): void {\n this.dictionary = {\n ...this.dictionary,\n [namespace]: structuredClone(values),\n };\n this.loadedNamespaces.add(namespace as string);\n\n for (const locale of Object.keys(this.compiledCache)) {\n delete this.compiledCache[locale]?.[namespace as string];\n }\n }\n}\n","import { resolveLocaleTemplate } from \"./resolve-locale.js\";\nimport type { KeyDictionary, LocaleFallbackMap, MultiDictionary } from \"./types.js\";\n\nfunction projectKeyDictionary<T extends KeyDictionary>(\n dictionary: T,\n locales: readonly string[],\n localeFallback?: LocaleFallbackMap\n): T {\n const targetLocales = [...new Set(locales)];\n const result = {} as T;\n\n for (const [key, localesByKey] of Object.entries(dictionary)) {\n const projected: Record<string, string> = {};\n\n for (const locale of targetLocales) {\n const resolved = resolveLocaleTemplate(localesByKey, locale, localeFallback);\n if (resolved !== undefined) {\n projected[locale] = resolved.template;\n }\n }\n\n if (Object.keys(projected).length > 0) {\n result[key as keyof T] = projected as T[keyof T];\n }\n }\n\n return result;\n}\n\n/**\n * Builds a dictionary that contains only the requested locales.\n * For each key and target locale, copies the direct template when present;\n * otherwise resolves through `localeFallback` (same rules as `.get()` at runtime).\n */\nexport function projectLocales<T extends KeyDictionary>(\n dictionary: T,\n locales: readonly string[],\n localeFallback?: LocaleFallbackMap\n): T {\n return projectKeyDictionary(dictionary, locales, localeFallback);\n}\n\n/**\n * Projects every namespace in a multi-namespace dictionary.\n */\nexport function projectNamespacesLocales<T extends MultiDictionary>(\n dictionary: T,\n locales: readonly string[],\n localeFallback?: LocaleFallbackMap\n): T {\n const result = {} as T;\n\n for (const namespace of Object.keys(dictionary) as (keyof T & string)[]) {\n const namespaceDictionary = dictionary[namespace];\n if (namespaceDictionary === undefined) {\n continue;\n }\n\n result[namespace] = projectKeyDictionary(\n namespaceDictionary,\n locales,\n localeFallback\n ) as T[typeof namespace];\n }\n\n return result;\n}\n","import type { ValidationIssue, ValidationResult } from \"./types.js\";\n\nfunction issueMessage(issue: ValidationIssue): string {\n switch (issue.kind) {\n case \"missing_key\":\n return `Missing required key at ${issue.path.join(\".\")}`;\n case \"invalid_input\":\n case \"invalid_locale_value\":\n case \"icu_syntax_error\":\n case \"locale_args_mismatch\":\n case \"variable_mismatch\":\n case \"variable_type_mismatch\":\n return issue.message;\n }\n}\n\nexport function formatIssues(issues: readonly ValidationIssue[]): string {\n return issues\n .map((issue) => {\n if (\"path\" in issue && issue.path.length > 0) {\n return `[${issue.path.join(\".\")}] ${issue.kind}: ${issueMessage(issue)}`;\n }\n return `${issue.kind}: ${issueMessage(issue)}`;\n })\n .join(\"\\n\");\n}\n\nexport class DictionaryValidationError extends Error {\n constructor(public readonly issues: readonly ValidationIssue[]) {\n super(formatIssues(issues));\n this.name = \"DictionaryValidationError\";\n }\n}\n\nexport function assertValidDictionary<T>(\n result: ValidationResult<T>\n): asserts result is { ok: true; data: T } {\n if (!result.ok) {\n throw new DictionaryValidationError(result.issues);\n }\n}\n","import type { MultiDictionary } from \"./types.js\";\nimport type { ValidationResult } from \"./validation/types.js\";\nimport { DictionaryValidationError } from \"./validation/errors.js\";\n\nexport interface EnsureNamespacesLoadedOptions<\n Schema extends MultiDictionary,\n NS extends keyof Schema & string,\n> {\n provider: Pick<\n { hasNamespace(namespace: NS): boolean; setNamespace(namespace: NS, values: Schema[NS]): void },\n \"hasNamespace\" | \"setNamespace\"\n >;\n resolveLoader: (namespace: NS) => () => Promise<unknown>;\n validate: (namespace: NS, raw: unknown) => ValidationResult<Schema[NS]>;\n}\n\nconst inFlightByProvider = new WeakMap<object, Map<string, Promise<void>>>();\n\nasync function ensureOneNamespace<Schema extends MultiDictionary, NS extends keyof Schema & string>(\n options: EnsureNamespacesLoadedOptions<Schema, NS>,\n namespace: NS\n): Promise<void> {\n if (options.provider.hasNamespace(namespace)) {\n return;\n }\n\n const providerKey = options.provider as object;\n let inFlight = inFlightByProvider.get(providerKey);\n if (!inFlight) {\n inFlight = new Map();\n inFlightByProvider.set(providerKey, inFlight);\n }\n\n const existing = inFlight.get(namespace);\n if (existing) {\n return existing;\n }\n\n const promise = (async () => {\n try {\n if (options.provider.hasNamespace(namespace)) {\n return;\n }\n\n const raw = await options.resolveLoader(namespace)();\n const result = options.validate(namespace, raw);\n if (!result.ok) {\n throw new DictionaryValidationError(result.issues);\n }\n\n options.provider.setNamespace(namespace, result.data);\n } finally {\n inFlight!.delete(namespace);\n }\n })();\n\n inFlight.set(namespace, promise);\n return promise;\n}\n\nexport async function ensureNamespacesLoadedImpl<\n Schema extends MultiDictionary,\n NS extends keyof Schema & string,\n>(options: EnsureNamespacesLoadedOptions<Schema, NS>, namespaces: NS[]): Promise<void> {\n if (namespaces.length === 0) {\n return;\n }\n\n await Promise.all(namespaces.map((namespace) => ensureOneNamespace(options, namespace)));\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xndrjs/i18n",
3
- "version": "0.3.0",
3
+ "version": "0.3.2",
4
4
  "description": "Compiler-first, type-safe ICU MessageFormat i18n with runtime dictionary override from external sources.",
5
5
  "private": false,
6
6
  "license": "MIT",
@@ -12,6 +12,8 @@ export interface InstanceFileOptions {
12
12
  localeFallbackConstName: string;
13
13
  factoryName: string;
14
14
  hasLocaleFallback: boolean;
15
+ hasLocaleType: boolean;
16
+ namespaceNames: string[];
15
17
  importExtension: ImportExtension;
16
18
  }
17
19
 
@@ -27,6 +29,8 @@ export function formatInstanceFile(options: InstanceFileOptions): string {
27
29
  localeFallbackConstName,
28
30
  factoryName,
29
31
  hasLocaleFallback,
32
+ hasLocaleType,
33
+ namespaceNames,
30
34
  importExtension,
31
35
  } = options;
32
36
 
@@ -45,21 +49,79 @@ export function formatInstanceFile(options: InstanceFileOptions): string {
45
49
  const providerOptions = hasLocaleFallback
46
50
  ? `, {\n localeFallback: ${localeFallbackConstName},\n }`
47
51
  : "";
48
- const fallbackImport = hasLocaleFallback
49
- ? `import { ${localeFallbackConstName}, type ${localeTypeName} } from '${typesImport}';\n`
52
+ const typesImportLine = hasLocaleType
53
+ ? hasLocaleFallback
54
+ ? `import { ${localeFallbackConstName}, type ${localeTypeName} } from '${typesImport}';\n`
55
+ : `import type { ${localeTypeName} } from '${typesImport}';\n`
50
56
  : "";
57
+ const localesParamType = hasLocaleType ? `readonly ${localeTypeName}[]` : "readonly string[]";
58
+ const projectLocalesFallbackArg = hasLocaleFallback ? `, ${localeFallbackConstName}` : "";
59
+ const coreImports = isSingle
60
+ ? "projectLocales as projectLocalesCore"
61
+ : "projectLocales as projectLocalesCore, projectNamespacesLocales as projectNamespacesLocalesCore";
62
+ const projectLocalesBlock = isSingle
63
+ ? `\n` +
64
+ `export function projectLocales(\n` +
65
+ ` dictionary: ${schemaTypeName},\n` +
66
+ ` locales: ${localesParamType},\n` +
67
+ `): ${schemaTypeName} {\n` +
68
+ ` return projectLocalesCore(dictionary, locales${projectLocalesFallbackArg});\n` +
69
+ `}\n`
70
+ : formatMultiProjectLocalesBlock(
71
+ schemaTypeName,
72
+ namespaceNames,
73
+ localesParamType,
74
+ projectLocalesFallbackArg
75
+ );
51
76
 
52
77
  return (
53
78
  `${GENERATED_FILE_BANNER}` +
54
- `import { ${providerClass} } from '@xndrjs/i18n';\n` +
79
+ `import { ${providerClass}, ${coreImports} } from '@xndrjs/i18n';\n` +
55
80
  `import { dictionary } from '${dictionaryImport}';\n` +
56
81
  initialDictionaryImport +
57
- fallbackImport +
82
+ typesImportLine +
58
83
  `\n` +
59
84
  `export function ${factoryName}(\n` +
60
85
  ` initialDictionary: ${initialDictionaryType} = dictionary,\n` +
61
86
  `) {\n` +
62
87
  ` return new ${providerClass}<${providerTypeArgs}>(initialDictionary${providerOptions});\n` +
88
+ `}\n` +
89
+ projectLocalesBlock
90
+ );
91
+ }
92
+
93
+ function formatMultiProjectLocalesBlock(
94
+ schemaTypeName: string,
95
+ namespaceNames: string[],
96
+ localesParamType: string,
97
+ projectLocalesFallbackArg: string
98
+ ): string {
99
+ const overloads = namespaceNames
100
+ .map(
101
+ (namespace) =>
102
+ `export function projectNamespaceLocales(\n` +
103
+ ` dictionary: ${schemaTypeName}["${namespace}"],\n` +
104
+ ` locales: ${localesParamType},\n` +
105
+ `): ${schemaTypeName}["${namespace}"];`
106
+ )
107
+ .join("\n");
108
+
109
+ return (
110
+ `\n` +
111
+ `export function projectLocales(\n` +
112
+ ` dictionary: ${schemaTypeName},\n` +
113
+ ` locales: ${localesParamType},\n` +
114
+ `): ${schemaTypeName} {\n` +
115
+ ` return projectNamespacesLocalesCore(dictionary, locales${projectLocalesFallbackArg});\n` +
116
+ `}\n` +
117
+ `\n` +
118
+ overloads +
119
+ `\n` +
120
+ `export function projectNamespaceLocales(\n` +
121
+ ` dictionary: ${schemaTypeName}[keyof ${schemaTypeName}],\n` +
122
+ ` locales: ${localesParamType},\n` +
123
+ `): ${schemaTypeName}[keyof ${schemaTypeName}] {\n` +
124
+ ` return projectLocalesCore(dictionary, locales${projectLocalesFallbackArg});\n` +
63
125
  `}\n`
64
126
  );
65
127
  }
@@ -0,0 +1,6 @@
1
+ {
2
+ "invoice_summary": {
3
+ "en": "You have {count, plural, one {1 invoice} other {{count} invoices}}",
4
+ "it": "Hai {count, plural, one {1 fattura} other {{count} fatture}}"
5
+ }
6
+ }
@@ -73,6 +73,10 @@ describe("generate-i18n-types", () => {
73
73
  expect(types).toContain("export const I18N_MODE = 'multi' as const");
74
74
  expect(types).toContain("export type AppLocale = 'en'");
75
75
  expect(factory).toContain("export function createI18n(");
76
+ expect(factory).toContain("export function projectLocales(");
77
+ expect(factory).toContain("projectNamespacesLocalesCore(dictionary, locales)");
78
+ expect(factory).toContain("export function projectNamespaceLocales(");
79
+ expect(factory).toContain("projectLocalesCore(dictionary, locales)");
76
80
  expect(factory).toContain("IcuTranslationProviderMulti");
77
81
  expect(types).toContain("login_button: never");
78
82
  expect(types).toContain("welcome: { name: string }");
@@ -221,6 +225,10 @@ describe("generate-i18n-types", () => {
221
225
  expect(types).toContain("export const I18N_MODE = 'single' as const");
222
226
  expect(types).toContain("export type AppLocale = 'en'");
223
227
  expect(factory).toContain("export function createI18n(");
228
+ expect(factory).toContain("export function projectLocales(");
229
+ expect(factory).toContain("dictionary: AppSchema");
230
+ expect(factory).not.toContain("projectNamespaceLocales");
231
+ expect(factory).not.toContain("projectNamespacesLocalesCore");
224
232
  expect(factory).toContain("IcuTranslationProviderSingle");
225
233
  expect(types).toContain("login_button: never;");
226
234
  expect(types).toContain("welcome: { name: string };");
@@ -289,6 +297,7 @@ describe("generate-i18n-types", () => {
289
297
  expect(types).toContain('"de-CH": "en"');
290
298
  expect(types).toContain("export type AppLocale = 'de-CH' | 'en'");
291
299
  expect(factory).toContain("localeFallback: LOCALE_FALLBACK");
300
+ expect(factory).toContain("projectLocalesCore(dictionary, locales, LOCALE_FALLBACK)");
292
301
  expect(factory).toContain(
293
302
  "IcuTranslationProviderSingle<AppSchema, AppParams, AppLocale, typeof LOCALE_FALLBACK>"
294
303
  );
@@ -358,6 +367,8 @@ describe("generate-i18n-types", () => {
358
367
  const factory = readFileSync(join(tempDir, "src/i18n/instance.generated.ts"), "utf8");
359
368
  expect(types).not.toContain("export const LOCALE_FALLBACK");
360
369
  expect(factory).not.toContain("localeFallback:");
370
+ expect(factory).toContain("projectLocalesCore(dictionary, locales)");
371
+ expect(factory).not.toContain("LOCALE_FALLBACK");
361
372
  expect(types).toContain("export type AppLocale = 'en' | 'it'");
362
373
  });
363
374
 
@@ -131,6 +131,8 @@ function main() {
131
131
  localeFallbackConstName,
132
132
  factoryName,
133
133
  hasLocaleFallback: Boolean(config.localeFallback),
134
+ hasLocaleType: Boolean(requestLocaleUnion),
135
+ namespaceNames: entries.map((entry) => entry.namespace),
134
136
  importExtension,
135
137
  });
136
138
 
@@ -0,0 +1,181 @@
1
+ import { mkdtempSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
2
+ import { tmpdir } from "node:os";
3
+ import { join } from "node:path";
4
+ import { spawnSync } from "node:child_process";
5
+ import { fileURLToPath } from "node:url";
6
+ import { afterEach, describe, expect, expectTypeOf, it } from "vitest";
7
+ import billingDictionary from "./fixtures/billing-namespace.json";
8
+
9
+ const codegenScript = fileURLToPath(new URL("./generate-i18n-types.ts", import.meta.url));
10
+ const packageRoot = fileURLToPath(new URL("../../", import.meta.url));
11
+
12
+ type BillingNamespace = typeof billingDictionary;
13
+
14
+ function runCodegen(cwd: string) {
15
+ return spawnSync("tsx", [codegenScript, "--config", "i18n.codegen.json"], {
16
+ cwd,
17
+ encoding: "utf8",
18
+ env: process.env,
19
+ });
20
+ }
21
+
22
+ function runTsc(projectPath: string) {
23
+ return spawnSync("pnpm", ["exec", "tsc", "--noEmit", "-p", projectPath], {
24
+ cwd: packageRoot,
25
+ encoding: "utf8",
26
+ env: process.env,
27
+ });
28
+ }
29
+
30
+ function setupMultiCodegenFixture(tempDir: string) {
31
+ const i18nDir = join(tempDir, "src/i18n");
32
+ const translationsDir = join(i18nDir, "translations");
33
+ mkdirSync(translationsDir, { recursive: true });
34
+
35
+ writeFileSync(
36
+ join(translationsDir, "default.json"),
37
+ JSON.stringify({
38
+ login_button: { en: "Login" },
39
+ })
40
+ );
41
+ writeFileSync(
42
+ join(translationsDir, "billing.json"),
43
+ readFileSync(
44
+ fileURLToPath(new URL("./fixtures/billing-namespace.json", import.meta.url)),
45
+ "utf8"
46
+ )
47
+ );
48
+
49
+ writeFileSync(
50
+ join(tempDir, "i18n.codegen.json"),
51
+ JSON.stringify({
52
+ namespaces: {
53
+ default: "src/i18n/translations/default.json",
54
+ billing: "src/i18n/translations/billing.json",
55
+ },
56
+ typesOutput: "src/i18n/i18n-types.generated.ts",
57
+ dictionaryOutput: "src/i18n/dictionary.generated.ts",
58
+ instanceOutput: "src/i18n/instance.generated.ts",
59
+ paramsTypeName: "AppParams",
60
+ schemaTypeName: "AppSchema",
61
+ localeFallback: {
62
+ en: null,
63
+ it: "en",
64
+ },
65
+ })
66
+ );
67
+
68
+ const codegen = runCodegen(tempDir);
69
+ expect(codegen.status, codegen.stderr || codegen.stdout).toBe(0);
70
+ }
71
+
72
+ function writeTscProject(tempDir: string) {
73
+ writeFileSync(
74
+ join(tempDir, "tsconfig.json"),
75
+ JSON.stringify(
76
+ {
77
+ compilerOptions: {
78
+ target: "ES2022",
79
+ module: "ESNext",
80
+ moduleResolution: "bundler",
81
+ strict: true,
82
+ noEmit: true,
83
+ resolveJsonModule: true,
84
+ esModuleInterop: true,
85
+ skipLibCheck: true,
86
+ paths: {
87
+ "@xndrjs/i18n": [join(packageRoot, "src/index.ts")],
88
+ },
89
+ },
90
+ include: ["src/**/*.ts"],
91
+ },
92
+ null,
93
+ 2
94
+ )
95
+ );
96
+ }
97
+
98
+ describe("codegen projectLocales + setNamespace", () => {
99
+ describe("generated instance output", () => {
100
+ let tempDir: string;
101
+
102
+ afterEach(() => {
103
+ if (tempDir) {
104
+ rmSync(tempDir, { recursive: true, force: true });
105
+ }
106
+ });
107
+
108
+ it("emits projectNamespaceLocales for multi mode with schema-typed namespace payloads", () => {
109
+ tempDir = mkdtempSync(join(tmpdir(), "xndrjs-i18n-project-locales-"));
110
+ setupMultiCodegenFixture(tempDir);
111
+
112
+ const factory = readFileSync(join(tempDir, "src/i18n/instance.generated.ts"), "utf8");
113
+ expect(factory).toContain('dictionary: AppSchema["billing"]');
114
+ expect(factory).toContain(
115
+ "projectNamespacesLocalesCore(dictionary, locales, LOCALE_FALLBACK)"
116
+ );
117
+ });
118
+
119
+ it("passes tsc when hydrating with projectNamespaceLocales before setNamespace", () => {
120
+ tempDir = mkdtempSync(join(tmpdir(), "xndrjs-i18n-project-locales-"));
121
+ setupMultiCodegenFixture(tempDir);
122
+
123
+ writeFileSync(
124
+ join(tempDir, "src/hydrate-billing.ts"),
125
+ [
126
+ `import { createI18n, projectNamespaceLocales } from "./i18n/instance.generated.js";`,
127
+ `import billingDictionary from "./i18n/translations/billing.json";`,
128
+ ``,
129
+ `const i18n = createI18n();`,
130
+ `i18n.setNamespace("billing", projectNamespaceLocales(billingDictionary, ["en"]));`,
131
+ ].join("\n")
132
+ );
133
+
134
+ writeTscProject(tempDir);
135
+
136
+ const tsc = runTsc(join(tempDir, "tsconfig.json"));
137
+ expect(tsc.status, `${tsc.stdout}\n${tsc.stderr}`).toBe(0);
138
+ });
139
+
140
+ it("passes tsc when replacing the full schema with projectLocales before setAll", () => {
141
+ tempDir = mkdtempSync(join(tmpdir(), "xndrjs-i18n-project-locales-"));
142
+ setupMultiCodegenFixture(tempDir);
143
+
144
+ writeFileSync(
145
+ join(tempDir, "src/hydrate-all.ts"),
146
+ [
147
+ `import { createI18n, projectLocales } from "./i18n/instance.generated.js";`,
148
+ `import type { AppSchema } from "./i18n/i18n-types.generated.js";`,
149
+ `import billingDictionary from "./i18n/translations/billing.json";`,
150
+ `import defaultDictionary from "./i18n/translations/default.json";`,
151
+ ``,
152
+ `const fullDictionary = {`,
153
+ ` default: defaultDictionary,`,
154
+ ` billing: billingDictionary,`,
155
+ `} satisfies AppSchema;`,
156
+ ``,
157
+ `const i18n = createI18n();`,
158
+ `i18n.setAll(projectLocales(fullDictionary, ["en"]));`,
159
+ ].join("\n")
160
+ );
161
+
162
+ writeTscProject(tempDir);
163
+
164
+ const tsc = runTsc(join(tempDir, "tsconfig.json"));
165
+ expect(tsc.status, `${tsc.stdout}\n${tsc.stderr}`).toBe(0);
166
+ });
167
+
168
+ it("types projectNamespaceLocales result as the namespace schema", () => {
169
+ type AppSchema = {
170
+ billing: BillingNamespace;
171
+ };
172
+
173
+ type ProjectNamespaceLocalesBilling = (
174
+ dictionary: AppSchema["billing"],
175
+ locales: readonly ("en" | "it")[]
176
+ ) => AppSchema["billing"];
177
+
178
+ expectTypeOf<ReturnType<ProjectNamespaceLocalesBilling>>().toEqualTypeOf<BillingNamespace>();
179
+ });
180
+ });
181
+ });
package/src/index.ts CHANGED
@@ -28,5 +28,7 @@ export {
28
28
  } from "./resolve-locale.js";
29
29
  export type { ResolvedLocaleTemplate } from "./resolve-locale.js";
30
30
 
31
+ export { projectLocales, projectNamespacesLocales } from "./project-locales.js";
32
+
31
33
  export { ensureNamespacesLoadedImpl } from "./ensure-namespace.js";
32
34
  export type { EnsureNamespacesLoadedOptions } from "./ensure-namespace.js";
@@ -0,0 +1,85 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { projectLocales, projectNamespacesLocales } from "./project-locales.js";
3
+
4
+ describe("projectLocales", () => {
5
+ const dictionary = {
6
+ login_button: { en: "Login", it: "Accedi" },
7
+ welcome: { en: "Welcome {name}!", it: "Benvenuto {name}!" },
8
+ empty_label: { en: "" },
9
+ };
10
+
11
+ const fallback = {
12
+ en: null,
13
+ "de-DE": "en",
14
+ "de-CH": "de-DE",
15
+ it: "en",
16
+ } as const;
17
+
18
+ it("keeps only the requested locales with direct templates", () => {
19
+ expect(projectLocales(dictionary, ["en", "it"])).toEqual({
20
+ login_button: { en: "Login", it: "Accedi" },
21
+ welcome: { en: "Welcome {name}!", it: "Benvenuto {name}!" },
22
+ empty_label: { en: "" },
23
+ });
24
+ });
25
+
26
+ it("drops locales not listed in the projection", () => {
27
+ expect(projectLocales(dictionary, ["en"])).toEqual({
28
+ login_button: { en: "Login" },
29
+ welcome: { en: "Welcome {name}!" },
30
+ empty_label: { en: "" },
31
+ });
32
+ });
33
+
34
+ it("resolves fallback templates for missing direct locale entries", () => {
35
+ expect(projectLocales(dictionary, ["de-CH"], fallback)).toEqual({
36
+ login_button: { "de-CH": "Login" },
37
+ welcome: { "de-CH": "Welcome {name}!" },
38
+ empty_label: { "de-CH": "" },
39
+ });
40
+ });
41
+
42
+ it("projects multiple locales with mixed direct and fallback resolution", () => {
43
+ expect(projectLocales(dictionary, ["it", "de-CH"], fallback)).toEqual({
44
+ login_button: { it: "Accedi", "de-CH": "Login" },
45
+ welcome: { it: "Benvenuto {name}!", "de-CH": "Welcome {name}!" },
46
+ empty_label: { it: "", "de-CH": "" },
47
+ });
48
+ });
49
+
50
+ it("omits a target locale when fallback cannot resolve a template", () => {
51
+ const partial = {
52
+ login_button: { it: "Accedi" },
53
+ };
54
+
55
+ expect(projectLocales(partial, ["en", "it"], fallback)).toEqual({
56
+ login_button: { it: "Accedi" },
57
+ });
58
+ });
59
+
60
+ it("deduplicates locales in the projection list", () => {
61
+ expect(projectLocales(dictionary, ["en", "en"])).toEqual({
62
+ login_button: { en: "Login" },
63
+ welcome: { en: "Welcome {name}!" },
64
+ empty_label: { en: "" },
65
+ });
66
+ });
67
+ });
68
+
69
+ describe("projectNamespacesLocales", () => {
70
+ it("projects each namespace independently", () => {
71
+ const dictionary = {
72
+ default: {
73
+ login_button: { en: "Login", it: "Accedi" },
74
+ },
75
+ billing: {
76
+ invoice_summary: { en: "{count} invoices", it: "{count} fatture" },
77
+ },
78
+ };
79
+
80
+ expect(projectNamespacesLocales(dictionary, ["en"])).toEqual({
81
+ default: { login_button: { en: "Login" } },
82
+ billing: { invoice_summary: { en: "{count} invoices" } },
83
+ });
84
+ });
85
+ });
@@ -0,0 +1,67 @@
1
+ import { resolveLocaleTemplate } from "./resolve-locale.js";
2
+ import type { KeyDictionary, LocaleFallbackMap, MultiDictionary } from "./types.js";
3
+
4
+ function projectKeyDictionary<T extends KeyDictionary>(
5
+ dictionary: T,
6
+ locales: readonly string[],
7
+ localeFallback?: LocaleFallbackMap
8
+ ): T {
9
+ const targetLocales = [...new Set(locales)];
10
+ const result = {} as T;
11
+
12
+ for (const [key, localesByKey] of Object.entries(dictionary)) {
13
+ const projected: Record<string, string> = {};
14
+
15
+ for (const locale of targetLocales) {
16
+ const resolved = resolveLocaleTemplate(localesByKey, locale, localeFallback);
17
+ if (resolved !== undefined) {
18
+ projected[locale] = resolved.template;
19
+ }
20
+ }
21
+
22
+ if (Object.keys(projected).length > 0) {
23
+ result[key as keyof T] = projected as T[keyof T];
24
+ }
25
+ }
26
+
27
+ return result;
28
+ }
29
+
30
+ /**
31
+ * Builds a dictionary that contains only the requested locales.
32
+ * For each key and target locale, copies the direct template when present;
33
+ * otherwise resolves through `localeFallback` (same rules as `.get()` at runtime).
34
+ */
35
+ export function projectLocales<T extends KeyDictionary>(
36
+ dictionary: T,
37
+ locales: readonly string[],
38
+ localeFallback?: LocaleFallbackMap
39
+ ): T {
40
+ return projectKeyDictionary(dictionary, locales, localeFallback);
41
+ }
42
+
43
+ /**
44
+ * Projects every namespace in a multi-namespace dictionary.
45
+ */
46
+ export function projectNamespacesLocales<T extends MultiDictionary>(
47
+ dictionary: T,
48
+ locales: readonly string[],
49
+ localeFallback?: LocaleFallbackMap
50
+ ): T {
51
+ const result = {} as T;
52
+
53
+ for (const namespace of Object.keys(dictionary) as (keyof T & string)[]) {
54
+ const namespaceDictionary = dictionary[namespace];
55
+ if (namespaceDictionary === undefined) {
56
+ continue;
57
+ }
58
+
59
+ result[namespace] = projectKeyDictionary(
60
+ namespaceDictionary,
61
+ locales,
62
+ localeFallback
63
+ ) as T[typeof namespace];
64
+ }
65
+
66
+ return result;
67
+ }