@xndrjs/i18n 0.8.1 → 0.8.2-alpha.1

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 (93) hide show
  1. package/README.md +1 -2
  2. package/dist/cli/audit.js +739 -0
  3. package/dist/cli/audit.js.map +1 -0
  4. package/dist/cli/codegen.js +1471 -0
  5. package/dist/cli/codegen.js.map +1 -0
  6. package/dist/cli/setup.js +148 -0
  7. package/dist/cli/setup.js.map +1 -0
  8. package/dist/codegen/index.js +43 -40
  9. package/dist/codegen/index.js.map +1 -1
  10. package/package.json +6 -9
  11. package/bin/audit.mjs +0 -14
  12. package/bin/codegen.mjs +0 -17
  13. package/bin/setup.mjs +0 -14
  14. package/src/IcuTranslationProviderMulti.test.ts +0 -215
  15. package/src/IcuTranslationProviderMulti.ts +0 -188
  16. package/src/audit/audit-dictionaries.test.ts +0 -134
  17. package/src/audit/audit-dictionaries.ts +0 -178
  18. package/src/audit/run-audit.test.ts +0 -169
  19. package/src/audit/run-audit.ts +0 -94
  20. package/src/builder-load-registry.test.ts +0 -44
  21. package/src/builder-load-registry.ts +0 -78
  22. package/src/builder-loaders.ts +0 -15
  23. package/src/builder-types.test.ts +0 -57
  24. package/src/builder-types.ts +0 -24
  25. package/src/builder.test.ts +0 -303
  26. package/src/builder.ts +0 -15
  27. package/src/codegen/codegen-config-schema.ts +0 -156
  28. package/src/codegen/config.test.ts +0 -231
  29. package/src/codegen/config.ts +0 -42
  30. package/src/codegen/constants.ts +0 -9
  31. package/src/codegen/delivery-artifacts.test.ts +0 -167
  32. package/src/codegen/delivery-artifacts.ts +0 -154
  33. package/src/codegen/dictionary-spec-contract.test.ts +0 -28
  34. package/src/codegen/dictionary-spec-contract.ts +0 -138
  35. package/src/codegen/emit/dictionary-schema-file.ts +0 -113
  36. package/src/codegen/emit/instance-file.test.ts +0 -71
  37. package/src/codegen/emit/instance-file.ts +0 -257
  38. package/src/codegen/emit/namespace-loaders-file.test.ts +0 -251
  39. package/src/codegen/emit/namespace-loaders-file.ts +0 -229
  40. package/src/codegen/emit/types-file.test.ts +0 -96
  41. package/src/codegen/emit/types-file.ts +0 -142
  42. package/src/codegen/fixtures/billing-namespace.json +0 -6
  43. package/src/codegen/generate-i18n-types.test.ts +0 -1137
  44. package/src/codegen/generate-i18n-types.ts +0 -20
  45. package/src/codegen/icu-analysis.ts +0 -110
  46. package/src/codegen/locale-fallback.ts +0 -73
  47. package/src/codegen/locale-policy.test.ts +0 -35
  48. package/src/codegen/locale-policy.ts +0 -30
  49. package/src/codegen/paths.ts +0 -44
  50. package/src/codegen/project-locales-set-namespace.test.ts +0 -134
  51. package/src/codegen/read-dictionary.test.ts +0 -594
  52. package/src/codegen/read-dictionary.ts +0 -260
  53. package/src/codegen/regenerate-namespaces.ts +0 -180
  54. package/src/codegen/run-codegen.test.ts +0 -230
  55. package/src/codegen/run-codegen.ts +0 -252
  56. package/src/codegen/types.ts +0 -10
  57. package/src/codegen/write-file-if-changed.test.ts +0 -42
  58. package/src/codegen/write-file-if-changed.ts +0 -20
  59. package/src/codegen-config/build-config.ts +0 -15
  60. package/src/codegen-config/codegen-config.test.ts +0 -36
  61. package/src/codegen-config/index.ts +0 -37
  62. package/src/codegen-config/type-names.ts +0 -20
  63. package/src/codegen-config/write-config.ts +0 -8
  64. package/src/engine.ts +0 -47
  65. package/src/fetch-artifact.ts +0 -16
  66. package/src/format-core.ts +0 -91
  67. package/src/i18n-handle.ts +0 -216
  68. package/src/icu/extract-variables.test.ts +0 -199
  69. package/src/icu/extract-variables.ts +0 -227
  70. package/src/icu/parse-template.ts +0 -31
  71. package/src/index.ts +0 -63
  72. package/src/load-state.ts +0 -15
  73. package/src/project-locales.test.ts +0 -280
  74. package/src/project-locales.ts +0 -211
  75. package/src/resolve-locale.test.ts +0 -91
  76. package/src/resolve-locale.ts +0 -88
  77. package/src/scope-multi.ts +0 -105
  78. package/src/scope-types.ts +0 -35
  79. package/src/scope.test.ts +0 -172
  80. package/src/serialized-state.test.ts +0 -114
  81. package/src/serialized-state.ts +0 -28
  82. package/src/setup/setup-i18n.test.ts +0 -85
  83. package/src/setup/setup-i18n.ts +0 -156
  84. package/src/types.ts +0 -65
  85. package/src/validation/create-args-schema.ts +0 -76
  86. package/src/validation/create-normalized-schema.ts +0 -52
  87. package/src/validation/errors.ts +0 -43
  88. package/src/validation/index.ts +0 -202
  89. package/src/validation/normalize.ts +0 -299
  90. package/src/validation/to-dictionary.ts +0 -67
  91. package/src/validation/types.ts +0 -82
  92. package/src/validation/validate-normalized.ts +0 -133
  93. package/src/validation/validation.test.ts +0 -562
