@xndrjs/i18n 0.2.0-alpha.1 → 0.2.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.
- package/dist/index.d.ts +1 -1
- package/dist/{types-CFWVT2PP.d.ts → types-C1CpXVOJ.d.ts} +7 -1
- package/dist/validation/index.d.ts +3 -2
- package/dist/validation/index.js +125 -28
- package/dist/validation/index.js.map +1 -1
- package/package.json +1 -1
- package/src/codegen/generate-i18n-types.test.ts +122 -0
- package/src/codegen/icu-analysis.ts +19 -5
- package/src/ensure-namespace.test.ts +1 -1
- package/src/icu/extract-variables.test.ts +199 -0
- package/src/icu/extract-variables.ts +160 -20
- package/src/icu/parse-template.ts +9 -2
- package/src/validation/normalize.ts +17 -10
- package/src/validation/validation.test.ts +104 -0
package/dist/index.d.ts
CHANGED
|
@@ -2,6 +2,12 @@ import { parse } from '@formatjs/icu-messageformat-parser';
|
|
|
2
2
|
|
|
3
3
|
type VariableType = "string" | "number" | "date";
|
|
4
4
|
type VariableSpec = Record<string, VariableType>;
|
|
5
|
+
type VariableRole = "simple" | "plural" | "selectordinal" | "select" | "number" | "date" | "time";
|
|
6
|
+
type VariableMeta = {
|
|
7
|
+
type: VariableType;
|
|
8
|
+
roles: Set<VariableRole>;
|
|
9
|
+
};
|
|
10
|
+
type VariableMetaSpec = Record<string, VariableMeta>;
|
|
5
11
|
type IcuAst = ReturnType<typeof parse>;
|
|
6
12
|
declare function extractVariables(nodes: IcuAst): VariableSpec;
|
|
7
13
|
declare function mergeVariableSpecs(target: VariableSpec, source: VariableSpec): VariableSpec;
|
|
@@ -74,4 +80,4 @@ type ValidationResult<T> = {
|
|
|
74
80
|
readonly issues: readonly ValidationIssue[];
|
|
75
81
|
};
|
|
76
82
|
|
|
77
|
-
export { type DictionarySpec as D, type NormalizedDictionary as N, type ParsedKeyEntry as P, type ValidationResult as V, type VariableSpec as a, type ValidationIssue as b, type
|
|
83
|
+
export { type DictionarySpec as D, type NormalizedDictionary as N, type ParsedKeyEntry as P, type ValidationResult as V, type VariableSpec as a, type ValidationIssue as b, type VariableMetaSpec as c, type NormalizedKeyDictionary as d, type NormalizedMultiDictionary as e, type ParsedLocaleEntry as f, type VariableType as g, extractVariables as h, mergeVariableSpecs as m, variableSpecsEqual as v };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { D as DictionarySpec, V as ValidationResult, N as NormalizedDictionary, a as VariableSpec, b as ValidationIssue } from '../types-
|
|
2
|
-
export {
|
|
1
|
+
import { D as DictionarySpec, V as ValidationResult, N as NormalizedDictionary, a as VariableSpec, b as ValidationIssue, c as VariableMetaSpec } from '../types-C1CpXVOJ.js';
|
|
2
|
+
export { d as NormalizedKeyDictionary, e as NormalizedMultiDictionary, P as ParsedKeyEntry, f as ParsedLocaleEntry, g as VariableType, h as extractVariables, m as mergeVariableSpecs, v as variableSpecsEqual } from '../types-C1CpXVOJ.js';
|
|
3
3
|
import { z } from 'zod';
|
|
4
4
|
import '@formatjs/icu-messageformat-parser';
|
|
5
5
|
|
|
@@ -30,6 +30,7 @@ declare function assertValidDictionary<T>(result: ValidationResult<T>): asserts
|
|
|
30
30
|
type ParseTemplateSuccess = {
|
|
31
31
|
readonly ok: true;
|
|
32
32
|
readonly args: VariableSpec;
|
|
33
|
+
readonly meta: VariableMetaSpec;
|
|
33
34
|
};
|
|
34
35
|
type ParseTemplateFailure = {
|
|
35
36
|
readonly ok: false;
|
package/dist/validation/index.js
CHANGED
|
@@ -2,32 +2,47 @@ import { parse } from '@formatjs/icu-messageformat-parser';
|
|
|
2
2
|
import { z } from 'zod';
|
|
3
3
|
|
|
4
4
|
// src/icu/extract-variables.ts
|
|
5
|
-
function
|
|
6
|
-
|
|
7
|
-
return true;
|
|
8
|
-
}
|
|
9
|
-
if (node.type === 6 && "pluralType" in node && node.pluralType) {
|
|
10
|
-
return true;
|
|
11
|
-
}
|
|
12
|
-
return false;
|
|
5
|
+
function isPluralOrOrdinalNode(node) {
|
|
6
|
+
return node.type === 6 && "pluralType" in node && Boolean(node.pluralType);
|
|
13
7
|
}
|
|
14
8
|
function hasVariableName(node) {
|
|
15
9
|
return "value" in node && typeof node.value === "string";
|
|
16
10
|
}
|
|
17
|
-
function
|
|
11
|
+
function addVariableMeta(variables, name, type, role) {
|
|
12
|
+
const existing = variables[name];
|
|
13
|
+
if (!existing) {
|
|
14
|
+
variables[name] = { type, roles: /* @__PURE__ */ new Set([role]) };
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
existing.roles.add(role);
|
|
18
|
+
existing.type = mergeVariableTypes(existing.type, type);
|
|
19
|
+
}
|
|
20
|
+
function mergeVariableTypes(left, right) {
|
|
21
|
+
if (left === right) {
|
|
22
|
+
return left;
|
|
23
|
+
}
|
|
24
|
+
if (left === "string" && right === "number" || left === "number" && right === "string") {
|
|
25
|
+
return "number";
|
|
26
|
+
}
|
|
27
|
+
return right;
|
|
28
|
+
}
|
|
29
|
+
function extractVariableMeta(nodes) {
|
|
18
30
|
const variables = {};
|
|
19
31
|
const walk = (walkNodes) => {
|
|
20
32
|
for (const node of walkNodes) {
|
|
21
|
-
if (node.type === 1) {
|
|
22
|
-
variables
|
|
23
|
-
} else if (
|
|
24
|
-
variables
|
|
25
|
-
} else if ((node
|
|
26
|
-
|
|
33
|
+
if (node.type === 1 && hasVariableName(node)) {
|
|
34
|
+
addVariableMeta(variables, node.value, "string", "simple");
|
|
35
|
+
} else if (node.type === 2 && hasVariableName(node)) {
|
|
36
|
+
addVariableMeta(variables, node.value, "number", "number");
|
|
37
|
+
} else if (isPluralOrOrdinalNode(node) && hasVariableName(node)) {
|
|
38
|
+
const role = node.pluralType === "ordinal" ? "selectordinal" : "plural";
|
|
39
|
+
addVariableMeta(variables, node.value, "number", role);
|
|
40
|
+
} else if (node.type === 3 && hasVariableName(node)) {
|
|
41
|
+
addVariableMeta(variables, node.value, "date", "date");
|
|
42
|
+
} else if (node.type === 4 && hasVariableName(node)) {
|
|
43
|
+
addVariableMeta(variables, node.value, "date", "time");
|
|
27
44
|
} else if (node.type === 5 && hasVariableName(node)) {
|
|
28
|
-
variables
|
|
29
|
-
} else if (node.type === 6 && hasVariableName(node)) {
|
|
30
|
-
variables[node.value] = variables[node.value] ?? "string";
|
|
45
|
+
addVariableMeta(variables, node.value, "string", "select");
|
|
31
46
|
}
|
|
32
47
|
if ("options" in node && node.options) {
|
|
33
48
|
for (const option of Object.values(node.options)) {
|
|
@@ -42,6 +57,81 @@ function extractVariables(nodes) {
|
|
|
42
57
|
walk(nodes);
|
|
43
58
|
return variables;
|
|
44
59
|
}
|
|
60
|
+
function variableMetaToSpec(meta) {
|
|
61
|
+
return Object.fromEntries(Object.entries(meta).map(([name, entry]) => [name, entry.type]));
|
|
62
|
+
}
|
|
63
|
+
function extractVariables(nodes) {
|
|
64
|
+
return variableMetaToSpec(extractVariableMeta(nodes));
|
|
65
|
+
}
|
|
66
|
+
function inferMergedVariableType(roles) {
|
|
67
|
+
const hasPlural = roles.has("plural");
|
|
68
|
+
const hasSelectordinal = roles.has("selectordinal");
|
|
69
|
+
const hasSelect = roles.has("select");
|
|
70
|
+
const hasSimple = roles.has("simple");
|
|
71
|
+
const hasNumber = roles.has("number");
|
|
72
|
+
const hasDate = roles.has("date");
|
|
73
|
+
const hasTime = roles.has("time");
|
|
74
|
+
if (hasPlural && hasSelectordinal) {
|
|
75
|
+
return "CONFLICT";
|
|
76
|
+
}
|
|
77
|
+
if (hasSelect && (hasPlural || hasSelectordinal || hasNumber)) {
|
|
78
|
+
return "CONFLICT";
|
|
79
|
+
}
|
|
80
|
+
if (hasPlural && hasSelect) {
|
|
81
|
+
return "CONFLICT";
|
|
82
|
+
}
|
|
83
|
+
if (hasSelectordinal && hasSelect) {
|
|
84
|
+
return "CONFLICT";
|
|
85
|
+
}
|
|
86
|
+
if (hasDate || hasTime) {
|
|
87
|
+
if (hasPlural || hasSelect || hasSelectordinal || hasSimple || hasNumber) {
|
|
88
|
+
return "CONFLICT";
|
|
89
|
+
}
|
|
90
|
+
return "date";
|
|
91
|
+
}
|
|
92
|
+
if (hasPlural || hasSelectordinal || hasNumber) {
|
|
93
|
+
return "number";
|
|
94
|
+
}
|
|
95
|
+
if (hasSelect || hasSimple) {
|
|
96
|
+
return "string";
|
|
97
|
+
}
|
|
98
|
+
return "string";
|
|
99
|
+
}
|
|
100
|
+
function mergeVariableMetaAcrossLocales(localeMetas) {
|
|
101
|
+
const keySignatures = localeMetas.map((meta) => Object.keys(meta).sort().join(","));
|
|
102
|
+
if (new Set(keySignatures).size > 1) {
|
|
103
|
+
return {
|
|
104
|
+
ok: false,
|
|
105
|
+
message: `Inconsistent ICU variable names across locales (keys: ${keySignatures.join(" vs ")})`
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
const merged = {};
|
|
109
|
+
const variableNames = new Set(localeMetas.flatMap((meta) => Object.keys(meta)));
|
|
110
|
+
for (const variableName of variableNames) {
|
|
111
|
+
const combinedRoles = /* @__PURE__ */ new Set();
|
|
112
|
+
for (const meta of localeMetas) {
|
|
113
|
+
for (const role of meta[variableName]?.roles ?? []) {
|
|
114
|
+
combinedRoles.add(role);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
const mergedType = inferMergedVariableType(combinedRoles);
|
|
118
|
+
if (mergedType === "CONFLICT") {
|
|
119
|
+
const rolesByLocale = localeMetas.map((meta) => {
|
|
120
|
+
const entry = meta[variableName];
|
|
121
|
+
if (!entry) {
|
|
122
|
+
return null;
|
|
123
|
+
}
|
|
124
|
+
return [...entry.roles].sort().join("|");
|
|
125
|
+
}).filter((value) => value !== null);
|
|
126
|
+
return {
|
|
127
|
+
ok: false,
|
|
128
|
+
message: `Incompatible ICU variable "${variableName}" across locales (roles: ${rolesByLocale.join(" vs ")})`
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
merged[variableName] = mergedType;
|
|
132
|
+
}
|
|
133
|
+
return { ok: true, merged };
|
|
134
|
+
}
|
|
45
135
|
function mergeVariableSpecs(target, source) {
|
|
46
136
|
const merged = { ...target };
|
|
47
137
|
for (const [varName, varType] of Object.entries(source)) {
|
|
@@ -70,7 +160,8 @@ function variableSpecsEqual(a, b) {
|
|
|
70
160
|
function parseTemplate(template) {
|
|
71
161
|
try {
|
|
72
162
|
const ast = parse(template);
|
|
73
|
-
|
|
163
|
+
const meta = extractVariableMeta(ast);
|
|
164
|
+
return { ok: true, args: variableMetaToSpec(meta), meta };
|
|
74
165
|
} catch (error) {
|
|
75
166
|
const message = error instanceof Error ? error.message : String(error);
|
|
76
167
|
return { ok: false, message };
|
|
@@ -104,8 +195,9 @@ function normalizeKeyDictionary(input, requiredKeys, keyPathPrefix) {
|
|
|
104
195
|
continue;
|
|
105
196
|
}
|
|
106
197
|
const locales = {};
|
|
198
|
+
const localeMetas = [];
|
|
107
199
|
let mergedArgs = {};
|
|
108
|
-
let
|
|
200
|
+
let localeArgsMismatch;
|
|
109
201
|
for (const [locale, template] of Object.entries(keyValue)) {
|
|
110
202
|
const localePath = [...keyPathPrefix, key, locale];
|
|
111
203
|
if (typeof template !== "string") {
|
|
@@ -126,23 +218,28 @@ function normalizeKeyDictionary(input, requiredKeys, keyPathPrefix) {
|
|
|
126
218
|
continue;
|
|
127
219
|
}
|
|
128
220
|
locales[locale] = { template, args: parsed.args };
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
221
|
+
localeMetas.push(parsed.meta);
|
|
222
|
+
}
|
|
223
|
+
if (localeMetas.length > 0) {
|
|
224
|
+
const merged = mergeVariableMetaAcrossLocales(localeMetas);
|
|
225
|
+
if (!merged.ok) {
|
|
133
226
|
const localeArgs = {};
|
|
134
227
|
for (const [loc, entry] of Object.entries(locales)) {
|
|
135
228
|
localeArgs[loc] = entry.args;
|
|
136
229
|
}
|
|
137
|
-
|
|
230
|
+
localeArgsMismatch = {
|
|
138
231
|
kind: "locale_args_mismatch",
|
|
139
232
|
path: [...keyPathPrefix, key],
|
|
140
233
|
locales: localeArgs,
|
|
141
|
-
message:
|
|
142
|
-
}
|
|
143
|
-
|
|
234
|
+
message: merged.message
|
|
235
|
+
};
|
|
236
|
+
} else {
|
|
237
|
+
mergedArgs = merged.merged;
|
|
144
238
|
}
|
|
145
239
|
}
|
|
240
|
+
if (localeArgsMismatch) {
|
|
241
|
+
issues.push(localeArgsMismatch);
|
|
242
|
+
}
|
|
146
243
|
keys[key] = { locales, mergedArgs };
|
|
147
244
|
}
|
|
148
245
|
if (issues.length > 0) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/icu/extract-variables.ts","../../src/icu/parse-template.ts","../../src/validation/normalize.ts","../../src/validation/to-dictionary.ts","../../src/validation/create-args-schema.ts","../../src/validation/create-normalized-schema.ts","../../src/validation/validate-normalized.ts","../../src/validation/errors.ts","../../src/validation/index.ts"],"names":["dictionary","z","key"],"mappings":"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","file":"index.js","sourcesContent":["import type { parse } from \"@formatjs/icu-messageformat-parser\";\n\nexport type VariableType = \"string\" | \"number\" | \"date\";\nexport type VariableSpec = Record<string, VariableType>;\n\ntype IcuAst = ReturnType<typeof parse>;\ntype IcuNode = IcuAst[number];\n\nfunction isNumericIcuNode(node: IcuNode): boolean {\n if (node.type === 2) {\n return true;\n }\n\n if (node.type === 6 && \"pluralType\" in node && node.pluralType) {\n return true;\n }\n\n return false;\n}\n\nfunction hasVariableName(node: IcuNode): node is Extract<IcuNode, { value: string }> {\n return \"value\" in node && typeof node.value === \"string\";\n}\n\nexport function extractVariables(nodes: IcuAst): VariableSpec {\n const variables: VariableSpec = {};\n\n const walk = (walkNodes: IcuAst) => {\n for (const node of walkNodes) {\n if (node.type === 1) {\n variables[node.value] = variables[node.value] ?? \"string\";\n } else if (isNumericIcuNode(node) && hasVariableName(node)) {\n variables[node.value] = \"number\";\n } else if ((node.type === 3 || node.type === 4) && hasVariableName(node)) {\n variables[node.value] = \"date\";\n } else if (node.type === 5 && hasVariableName(node)) {\n variables[node.value] = variables[node.value] ?? \"string\";\n } else if (node.type === 6 && hasVariableName(node)) {\n variables[node.value] = variables[node.value] ?? \"string\";\n }\n\n if (\"options\" in node && node.options) {\n for (const option of Object.values(node.options)) {\n walk(option.value);\n }\n }\n\n if (\"children\" in node && Array.isArray(node.children)) {\n walk(node.children);\n }\n }\n };\n\n walk(nodes);\n return variables;\n}\n\nexport function mergeVariableSpecs(target: VariableSpec, source: VariableSpec): VariableSpec {\n const merged = { ...target };\n\n for (const [varName, varType] of Object.entries(source)) {\n const existing = merged[varName];\n if (existing && existing !== varType) {\n merged[varName] = \"number\";\n } else {\n merged[varName] = varType;\n }\n }\n\n return merged;\n}\n\nexport function variableSpecsEqual(a: VariableSpec, b: VariableSpec): boolean {\n const aKeys = Object.keys(a).sort();\n const bKeys = Object.keys(b).sort();\n\n if (aKeys.length !== bKeys.length) {\n return false;\n }\n\n return aKeys.every((key, index) => {\n if (key !== bKeys[index]) {\n return false;\n }\n return a[key] === b[key];\n });\n}\n","import { parse } from \"@formatjs/icu-messageformat-parser\";\nimport { extractVariables, type VariableSpec } from \"./extract-variables.js\";\n\nexport type ParseTemplateSuccess = {\n readonly ok: true;\n readonly args: VariableSpec;\n};\n\nexport type ParseTemplateFailure = {\n readonly ok: false;\n readonly message: string;\n};\n\nexport type ParseTemplateResult = ParseTemplateSuccess | ParseTemplateFailure;\n\nexport function parseTemplate(template: string): ParseTemplateResult {\n try {\n const ast = parse(template);\n return { ok: true, args: extractVariables(ast) };\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n return { ok: false, message };\n }\n}\n","import { variableSpecsEqual } from \"../icu/extract-variables.js\";\nimport { parseTemplate } from \"../icu/parse-template.js\";\nimport type {\n DictionarySpec,\n NormalizedDictionary,\n NormalizedKeyDictionary,\n ParsedKeyEntry,\n ValidationIssue,\n ValidationResult,\n} from \"./types.js\";\n\nfunction failure(issues: ValidationIssue[]): ValidationResult<NormalizedDictionary> {\n return { ok: false, issues };\n}\n\nfunction success(data: NormalizedDictionary): ValidationResult<NormalizedDictionary> {\n return { ok: true, data };\n}\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction normalizeKeyDictionary(\n input: Record<string, unknown>,\n requiredKeys: readonly string[],\n keyPathPrefix: readonly string[]\n): ValidationResult<NormalizedKeyDictionary> {\n const issues: ValidationIssue[] = [];\n const keys: Record<string, ParsedKeyEntry> = {};\n\n for (const key of requiredKeys) {\n if (!(key in input)) {\n issues.push({ kind: \"missing_key\", path: [...keyPathPrefix, key] });\n continue;\n }\n\n const keyValue = input[key];\n if (!isPlainObject(keyValue)) {\n issues.push({\n kind: \"invalid_input\",\n message: `Expected object for key \"${key}\", got ${typeof keyValue}`,\n });\n continue;\n }\n\n const locales: Record<string, ParsedKeyEntry[\"locales\"][string]> = {};\n let mergedArgs: ParsedKeyEntry[\"mergedArgs\"] = {};\n let hasLocaleArgs = false;\n\n for (const [locale, template] of Object.entries(keyValue)) {\n const localePath = [...keyPathPrefix, key, locale];\n\n if (typeof template !== \"string\") {\n issues.push({\n kind: \"invalid_locale_value\",\n path: localePath,\n message: `expected string, got ${typeof template}`,\n });\n continue;\n }\n\n const parsed = parseTemplate(template);\n if (!parsed.ok) {\n issues.push({\n kind: \"icu_syntax_error\",\n path: localePath,\n message: parsed.message,\n });\n continue;\n }\n\n locales[locale] = { template, args: parsed.args };\n\n if (!hasLocaleArgs) {\n mergedArgs = { ...parsed.args };\n hasLocaleArgs = true;\n } else if (!variableSpecsEqual(mergedArgs, parsed.args)) {\n const localeArgs: Record<string, ParsedKeyEntry[\"mergedArgs\"]> = {};\n for (const [loc, entry] of Object.entries(locales)) {\n localeArgs[loc] = entry.args;\n }\n issues.push({\n kind: \"locale_args_mismatch\",\n path: [...keyPathPrefix, key],\n locales: localeArgs,\n message: `Inconsistent ICU variables across locales for key \"${key}\"`,\n });\n break;\n }\n }\n\n keys[key] = { locales, mergedArgs };\n }\n\n if (issues.length > 0) {\n return { ok: false, issues };\n }\n\n return { ok: true, data: keys };\n}\n\nexport function normalizeDictionary(\n input: unknown,\n spec: DictionarySpec\n): ValidationResult<NormalizedDictionary> {\n if (!isPlainObject(input)) {\n return failure([\n {\n kind: \"invalid_input\",\n message: `Expected object, got ${input === null ? \"null\" : typeof input}`,\n },\n ]);\n }\n\n if (spec.mode === \"single\") {\n const result = normalizeKeyDictionary(input, spec.requiredKeys, []);\n if (!result.ok) {\n return result;\n }\n\n return success({ mode: \"single\", keys: result.data });\n }\n\n const issues: ValidationIssue[] = [];\n const namespaces: Record<string, NormalizedKeyDictionary> = {};\n\n for (const [namespace, requiredKeys] of Object.entries(spec.requiredKeys)) {\n if (!(namespace in input)) {\n for (const key of requiredKeys) {\n issues.push({ kind: \"missing_key\", path: [namespace, key] });\n }\n continue;\n }\n\n const namespaceValue = input[namespace];\n if (!isPlainObject(namespaceValue)) {\n issues.push({\n kind: \"invalid_input\",\n message: `Expected object for namespace \"${namespace}\", got ${typeof namespaceValue}`,\n });\n continue;\n }\n\n const result = normalizeKeyDictionary(namespaceValue, requiredKeys, [namespace]);\n if (!result.ok) {\n issues.push(...result.issues);\n } else {\n namespaces[namespace] = result.data;\n }\n }\n\n if (issues.length > 0) {\n return failure(issues);\n }\n\n return success({ mode: \"multi\", namespaces });\n}\n","import type { NormalizedDictionary } from \"./types.js\";\n\ntype LocaleDictionary = Record<string, string>;\ntype KeyDictionary = Record<string, LocaleDictionary>;\ntype MultiDictionary = Record<string, KeyDictionary>;\n\nexport function toDictionary(normalized: NormalizedDictionary): KeyDictionary | MultiDictionary {\n if (normalized.mode === \"single\") {\n const dictionary: KeyDictionary = {};\n\n for (const [key, entry] of Object.entries(normalized.keys)) {\n const locales: LocaleDictionary = {};\n for (const [locale, localeEntry] of Object.entries(entry.locales)) {\n locales[locale] = localeEntry.template;\n }\n dictionary[key] = locales;\n }\n\n return dictionary;\n }\n\n const dictionary: MultiDictionary = {};\n\n for (const [namespace, keys] of Object.entries(normalized.namespaces)) {\n const namespaceDictionary: KeyDictionary = {};\n\n for (const [key, entry] of Object.entries(keys)) {\n const locales: LocaleDictionary = {};\n for (const [locale, localeEntry] of Object.entries(entry.locales)) {\n locales[locale] = localeEntry.template;\n }\n namespaceDictionary[key] = locales;\n }\n\n dictionary[namespace] = namespaceDictionary;\n }\n\n return dictionary;\n}\n\nexport function toNamespaceDictionary(\n normalized: NormalizedDictionary,\n namespace: string\n): KeyDictionary {\n if (normalized.mode !== \"multi\") {\n throw new Error(\n `[i18n] toNamespaceDictionary requires multi-mode normalized dictionary, got \"${normalized.mode}\"`\n );\n }\n\n const keys = normalized.namespaces[namespace];\n if (!keys) {\n return {};\n }\n\n const dictionary: KeyDictionary = {};\n\n for (const [key, entry] of Object.entries(keys)) {\n const locales: LocaleDictionary = {};\n for (const [locale, localeEntry] of Object.entries(entry.locales)) {\n locales[locale] = localeEntry.template;\n }\n dictionary[key] = locales;\n }\n\n return dictionary;\n}\n","import { z } from \"zod\";\nimport type { VariableSpec, VariableType } from \"../icu/extract-variables.js\";\n\nexport const variableTypeSchema = z.enum([\"string\", \"number\", \"date\"]);\n\nfunction formatVariableMismatch(expected: VariableSpec, found: VariableSpec): string {\n const expectedKeys = Object.keys(expected).sort();\n const foundKeys = Object.keys(found).sort();\n return `Expected variables [${expectedKeys.join(\", \")}], found [${foundKeys.join(\", \")}]`;\n}\n\nexport function createArgsSchema(expected: VariableSpec): z.ZodType<VariableSpec> {\n return z.record(z.string(), variableTypeSchema).superRefine((found, ctx) => {\n const expectedKeys = Object.keys(expected).sort();\n const foundKeys = Object.keys(found).sort();\n\n if (expectedKeys.join() !== foundKeys.join()) {\n ctx.addIssue({\n code: \"custom\",\n message: formatVariableMismatch(expected, found),\n });\n return;\n }\n\n for (const [name, type] of Object.entries(expected)) {\n if (found[name] !== type) {\n ctx.addIssue({\n code: \"custom\",\n message: `Variable \"${name}\": expected ${type}, found ${found[name]}`,\n });\n }\n }\n });\n}\n\nexport function mapArgsSchemaError(\n expected: VariableSpec,\n found: VariableSpec,\n message: string,\n path: readonly string[]\n) {\n const expectedKeys = Object.keys(expected).sort();\n const foundKeys = Object.keys(found).sort();\n\n if (expectedKeys.join() !== foundKeys.join()) {\n return {\n kind: \"variable_mismatch\" as const,\n path,\n expected,\n found,\n message,\n };\n }\n\n for (const [name, type] of Object.entries(expected)) {\n const foundType = found[name];\n if (foundType !== undefined && foundType !== type) {\n return {\n kind: \"variable_type_mismatch\" as const,\n path,\n variable: name,\n expected: type,\n found: foundType as VariableType,\n message,\n };\n }\n }\n\n return {\n kind: \"variable_mismatch\" as const,\n path,\n expected,\n found,\n message,\n };\n}\n","import { z } from \"zod\";\nimport type { VariableSpec } from \"../icu/extract-variables.js\";\nimport { createArgsSchema, variableTypeSchema } from \"./create-args-schema.js\";\nimport type { DictionarySpec, NormalizedDictionary, ParsedKeyEntry } from \"./types.js\";\n\nconst parsedLocaleEntrySchema = z.object({\n template: z.string(),\n args: z.record(z.string(), variableTypeSchema),\n});\n\nconst parsedKeyEntrySchema = z.object({\n locales: z.record(z.string(), parsedLocaleEntrySchema),\n mergedArgs: z.record(z.string(), variableTypeSchema),\n});\n\nfunction createKeyDictionarySchema(\n argsByKey: Readonly<Record<string, VariableSpec>>\n): z.ZodType<Record<string, ParsedKeyEntry>> {\n const keySchemas = Object.fromEntries(\n Object.entries(argsByKey).map(([key, expectedArgs]) => [\n key,\n parsedKeyEntrySchema.extend({\n mergedArgs: createArgsSchema(expectedArgs),\n }),\n ])\n );\n\n return z.object(keySchemas);\n}\n\nexport function createNormalizedDictionarySchema(\n spec: DictionarySpec\n): z.ZodType<NormalizedDictionary> {\n if (spec.mode === \"single\") {\n return z.object({\n mode: z.literal(\"single\"),\n keys: createKeyDictionarySchema(spec.argsByKey),\n }) as z.ZodType<NormalizedDictionary>;\n }\n\n const namespaceSchemas = Object.fromEntries(\n Object.entries(spec.argsByKey).map(([namespace, argsByKey]) => [\n namespace,\n createKeyDictionarySchema(argsByKey),\n ])\n );\n\n return z.object({\n mode: z.literal(\"multi\"),\n namespaces: z.object(namespaceSchemas),\n }) as z.ZodType<NormalizedDictionary>;\n}\n","import type { ZodError } from \"zod\";\nimport { mapArgsSchemaError } from \"./create-args-schema.js\";\nimport { createNormalizedDictionarySchema } from \"./create-normalized-schema.js\";\nimport type {\n DictionarySpec,\n NormalizedDictionary,\n ValidationIssue,\n ValidationResult,\n VariableSpec,\n} from \"./types.js\";\n\nfunction getExpectedArgs(spec: DictionarySpec, path: readonly string[]): VariableSpec | undefined {\n if (spec.mode === \"single\") {\n const key = path[path.length - 1];\n if (typeof key !== \"string\") {\n return undefined;\n }\n return spec.argsByKey[key];\n }\n\n const namespace = path[0];\n const key = path[path.length - 1];\n if (typeof namespace !== \"string\" || typeof key !== \"string\") {\n return undefined;\n }\n\n return spec.argsByKey[namespace]?.[key];\n}\n\nfunction getFoundArgs(\n normalized: NormalizedDictionary,\n path: readonly string[]\n): VariableSpec | undefined {\n if (normalized.mode === \"single\") {\n const key = path[path.length - 1];\n if (typeof key !== \"string\") {\n return undefined;\n }\n return normalized.keys[key]?.mergedArgs;\n }\n\n const namespace = path[0];\n const key = path[path.length - 1];\n if (typeof namespace !== \"string\" || typeof key !== \"string\") {\n return undefined;\n }\n\n return normalized.namespaces[namespace]?.[key]?.mergedArgs;\n}\n\nfunction mapZodError(\n error: ZodError,\n spec: DictionarySpec,\n normalized: NormalizedDictionary\n): ValidationIssue[] {\n return error.issues.map((issue) => {\n const path = issue.path.map(String);\n\n if (path.includes(\"mergedArgs\")) {\n const keyPath = path.slice(0, path.indexOf(\"mergedArgs\"));\n const expected = getExpectedArgs(spec, keyPath);\n const found = getFoundArgs(normalized, keyPath);\n\n if (expected && found) {\n return mapArgsSchemaError(expected, found, issue.message, keyPath);\n }\n }\n\n return {\n kind: \"invalid_input\" as const,\n message: path.length > 0 ? `${path.join(\".\")}: ${issue.message}` : issue.message,\n };\n });\n}\n\nexport function validateNormalizedDictionary(\n normalized: NormalizedDictionary,\n spec: DictionarySpec\n): ValidationResult<NormalizedDictionary> {\n const schema = createNormalizedDictionarySchema(spec);\n const result = schema.safeParse(normalized);\n\n if (result.success) {\n return { ok: true, data: result.data };\n }\n\n return {\n ok: false,\n issues: mapZodError(result.error, spec, normalized),\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 { normalizeDictionary } from \"./normalize.js\";\nimport { toDictionary, toNamespaceDictionary } from \"./to-dictionary.js\";\nimport type { DictionarySpec, ValidationResult } from \"./types.js\";\nimport { validateNormalizedDictionary } from \"./validate-normalized.js\";\n\nexport function validateExternalDictionary<TSchema>(\n input: unknown,\n spec: DictionarySpec\n): ValidationResult<TSchema> {\n const step1 = normalizeDictionary(input, spec);\n if (!step1.ok) {\n return step1;\n }\n\n const step2 = validateNormalizedDictionary(step1.data, spec);\n if (!step2.ok) {\n return step2;\n }\n\n return { ok: true, data: toDictionary(step2.data) as TSchema };\n}\n\nexport function validateExternalNamespace<TNamespaceSchema>(\n namespace: string,\n input: unknown,\n spec: DictionarySpec\n): ValidationResult<TNamespaceSchema> {\n if (spec.mode !== \"multi\") {\n return {\n ok: false,\n issues: [\n {\n kind: \"invalid_input\",\n message: \"validateExternalNamespace requires multi-mode dictionary spec\",\n },\n ],\n };\n }\n\n const wrapped = { [namespace]: input };\n const namespaceSpec: DictionarySpec = {\n mode: \"multi\",\n requiredKeys: {\n [namespace]: spec.requiredKeys[namespace] ?? [],\n },\n argsByKey: {\n [namespace]: spec.argsByKey[namespace] ?? {},\n },\n };\n\n const step1 = normalizeDictionary(wrapped, namespaceSpec);\n if (!step1.ok) {\n return step1;\n }\n\n const step2 = validateNormalizedDictionary(step1.data, namespaceSpec);\n if (!step2.ok) {\n return step2;\n }\n\n return {\n ok: true,\n data: toNamespaceDictionary(step2.data, namespace) as TNamespaceSchema,\n };\n}\n\nexport { normalizeDictionary, validateNormalizedDictionary, toDictionary, toNamespaceDictionary };\nexport { createArgsSchema } from \"./create-args-schema.js\";\nexport { createNormalizedDictionarySchema } from \"./create-normalized-schema.js\";\nexport { DictionaryValidationError, assertValidDictionary, formatIssues } from \"./errors.js\";\nexport type {\n DictionarySpec,\n NormalizedDictionary,\n NormalizedKeyDictionary,\n NormalizedMultiDictionary,\n ParsedKeyEntry,\n ParsedLocaleEntry,\n ValidationIssue,\n ValidationResult,\n VariableSpec,\n VariableType,\n} from \"./types.js\";\n\nexport type { ParseTemplateResult } from \"../icu/parse-template.js\";\nexport { parseTemplate } from \"../icu/parse-template.js\";\nexport {\n extractVariables,\n mergeVariableSpecs,\n variableSpecsEqual,\n} from \"../icu/extract-variables.js\";\n"]}
|
|
1
|
+
{"version":3,"sources":["../../src/icu/extract-variables.ts","../../src/icu/parse-template.ts","../../src/validation/normalize.ts","../../src/validation/to-dictionary.ts","../../src/validation/create-args-schema.ts","../../src/validation/create-normalized-schema.ts","../../src/validation/validate-normalized.ts","../../src/validation/errors.ts","../../src/validation/index.ts"],"names":["dictionary","z","key"],"mappings":"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","file":"index.js","sourcesContent":["import type { parse } from \"@formatjs/icu-messageformat-parser\";\n\nexport type VariableType = \"string\" | \"number\" | \"date\";\nexport type VariableSpec = Record<string, VariableType>;\n\nexport type VariableRole =\n | \"simple\"\n | \"plural\"\n | \"selectordinal\"\n | \"select\"\n | \"number\"\n | \"date\"\n | \"time\";\n\nexport type VariableMeta = {\n type: VariableType;\n roles: Set<VariableRole>;\n};\n\nexport type VariableMetaSpec = Record<string, VariableMeta>;\n\ntype IcuAst = ReturnType<typeof parse>;\ntype IcuNode = IcuAst[number];\n\nfunction isPluralOrOrdinalNode(node: IcuNode): node is Extract<IcuNode, { type: 6 }> {\n return node.type === 6 && \"pluralType\" in node && Boolean(node.pluralType);\n}\n\nfunction hasVariableName(node: IcuNode): node is Extract<IcuNode, { value: string }> {\n return \"value\" in node && typeof node.value === \"string\";\n}\n\nfunction addVariableMeta(\n variables: VariableMetaSpec,\n name: string,\n type: VariableType,\n role: VariableRole\n): void {\n const existing = variables[name];\n if (!existing) {\n variables[name] = { type, roles: new Set([role]) };\n return;\n }\n\n existing.roles.add(role);\n existing.type = mergeVariableTypes(existing.type, type);\n}\n\nfunction mergeVariableTypes(left: VariableType, right: VariableType): VariableType {\n if (left === right) {\n return left;\n }\n\n if ((left === \"string\" && right === \"number\") || (left === \"number\" && right === \"string\")) {\n return \"number\";\n }\n\n return right;\n}\n\nexport function extractVariableMeta(nodes: IcuAst): VariableMetaSpec {\n const variables: VariableMetaSpec = {};\n\n const walk = (walkNodes: IcuAst) => {\n for (const node of walkNodes) {\n if (node.type === 1 && hasVariableName(node)) {\n addVariableMeta(variables, node.value, \"string\", \"simple\");\n } else if (node.type === 2 && hasVariableName(node)) {\n addVariableMeta(variables, node.value, \"number\", \"number\");\n } else if (isPluralOrOrdinalNode(node) && hasVariableName(node)) {\n const role = node.pluralType === \"ordinal\" ? \"selectordinal\" : \"plural\";\n addVariableMeta(variables, node.value, \"number\", role);\n } else if (node.type === 3 && hasVariableName(node)) {\n addVariableMeta(variables, node.value, \"date\", \"date\");\n } else if (node.type === 4 && hasVariableName(node)) {\n addVariableMeta(variables, node.value, \"date\", \"time\");\n } else if (node.type === 5 && hasVariableName(node)) {\n addVariableMeta(variables, node.value, \"string\", \"select\");\n }\n\n if (\"options\" in node && node.options) {\n for (const option of Object.values(node.options)) {\n walk(option.value);\n }\n }\n\n if (\"children\" in node && Array.isArray(node.children)) {\n walk(node.children);\n }\n }\n };\n\n walk(nodes);\n return variables;\n}\n\nexport function variableMetaToSpec(meta: VariableMetaSpec): VariableSpec {\n return Object.fromEntries(Object.entries(meta).map(([name, entry]) => [name, entry.type]));\n}\n\nexport function extractVariables(nodes: IcuAst): VariableSpec {\n return variableMetaToSpec(extractVariableMeta(nodes));\n}\n\nexport function inferMergedVariableType(roles: Set<VariableRole>): VariableType | \"CONFLICT\" {\n const hasPlural = roles.has(\"plural\");\n const hasSelectordinal = roles.has(\"selectordinal\");\n const hasSelect = roles.has(\"select\");\n const hasSimple = roles.has(\"simple\");\n const hasNumber = roles.has(\"number\");\n const hasDate = roles.has(\"date\");\n const hasTime = roles.has(\"time\");\n\n // Incompatible numeric ICU constructs on the same variable.\n if (hasPlural && hasSelectordinal) {\n return \"CONFLICT\";\n }\n\n // Select (string-family) vs plural / ordinal / number-format (number-family).\n if (hasSelect && (hasPlural || hasSelectordinal || hasNumber)) {\n return \"CONFLICT\";\n }\n\n // Legacy explicit pairs — covered by the rule above, kept for clarity.\n if (hasPlural && hasSelect) {\n return \"CONFLICT\";\n }\n\n if (hasSelectordinal && hasSelect) {\n return \"CONFLICT\";\n }\n\n if (hasDate || hasTime) {\n if (hasPlural || hasSelect || hasSelectordinal || hasSimple || hasNumber) {\n return \"CONFLICT\";\n }\n return \"date\";\n }\n\n if (hasPlural || hasSelectordinal || hasNumber) {\n return \"number\";\n }\n\n if (hasSelect || hasSimple) {\n return \"string\";\n }\n\n return \"string\";\n}\n\nexport function mergeVariableMetaAcrossLocales(\n localeMetas: readonly VariableMetaSpec[]\n): { ok: true; merged: VariableSpec } | { ok: false; message: string } {\n const keySignatures = localeMetas.map((meta) => Object.keys(meta).sort().join(\",\"));\n if (new Set(keySignatures).size > 1) {\n return {\n ok: false,\n message: `Inconsistent ICU variable names across locales (keys: ${keySignatures.join(\" vs \")})`,\n };\n }\n\n const merged: VariableSpec = {};\n const variableNames = new Set(localeMetas.flatMap((meta) => Object.keys(meta)));\n\n for (const variableName of variableNames) {\n const combinedRoles = new Set<VariableRole>();\n\n for (const meta of localeMetas) {\n for (const role of meta[variableName]?.roles ?? []) {\n combinedRoles.add(role);\n }\n }\n\n const mergedType = inferMergedVariableType(combinedRoles);\n if (mergedType === \"CONFLICT\") {\n const rolesByLocale = localeMetas\n .map((meta) => {\n const entry = meta[variableName];\n if (!entry) {\n return null;\n }\n return [...entry.roles].sort().join(\"|\");\n })\n .filter((value): value is string => value !== null);\n\n return {\n ok: false,\n message: `Incompatible ICU variable \"${variableName}\" across locales (roles: ${rolesByLocale.join(\" vs \")})`,\n };\n }\n\n merged[variableName] = mergedType;\n }\n\n return { ok: true, merged };\n}\n\nexport function mergeVariableSpecs(target: VariableSpec, source: VariableSpec): VariableSpec {\n const merged = { ...target };\n\n for (const [varName, varType] of Object.entries(source)) {\n const existing = merged[varName];\n if (existing && existing !== varType) {\n merged[varName] = \"number\";\n } else {\n merged[varName] = varType;\n }\n }\n\n return merged;\n}\n\nexport function variableSpecsEqual(a: VariableSpec, b: VariableSpec): boolean {\n const aKeys = Object.keys(a).sort();\n const bKeys = Object.keys(b).sort();\n\n if (aKeys.length !== bKeys.length) {\n return false;\n }\n\n return aKeys.every((key, index) => {\n if (key !== bKeys[index]) {\n return false;\n }\n return a[key] === b[key];\n });\n}\n","import { parse } from \"@formatjs/icu-messageformat-parser\";\nimport {\n extractVariableMeta,\n variableMetaToSpec,\n type VariableMetaSpec,\n type VariableSpec,\n} from \"./extract-variables.js\";\n\nexport type ParseTemplateSuccess = {\n readonly ok: true;\n readonly args: VariableSpec;\n readonly meta: VariableMetaSpec;\n};\n\nexport type ParseTemplateFailure = {\n readonly ok: false;\n readonly message: string;\n};\n\nexport type ParseTemplateResult = ParseTemplateSuccess | ParseTemplateFailure;\n\nexport function parseTemplate(template: string): ParseTemplateResult {\n try {\n const ast = parse(template);\n const meta = extractVariableMeta(ast);\n return { ok: true, args: variableMetaToSpec(meta), meta };\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n return { ok: false, message };\n }\n}\n","import { mergeVariableMetaAcrossLocales, type VariableMetaSpec } from \"../icu/extract-variables.js\";\nimport { parseTemplate } from \"../icu/parse-template.js\";\nimport type {\n DictionarySpec,\n NormalizedDictionary,\n NormalizedKeyDictionary,\n ParsedKeyEntry,\n ValidationIssue,\n ValidationResult,\n} from \"./types.js\";\n\nfunction failure(issues: ValidationIssue[]): ValidationResult<NormalizedDictionary> {\n return { ok: false, issues };\n}\n\nfunction success(data: NormalizedDictionary): ValidationResult<NormalizedDictionary> {\n return { ok: true, data };\n}\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction normalizeKeyDictionary(\n input: Record<string, unknown>,\n requiredKeys: readonly string[],\n keyPathPrefix: readonly string[]\n): ValidationResult<NormalizedKeyDictionary> {\n const issues: ValidationIssue[] = [];\n const keys: Record<string, ParsedKeyEntry> = {};\n\n for (const key of requiredKeys) {\n if (!(key in input)) {\n issues.push({ kind: \"missing_key\", path: [...keyPathPrefix, key] });\n continue;\n }\n\n const keyValue = input[key];\n if (!isPlainObject(keyValue)) {\n issues.push({\n kind: \"invalid_input\",\n message: `Expected object for key \"${key}\", got ${typeof keyValue}`,\n });\n continue;\n }\n\n const locales: Record<string, ParsedKeyEntry[\"locales\"][string]> = {};\n const localeMetas: VariableMetaSpec[] = [];\n let mergedArgs: ParsedKeyEntry[\"mergedArgs\"] = {};\n let localeArgsMismatch: ValidationIssue | undefined;\n\n for (const [locale, template] of Object.entries(keyValue)) {\n const localePath = [...keyPathPrefix, key, locale];\n\n if (typeof template !== \"string\") {\n issues.push({\n kind: \"invalid_locale_value\",\n path: localePath,\n message: `expected string, got ${typeof template}`,\n });\n continue;\n }\n\n const parsed = parseTemplate(template);\n if (!parsed.ok) {\n issues.push({\n kind: \"icu_syntax_error\",\n path: localePath,\n message: parsed.message,\n });\n continue;\n }\n\n locales[locale] = { template, args: parsed.args };\n localeMetas.push(parsed.meta);\n }\n\n if (localeMetas.length > 0) {\n const merged = mergeVariableMetaAcrossLocales(localeMetas);\n if (!merged.ok) {\n const localeArgs: Record<string, ParsedKeyEntry[\"mergedArgs\"]> = {};\n for (const [loc, entry] of Object.entries(locales)) {\n localeArgs[loc] = entry.args;\n }\n localeArgsMismatch = {\n kind: \"locale_args_mismatch\",\n path: [...keyPathPrefix, key],\n locales: localeArgs,\n message: merged.message,\n };\n } else {\n mergedArgs = merged.merged;\n }\n }\n\n if (localeArgsMismatch) {\n issues.push(localeArgsMismatch);\n }\n\n keys[key] = { locales, mergedArgs };\n }\n\n if (issues.length > 0) {\n return { ok: false, issues };\n }\n\n return { ok: true, data: keys };\n}\n\nexport function normalizeDictionary(\n input: unknown,\n spec: DictionarySpec\n): ValidationResult<NormalizedDictionary> {\n if (!isPlainObject(input)) {\n return failure([\n {\n kind: \"invalid_input\",\n message: `Expected object, got ${input === null ? \"null\" : typeof input}`,\n },\n ]);\n }\n\n if (spec.mode === \"single\") {\n const result = normalizeKeyDictionary(input, spec.requiredKeys, []);\n if (!result.ok) {\n return result;\n }\n\n return success({ mode: \"single\", keys: result.data });\n }\n\n const issues: ValidationIssue[] = [];\n const namespaces: Record<string, NormalizedKeyDictionary> = {};\n\n for (const [namespace, requiredKeys] of Object.entries(spec.requiredKeys)) {\n if (!(namespace in input)) {\n for (const key of requiredKeys) {\n issues.push({ kind: \"missing_key\", path: [namespace, key] });\n }\n continue;\n }\n\n const namespaceValue = input[namespace];\n if (!isPlainObject(namespaceValue)) {\n issues.push({\n kind: \"invalid_input\",\n message: `Expected object for namespace \"${namespace}\", got ${typeof namespaceValue}`,\n });\n continue;\n }\n\n const result = normalizeKeyDictionary(namespaceValue, requiredKeys, [namespace]);\n if (!result.ok) {\n issues.push(...result.issues);\n } else {\n namespaces[namespace] = result.data;\n }\n }\n\n if (issues.length > 0) {\n return failure(issues);\n }\n\n return success({ mode: \"multi\", namespaces });\n}\n","import type { NormalizedDictionary } from \"./types.js\";\n\ntype LocaleDictionary = Record<string, string>;\ntype KeyDictionary = Record<string, LocaleDictionary>;\ntype MultiDictionary = Record<string, KeyDictionary>;\n\nexport function toDictionary(normalized: NormalizedDictionary): KeyDictionary | MultiDictionary {\n if (normalized.mode === \"single\") {\n const dictionary: KeyDictionary = {};\n\n for (const [key, entry] of Object.entries(normalized.keys)) {\n const locales: LocaleDictionary = {};\n for (const [locale, localeEntry] of Object.entries(entry.locales)) {\n locales[locale] = localeEntry.template;\n }\n dictionary[key] = locales;\n }\n\n return dictionary;\n }\n\n const dictionary: MultiDictionary = {};\n\n for (const [namespace, keys] of Object.entries(normalized.namespaces)) {\n const namespaceDictionary: KeyDictionary = {};\n\n for (const [key, entry] of Object.entries(keys)) {\n const locales: LocaleDictionary = {};\n for (const [locale, localeEntry] of Object.entries(entry.locales)) {\n locales[locale] = localeEntry.template;\n }\n namespaceDictionary[key] = locales;\n }\n\n dictionary[namespace] = namespaceDictionary;\n }\n\n return dictionary;\n}\n\nexport function toNamespaceDictionary(\n normalized: NormalizedDictionary,\n namespace: string\n): KeyDictionary {\n if (normalized.mode !== \"multi\") {\n throw new Error(\n `[i18n] toNamespaceDictionary requires multi-mode normalized dictionary, got \"${normalized.mode}\"`\n );\n }\n\n const keys = normalized.namespaces[namespace];\n if (!keys) {\n return {};\n }\n\n const dictionary: KeyDictionary = {};\n\n for (const [key, entry] of Object.entries(keys)) {\n const locales: LocaleDictionary = {};\n for (const [locale, localeEntry] of Object.entries(entry.locales)) {\n locales[locale] = localeEntry.template;\n }\n dictionary[key] = locales;\n }\n\n return dictionary;\n}\n","import { z } from \"zod\";\nimport type { VariableSpec, VariableType } from \"../icu/extract-variables.js\";\n\nexport const variableTypeSchema = z.enum([\"string\", \"number\", \"date\"]);\n\nfunction formatVariableMismatch(expected: VariableSpec, found: VariableSpec): string {\n const expectedKeys = Object.keys(expected).sort();\n const foundKeys = Object.keys(found).sort();\n return `Expected variables [${expectedKeys.join(\", \")}], found [${foundKeys.join(\", \")}]`;\n}\n\nexport function createArgsSchema(expected: VariableSpec): z.ZodType<VariableSpec> {\n return z.record(z.string(), variableTypeSchema).superRefine((found, ctx) => {\n const expectedKeys = Object.keys(expected).sort();\n const foundKeys = Object.keys(found).sort();\n\n if (expectedKeys.join() !== foundKeys.join()) {\n ctx.addIssue({\n code: \"custom\",\n message: formatVariableMismatch(expected, found),\n });\n return;\n }\n\n for (const [name, type] of Object.entries(expected)) {\n if (found[name] !== type) {\n ctx.addIssue({\n code: \"custom\",\n message: `Variable \"${name}\": expected ${type}, found ${found[name]}`,\n });\n }\n }\n });\n}\n\nexport function mapArgsSchemaError(\n expected: VariableSpec,\n found: VariableSpec,\n message: string,\n path: readonly string[]\n) {\n const expectedKeys = Object.keys(expected).sort();\n const foundKeys = Object.keys(found).sort();\n\n if (expectedKeys.join() !== foundKeys.join()) {\n return {\n kind: \"variable_mismatch\" as const,\n path,\n expected,\n found,\n message,\n };\n }\n\n for (const [name, type] of Object.entries(expected)) {\n const foundType = found[name];\n if (foundType !== undefined && foundType !== type) {\n return {\n kind: \"variable_type_mismatch\" as const,\n path,\n variable: name,\n expected: type,\n found: foundType as VariableType,\n message,\n };\n }\n }\n\n return {\n kind: \"variable_mismatch\" as const,\n path,\n expected,\n found,\n message,\n };\n}\n","import { z } from \"zod\";\nimport type { VariableSpec } from \"../icu/extract-variables.js\";\nimport { createArgsSchema, variableTypeSchema } from \"./create-args-schema.js\";\nimport type { DictionarySpec, NormalizedDictionary, ParsedKeyEntry } from \"./types.js\";\n\nconst parsedLocaleEntrySchema = z.object({\n template: z.string(),\n args: z.record(z.string(), variableTypeSchema),\n});\n\nconst parsedKeyEntrySchema = z.object({\n locales: z.record(z.string(), parsedLocaleEntrySchema),\n mergedArgs: z.record(z.string(), variableTypeSchema),\n});\n\nfunction createKeyDictionarySchema(\n argsByKey: Readonly<Record<string, VariableSpec>>\n): z.ZodType<Record<string, ParsedKeyEntry>> {\n const keySchemas = Object.fromEntries(\n Object.entries(argsByKey).map(([key, expectedArgs]) => [\n key,\n parsedKeyEntrySchema.extend({\n mergedArgs: createArgsSchema(expectedArgs),\n }),\n ])\n );\n\n return z.object(keySchemas);\n}\n\nexport function createNormalizedDictionarySchema(\n spec: DictionarySpec\n): z.ZodType<NormalizedDictionary> {\n if (spec.mode === \"single\") {\n return z.object({\n mode: z.literal(\"single\"),\n keys: createKeyDictionarySchema(spec.argsByKey),\n }) as z.ZodType<NormalizedDictionary>;\n }\n\n const namespaceSchemas = Object.fromEntries(\n Object.entries(spec.argsByKey).map(([namespace, argsByKey]) => [\n namespace,\n createKeyDictionarySchema(argsByKey),\n ])\n );\n\n return z.object({\n mode: z.literal(\"multi\"),\n namespaces: z.object(namespaceSchemas),\n }) as z.ZodType<NormalizedDictionary>;\n}\n","import type { ZodError } from \"zod\";\nimport { mapArgsSchemaError } from \"./create-args-schema.js\";\nimport { createNormalizedDictionarySchema } from \"./create-normalized-schema.js\";\nimport type {\n DictionarySpec,\n NormalizedDictionary,\n ValidationIssue,\n ValidationResult,\n VariableSpec,\n} from \"./types.js\";\n\nfunction getExpectedArgs(spec: DictionarySpec, path: readonly string[]): VariableSpec | undefined {\n if (spec.mode === \"single\") {\n const key = path[path.length - 1];\n if (typeof key !== \"string\") {\n return undefined;\n }\n return spec.argsByKey[key];\n }\n\n const namespace = path[0];\n const key = path[path.length - 1];\n if (typeof namespace !== \"string\" || typeof key !== \"string\") {\n return undefined;\n }\n\n return spec.argsByKey[namespace]?.[key];\n}\n\nfunction getFoundArgs(\n normalized: NormalizedDictionary,\n path: readonly string[]\n): VariableSpec | undefined {\n if (normalized.mode === \"single\") {\n const key = path[path.length - 1];\n if (typeof key !== \"string\") {\n return undefined;\n }\n return normalized.keys[key]?.mergedArgs;\n }\n\n const namespace = path[0];\n const key = path[path.length - 1];\n if (typeof namespace !== \"string\" || typeof key !== \"string\") {\n return undefined;\n }\n\n return normalized.namespaces[namespace]?.[key]?.mergedArgs;\n}\n\nfunction mapZodError(\n error: ZodError,\n spec: DictionarySpec,\n normalized: NormalizedDictionary\n): ValidationIssue[] {\n return error.issues.map((issue) => {\n const path = issue.path.map(String);\n\n if (path.includes(\"mergedArgs\")) {\n const keyPath = path.slice(0, path.indexOf(\"mergedArgs\"));\n const expected = getExpectedArgs(spec, keyPath);\n const found = getFoundArgs(normalized, keyPath);\n\n if (expected && found) {\n return mapArgsSchemaError(expected, found, issue.message, keyPath);\n }\n }\n\n return {\n kind: \"invalid_input\" as const,\n message: path.length > 0 ? `${path.join(\".\")}: ${issue.message}` : issue.message,\n };\n });\n}\n\nexport function validateNormalizedDictionary(\n normalized: NormalizedDictionary,\n spec: DictionarySpec\n): ValidationResult<NormalizedDictionary> {\n const schema = createNormalizedDictionarySchema(spec);\n const result = schema.safeParse(normalized);\n\n if (result.success) {\n return { ok: true, data: result.data };\n }\n\n return {\n ok: false,\n issues: mapZodError(result.error, spec, normalized),\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 { normalizeDictionary } from \"./normalize.js\";\nimport { toDictionary, toNamespaceDictionary } from \"./to-dictionary.js\";\nimport type { DictionarySpec, ValidationResult } from \"./types.js\";\nimport { validateNormalizedDictionary } from \"./validate-normalized.js\";\n\nexport function validateExternalDictionary<TSchema>(\n input: unknown,\n spec: DictionarySpec\n): ValidationResult<TSchema> {\n const step1 = normalizeDictionary(input, spec);\n if (!step1.ok) {\n return step1;\n }\n\n const step2 = validateNormalizedDictionary(step1.data, spec);\n if (!step2.ok) {\n return step2;\n }\n\n return { ok: true, data: toDictionary(step2.data) as TSchema };\n}\n\nexport function validateExternalNamespace<TNamespaceSchema>(\n namespace: string,\n input: unknown,\n spec: DictionarySpec\n): ValidationResult<TNamespaceSchema> {\n if (spec.mode !== \"multi\") {\n return {\n ok: false,\n issues: [\n {\n kind: \"invalid_input\",\n message: \"validateExternalNamespace requires multi-mode dictionary spec\",\n },\n ],\n };\n }\n\n const wrapped = { [namespace]: input };\n const namespaceSpec: DictionarySpec = {\n mode: \"multi\",\n requiredKeys: {\n [namespace]: spec.requiredKeys[namespace] ?? [],\n },\n argsByKey: {\n [namespace]: spec.argsByKey[namespace] ?? {},\n },\n };\n\n const step1 = normalizeDictionary(wrapped, namespaceSpec);\n if (!step1.ok) {\n return step1;\n }\n\n const step2 = validateNormalizedDictionary(step1.data, namespaceSpec);\n if (!step2.ok) {\n return step2;\n }\n\n return {\n ok: true,\n data: toNamespaceDictionary(step2.data, namespace) as TNamespaceSchema,\n };\n}\n\nexport { normalizeDictionary, validateNormalizedDictionary, toDictionary, toNamespaceDictionary };\nexport { createArgsSchema } from \"./create-args-schema.js\";\nexport { createNormalizedDictionarySchema } from \"./create-normalized-schema.js\";\nexport { DictionaryValidationError, assertValidDictionary, formatIssues } from \"./errors.js\";\nexport type {\n DictionarySpec,\n NormalizedDictionary,\n NormalizedKeyDictionary,\n NormalizedMultiDictionary,\n ParsedKeyEntry,\n ParsedLocaleEntry,\n ValidationIssue,\n ValidationResult,\n VariableSpec,\n VariableType,\n} from \"./types.js\";\n\nexport type { ParseTemplateResult } from \"../icu/parse-template.js\";\nexport { parseTemplate } from \"../icu/parse-template.js\";\nexport {\n extractVariables,\n mergeVariableSpecs,\n variableSpecsEqual,\n} from \"../icu/extract-variables.js\";\n"]}
|
package/package.json
CHANGED
|
@@ -448,6 +448,128 @@ describe("generate-i18n-types", () => {
|
|
|
448
448
|
expect(result.stderr).toContain("loadOnInit");
|
|
449
449
|
});
|
|
450
450
|
|
|
451
|
+
it("infers number when English plural and Italian simple interpolation share a key", () => {
|
|
452
|
+
tempDir = mkdtempSync(join(tmpdir(), "xndrjs-i18n-codegen-"));
|
|
453
|
+
mkdirSync(join(tempDir, "src/i18n/translations"), { recursive: true });
|
|
454
|
+
|
|
455
|
+
writeFileSync(
|
|
456
|
+
join(tempDir, "src/i18n/translations/translations.json"),
|
|
457
|
+
JSON.stringify({
|
|
458
|
+
invoice_count: {
|
|
459
|
+
en: "You have {count, plural, one {1 invoice} other {# invoices}}",
|
|
460
|
+
it: "Hai {count} fatture",
|
|
461
|
+
},
|
|
462
|
+
})
|
|
463
|
+
);
|
|
464
|
+
writeFileSync(
|
|
465
|
+
join(tempDir, "i18n.codegen.json"),
|
|
466
|
+
JSON.stringify({
|
|
467
|
+
dictionary: "src/i18n/translations/translations.json",
|
|
468
|
+
typesOutput: "src/i18n/i18n-types.generated.ts",
|
|
469
|
+
dictionaryOutput: "src/i18n/dictionary.generated.ts",
|
|
470
|
+
instanceOutput: "src/i18n/instance.generated.ts",
|
|
471
|
+
paramsTypeName: "AppParams",
|
|
472
|
+
schemaTypeName: "AppSchema",
|
|
473
|
+
})
|
|
474
|
+
);
|
|
475
|
+
|
|
476
|
+
const result = runCodegen(tempDir);
|
|
477
|
+
expect(result.status).toBe(0);
|
|
478
|
+
|
|
479
|
+
const types = readFileSync(join(tempDir, "src/i18n/i18n-types.generated.ts"), "utf8");
|
|
480
|
+
expect(types).toContain("invoice_count: { count: number }");
|
|
481
|
+
});
|
|
482
|
+
|
|
483
|
+
it("fails when plural and select disagree on the same variable across locales", () => {
|
|
484
|
+
tempDir = mkdtempSync(join(tmpdir(), "xndrjs-i18n-codegen-"));
|
|
485
|
+
mkdirSync(join(tempDir, "src/i18n/translations"), { recursive: true });
|
|
486
|
+
|
|
487
|
+
writeFileSync(
|
|
488
|
+
join(tempDir, "src/i18n/translations/translations.json"),
|
|
489
|
+
JSON.stringify({
|
|
490
|
+
broken: {
|
|
491
|
+
en: "{myVar, select, other {x}}",
|
|
492
|
+
it: "{myVar, plural, one {1} other {#}}",
|
|
493
|
+
},
|
|
494
|
+
})
|
|
495
|
+
);
|
|
496
|
+
writeFileSync(
|
|
497
|
+
join(tempDir, "i18n.codegen.json"),
|
|
498
|
+
JSON.stringify({
|
|
499
|
+
dictionary: "src/i18n/translations/translations.json",
|
|
500
|
+
typesOutput: "src/i18n/i18n-types.generated.ts",
|
|
501
|
+
dictionaryOutput: "src/i18n/dictionary.generated.ts",
|
|
502
|
+
instanceOutput: "src/i18n/instance.generated.ts",
|
|
503
|
+
paramsTypeName: "AppParams",
|
|
504
|
+
schemaTypeName: "AppSchema",
|
|
505
|
+
})
|
|
506
|
+
);
|
|
507
|
+
|
|
508
|
+
const result = runCodegen(tempDir);
|
|
509
|
+
expect(result.status).not.toBe(0);
|
|
510
|
+
expect(result.stderr).toContain('Incompatible ICU variable "myVar"');
|
|
511
|
+
});
|
|
512
|
+
|
|
513
|
+
it("fails when plural and selectordinal disagree on the same variable across locales", () => {
|
|
514
|
+
tempDir = mkdtempSync(join(tmpdir(), "xndrjs-i18n-codegen-"));
|
|
515
|
+
mkdirSync(join(tempDir, "src/i18n/translations"), { recursive: true });
|
|
516
|
+
|
|
517
|
+
writeFileSync(
|
|
518
|
+
join(tempDir, "src/i18n/translations/translations.json"),
|
|
519
|
+
JSON.stringify({
|
|
520
|
+
broken: {
|
|
521
|
+
en: "{rank, plural, one {1} other {#}}",
|
|
522
|
+
it: "{rank, selectordinal, one {#°} other {#°}}",
|
|
523
|
+
},
|
|
524
|
+
})
|
|
525
|
+
);
|
|
526
|
+
writeFileSync(
|
|
527
|
+
join(tempDir, "i18n.codegen.json"),
|
|
528
|
+
JSON.stringify({
|
|
529
|
+
dictionary: "src/i18n/translations/translations.json",
|
|
530
|
+
typesOutput: "src/i18n/i18n-types.generated.ts",
|
|
531
|
+
dictionaryOutput: "src/i18n/dictionary.generated.ts",
|
|
532
|
+
instanceOutput: "src/i18n/instance.generated.ts",
|
|
533
|
+
paramsTypeName: "AppParams",
|
|
534
|
+
schemaTypeName: "AppSchema",
|
|
535
|
+
})
|
|
536
|
+
);
|
|
537
|
+
|
|
538
|
+
const result = runCodegen(tempDir);
|
|
539
|
+
expect(result.status).not.toBe(0);
|
|
540
|
+
expect(result.stderr).toContain('Incompatible ICU variable "rank"');
|
|
541
|
+
});
|
|
542
|
+
|
|
543
|
+
it("fails when select and number format disagree on the same variable across locales", () => {
|
|
544
|
+
tempDir = mkdtempSync(join(tmpdir(), "xndrjs-i18n-codegen-"));
|
|
545
|
+
mkdirSync(join(tempDir, "src/i18n/translations"), { recursive: true });
|
|
546
|
+
|
|
547
|
+
writeFileSync(
|
|
548
|
+
join(tempDir, "src/i18n/translations/translations.json"),
|
|
549
|
+
JSON.stringify({
|
|
550
|
+
broken: {
|
|
551
|
+
en: "{value, select, other {x}}",
|
|
552
|
+
it: "{value, number}",
|
|
553
|
+
},
|
|
554
|
+
})
|
|
555
|
+
);
|
|
556
|
+
writeFileSync(
|
|
557
|
+
join(tempDir, "i18n.codegen.json"),
|
|
558
|
+
JSON.stringify({
|
|
559
|
+
dictionary: "src/i18n/translations/translations.json",
|
|
560
|
+
typesOutput: "src/i18n/i18n-types.generated.ts",
|
|
561
|
+
dictionaryOutput: "src/i18n/dictionary.generated.ts",
|
|
562
|
+
instanceOutput: "src/i18n/instance.generated.ts",
|
|
563
|
+
paramsTypeName: "AppParams",
|
|
564
|
+
schemaTypeName: "AppSchema",
|
|
565
|
+
})
|
|
566
|
+
);
|
|
567
|
+
|
|
568
|
+
const result = runCodegen(tempDir);
|
|
569
|
+
expect(result.status).not.toBe(0);
|
|
570
|
+
expect(result.stderr).toContain('Incompatible ICU variable "value"');
|
|
571
|
+
});
|
|
572
|
+
|
|
451
573
|
it("keeps multi mode when namespaces has a single entry", () => {
|
|
452
574
|
tempDir = mkdtempSync(join(tmpdir(), "xndrjs-i18n-codegen-"));
|
|
453
575
|
mkdirSync(join(tempDir, "src/i18n/translations"), { recursive: true });
|
|
@@ -2,8 +2,8 @@ import fs from "node:fs";
|
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { parse } from "@formatjs/icu-messageformat-parser";
|
|
4
4
|
import {
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
extractVariableMeta,
|
|
6
|
+
mergeVariableMetaAcrossLocales,
|
|
7
7
|
type VariableSpec,
|
|
8
8
|
} from "../icu/extract-variables.js";
|
|
9
9
|
import type { DictionaryJson, NamespaceEntry } from "./types.js";
|
|
@@ -52,7 +52,7 @@ export function analyzeDictionaries(
|
|
|
52
52
|
argsSpecByNamespace[entry.namespace] = {};
|
|
53
53
|
|
|
54
54
|
for (const [key, localesByKey] of Object.entries(dictionary)) {
|
|
55
|
-
const
|
|
55
|
+
const localeMetas: ReturnType<typeof extractVariableMeta>[] = [];
|
|
56
56
|
|
|
57
57
|
for (const locale of Object.keys(localesByKey)) {
|
|
58
58
|
locales.add(locale);
|
|
@@ -61,8 +61,7 @@ export function analyzeDictionaries(
|
|
|
61
61
|
for (const [locale, template] of Object.entries(localesByKey)) {
|
|
62
62
|
try {
|
|
63
63
|
const ast = parse(template);
|
|
64
|
-
|
|
65
|
-
Object.assign(variables, mergeVariableSpecs(variables, extracted));
|
|
64
|
+
localeMetas.push(extractVariableMeta(ast));
|
|
66
65
|
} catch (error) {
|
|
67
66
|
hasErrors = true;
|
|
68
67
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -72,6 +71,21 @@ export function analyzeDictionaries(
|
|
|
72
71
|
}
|
|
73
72
|
}
|
|
74
73
|
|
|
74
|
+
if (localeMetas.length === 0) {
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const mergedVariables = mergeVariableMetaAcrossLocales(localeMetas);
|
|
79
|
+
if (!mergedVariables.ok) {
|
|
80
|
+
hasErrors = true;
|
|
81
|
+
console.error(
|
|
82
|
+
`[Codegen Error] ${mergedVariables.message} — namespace "${entry.namespace}", key "${key}"`
|
|
83
|
+
);
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const variables = mergedVariables.merged;
|
|
88
|
+
|
|
75
89
|
paramsByNamespace[entry.namespace]![key] = paramsTypeForVariables(variables);
|
|
76
90
|
argsSpecByNamespace[entry.namespace]![key] = variables;
|
|
77
91
|
}
|
|
@@ -89,7 +89,7 @@ describe("ensureNamespacesLoadedImpl", () => {
|
|
|
89
89
|
const options = {
|
|
90
90
|
provider,
|
|
91
91
|
resolveLoader: () => load,
|
|
92
|
-
validate: () => ({ ok: true, data: billingNs }),
|
|
92
|
+
validate: () => ({ ok: true, data: billingNs }) as const,
|
|
93
93
|
};
|
|
94
94
|
|
|
95
95
|
const first = ensureNamespacesLoadedImpl(options, ["billing"]);
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
import { parse } from "@formatjs/icu-messageformat-parser";
|
|
2
|
+
import { describe, expect, it } from "vitest";
|
|
3
|
+
import {
|
|
4
|
+
extractVariableMeta,
|
|
5
|
+
inferMergedVariableType,
|
|
6
|
+
mergeVariableMetaAcrossLocales,
|
|
7
|
+
variableMetaToSpec,
|
|
8
|
+
type VariableRole,
|
|
9
|
+
} from "./extract-variables.js";
|
|
10
|
+
|
|
11
|
+
function metaFor(template: string) {
|
|
12
|
+
return extractVariableMeta(parse(template));
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function mergeTemplates(...templates: string[]) {
|
|
16
|
+
return mergeVariableMetaAcrossLocales(templates.map((template) => metaFor(template)));
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function roles(...items: VariableRole[]) {
|
|
20
|
+
return new Set(items);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
describe("inferMergedVariableType", () => {
|
|
24
|
+
describe("string family", () => {
|
|
25
|
+
it.each<[VariableRole[], "string"]>([
|
|
26
|
+
[["simple"], "string"],
|
|
27
|
+
[["select"], "string"],
|
|
28
|
+
[["simple", "select"], "string"],
|
|
29
|
+
])("accepts %j → %s", (items, expected) => {
|
|
30
|
+
expect(inferMergedVariableType(roles(...items))).toBe(expected);
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
describe("number family", () => {
|
|
35
|
+
it.each<[VariableRole[], "number"]>([
|
|
36
|
+
[["plural"], "number"],
|
|
37
|
+
[["selectordinal"], "number"],
|
|
38
|
+
[["number"], "number"],
|
|
39
|
+
[["simple", "plural"], "number"],
|
|
40
|
+
[["simple", "selectordinal"], "number"],
|
|
41
|
+
[["simple", "number"], "number"],
|
|
42
|
+
[["plural", "number"], "number"],
|
|
43
|
+
[["selectordinal", "number"], "number"],
|
|
44
|
+
])("accepts %j → %s", (items, expected) => {
|
|
45
|
+
expect(inferMergedVariableType(roles(...items))).toBe(expected);
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
describe("date family", () => {
|
|
50
|
+
it.each<[VariableRole[], "date"]>([
|
|
51
|
+
[["date"], "date"],
|
|
52
|
+
[["time"], "date"],
|
|
53
|
+
[["date", "time"], "date"],
|
|
54
|
+
])("accepts %j → %s", (items, expected) => {
|
|
55
|
+
expect(inferMergedVariableType(roles(...items))).toBe(expected);
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
describe("conflicts", () => {
|
|
60
|
+
it.each<{ label: string; roles: VariableRole[] }>([
|
|
61
|
+
{ label: "plural + select", roles: ["plural", "select"] },
|
|
62
|
+
{ label: "selectordinal + select", roles: ["selectordinal", "select"] },
|
|
63
|
+
{ label: "plural + selectordinal", roles: ["plural", "selectordinal"] },
|
|
64
|
+
{ label: "select + number", roles: ["select", "number"] },
|
|
65
|
+
{ label: "select + simple + number", roles: ["select", "simple", "number"] },
|
|
66
|
+
{ label: "plural + simple + select", roles: ["plural", "simple", "select"] },
|
|
67
|
+
{ label: "date + simple", roles: ["date", "simple"] },
|
|
68
|
+
{ label: "date + plural", roles: ["date", "plural"] },
|
|
69
|
+
{ label: "time + select", roles: ["time", "select"] },
|
|
70
|
+
{ label: "date + number", roles: ["date", "number"] },
|
|
71
|
+
])("rejects $label", ({ roles: roleList }) => {
|
|
72
|
+
expect(inferMergedVariableType(roles(...roleList))).toBe("CONFLICT");
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
describe("mergeVariableMetaAcrossLocales", () => {
|
|
78
|
+
describe("compatible cross-locale merges", () => {
|
|
79
|
+
it("infers string for simple interpolation in every locale", () => {
|
|
80
|
+
const result = mergeTemplates("Welcome {name}!", "Benvenuto {name}!");
|
|
81
|
+
|
|
82
|
+
expect(result).toEqual({ ok: true, merged: { name: "string" } });
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it("infers string when one locale uses select and another uses simple interpolation", () => {
|
|
86
|
+
const result = mergeTemplates(
|
|
87
|
+
"{gender, select, female {she} other {they}}",
|
|
88
|
+
"Pronome: {gender}"
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
expect(result).toEqual({ ok: true, merged: { gender: "string" } });
|
|
92
|
+
expect(variableMetaToSpec(metaFor("Pronome: {gender}"))).toEqual({ gender: "string" });
|
|
93
|
+
expect(variableMetaToSpec(metaFor("{gender, select, other {x}}"))).toEqual({
|
|
94
|
+
gender: "string",
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it("infers number when English uses plural and Italian uses simple interpolation", () => {
|
|
99
|
+
const result = mergeTemplates(
|
|
100
|
+
"You have {count, plural, one {1 invoice} other {# invoices}}",
|
|
101
|
+
"Hai {count} fatture"
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
expect(result).toEqual({ ok: true, merged: { count: "number" } });
|
|
105
|
+
expect(variableMetaToSpec(metaFor("Hai {count} fatture"))).toEqual({ count: "string" });
|
|
106
|
+
expect(
|
|
107
|
+
variableMetaToSpec(metaFor("You have {count, plural, one {1 invoice} other {# invoices}}"))
|
|
108
|
+
).toEqual({ count: "number" });
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it("infers number when one locale uses selectordinal and another uses simple interpolation", () => {
|
|
112
|
+
const result = mergeTemplates(
|
|
113
|
+
"You finished {position, selectordinal, one {#st} other {#th}}",
|
|
114
|
+
"Sei al {position} posto"
|
|
115
|
+
);
|
|
116
|
+
|
|
117
|
+
expect(result).toEqual({ ok: true, merged: { position: "number" } });
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it("infers number when one locale uses plural and another uses number format", () => {
|
|
121
|
+
const result = mergeTemplates(
|
|
122
|
+
"{amount, plural, one {1} other {#}}",
|
|
123
|
+
"Totale: {amount, number}"
|
|
124
|
+
);
|
|
125
|
+
|
|
126
|
+
expect(result).toEqual({ ok: true, merged: { amount: "number" } });
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it("infers date when locales use date and time on the same variable", () => {
|
|
130
|
+
const result = mergeTemplates("{due, date, short}", "{due, time, short}");
|
|
131
|
+
|
|
132
|
+
expect(result).toEqual({ ok: true, merged: { due: "date" } });
|
|
133
|
+
});
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
describe("incompatible cross-locale merges", () => {
|
|
137
|
+
it("rejects plural and select on the same variable", () => {
|
|
138
|
+
const result = mergeTemplates(
|
|
139
|
+
"{myVar, select, other {x}}",
|
|
140
|
+
"{myVar, plural, one {1} other {#}}"
|
|
141
|
+
);
|
|
142
|
+
|
|
143
|
+
expect(result.ok).toBe(false);
|
|
144
|
+
if (!result.ok) {
|
|
145
|
+
expect(result.message).toContain('Incompatible ICU variable "myVar"');
|
|
146
|
+
}
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
it("rejects plural and selectordinal on the same variable", () => {
|
|
150
|
+
const result = mergeTemplates(
|
|
151
|
+
"{rank, plural, one {1} other {#}}",
|
|
152
|
+
"{rank, selectordinal, one {#st} other {#th}}"
|
|
153
|
+
);
|
|
154
|
+
|
|
155
|
+
expect(result.ok).toBe(false);
|
|
156
|
+
if (!result.ok) {
|
|
157
|
+
expect(result.message).toContain('Incompatible ICU variable "rank"');
|
|
158
|
+
expect(result.message).toContain("plural");
|
|
159
|
+
expect(result.message).toContain("selectordinal");
|
|
160
|
+
}
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
it("rejects select and number format on the same variable", () => {
|
|
164
|
+
const result = mergeTemplates("{value, select, other {x}}", "Valore: {value, number}");
|
|
165
|
+
|
|
166
|
+
expect(result.ok).toBe(false);
|
|
167
|
+
if (!result.ok) {
|
|
168
|
+
expect(result.message).toContain('Incompatible ICU variable "value"');
|
|
169
|
+
}
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
it("rejects date and simple interpolation on the same variable", () => {
|
|
173
|
+
const result = mergeTemplates("{due, date, short}", "Scadenza {due}");
|
|
174
|
+
|
|
175
|
+
expect(result.ok).toBe(false);
|
|
176
|
+
if (!result.ok) {
|
|
177
|
+
expect(result.message).toContain('Incompatible ICU variable "due"');
|
|
178
|
+
}
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
it("rejects different variable names across locales", () => {
|
|
182
|
+
const result = mergeTemplates("Welcome {name}!", "Benvenuto {nome}!");
|
|
183
|
+
|
|
184
|
+
expect(result.ok).toBe(false);
|
|
185
|
+
if (!result.ok) {
|
|
186
|
+
expect(result.message).toContain("Inconsistent ICU variable names across locales");
|
|
187
|
+
}
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
it("rejects when one locale omits a variable present in another", () => {
|
|
191
|
+
const result = mergeTemplates("Welcome {name}!", "{name} and {count}");
|
|
192
|
+
|
|
193
|
+
expect(result.ok).toBe(false);
|
|
194
|
+
if (!result.ok) {
|
|
195
|
+
expect(result.message).toContain("Inconsistent ICU variable names across locales");
|
|
196
|
+
}
|
|
197
|
+
});
|
|
198
|
+
});
|
|
199
|
+
});
|
|
@@ -3,40 +3,79 @@ import type { parse } from "@formatjs/icu-messageformat-parser";
|
|
|
3
3
|
export type VariableType = "string" | "number" | "date";
|
|
4
4
|
export type VariableSpec = Record<string, VariableType>;
|
|
5
5
|
|
|
6
|
+
export type VariableRole =
|
|
7
|
+
| "simple"
|
|
8
|
+
| "plural"
|
|
9
|
+
| "selectordinal"
|
|
10
|
+
| "select"
|
|
11
|
+
| "number"
|
|
12
|
+
| "date"
|
|
13
|
+
| "time";
|
|
14
|
+
|
|
15
|
+
export type VariableMeta = {
|
|
16
|
+
type: VariableType;
|
|
17
|
+
roles: Set<VariableRole>;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export type VariableMetaSpec = Record<string, VariableMeta>;
|
|
21
|
+
|
|
6
22
|
type IcuAst = ReturnType<typeof parse>;
|
|
7
23
|
type IcuNode = IcuAst[number];
|
|
8
24
|
|
|
9
|
-
function
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
25
|
+
function isPluralOrOrdinalNode(node: IcuNode): node is Extract<IcuNode, { type: 6 }> {
|
|
26
|
+
return node.type === 6 && "pluralType" in node && Boolean(node.pluralType);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function hasVariableName(node: IcuNode): node is Extract<IcuNode, { value: string }> {
|
|
30
|
+
return "value" in node && typeof node.value === "string";
|
|
31
|
+
}
|
|
13
32
|
|
|
14
|
-
|
|
15
|
-
|
|
33
|
+
function addVariableMeta(
|
|
34
|
+
variables: VariableMetaSpec,
|
|
35
|
+
name: string,
|
|
36
|
+
type: VariableType,
|
|
37
|
+
role: VariableRole
|
|
38
|
+
): void {
|
|
39
|
+
const existing = variables[name];
|
|
40
|
+
if (!existing) {
|
|
41
|
+
variables[name] = { type, roles: new Set([role]) };
|
|
42
|
+
return;
|
|
16
43
|
}
|
|
17
44
|
|
|
18
|
-
|
|
45
|
+
existing.roles.add(role);
|
|
46
|
+
existing.type = mergeVariableTypes(existing.type, type);
|
|
19
47
|
}
|
|
20
48
|
|
|
21
|
-
function
|
|
22
|
-
|
|
49
|
+
function mergeVariableTypes(left: VariableType, right: VariableType): VariableType {
|
|
50
|
+
if (left === right) {
|
|
51
|
+
return left;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if ((left === "string" && right === "number") || (left === "number" && right === "string")) {
|
|
55
|
+
return "number";
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return right;
|
|
23
59
|
}
|
|
24
60
|
|
|
25
|
-
export function
|
|
26
|
-
const variables:
|
|
61
|
+
export function extractVariableMeta(nodes: IcuAst): VariableMetaSpec {
|
|
62
|
+
const variables: VariableMetaSpec = {};
|
|
27
63
|
|
|
28
64
|
const walk = (walkNodes: IcuAst) => {
|
|
29
65
|
for (const node of walkNodes) {
|
|
30
|
-
if (node.type === 1) {
|
|
31
|
-
variables
|
|
32
|
-
} else if (
|
|
33
|
-
variables
|
|
34
|
-
} else if ((node
|
|
35
|
-
|
|
66
|
+
if (node.type === 1 && hasVariableName(node)) {
|
|
67
|
+
addVariableMeta(variables, node.value, "string", "simple");
|
|
68
|
+
} else if (node.type === 2 && hasVariableName(node)) {
|
|
69
|
+
addVariableMeta(variables, node.value, "number", "number");
|
|
70
|
+
} else if (isPluralOrOrdinalNode(node) && hasVariableName(node)) {
|
|
71
|
+
const role = node.pluralType === "ordinal" ? "selectordinal" : "plural";
|
|
72
|
+
addVariableMeta(variables, node.value, "number", role);
|
|
73
|
+
} else if (node.type === 3 && hasVariableName(node)) {
|
|
74
|
+
addVariableMeta(variables, node.value, "date", "date");
|
|
75
|
+
} else if (node.type === 4 && hasVariableName(node)) {
|
|
76
|
+
addVariableMeta(variables, node.value, "date", "time");
|
|
36
77
|
} else if (node.type === 5 && hasVariableName(node)) {
|
|
37
|
-
variables
|
|
38
|
-
} else if (node.type === 6 && hasVariableName(node)) {
|
|
39
|
-
variables[node.value] = variables[node.value] ?? "string";
|
|
78
|
+
addVariableMeta(variables, node.value, "string", "select");
|
|
40
79
|
}
|
|
41
80
|
|
|
42
81
|
if ("options" in node && node.options) {
|
|
@@ -55,6 +94,107 @@ export function extractVariables(nodes: IcuAst): VariableSpec {
|
|
|
55
94
|
return variables;
|
|
56
95
|
}
|
|
57
96
|
|
|
97
|
+
export function variableMetaToSpec(meta: VariableMetaSpec): VariableSpec {
|
|
98
|
+
return Object.fromEntries(Object.entries(meta).map(([name, entry]) => [name, entry.type]));
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export function extractVariables(nodes: IcuAst): VariableSpec {
|
|
102
|
+
return variableMetaToSpec(extractVariableMeta(nodes));
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export function inferMergedVariableType(roles: Set<VariableRole>): VariableType | "CONFLICT" {
|
|
106
|
+
const hasPlural = roles.has("plural");
|
|
107
|
+
const hasSelectordinal = roles.has("selectordinal");
|
|
108
|
+
const hasSelect = roles.has("select");
|
|
109
|
+
const hasSimple = roles.has("simple");
|
|
110
|
+
const hasNumber = roles.has("number");
|
|
111
|
+
const hasDate = roles.has("date");
|
|
112
|
+
const hasTime = roles.has("time");
|
|
113
|
+
|
|
114
|
+
// Incompatible numeric ICU constructs on the same variable.
|
|
115
|
+
if (hasPlural && hasSelectordinal) {
|
|
116
|
+
return "CONFLICT";
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Select (string-family) vs plural / ordinal / number-format (number-family).
|
|
120
|
+
if (hasSelect && (hasPlural || hasSelectordinal || hasNumber)) {
|
|
121
|
+
return "CONFLICT";
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Legacy explicit pairs — covered by the rule above, kept for clarity.
|
|
125
|
+
if (hasPlural && hasSelect) {
|
|
126
|
+
return "CONFLICT";
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (hasSelectordinal && hasSelect) {
|
|
130
|
+
return "CONFLICT";
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (hasDate || hasTime) {
|
|
134
|
+
if (hasPlural || hasSelect || hasSelectordinal || hasSimple || hasNumber) {
|
|
135
|
+
return "CONFLICT";
|
|
136
|
+
}
|
|
137
|
+
return "date";
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (hasPlural || hasSelectordinal || hasNumber) {
|
|
141
|
+
return "number";
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (hasSelect || hasSimple) {
|
|
145
|
+
return "string";
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return "string";
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export function mergeVariableMetaAcrossLocales(
|
|
152
|
+
localeMetas: readonly VariableMetaSpec[]
|
|
153
|
+
): { ok: true; merged: VariableSpec } | { ok: false; message: string } {
|
|
154
|
+
const keySignatures = localeMetas.map((meta) => Object.keys(meta).sort().join(","));
|
|
155
|
+
if (new Set(keySignatures).size > 1) {
|
|
156
|
+
return {
|
|
157
|
+
ok: false,
|
|
158
|
+
message: `Inconsistent ICU variable names across locales (keys: ${keySignatures.join(" vs ")})`,
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const merged: VariableSpec = {};
|
|
163
|
+
const variableNames = new Set(localeMetas.flatMap((meta) => Object.keys(meta)));
|
|
164
|
+
|
|
165
|
+
for (const variableName of variableNames) {
|
|
166
|
+
const combinedRoles = new Set<VariableRole>();
|
|
167
|
+
|
|
168
|
+
for (const meta of localeMetas) {
|
|
169
|
+
for (const role of meta[variableName]?.roles ?? []) {
|
|
170
|
+
combinedRoles.add(role);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const mergedType = inferMergedVariableType(combinedRoles);
|
|
175
|
+
if (mergedType === "CONFLICT") {
|
|
176
|
+
const rolesByLocale = localeMetas
|
|
177
|
+
.map((meta) => {
|
|
178
|
+
const entry = meta[variableName];
|
|
179
|
+
if (!entry) {
|
|
180
|
+
return null;
|
|
181
|
+
}
|
|
182
|
+
return [...entry.roles].sort().join("|");
|
|
183
|
+
})
|
|
184
|
+
.filter((value): value is string => value !== null);
|
|
185
|
+
|
|
186
|
+
return {
|
|
187
|
+
ok: false,
|
|
188
|
+
message: `Incompatible ICU variable "${variableName}" across locales (roles: ${rolesByLocale.join(" vs ")})`,
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
merged[variableName] = mergedType;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
return { ok: true, merged };
|
|
196
|
+
}
|
|
197
|
+
|
|
58
198
|
export function mergeVariableSpecs(target: VariableSpec, source: VariableSpec): VariableSpec {
|
|
59
199
|
const merged = { ...target };
|
|
60
200
|
|
|
@@ -1,9 +1,15 @@
|
|
|
1
1
|
import { parse } from "@formatjs/icu-messageformat-parser";
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
extractVariableMeta,
|
|
4
|
+
variableMetaToSpec,
|
|
5
|
+
type VariableMetaSpec,
|
|
6
|
+
type VariableSpec,
|
|
7
|
+
} from "./extract-variables.js";
|
|
3
8
|
|
|
4
9
|
export type ParseTemplateSuccess = {
|
|
5
10
|
readonly ok: true;
|
|
6
11
|
readonly args: VariableSpec;
|
|
12
|
+
readonly meta: VariableMetaSpec;
|
|
7
13
|
};
|
|
8
14
|
|
|
9
15
|
export type ParseTemplateFailure = {
|
|
@@ -16,7 +22,8 @@ export type ParseTemplateResult = ParseTemplateSuccess | ParseTemplateFailure;
|
|
|
16
22
|
export function parseTemplate(template: string): ParseTemplateResult {
|
|
17
23
|
try {
|
|
18
24
|
const ast = parse(template);
|
|
19
|
-
|
|
25
|
+
const meta = extractVariableMeta(ast);
|
|
26
|
+
return { ok: true, args: variableMetaToSpec(meta), meta };
|
|
20
27
|
} catch (error) {
|
|
21
28
|
const message = error instanceof Error ? error.message : String(error);
|
|
22
29
|
return { ok: false, message };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { mergeVariableMetaAcrossLocales, type VariableMetaSpec } from "../icu/extract-variables.js";
|
|
2
2
|
import { parseTemplate } from "../icu/parse-template.js";
|
|
3
3
|
import type {
|
|
4
4
|
DictionarySpec,
|
|
@@ -45,8 +45,9 @@ function normalizeKeyDictionary(
|
|
|
45
45
|
}
|
|
46
46
|
|
|
47
47
|
const locales: Record<string, ParsedKeyEntry["locales"][string]> = {};
|
|
48
|
+
const localeMetas: VariableMetaSpec[] = [];
|
|
48
49
|
let mergedArgs: ParsedKeyEntry["mergedArgs"] = {};
|
|
49
|
-
let
|
|
50
|
+
let localeArgsMismatch: ValidationIssue | undefined;
|
|
50
51
|
|
|
51
52
|
for (const [locale, template] of Object.entries(keyValue)) {
|
|
52
53
|
const localePath = [...keyPathPrefix, key, locale];
|
|
@@ -71,25 +72,31 @@ function normalizeKeyDictionary(
|
|
|
71
72
|
}
|
|
72
73
|
|
|
73
74
|
locales[locale] = { template, args: parsed.args };
|
|
75
|
+
localeMetas.push(parsed.meta);
|
|
76
|
+
}
|
|
74
77
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
} else if (!variableSpecsEqual(mergedArgs, parsed.args)) {
|
|
78
|
+
if (localeMetas.length > 0) {
|
|
79
|
+
const merged = mergeVariableMetaAcrossLocales(localeMetas);
|
|
80
|
+
if (!merged.ok) {
|
|
79
81
|
const localeArgs: Record<string, ParsedKeyEntry["mergedArgs"]> = {};
|
|
80
82
|
for (const [loc, entry] of Object.entries(locales)) {
|
|
81
83
|
localeArgs[loc] = entry.args;
|
|
82
84
|
}
|
|
83
|
-
|
|
85
|
+
localeArgsMismatch = {
|
|
84
86
|
kind: "locale_args_mismatch",
|
|
85
87
|
path: [...keyPathPrefix, key],
|
|
86
88
|
locales: localeArgs,
|
|
87
|
-
message:
|
|
88
|
-
}
|
|
89
|
-
|
|
89
|
+
message: merged.message,
|
|
90
|
+
};
|
|
91
|
+
} else {
|
|
92
|
+
mergedArgs = merged.merged;
|
|
90
93
|
}
|
|
91
94
|
}
|
|
92
95
|
|
|
96
|
+
if (localeArgsMismatch) {
|
|
97
|
+
issues.push(localeArgsMismatch);
|
|
98
|
+
}
|
|
99
|
+
|
|
93
100
|
keys[key] = { locales, mergedArgs };
|
|
94
101
|
}
|
|
95
102
|
|
|
@@ -87,6 +87,110 @@ describe("normalizeDictionary", () => {
|
|
|
87
87
|
expect(result.issues[0]?.kind).toBe("locale_args_mismatch");
|
|
88
88
|
}
|
|
89
89
|
});
|
|
90
|
+
|
|
91
|
+
it("accepts plural in one locale and simple interpolation in another for the same variable", () => {
|
|
92
|
+
const result = normalizeDictionary(
|
|
93
|
+
{
|
|
94
|
+
login_button: { en: "Login" },
|
|
95
|
+
welcome: { en: "Welcome {name}!" },
|
|
96
|
+
invoice_count: {
|
|
97
|
+
en: "You have {count, plural, one {1 invoice} other {# invoices}}",
|
|
98
|
+
it: "Hai {count} fatture",
|
|
99
|
+
},
|
|
100
|
+
},
|
|
101
|
+
spec
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
expect(result.ok).toBe(true);
|
|
105
|
+
if (result.ok && result.data.mode === "single") {
|
|
106
|
+
expect(result.data.keys.invoice_count?.mergedArgs).toEqual({ count: "number" });
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it("rejects plural and select on the same variable across locales", () => {
|
|
111
|
+
const result = normalizeDictionary(
|
|
112
|
+
{
|
|
113
|
+
login_button: { en: "Login" },
|
|
114
|
+
welcome: { en: "Welcome {name}!" },
|
|
115
|
+
invoice_count: {
|
|
116
|
+
en: "{count, select, other {{count}}}",
|
|
117
|
+
it: "{count, plural, one {1} other {#}}",
|
|
118
|
+
},
|
|
119
|
+
},
|
|
120
|
+
spec
|
|
121
|
+
);
|
|
122
|
+
|
|
123
|
+
expect(result.ok).toBe(false);
|
|
124
|
+
if (!result.ok) {
|
|
125
|
+
expect(result.issues.some((issue) => issue.kind === "locale_args_mismatch")).toBe(true);
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it("rejects plural and selectordinal on the same variable across locales", () => {
|
|
130
|
+
const result = normalizeDictionary(
|
|
131
|
+
{
|
|
132
|
+
login_button: { en: "Login" },
|
|
133
|
+
welcome: { en: "Welcome {name}!" },
|
|
134
|
+
invoice_count: {
|
|
135
|
+
en: "{count, plural, one {1} other {#}}",
|
|
136
|
+
it: "{count, selectordinal, one {#°} other {#°}}",
|
|
137
|
+
},
|
|
138
|
+
},
|
|
139
|
+
spec
|
|
140
|
+
);
|
|
141
|
+
|
|
142
|
+
expect(result.ok).toBe(false);
|
|
143
|
+
if (!result.ok) {
|
|
144
|
+
expect(result.issues.some((issue) => issue.kind === "locale_args_mismatch")).toBe(true);
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
it("rejects select and number format on the same variable across locales", () => {
|
|
149
|
+
const result = normalizeDictionary(
|
|
150
|
+
{
|
|
151
|
+
login_button: { en: "Login" },
|
|
152
|
+
welcome: { en: "Welcome {name}!" },
|
|
153
|
+
invoice_count: {
|
|
154
|
+
en: "{count, select, other {x}}",
|
|
155
|
+
it: "{count, number}",
|
|
156
|
+
},
|
|
157
|
+
},
|
|
158
|
+
spec
|
|
159
|
+
);
|
|
160
|
+
|
|
161
|
+
expect(result.ok).toBe(false);
|
|
162
|
+
if (!result.ok) {
|
|
163
|
+
expect(result.issues.some((issue) => issue.kind === "locale_args_mismatch")).toBe(true);
|
|
164
|
+
}
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
it("accepts select in one locale and simple interpolation in another", () => {
|
|
168
|
+
const result = normalizeDictionary(
|
|
169
|
+
{
|
|
170
|
+
login_button: { en: "Login" },
|
|
171
|
+
welcome: {
|
|
172
|
+
en: "{gender, select, female {she} other {they}}",
|
|
173
|
+
it: "Pronome: {gender}",
|
|
174
|
+
},
|
|
175
|
+
invoice_count: {
|
|
176
|
+
en: "You have {count, plural, one {1 invoice} other {# invoices}}",
|
|
177
|
+
},
|
|
178
|
+
},
|
|
179
|
+
{
|
|
180
|
+
...spec,
|
|
181
|
+
requiredKeys: ["login_button", "welcome", "invoice_count"],
|
|
182
|
+
argsByKey: {
|
|
183
|
+
...spec.argsByKey,
|
|
184
|
+
welcome: { gender: "string" },
|
|
185
|
+
},
|
|
186
|
+
}
|
|
187
|
+
);
|
|
188
|
+
|
|
189
|
+
expect(result.ok).toBe(true);
|
|
190
|
+
if (result.ok && result.data.mode === "single") {
|
|
191
|
+
expect(result.data.keys.welcome?.mergedArgs).toEqual({ gender: "string" });
|
|
192
|
+
}
|
|
193
|
+
});
|
|
90
194
|
});
|
|
91
195
|
|
|
92
196
|
describe("validateNormalizedDictionary", () => {
|