@xndrjs/i18n 0.8.0 → 0.8.2-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (94) 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/dist/index.d.ts +20 -1
  11. package/dist/index.js +56 -10
  12. package/dist/index.js.map +1 -1
  13. package/package.json +6 -9
  14. package/bin/audit.mjs +0 -14
  15. package/bin/codegen.mjs +0 -17
  16. package/bin/setup.mjs +0 -14
  17. package/src/IcuTranslationProviderMulti.test.ts +0 -215
  18. package/src/IcuTranslationProviderMulti.ts +0 -176
  19. package/src/audit/audit-dictionaries.test.ts +0 -134
  20. package/src/audit/audit-dictionaries.ts +0 -178
  21. package/src/audit/run-audit.test.ts +0 -169
  22. package/src/audit/run-audit.ts +0 -94
  23. package/src/builder-load-registry.ts +0 -39
  24. package/src/builder-loaders.ts +0 -15
  25. package/src/builder-types.test.ts +0 -49
  26. package/src/builder-types.ts +0 -24
  27. package/src/builder.test.ts +0 -260
  28. package/src/builder.ts +0 -10
  29. package/src/codegen/codegen-config-schema.ts +0 -156
  30. package/src/codegen/config.test.ts +0 -231
  31. package/src/codegen/config.ts +0 -42
  32. package/src/codegen/constants.ts +0 -9
  33. package/src/codegen/delivery-artifacts.test.ts +0 -167
  34. package/src/codegen/delivery-artifacts.ts +0 -154
  35. package/src/codegen/dictionary-spec-contract.test.ts +0 -28
  36. package/src/codegen/dictionary-spec-contract.ts +0 -138
  37. package/src/codegen/emit/dictionary-schema-file.ts +0 -113
  38. package/src/codegen/emit/instance-file.test.ts +0 -71
  39. package/src/codegen/emit/instance-file.ts +0 -257
  40. package/src/codegen/emit/namespace-loaders-file.test.ts +0 -251
  41. package/src/codegen/emit/namespace-loaders-file.ts +0 -229
  42. package/src/codegen/emit/types-file.test.ts +0 -96
  43. package/src/codegen/emit/types-file.ts +0 -142
  44. package/src/codegen/fixtures/billing-namespace.json +0 -6
  45. package/src/codegen/generate-i18n-types.test.ts +0 -1137
  46. package/src/codegen/generate-i18n-types.ts +0 -20
  47. package/src/codegen/icu-analysis.ts +0 -110
  48. package/src/codegen/locale-fallback.ts +0 -73
  49. package/src/codegen/locale-policy.test.ts +0 -35
  50. package/src/codegen/locale-policy.ts +0 -30
  51. package/src/codegen/paths.ts +0 -44
  52. package/src/codegen/project-locales-set-namespace.test.ts +0 -134
  53. package/src/codegen/read-dictionary.test.ts +0 -594
  54. package/src/codegen/read-dictionary.ts +0 -260
  55. package/src/codegen/regenerate-namespaces.ts +0 -180
  56. package/src/codegen/run-codegen.test.ts +0 -230
  57. package/src/codegen/run-codegen.ts +0 -252
  58. package/src/codegen/types.ts +0 -10
  59. package/src/codegen/write-file-if-changed.test.ts +0 -42
  60. package/src/codegen/write-file-if-changed.ts +0 -20
  61. package/src/codegen-config/build-config.ts +0 -15
  62. package/src/codegen-config/codegen-config.test.ts +0 -36
  63. package/src/codegen-config/index.ts +0 -37
  64. package/src/codegen-config/type-names.ts +0 -20
  65. package/src/codegen-config/write-config.ts +0 -8
  66. package/src/engine.ts +0 -47
  67. package/src/fetch-artifact.ts +0 -16
  68. package/src/format-core.ts +0 -91
  69. package/src/i18n-handle.ts +0 -202
  70. package/src/icu/extract-variables.test.ts +0 -199
  71. package/src/icu/extract-variables.ts +0 -227
  72. package/src/icu/parse-template.ts +0 -31
  73. package/src/index.ts +0 -58
  74. package/src/project-locales.test.ts +0 -280
  75. package/src/project-locales.ts +0 -211
  76. package/src/resolve-locale.test.ts +0 -91
  77. package/src/resolve-locale.ts +0 -88
  78. package/src/scope-multi.ts +0 -105
  79. package/src/scope-types.ts +0 -35
  80. package/src/scope.test.ts +0 -172
  81. package/src/serialized-state.test.ts +0 -114
  82. package/src/serialized-state.ts +0 -28
  83. package/src/setup/setup-i18n.test.ts +0 -85
  84. package/src/setup/setup-i18n.ts +0 -156
  85. package/src/types.ts +0 -65
  86. package/src/validation/create-args-schema.ts +0 -76
  87. package/src/validation/create-normalized-schema.ts +0 -52
  88. package/src/validation/errors.ts +0 -43
  89. package/src/validation/index.ts +0 -202
  90. package/src/validation/normalize.ts +0 -299
  91. package/src/validation/to-dictionary.ts +0 -67
  92. package/src/validation/types.ts +0 -82
  93. package/src/validation/validate-normalized.ts +0 -133
  94. package/src/validation/validation.test.ts +0 -562