@@ -1,156 +0,0 @@
1
- import fs from "node:fs";
2
- import path from "node:path";
3
- import { fileURLToPath } from "node:url";
4
- import { buildCodegenConfig } from "../codegen-config/build-config.js";
5
- import { inferProjectName } from "../codegen-config/type-names.js";
6
- import { writeCodegenConfig } from "../codegen-config/write-config.js";
7
- import { DEFAULT_IMPORT_EXTENSION, importExtensionSuffix } from "../codegen/paths.js";
8
-
9
- export { buildCodegenConfig } from "../codegen-config/build-config.js";
10
-
11
- export interface SetupOptions {
12
- targetDir: string;
13
- project?: string | undefined;
14
- force?: boolean | undefined;
15
- }
16
-
17
- export interface SetupResult {
18
- targetDir: string;
19
- project: string;
20
- created: string[];
21
- }
22
-
23
- const I18N_ROOT = "i18n";
24
- const CONFIG_FILE = `${I18N_ROOT}/i18n.codegen.json`;
25
- /** Paths in i18n.codegen.json are relative to the i18n/ folder */
26
- const GENERATED_DIR = "generated";
27
- const TRANSLATIONS_DIR = "translations";
28
-
29
- const DEFAULT_STARTER = {
30
- welcome: { en: "Welcome {name}!" },
31
- };
32
-
33
- const DEFAULT_IMPORT_SUFFIX = importExtensionSuffix(DEFAULT_IMPORT_EXTENSION);
34
-
35
- const INDEX_TS =
36
- `import { createI18n } from "./generated/instance.generated${DEFAULT_IMPORT_SUFFIX}";\n\n` +
37
- `export * from "./generated/instance.generated${DEFAULT_IMPORT_SUFFIX}";\n` +
38
- `export * from "./generated/i18n-types.generated${DEFAULT_IMPORT_SUFFIX}";\n` +
39
- `export * from "./generated/namespace-loaders.generated${DEFAULT_IMPORT_SUFFIX}";\n\n` +
40
- `export const i18n = createI18n();\n`;
41
-
42
- function writeJson(filePath: string, value: unknown): void {
43
- fs.mkdirSync(path.dirname(filePath), { recursive: true });
44
- fs.writeFileSync(filePath, `${JSON.stringify(value, null, 2)}\n`);
45
- }
46
-
47
- function writeText(filePath: string, content: string): void {
48
- fs.mkdirSync(path.dirname(filePath), { recursive: true });
49
- fs.writeFileSync(filePath, content);
50
- }
51
-
52
- function relative(fromRoot: string, filePath: string): string {
53
- return path.relative(fromRoot, filePath).replace(/\\/g, "/");
54
- }
55
-
56
- export function runSetup(options: SetupOptions): SetupResult {
57
- const targetDir = path.resolve(options.targetDir);
58
- const project =
59
- options.project ?? inferProjectName(path.basename(targetDir.replace(/[/\\]$/, "") || "app"));
60
-
61
- if (!/^[A-Z][a-zA-Z0-9]*$/.test(project)) {
62
- throw new Error(
63
- `[Setup Error] Invalid project name "${project}". Use PascalCase (e.g. MyApp) via --project.`
64
- );
65
- }
66
-
67
- const configPath = path.join(targetDir, CONFIG_FILE);
68
- if (fs.existsSync(configPath) && !options.force) {
69
- throw new Error(
70
- `[Setup Error] ${relative(process.cwd(), configPath)} already exists. Use --force to overwrite.`
71
- );
72
- }
73
-
74
- const created: string[] = [];
75
-
76
- writeCodegenConfig(configPath, buildCodegenConfig(project));
77
- created.push(relative(targetDir, configPath));
78
-
79
- const defaultPath = path.join(targetDir, I18N_ROOT, TRANSLATIONS_DIR, "default.json");
80
- writeJson(defaultPath, DEFAULT_STARTER);
81
- created.push(relative(targetDir, defaultPath));
82
-
83
- const indexPath = path.join(targetDir, I18N_ROOT, "index.ts");
84
- writeText(indexPath, INDEX_TS);
85
- created.push(relative(targetDir, indexPath));
86
-
87
- fs.mkdirSync(path.join(targetDir, I18N_ROOT, GENERATED_DIR), { recursive: true });
88
-
89
- return { targetDir, project, created };
90
- }
91
-
92
- export function parseSetupArgs(argv: string[]): SetupOptions {
93
- const first = argv[0];
94
-
95
- let targetDir = ".";
96
- let project: string | undefined;
97
- let force = false;
98
- let startIndex = 0;
99
-
100
- if (first === "multi") {
101
- startIndex = 1;
102
- } else if (first !== undefined && !first.startsWith("-")) {
103
- // positional targetDir without mode
104
- } else if (first === undefined || first.startsWith("-")) {
105
- // flags only / empty
106
- }
107
-
108
- for (let index = startIndex; index < argv.length; index++) {
109
- const arg = argv[index]!;
110
- if (arg === "--project") {
111
- project = argv[++index];
112
- if (!project) {
113
- throw new Error("[Setup Error] --project requires a value.");
114
- }
115
- continue;
116
- }
117
- if (arg === "--force") {
118
- force = true;
119
- continue;
120
- }
121
- if (arg.startsWith("-")) {
122
- throw new Error(`[Setup Error] Unknown flag: ${arg}`);
123
- }
124
- targetDir = arg;
125
- }
126
-
127
- return { targetDir, project, force };
128
- }
129
-
130
- function main() {
131
- try {
132
- const options = parseSetupArgs(process.argv.slice(2));
133
- const result = runSetup(options);
134
-
135
- console.log(`✅ Setup i18n in ${result.targetDir}`);
136
- console.log(
137
- ` Project types: ${result.project}Params, ${result.project}Schema, ${result.project}Locale`
138
- );
139
- console.log(` Created: ${result.created.join(", ")}`);
140
- const targetLabel = path.relative(process.cwd(), result.targetDir) || ".";
141
-
142
- console.log("");
143
- console.log(`Add to package.json scripts (from ${targetLabel}):`);
144
- console.log(` "i18n:codegen": "xndrjs-i18n-codegen --config ${CONFIG_FILE}"`);
145
- console.log("");
146
- console.log("Then run your package manager's run script (pnpm/npm/yarn).");
147
- } catch (error) {
148
- const message = error instanceof Error ? error.message : String(error);
149
- console.error(message);
150
- process.exit(1);
151
- }
152
- }
153
-
154
- if (process.argv[1] && fileURLToPath(import.meta.url) === path.resolve(process.argv[1])) {
155
- main();
156
- }
package/src/types.ts DELETED
@@ -1,65 +0,0 @@
1
- import type { IntlMessageFormat } from "intl-messageformat";
2
-
3
- export type LocaleDictionary = Record<string, string>;
4
- export type KeyDictionary = Record<string, LocaleDictionary>;
5
- export type MultiDictionary = Record<string, KeyDictionary>;
6
-
7
- /** Locales present on any key of a flat (per-namespace) key dictionary. */
8
- export type LocaleOfKeys<Schema extends KeyDictionary> = {
9
- [K in keyof Schema]: keyof Schema[K];
10
- }[keyof Schema] &
11
- string;
12
-
13
- export type LocaleOfMulti<Schema extends MultiDictionary> = {
14
- [NS in keyof Schema]: {
15
- [K in keyof Schema[NS]]: keyof Schema[NS][K];
16
- }[keyof Schema[NS]];
17
- }[keyof Schema] &
18
- string;
19
-
20
- /** Subset of schema keys with any subset of project locales per key — valid input for merge operations. */
21
- export type PartialKeyDictionary<
22
- T extends KeyDictionary,
23
- Locales extends string = LocaleOfKeys<T>,
24
- > = {
25
- [K in keyof T]?: Partial<Record<Locales, string>>;
26
- };
27
-
28
- /** Subset of schema namespaces with partial keys/locales — valid input for multi merge operations. */
29
- export type PartialMultiDictionary<
30
- T extends MultiDictionary,
31
- Locales extends string = LocaleOfMulti<T>,
32
- > = {
33
- [NS in keyof T]?: PartialKeyDictionary<T[NS], Locales>;
34
- };
35
-
36
- export type LocaleFallbackMap = Record<string, string | null>;
37
-
38
- export type MissingTranslationContext = {
39
- /** Only set by the multi-namespace provider. */
40
- namespace?: string;
41
- key: string;
42
- locale: string;
43
- fallbackChain: string;
44
- };
45
-
46
- export type OnMissingTranslation =
47
- | "throw"
48
- | "key"
49
- | ((context: MissingTranslationContext) => string);
50
-
51
- export type IcuTranslationProviderOptions<
52
- Fallback extends LocaleFallbackMap | undefined = undefined,
53
- > = {
54
- localeFallback?: Fallback;
55
- /**
56
- * Strategy when no template resolves for a key/locale (after walking the
57
- * fallback chain). "throw" (default) raises an error, "key" returns the
58
- * translation key itself, a function receives the context and returns the
59
- * string to display. ICU syntax and formatting errors always throw.
60
- */
61
- onMissing?: OnMissingTranslation;
62
- };
63
-
64
- export type LocaleCache = Record<string, IntlMessageFormat>;
65
- export type MultiCompiledCache = Record<string, Record<string, LocaleCache>>;
@@ -1,76 +0,0 @@
1
- import { z } from "zod";
2
- import type { VariableSpec, VariableType } from "../icu/extract-variables.js";
3
-
4
- export const variableTypeSchema = z.enum(["string", "number", "date"]);
5
-
6
- function formatVariableMismatch(expected: VariableSpec, found: VariableSpec): string {
7
- const expectedKeys = Object.keys(expected).sort();
8
- const foundKeys = Object.keys(found).sort();
9
- return `Expected variables [${expectedKeys.join(", ")}], found [${foundKeys.join(", ")}]`;
10
- }
11
-
12
- export function createArgsSchema(expected: VariableSpec): z.ZodType<VariableSpec> {
13
- return z.record(z.string(), variableTypeSchema).superRefine((found, ctx) => {
14
- const expectedKeys = Object.keys(expected).sort();
15
- const foundKeys = Object.keys(found).sort();
16
-
17
- if (expectedKeys.join() !== foundKeys.join()) {
18
- ctx.addIssue({
19
- code: "custom",
20
- message: formatVariableMismatch(expected, found),
21
- });
22
- return;
23
- }
24
-
25
- for (const [name, type] of Object.entries(expected)) {
26
- if (found[name] !== type) {
27
- ctx.addIssue({
28
- code: "custom",
29
- message: `Variable "${name}": expected ${type}, found ${found[name]}`,
30
- });
31
- }
32
- }
33
- });
34
- }
35
-
36
- export function mapArgsSchemaError(
37
- expected: VariableSpec,
38
- found: VariableSpec,
39
- message: string,
40
- path: readonly string[]
41
- ) {
42
- const expectedKeys = Object.keys(expected).sort();
43
- const foundKeys = Object.keys(found).sort();
44
-
45
- if (expectedKeys.join() !== foundKeys.join()) {
46
- return {
47
- kind: "variable_mismatch" as const,
48
- path,
49
- expected,
50
- found,
51
- message,
52
- };
53
- }
54
-
55
- for (const [name, type] of Object.entries(expected)) {
56
- const foundType = found[name];
57
- if (foundType !== undefined && foundType !== type) {
58
- return {
59
- kind: "variable_type_mismatch" as const,
60
- path,
61
- variable: name,
62
- expected: type,
63
- found: foundType as VariableType,
64
- message,
65
- };
66
- }
67
- }
68
-
69
- return {
70
- kind: "variable_mismatch" as const,
71
- path,
72
- expected,
73
- found,
74
- message,
75
- };
76
- }
@@ -1,52 +0,0 @@
1
- import { z } from "zod";
2
- import type { VariableSpec } from "../icu/extract-variables.js";
3
- import { createArgsSchema, variableTypeSchema } from "./create-args-schema.js";
4
- import type { DictionarySpec, NormalizedDictionary, ParsedKeyEntry } from "./types.js";
5
-
6
- const parsedLocaleEntrySchema = z.object({
7
- template: z.string(),
8
- args: z.record(z.string(), variableTypeSchema),
9
- });
10
-
11
- const parsedKeyEntrySchema = z.object({
12
- locales: z.record(z.string(), parsedLocaleEntrySchema),
13
- mergedArgs: z.record(z.string(), variableTypeSchema),
14
- });
15
-
16
- export function createKeyDictionarySchema(
17
- argsByKey: Readonly<Record<string, VariableSpec>>
18
- ): z.ZodType<Record<string, ParsedKeyEntry>> {
19
- const keySchemas = Object.fromEntries(
20
- Object.entries(argsByKey).map(([key, expectedArgs]) => [
21
- key,
22
- parsedKeyEntrySchema.extend({
23
- mergedArgs: createArgsSchema(expectedArgs),
24
- }),
25
- ])
26
- );
27
-
28
- return z.object(keySchemas);
29
- }
30
-
31
- export function createNormalizedDictionarySchema(
32
- spec: DictionarySpec
33
- ): z.ZodType<NormalizedDictionary> {
34
- if (spec.mode === "single") {
35
- return z.object({
36
- mode: z.literal("single"),
37
- keys: createKeyDictionarySchema(spec.argsByKey),
38
- }) as z.ZodType<NormalizedDictionary>;
39
- }
40
-
41
- const namespaceSchemas = Object.fromEntries(
42
- Object.entries(spec.argsByKey).map(([namespace, argsByKey]) => [
43
- namespace,
44
- createKeyDictionarySchema(argsByKey),
45
- ])
46
- );
47
-
48
- return z.object({
49
- mode: z.literal("multi"),
50
- namespaces: z.object(namespaceSchemas),
51
- }) as z.ZodType<NormalizedDictionary>;
52
- }
@@ -1,43 +0,0 @@
1
- import type { ValidationIssue, ValidationResult } from "./types.js";
2
-
3
- function issueMessage(issue: ValidationIssue): string {
4
- switch (issue.kind) {
5
- case "missing_key":
6
- return `Missing required key at ${issue.path.join(".")}`;
7
- case "unknown_key":
8
- return `Unknown key at ${issue.path.join(".")}`;
9
- case "invalid_input":
10
- case "invalid_locale_value":
11
- case "icu_syntax_error":
12
- case "locale_args_mismatch":
13
- case "variable_mismatch":
14
- case "variable_type_mismatch":
15
- return issue.message;
16
- }
17
- }
18
-
19
- export function formatIssues(issues: readonly ValidationIssue[]): string {
20
- return issues
21
- .map((issue) => {
22
- if ("path" in issue && issue.path.length > 0) {
23
- return `[${issue.path.join(".")}] ${issue.kind}: ${issueMessage(issue)}`;
24
- }
25
- return `${issue.kind}: ${issueMessage(issue)}`;
26
- })
27
- .join("\n");
28
- }
29
-
30
- export class DictionaryValidationError extends Error {
31
- constructor(public readonly issues: readonly ValidationIssue[]) {
32
- super(formatIssues(issues));
33
- this.name = "DictionaryValidationError";
34
- }
35
- }
36
-
37
- export function assertValidDictionary<T>(
38
- result: ValidationResult<T>
39
- ): asserts result is { ok: true; data: T } {
40
- if (!result.ok) {
41
- throw new DictionaryValidationError(result.issues);
42
- }
43
- }
@@ -1,202 +0,0 @@
1
- /**
2
- * External payload validation for server-side codegen/ingest (CMS → authoring files).
3
- * Not a runtime patch API — validate before writing authoring, then `regenerateNamespaces` / `load()`.
4
- */
5
- import { normalizeDictionaryPartial, normalizeKeyDictionaryPartial } from "./normalize.js";
6
- import { toDictionary, toNamespaceDictionary } from "./to-dictionary.js";
7
- import type {
8
- DictionarySpec,
9
- NormalizedKeyDictionary,
10
- ValidationIssue,
11
- ValidationResult,
12
- } from "./types.js";
13
- import { validateNormalizedKeyDictionaryPartial } from "./validate-normalized.js";
14
-
15
- export function validateExternalDictionaryPartial<TSchema>(
16
- input: unknown,
17
- spec: DictionarySpec
18
- ): ValidationResult<Partial<TSchema>> {
19
- if (spec.mode === "single") {
20
- const step1 = normalizeKeyDictionaryPartial(input, spec.requiredKeys, spec.argsByKey, []);
21
- if (!step1.ok) {
22
- return step1;
23
- }
24
-
25
- const step2 = validateNormalizedKeyDictionaryPartial(step1.data, spec.argsByKey, spec, []);
26
- if (!step2.ok) {
27
- return step2;
28
- }
29
-
30
- return {
31
- ok: true,
32
- data: toDictionary({ mode: "single", keys: step2.data }) as Partial<TSchema>,
33
- };
34
- }
35
-
36
- const step1 = normalizeDictionaryPartial(input, spec);
37
- if (!step1.ok) {
38
- return step1;
39
- }
40
-
41
- if (step1.data.mode !== "multi") {
42
- return {
43
- ok: false,
44
- issues: [
45
- {
46
- kind: "invalid_input",
47
- message: "validateExternalDictionaryPartial requires multi-mode dictionary spec",
48
- },
49
- ],
50
- };
51
- }
52
-
53
- const issues: ValidationIssue[] = [];
54
- const namespaces: Record<string, NormalizedKeyDictionary> = {};
55
-
56
- for (const [namespace, keys] of Object.entries(step1.data.namespaces)) {
57
- const step2 = validateNormalizedKeyDictionaryPartial(
58
- keys,
59
- spec.argsByKey[namespace] ?? {},
60
- spec,
61
- [namespace]
62
- );
63
- if (!step2.ok) {
64
- issues.push(...step2.issues);
65
- } else {
66
- namespaces[namespace] = step2.data;
67
- }
68
- }
69
-
70
- if (issues.length > 0) {
71
- return { ok: false, issues };
72
- }
73
-
74
- return {
75
- ok: true,
76
- data: toDictionary({ mode: "multi", namespaces }) as Partial<TSchema>,
77
- };
78
- }
79
-
80
- export function validateExternalNamespacePartial<TNamespaceSchema>(
81
- namespace: string,
82
- input: unknown,
83
- spec: DictionarySpec
84
- ): ValidationResult<Partial<TNamespaceSchema>> {
85
- if (spec.mode !== "multi") {
86
- return {
87
- ok: false,
88
- issues: [
89
- {
90
- kind: "invalid_input",
91
- message: "validateExternalNamespacePartial requires multi-mode dictionary spec",
92
- },
93
- ],
94
- };
95
- }
96
-
97
- const knownKeys = spec.requiredKeys[namespace];
98
- if (!knownKeys) {
99
- return {
100
- ok: false,
101
- issues: [{ kind: "unknown_key", path: [namespace] }],
102
- };
103
- }
104
-
105
- const step1 = normalizeKeyDictionaryPartial(input, knownKeys, spec.argsByKey[namespace] ?? {}, [
106
- namespace,
107
- ]);
108
- if (!step1.ok) {
109
- return step1;
110
- }
111
-
112
- const step2 = validateNormalizedKeyDictionaryPartial(
113
- step1.data,
114
- spec.argsByKey[namespace] ?? {},
115
- spec,
116
- [namespace]
117
- );
118
- if (!step2.ok) {
119
- return step2;
120
- }
121
-
122
- return {
123
- ok: true,
124
- data: toNamespaceDictionary(
125
- { mode: "multi", namespaces: { [namespace]: step2.data } },
126
- namespace
127
- ) as Partial<TNamespaceSchema>,
128
- };
129
- }
130
-
131
- export function validateExternalKey<TKeySlice>(
132
- key: string,
133
- input: unknown,
134
- spec: Extract<DictionarySpec, { mode: "single" }>
135
- ): ValidationResult<TKeySlice>;
136
-
137
- export function validateExternalKey<TKeySlice>(
138
- namespace: string,
139
- key: string,
140
- input: unknown,
141
- spec: Extract<DictionarySpec, { mode: "multi" }>
142
- ): ValidationResult<TKeySlice>;
143
-
144
- export function validateExternalKey<TKeySlice>(
145
- namespaceOrKey: string,
146
- keyOrInput: string | unknown,
147
- inputOrSpec?: unknown,
148
- specMaybe?: DictionarySpec
149
- ): ValidationResult<TKeySlice> {
150
- if (specMaybe?.mode === "multi") {
151
- const namespace = namespaceOrKey;
152
- const key = keyOrInput as string;
153
- const input = inputOrSpec;
154
- const spec = specMaybe;
155
-
156
- return validateExternalNamespacePartial<TKeySlice>(
157
- namespace,
158
- { [key]: input },
159
- spec
160
- ) as ValidationResult<TKeySlice>;
161
- }
162
-
163
- const key = namespaceOrKey;
164
- const input = keyOrInput;
165
- const spec = inputOrSpec as Extract<DictionarySpec, { mode: "single" }>;
166
-
167
- return validateExternalDictionaryPartial<TKeySlice>(
168
- { [key]: input },
169
- spec
170
- ) as ValidationResult<TKeySlice>;
171
- }
172
-
173
- export {
174
- normalizeDictionaryPartial,
175
- normalizeKeyDictionaryPartial,
176
- toDictionary,
177
- toNamespaceDictionary,
178
- };
179
- export { validateNormalizedKeyDictionaryPartial } from "./validate-normalized.js";
180
- export { createArgsSchema } from "./create-args-schema.js";
181
- export { createNormalizedDictionarySchema } from "./create-normalized-schema.js";
182
- export { DictionaryValidationError, assertValidDictionary, formatIssues } from "./errors.js";
183
- export type {
184
- DictionarySpec,
185
- NormalizedDictionary,
186
- NormalizedKeyDictionary,
187
- NormalizedMultiDictionary,
188
- ParsedKeyEntry,
189
- ParsedLocaleEntry,
190
- ValidationIssue,
191
- ValidationResult,
192
- VariableSpec,
193
- VariableType,
194
- } from "./types.js";
195
-
196
- export type { ParseTemplateResult } from "../icu/parse-template.js";
197
- export { parseTemplate } from "../icu/parse-template.js";
198
- export {
199
- extractVariables,
200
- mergeVariableSpecs,
201
- variableSpecsEqual,
202
- } from "../icu/extract-variables.js";