@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.
- package/README.md +1 -2
- package/dist/cli/audit.js +739 -0
- package/dist/cli/audit.js.map +1 -0
- package/dist/cli/codegen.js +1471 -0
- package/dist/cli/codegen.js.map +1 -0
- package/dist/cli/setup.js +148 -0
- package/dist/cli/setup.js.map +1 -0
- package/dist/codegen/index.js +43 -40
- package/dist/codegen/index.js.map +1 -1
- package/dist/index.d.ts +20 -1
- package/dist/index.js +56 -10
- package/dist/index.js.map +1 -1
- package/package.json +6 -9
- package/bin/audit.mjs +0 -14
- package/bin/codegen.mjs +0 -17
- package/bin/setup.mjs +0 -14
- package/src/IcuTranslationProviderMulti.test.ts +0 -215
- package/src/IcuTranslationProviderMulti.ts +0 -176
- package/src/audit/audit-dictionaries.test.ts +0 -134
- package/src/audit/audit-dictionaries.ts +0 -178
- package/src/audit/run-audit.test.ts +0 -169
- package/src/audit/run-audit.ts +0 -94
- package/src/builder-load-registry.ts +0 -39
- package/src/builder-loaders.ts +0 -15
- package/src/builder-types.test.ts +0 -49
- package/src/builder-types.ts +0 -24
- package/src/builder.test.ts +0 -260
- package/src/builder.ts +0 -10
- package/src/codegen/codegen-config-schema.ts +0 -156
- package/src/codegen/config.test.ts +0 -231
- package/src/codegen/config.ts +0 -42
- package/src/codegen/constants.ts +0 -9
- package/src/codegen/delivery-artifacts.test.ts +0 -167
- package/src/codegen/delivery-artifacts.ts +0 -154
- package/src/codegen/dictionary-spec-contract.test.ts +0 -28
- package/src/codegen/dictionary-spec-contract.ts +0 -138
- package/src/codegen/emit/dictionary-schema-file.ts +0 -113
- package/src/codegen/emit/instance-file.test.ts +0 -71
- package/src/codegen/emit/instance-file.ts +0 -257
- package/src/codegen/emit/namespace-loaders-file.test.ts +0 -251
- package/src/codegen/emit/namespace-loaders-file.ts +0 -229
- package/src/codegen/emit/types-file.test.ts +0 -96
- package/src/codegen/emit/types-file.ts +0 -142
- package/src/codegen/fixtures/billing-namespace.json +0 -6
- package/src/codegen/generate-i18n-types.test.ts +0 -1137
- package/src/codegen/generate-i18n-types.ts +0 -20
- package/src/codegen/icu-analysis.ts +0 -110
- package/src/codegen/locale-fallback.ts +0 -73
- package/src/codegen/locale-policy.test.ts +0 -35
- package/src/codegen/locale-policy.ts +0 -30
- package/src/codegen/paths.ts +0 -44
- package/src/codegen/project-locales-set-namespace.test.ts +0 -134
- package/src/codegen/read-dictionary.test.ts +0 -594
- package/src/codegen/read-dictionary.ts +0 -260
- package/src/codegen/regenerate-namespaces.ts +0 -180
- package/src/codegen/run-codegen.test.ts +0 -230
- package/src/codegen/run-codegen.ts +0 -252
- package/src/codegen/types.ts +0 -10
- package/src/codegen/write-file-if-changed.test.ts +0 -42
- package/src/codegen/write-file-if-changed.ts +0 -20
- package/src/codegen-config/build-config.ts +0 -15
- package/src/codegen-config/codegen-config.test.ts +0 -36
- package/src/codegen-config/index.ts +0 -37
- package/src/codegen-config/type-names.ts +0 -20
- package/src/codegen-config/write-config.ts +0 -8
- package/src/engine.ts +0 -47
- package/src/fetch-artifact.ts +0 -16
- package/src/format-core.ts +0 -91
- package/src/i18n-handle.ts +0 -202
- package/src/icu/extract-variables.test.ts +0 -199
- package/src/icu/extract-variables.ts +0 -227
- package/src/icu/parse-template.ts +0 -31
- package/src/index.ts +0 -58
- package/src/project-locales.test.ts +0 -280
- package/src/project-locales.ts +0 -211
- package/src/resolve-locale.test.ts +0 -91
- package/src/resolve-locale.ts +0 -88
- package/src/scope-multi.ts +0 -105
- package/src/scope-types.ts +0 -35
- package/src/scope.test.ts +0 -172
- package/src/serialized-state.test.ts +0 -114
- package/src/serialized-state.ts +0 -28
- package/src/setup/setup-i18n.test.ts +0 -85
- package/src/setup/setup-i18n.ts +0 -156
- package/src/types.ts +0 -65
- package/src/validation/create-args-schema.ts +0 -76
- package/src/validation/create-normalized-schema.ts +0 -52
- package/src/validation/errors.ts +0 -43
- package/src/validation/index.ts +0 -202
- package/src/validation/normalize.ts +0 -299
- package/src/validation/to-dictionary.ts +0 -67
- package/src/validation/types.ts +0 -82
- package/src/validation/validate-normalized.ts +0 -133
- 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
|
-
}
|
package/src/validation/types.ts
DELETED
|
@@ -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
|
-
}
|