@@ -1,299 +0,0 @@
1
- import { mergeVariableMetaAcrossLocales, type VariableMetaSpec } from "../icu/extract-variables.js";
2
- import { parseTemplate } from "../icu/parse-template.js";
3
- import type { VariableSpec } from "../icu/extract-variables.js";
4
- import type {
5
- DictionarySpec,
6
- NormalizedDictionary,
7
- NormalizedKeyDictionary,
8
- ParsedKeyEntry,
9
- ValidationIssue,
10
- ValidationResult,
11
- } from "./types.js";
12
-
13
- function failure(issues: ValidationIssue[]): ValidationResult<NormalizedDictionary> {
14
- return { ok: false, issues };
15
- }
16
-
17
- function success(data: NormalizedDictionary): ValidationResult<NormalizedDictionary> {
18
- return { ok: true, data };
19
- }
20
-
21
- function isPlainObject(value: unknown): value is Record<string, unknown> {
22
- return typeof value === "object" && value !== null && !Array.isArray(value);
23
- }
24
-
25
- function normalizeSingleKey(
26
- key: string,
27
- keyValue: Record<string, unknown>,
28
- keyPathPrefix: readonly string[]
29
- ): { entry: ParsedKeyEntry; issues: ValidationIssue[] } {
30
- const issues: ValidationIssue[] = [];
31
- const locales: Record<string, ParsedKeyEntry["locales"][string]> = {};
32
- const localeMetas: VariableMetaSpec[] = [];
33
- let mergedArgs: ParsedKeyEntry["mergedArgs"] = {};
34
- let localeArgsMismatch: ValidationIssue | undefined;
35
-
36
- for (const [locale, template] of Object.entries(keyValue)) {
37
- const localePath = [...keyPathPrefix, key, locale];
38
-
39
- if (typeof template !== "string") {
40
- issues.push({
41
- kind: "invalid_locale_value",
42
- path: localePath,
43
- message: `expected string, got ${typeof template}`,
44
- });
45
- continue;
46
- }
47
-
48
- const parsed = parseTemplate(template);
49
- if (!parsed.ok) {
50
- issues.push({
51
- kind: "icu_syntax_error",
52
- path: localePath,
53
- message: parsed.message,
54
- });
55
- continue;
56
- }
57
-
58
- locales[locale] = { template, args: parsed.args };
59
- localeMetas.push(parsed.meta);
60
- }
61
-
62
- if (localeMetas.length > 0) {
63
- const merged = mergeVariableMetaAcrossLocales(localeMetas);
64
- if (!merged.ok) {
65
- const localeArgs: Record<string, ParsedKeyEntry["mergedArgs"]> = {};
66
- for (const [loc, entry] of Object.entries(locales)) {
67
- localeArgs[loc] = entry.args;
68
- }
69
- localeArgsMismatch = {
70
- kind: "locale_args_mismatch",
71
- path: [...keyPathPrefix, key],
72
- locales: localeArgs,
73
- message: merged.message,
74
- };
75
- } else {
76
- mergedArgs = merged.merged;
77
- }
78
- }
79
-
80
- if (localeArgsMismatch) {
81
- issues.push(localeArgsMismatch);
82
- }
83
-
84
- return { entry: { locales, mergedArgs }, issues };
85
- }
86
-
87
- function normalizeKeyDictionary(
88
- input: Record<string, unknown>,
89
- requiredKeys: readonly string[],
90
- keyPathPrefix: readonly string[]
91
- ): ValidationResult<NormalizedKeyDictionary> {
92
- const issues: ValidationIssue[] = [];
93
- const keys: Record<string, ParsedKeyEntry> = {};
94
-
95
- for (const key of requiredKeys) {
96
- if (!(key in input)) {
97
- issues.push({ kind: "missing_key", path: [...keyPathPrefix, key] });
98
- continue;
99
- }
100
-
101
- const keyValue = input[key];
102
- if (!isPlainObject(keyValue)) {
103
- issues.push({
104
- kind: "invalid_input",
105
- message: `Expected object for key "${key}", got ${typeof keyValue}`,
106
- });
107
- continue;
108
- }
109
-
110
- const result = normalizeSingleKey(key, keyValue, keyPathPrefix);
111
- issues.push(...result.issues);
112
- keys[key] = result.entry;
113
- }
114
-
115
- if (issues.length > 0) {
116
- return { ok: false, issues };
117
- }
118
-
119
- return { ok: true, data: keys };
120
- }
121
-
122
- /**
123
- * Validation step 1 (partial): coerce external input into a normalized key dictionary
124
- * for only the keys present in the payload.
125
- */
126
- export function normalizeKeyDictionaryPartial(
127
- input: unknown,
128
- knownKeys: readonly string[],
129
- _argsByKey: Readonly<Record<string, VariableSpec>>,
130
- keyPathPrefix: readonly string[]
131
- ): ValidationResult<NormalizedKeyDictionary> {
132
- if (!isPlainObject(input)) {
133
- return {
134
- ok: false,
135
- issues: [
136
- {
137
- kind: "invalid_input",
138
- message: `Expected object, got ${input === null ? "null" : typeof input}`,
139
- },
140
- ],
141
- };
142
- }
143
-
144
- const knownKeysSet = new Set(knownKeys);
145
- const issues: ValidationIssue[] = [];
146
- const keys: Record<string, ParsedKeyEntry> = {};
147
-
148
- for (const key of Object.keys(input)) {
149
- if (!knownKeysSet.has(key)) {
150
- issues.push({ kind: "unknown_key", path: [...keyPathPrefix, key] });
151
- continue;
152
- }
153
-
154
- const keyValue = input[key];
155
- if (!isPlainObject(keyValue)) {
156
- issues.push({
157
- kind: "invalid_input",
158
- message: `Expected object for key "${key}", got ${typeof keyValue}`,
159
- });
160
- continue;
161
- }
162
-
163
- if (Object.keys(keyValue).length === 0) {
164
- issues.push({
165
- kind: "invalid_input",
166
- message: `Key "${key}" must include at least one locale`,
167
- });
168
- continue;
169
- }
170
-
171
- const result = normalizeSingleKey(key, keyValue, keyPathPrefix);
172
- issues.push(...result.issues);
173
- keys[key] = result.entry;
174
- }
175
-
176
- if (issues.length > 0) {
177
- return { ok: false, issues };
178
- }
179
-
180
- return { ok: true, data: keys };
181
- }
182
-
183
- /**
184
- * Validation step 1: coerce external input into a normalized dictionary shape
185
- * (keys, locales, ICU args) against a `DictionarySpec`.
186
- */
187
- export function normalizeDictionary(
188
- input: unknown,
189
- spec: DictionarySpec
190
- ): ValidationResult<NormalizedDictionary> {
191
- if (!isPlainObject(input)) {
192
- return failure([
193
- {
194
- kind: "invalid_input",
195
- message: `Expected object, got ${input === null ? "null" : typeof input}`,
196
- },
197
- ]);
198
- }
199
-
200
- if (spec.mode === "single") {
201
- const result = normalizeKeyDictionary(input, spec.requiredKeys, []);
202
- if (!result.ok) {
203
- return result;
204
- }
205
-
206
- return success({ mode: "single", keys: result.data });
207
- }
208
-
209
- const issues: ValidationIssue[] = [];
210
- const namespaces: Record<string, NormalizedKeyDictionary> = {};
211
-
212
- for (const [namespace, requiredKeys] of Object.entries(spec.requiredKeys)) {
213
- if (!(namespace in input)) {
214
- for (const key of requiredKeys) {
215
- issues.push({ kind: "missing_key", path: [namespace, key] });
216
- }
217
- continue;
218
- }
219
-
220
- const namespaceValue = input[namespace];
221
- if (!isPlainObject(namespaceValue)) {
222
- issues.push({
223
- kind: "invalid_input",
224
- message: `Expected object for namespace "${namespace}", got ${typeof namespaceValue}`,
225
- });
226
- continue;
227
- }
228
-
229
- const result = normalizeKeyDictionary(namespaceValue, requiredKeys, [namespace]);
230
- if (!result.ok) {
231
- issues.push(...result.issues);
232
- } else {
233
- namespaces[namespace] = result.data;
234
- }
235
- }
236
-
237
- if (issues.length > 0) {
238
- return failure(issues);
239
- }
240
-
241
- return success({ mode: "multi", namespaces });
242
- }
243
-
244
- /**
245
- * Validation step 1 (partial, multi): coerce external input into normalized namespace
246
- * slices for only the namespaces and keys present in the payload.
247
- */
248
- export function normalizeDictionaryPartial(
249
- input: unknown,
250
- spec: Extract<DictionarySpec, { mode: "multi" }>
251
- ): ValidationResult<NormalizedDictionary> {
252
- if (!isPlainObject(input)) {
253
- return failure([
254
- {
255
- kind: "invalid_input",
256
- message: `Expected object, got ${input === null ? "null" : typeof input}`,
257
- },
258
- ]);
259
- }
260
-
261
- const knownNamespaces = Object.keys(spec.requiredKeys);
262
- const knownNamespacesSet = new Set(knownNamespaces);
263
- const issues: ValidationIssue[] = [];
264
- const namespaces: Record<string, NormalizedKeyDictionary> = {};
265
-
266
- for (const namespace of Object.keys(input)) {
267
- if (!knownNamespacesSet.has(namespace)) {
268
- issues.push({ kind: "unknown_key", path: [namespace] });
269
- continue;
270
- }
271
-
272
- const namespaceValue = input[namespace];
273
- if (!isPlainObject(namespaceValue)) {
274
- issues.push({
275
- kind: "invalid_input",
276
- message: `Expected object for namespace "${namespace}", got ${typeof namespaceValue}`,
277
- });
278
- continue;
279
- }
280
-
281
- const result = normalizeKeyDictionaryPartial(
282
- namespaceValue,
283
- spec.requiredKeys[namespace] ?? [],
284
- spec.argsByKey[namespace] ?? {},
285
- [namespace]
286
- );
287
- if (!result.ok) {
288
- issues.push(...result.issues);
289
- } else {
290
- namespaces[namespace] = result.data;
291
- }
292
- }
293
-
294
- if (issues.length > 0) {
295
- return failure(issues);
296
- }
297
-
298
- return success({ mode: "multi", namespaces });
299
- }
@@ -1,67 +0,0 @@
1
- import type { NormalizedDictionary } from "./types.js";
2
-
3
- type LocaleDictionary = Record<string, string>;
4
- type KeyDictionary = Record<string, LocaleDictionary>;
5
- type MultiDictionary = Record<string, KeyDictionary>;
6
-
7
- export function toDictionary(normalized: NormalizedDictionary): KeyDictionary | MultiDictionary {
8
- if (normalized.mode === "single") {
9
- const dictionary: KeyDictionary = {};
10
-
11
- for (const [key, entry] of Object.entries(normalized.keys)) {
12
- const locales: LocaleDictionary = {};
13
- for (const [locale, localeEntry] of Object.entries(entry.locales)) {
14
- locales[locale] = localeEntry.template;
15
- }
16
- dictionary[key] = locales;
17
- }
18
-
19
- return dictionary;
20
- }
21
-
22
- const dictionary: MultiDictionary = {};
23
-
24
- for (const [namespace, keys] of Object.entries(normalized.namespaces)) {
25
- const namespaceDictionary: KeyDictionary = {};
26
-
27
- for (const [key, entry] of Object.entries(keys)) {
28
- const locales: LocaleDictionary = {};
29
- for (const [locale, localeEntry] of Object.entries(entry.locales)) {
30
- locales[locale] = localeEntry.template;
31
- }
32
- namespaceDictionary[key] = locales;
33
- }
34
-
35
- dictionary[namespace] = namespaceDictionary;
36
- }
37
-
38
- return dictionary;
39
- }
40
-
41
- export function toNamespaceDictionary(
42
- normalized: NormalizedDictionary,
43
- namespace: string
44
- ): KeyDictionary {
45
- if (normalized.mode !== "multi") {
46
- throw new Error(
47
- `[i18n] toNamespaceDictionary requires multi-mode normalized dictionary, got "${normalized.mode}"`
48
- );
49
- }
50
-
51
- const keys = normalized.namespaces[namespace];
52
- if (!keys) {
53
- return {};
54
- }
55
-
56
- const dictionary: KeyDictionary = {};
57
-
58
- for (const [key, entry] of Object.entries(keys)) {
59
- const locales: LocaleDictionary = {};
60
- for (const [locale, localeEntry] of Object.entries(entry.locales)) {
61
- locales[locale] = localeEntry.template;
62
- }
63
- dictionary[key] = locales;
64
- }
65
-
66
- return dictionary;
67
- }
@@ -1,82 +0,0 @@
1
- import type { VariableSpec, VariableType } from "../icu/extract-variables.js";
2
-
3
- export type { VariableSpec, VariableType };
4
-
5
- export type ParsedLocaleEntry = {
6
- readonly template: string;
7
- readonly args: VariableSpec;
8
- };
9
-
10
- export type ParsedKeyEntry = {
11
- readonly locales: Readonly<Record<string, ParsedLocaleEntry>>;
12
- readonly mergedArgs: VariableSpec;
13
- };
14
-
15
- export type NormalizedKeyDictionary = Readonly<Record<string, ParsedKeyEntry>>;
16
-
17
- export type NormalizedMultiDictionary = Readonly<Record<string, NormalizedKeyDictionary>>;
18
-
19
- export type NormalizedDictionary =
20
- | { readonly mode: "single"; readonly keys: NormalizedKeyDictionary }
21
- | { readonly mode: "multi"; readonly namespaces: NormalizedMultiDictionary };
22
-
23
- export type DictionarySpec =
24
- | {
25
- readonly mode: "single";
26
- readonly requiredKeys: readonly string[];
27
- readonly argsByKey: Readonly<Record<string, VariableSpec>>;
28
- }
29
- | {
30
- readonly mode: "multi";
31
- readonly requiredKeys: Readonly<Record<string, readonly string[]>>;
32
- readonly argsByKey: Readonly<Record<string, Readonly<Record<string, VariableSpec>>>>;
33
- };
34
-
35
- export type ValidationIssue =
36
- | {
37
- readonly kind: "invalid_input";
38
- readonly message: string;
39
- }
40
- | {
41
- readonly kind: "missing_key";
42
- readonly path: readonly string[];
43
- }
44
- | {
45
- readonly kind: "unknown_key";
46
- readonly path: readonly string[];
47
- }
48
- | {
49
- readonly kind: "invalid_locale_value";
50
- readonly path: readonly string[];
51
- readonly message: string;
52
- }
53
- | {
54
- readonly kind: "icu_syntax_error";
55
- readonly path: readonly string[];
56
- readonly message: string;
57
- }
58
- | {
59
- readonly kind: "locale_args_mismatch";
60
- readonly path: readonly string[];
61
- readonly locales: Readonly<Record<string, VariableSpec>>;
62
- readonly message: string;
63
- }
64
- | {
65
- readonly kind: "variable_mismatch";
66
- readonly path: readonly string[];
67
- readonly expected: VariableSpec;
68
- readonly found: VariableSpec;
69
- readonly message: string;
70
- }
71
- | {
72
- readonly kind: "variable_type_mismatch";
73
- readonly path: readonly string[];
74
- readonly variable: string;
75
- readonly expected: VariableType;
76
- readonly found: VariableType;
77
- readonly message: string;
78
- };
79
-
80
- export type ValidationResult<T> =
81
- | { readonly ok: true; readonly data: T }
82
- | { readonly ok: false; readonly issues: readonly ValidationIssue[] };
@@ -1,133 +0,0 @@
1
- import type { ZodError } from "zod";
2
- import { mapArgsSchemaError } from "./create-args-schema.js";
3
- import {
4
- createKeyDictionarySchema,
5
- createNormalizedDictionarySchema,
6
- } from "./create-normalized-schema.js";
7
- import type {
8
- DictionarySpec,
9
- NormalizedDictionary,
10
- NormalizedKeyDictionary,
11
- ValidationIssue,
12
- ValidationResult,
13
- VariableSpec,
14
- } from "./types.js";
15
-
16
- function getExpectedArgs(spec: DictionarySpec, path: readonly string[]): VariableSpec | undefined {
17
- if (spec.mode === "single") {
18
- const key = path[path.length - 1];
19
- if (typeof key !== "string") {
20
- return undefined;
21
- }
22
- return spec.argsByKey[key];
23
- }
24
-
25
- const namespace = path[0];
26
- const key = path[path.length - 1];
27
- if (typeof namespace !== "string" || typeof key !== "string") {
28
- return undefined;
29
- }
30
-
31
- return spec.argsByKey[namespace]?.[key];
32
- }
33
-
34
- function getFoundArgs(
35
- normalized: NormalizedDictionary,
36
- path: readonly string[]
37
- ): VariableSpec | undefined {
38
- if (normalized.mode === "single") {
39
- const key = path[path.length - 1];
40
- if (typeof key !== "string") {
41
- return undefined;
42
- }
43
- return normalized.keys[key]?.mergedArgs;
44
- }
45
-
46
- const namespace = path[0];
47
- const key = path[path.length - 1];
48
- if (typeof namespace !== "string" || typeof key !== "string") {
49
- return undefined;
50
- }
51
-
52
- return normalized.namespaces[namespace]?.[key]?.mergedArgs;
53
- }
54
-
55
- function mapZodError(
56
- error: ZodError,
57
- spec: DictionarySpec,
58
- normalized: NormalizedDictionary
59
- ): ValidationIssue[] {
60
- return error.issues.map((issue) => {
61
- const path = issue.path.map(String);
62
-
63
- if (path.includes("mergedArgs")) {
64
- const keyPath = path.slice(0, path.indexOf("mergedArgs"));
65
- const expected = getExpectedArgs(spec, keyPath);
66
- const found = getFoundArgs(normalized, keyPath);
67
-
68
- if (expected && found) {
69
- return mapArgsSchemaError(expected, found, issue.message, keyPath);
70
- }
71
- }
72
-
73
- return {
74
- kind: "invalid_input" as const,
75
- message: path.length > 0 ? `${path.join(".")}: ${issue.message}` : issue.message,
76
- };
77
- });
78
- }
79
-
80
- export function validateNormalizedKeyDictionaryPartial(
81
- keys: NormalizedKeyDictionary,
82
- argsByKey: Readonly<Record<string, VariableSpec>>,
83
- spec: DictionarySpec,
84
- keyPathPrefix: readonly string[]
85
- ): ValidationResult<NormalizedKeyDictionary> {
86
- const presentKeys = Object.keys(keys);
87
- const subsetArgsByKey = Object.fromEntries(
88
- presentKeys.filter((key) => key in argsByKey).map((key) => [key, argsByKey[key]!])
89
- );
90
-
91
- if (presentKeys.length === 0) {
92
- return { ok: true, data: keys };
93
- }
94
-
95
- const schema = createKeyDictionarySchema(subsetArgsByKey);
96
- const result = schema.safeParse(keys);
97
-
98
- if (result.success) {
99
- return { ok: true, data: result.data };
100
- }
101
-
102
- const normalized: NormalizedDictionary =
103
- spec.mode === "single"
104
- ? { mode: "single", keys }
105
- : {
106
- mode: "multi",
107
- namespaces: {
108
- [keyPathPrefix[0] ?? ""]: keys,
109
- },
110
- };
111
-
112
- return {
113
- ok: false,
114
- issues: mapZodError(result.error, spec, normalized),
115
- };
116
- }
117
-
118
- export function validateNormalizedDictionary(
119
- normalized: NormalizedDictionary,
120
- spec: DictionarySpec
121
- ): ValidationResult<NormalizedDictionary> {
122
- const schema = createNormalizedDictionarySchema(spec);
123
- const result = schema.safeParse(normalized);
124
-
125
- if (result.success) {
126
- return { ok: true, data: result.data };
127
- }
128
-
129
- return {
130
- ok: false,
131
- issues: mapZodError(result.error, spec, normalized),
132
- };
133
- }