@form-engine-ts/core 4.7.0 → 4.8.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 +3 -0
- package/dist/index.cjs +156 -66
- package/dist/index.d.cts +9 -1
- package/dist/index.d.ts +9 -1
- package/dist/index.js +155 -66
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -52,6 +52,9 @@ Required locales cover every source text that exists on the form, its fields, op
|
|
|
52
52
|
safe maximum-length corrections while purging unregistered locale content. Pass
|
|
53
53
|
`{ policy: { allowedLocales, maxLocales } }` to `populateSchemaTranslations` to reject inadmissible targets before the
|
|
54
54
|
translation adapter runs.
|
|
55
|
+
`normalizeLocale(locale)` returns the canonical BCP 47 tag, accepts underscore-separated compatibility input such as
|
|
56
|
+
`ja_JP`, and returns `null` for invalid tags. Schema validation, sanitization, locale policy checks, and translation
|
|
57
|
+
slot lookup use this same normalization so equivalent locale spellings cannot bypass constraints.
|
|
55
58
|
|
|
56
59
|
Fields can use a `displayRule` with nested `all`/`any` condition groups and `show` or `hide` actions. Supported
|
|
57
60
|
operators include equality, containment, emptiness, and numeric comparisons; the legacy `displayCondition` and
|
package/dist/index.cjs
CHANGED
|
@@ -57,6 +57,7 @@ __export(index_exports, {
|
|
|
57
57
|
matchesSubmissionFilter: () => matchesSubmissionFilter,
|
|
58
58
|
matchesSubmissionPageFilters: () => matchesSubmissionPageFilters,
|
|
59
59
|
migrateSchemaTranslationMetadata: () => migrateSchemaTranslationMetadata,
|
|
60
|
+
normalizeLocale: () => normalizeLocale,
|
|
60
61
|
normalizeSubmissionPageSize: () => normalizeSubmissionPageSize,
|
|
61
62
|
pipeResponsesToCsvStream: () => pipeResponsesToCsvStream,
|
|
62
63
|
populateSchemaTranslations: () => populateSchemaTranslations,
|
|
@@ -74,11 +75,27 @@ __export(index_exports, {
|
|
|
74
75
|
});
|
|
75
76
|
module.exports = __toCommonJS(index_exports);
|
|
76
77
|
|
|
78
|
+
// src/locale.ts
|
|
79
|
+
var normalizeLocale = (rawLocale) => {
|
|
80
|
+
if (!rawLocale || typeof rawLocale !== "string") return null;
|
|
81
|
+
const trimmed = rawLocale.trim().replace(/_/gu, "-");
|
|
82
|
+
if (trimmed.length === 0) return null;
|
|
83
|
+
try {
|
|
84
|
+
return Intl.getCanonicalLocales(trimmed)[0] ?? null;
|
|
85
|
+
} catch {
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
function canonicalLocaleOrRaw(rawLocale) {
|
|
90
|
+
return normalizeLocale(rawLocale) ?? rawLocale;
|
|
91
|
+
}
|
|
92
|
+
|
|
77
93
|
// src/policy.ts
|
|
78
94
|
function collectRecordKeys(value, path, pathsByLocale) {
|
|
79
|
-
for (const
|
|
95
|
+
for (const rawLocale of Object.keys(value ?? {})) {
|
|
96
|
+
const locale = canonicalLocaleOrRaw(rawLocale);
|
|
80
97
|
const paths = pathsByLocale.get(locale) ?? [];
|
|
81
|
-
paths.push(`${path}.${
|
|
98
|
+
paths.push(`${path}.${rawLocale}`);
|
|
82
99
|
pathsByLocale.set(locale, paths);
|
|
83
100
|
}
|
|
84
101
|
}
|
|
@@ -109,13 +126,15 @@ function collectSchemaLocales(schema) {
|
|
|
109
126
|
});
|
|
110
127
|
const translationLocales = new Set(pathsByLocale.keys());
|
|
111
128
|
const allUniqueLocales = /* @__PURE__ */ new Set([
|
|
112
|
-
...schema.defaultLocale === void 0 ? [] : [schema.defaultLocale],
|
|
113
|
-
...schema.supportedLocales ?? [],
|
|
129
|
+
...schema.defaultLocale === void 0 ? [] : [canonicalLocaleOrRaw(schema.defaultLocale)],
|
|
130
|
+
...(schema.supportedLocales ?? []).map(canonicalLocaleOrRaw),
|
|
114
131
|
...translationLocales
|
|
115
132
|
]);
|
|
133
|
+
const defaultLocale = schema.defaultLocale === void 0 ? void 0 : canonicalLocaleOrRaw(schema.defaultLocale);
|
|
134
|
+
const supportedLocales = (schema.supportedLocales ?? []).map(canonicalLocaleOrRaw);
|
|
116
135
|
return {
|
|
117
|
-
...
|
|
118
|
-
supportedLocales
|
|
136
|
+
...defaultLocale === void 0 ? {} : { defaultLocale },
|
|
137
|
+
supportedLocales,
|
|
119
138
|
translationLocales,
|
|
120
139
|
allUniqueLocales,
|
|
121
140
|
translationLocalePaths: pathsByLocale
|
|
@@ -138,7 +157,12 @@ function displayRuleSourceIds(field) {
|
|
|
138
157
|
}
|
|
139
158
|
function registeredEntries(value, registeredLocales) {
|
|
140
159
|
if (value === void 0) return void 0;
|
|
141
|
-
const entries =
|
|
160
|
+
const entries = [];
|
|
161
|
+
for (const [rawLocale, entry] of Object.entries(value)) {
|
|
162
|
+
const locale = normalizeLocale(rawLocale);
|
|
163
|
+
if (locale === null || !registeredLocales.has(locale)) continue;
|
|
164
|
+
entries.push([locale, entry]);
|
|
165
|
+
}
|
|
142
166
|
return entries.length === 0 ? void 0 : Object.fromEntries(entries);
|
|
143
167
|
}
|
|
144
168
|
function sanitizeNodeLocales(node, registeredLocales) {
|
|
@@ -176,6 +200,16 @@ function sanitizePageLocales(page, registeredLocales) {
|
|
|
176
200
|
const translations = registeredEntries(page.translations, registeredLocales);
|
|
177
201
|
return { ...base, ...translations === void 0 ? {} : { translations } };
|
|
178
202
|
}
|
|
203
|
+
function normalizedLocaleList(locales) {
|
|
204
|
+
return [
|
|
205
|
+
...new Set(
|
|
206
|
+
locales.flatMap((locale) => {
|
|
207
|
+
const normalized = normalizeLocale(locale);
|
|
208
|
+
return normalized === null ? [] : [normalized];
|
|
209
|
+
})
|
|
210
|
+
)
|
|
211
|
+
];
|
|
212
|
+
}
|
|
179
213
|
function sanitizeFieldConstraints(field, policy) {
|
|
180
214
|
const constraint = policy?.fieldConstraints?.[field.type];
|
|
181
215
|
if (constraint === void 0) return field;
|
|
@@ -290,10 +324,9 @@ function validateSchemaStructure(schema) {
|
|
|
290
324
|
}
|
|
291
325
|
function sanitizeSchema(schema, options = {}) {
|
|
292
326
|
const existingQuestionIds = new Set(schema.fields.map((field) => field.id));
|
|
293
|
-
const
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
]);
|
|
327
|
+
const defaultLocale = schema.defaultLocale === void 0 ? null : normalizeLocale(schema.defaultLocale);
|
|
328
|
+
const supportedLocales = schema.supportedLocales === void 0 ? void 0 : normalizedLocaleList(schema.supportedLocales);
|
|
329
|
+
const registeredLocales = /* @__PURE__ */ new Set([...defaultLocale === null ? [] : [defaultLocale], ...supportedLocales ?? []]);
|
|
297
330
|
const cyclic = cyclicQuestionIds(schema.fields);
|
|
298
331
|
const sanitizedFields = schema.fields.map((sourceField) => {
|
|
299
332
|
const field = sanitizeFieldConstraints(sanitizeFieldLocales(sourceField, registeredLocales), options.policy);
|
|
@@ -311,10 +344,17 @@ function sanitizeSchema(schema, options = {}) {
|
|
|
311
344
|
return sanitized;
|
|
312
345
|
});
|
|
313
346
|
const localizedSchema = sanitizeNodeLocales(schema, registeredLocales);
|
|
314
|
-
const {
|
|
347
|
+
const {
|
|
348
|
+
defaultLocale: _defaultLocale,
|
|
349
|
+
supportedLocales: _supportedLocales,
|
|
350
|
+
translations: _translations,
|
|
351
|
+
...schemaWithoutLocaleContent
|
|
352
|
+
} = localizedSchema;
|
|
315
353
|
const translations = registeredEntries(schema.translations, registeredLocales);
|
|
316
354
|
const base = {
|
|
317
355
|
...schemaWithoutLocaleContent,
|
|
356
|
+
...defaultLocale === null ? {} : { defaultLocale },
|
|
357
|
+
...supportedLocales === void 0 ? {} : { supportedLocales },
|
|
318
358
|
...translations === void 0 ? {} : { translations },
|
|
319
359
|
fields: sanitizedFields
|
|
320
360
|
};
|
|
@@ -367,6 +407,12 @@ function isRecord(value) {
|
|
|
367
407
|
function isNonEmptyString(value) {
|
|
368
408
|
return typeof value === "string" && value.trim().length > 0;
|
|
369
409
|
}
|
|
410
|
+
function localeRecordEntry(record, locale) {
|
|
411
|
+
for (const [candidate, value] of Object.entries(record ?? {})) {
|
|
412
|
+
if (canonicalLocaleOrRaw(candidate) === locale) return value;
|
|
413
|
+
}
|
|
414
|
+
return void 0;
|
|
415
|
+
}
|
|
370
416
|
function issue(issues, path, code, message) {
|
|
371
417
|
issues.push({ path, code, message });
|
|
372
418
|
}
|
|
@@ -729,51 +775,55 @@ function collectSchemaText(schema) {
|
|
|
729
775
|
return entries;
|
|
730
776
|
}
|
|
731
777
|
function addRequiredTranslationIssues(schema, locale, issues) {
|
|
732
|
-
if (!(schema.supportedLocales ?? []).
|
|
778
|
+
if (!(schema.supportedLocales ?? []).some((candidate) => canonicalLocaleOrRaw(candidate) === locale)) {
|
|
733
779
|
issue(issues, "supportedLocales", "required_locale_missing", `Required locale ${locale} is missing.`);
|
|
734
780
|
}
|
|
735
|
-
if (
|
|
781
|
+
if (schema.defaultLocale !== void 0 && canonicalLocaleOrRaw(schema.defaultLocale) === locale) return;
|
|
782
|
+
const formTranslation = localeRecordEntry(schema.translations, locale);
|
|
736
783
|
const required = [
|
|
737
|
-
{ path: `translations.${locale}.title`, value:
|
|
784
|
+
{ path: `translations.${locale}.title`, value: formTranslation?.title }
|
|
738
785
|
];
|
|
739
786
|
if (schema.description !== void 0)
|
|
740
|
-
required.push({ path: `translations.${locale}.description`, value:
|
|
787
|
+
required.push({ path: `translations.${locale}.description`, value: formTranslation?.description });
|
|
741
788
|
if (schema.completionMessage !== void 0) {
|
|
742
789
|
required.push({
|
|
743
790
|
path: `translations.${locale}.completionMessage`,
|
|
744
|
-
value:
|
|
791
|
+
value: formTranslation?.completionMessage
|
|
745
792
|
});
|
|
746
793
|
}
|
|
747
794
|
schema.fields.forEach((field, fieldIndex) => {
|
|
795
|
+
const fieldTranslation = localeRecordEntry(field.translations, locale);
|
|
748
796
|
required.push({
|
|
749
797
|
path: `fields[${fieldIndex}].translations.${locale}.title`,
|
|
750
|
-
value:
|
|
798
|
+
value: fieldTranslation?.title
|
|
751
799
|
});
|
|
752
800
|
if (field.description !== void 0) {
|
|
753
801
|
required.push({
|
|
754
802
|
path: `fields[${fieldIndex}].translations.${locale}.description`,
|
|
755
|
-
value:
|
|
803
|
+
value: fieldTranslation?.description
|
|
756
804
|
});
|
|
757
805
|
}
|
|
758
806
|
if (!("options" in field)) return;
|
|
759
807
|
field.options.forEach((option, optionIndex) => {
|
|
808
|
+
const optionTranslation = localeRecordEntry(option.translations, locale);
|
|
760
809
|
required.push({
|
|
761
810
|
path: `fields[${fieldIndex}].options[${optionIndex}].translations.${locale}`,
|
|
762
|
-
value:
|
|
811
|
+
value: optionTranslation
|
|
763
812
|
});
|
|
764
813
|
});
|
|
765
814
|
});
|
|
766
815
|
schema.pages?.forEach((page, pageIndex) => {
|
|
816
|
+
const pageTranslation = localeRecordEntry(page.translations, locale);
|
|
767
817
|
if (page.title !== void 0) {
|
|
768
818
|
required.push({
|
|
769
819
|
path: `pages[${pageIndex}].translations.${locale}.title`,
|
|
770
|
-
value:
|
|
820
|
+
value: pageTranslation?.title
|
|
771
821
|
});
|
|
772
822
|
}
|
|
773
823
|
if (page.description !== void 0) {
|
|
774
824
|
required.push({
|
|
775
825
|
path: `pages[${pageIndex}].translations.${locale}.description`,
|
|
776
|
-
value:
|
|
826
|
+
value: pageTranslation?.description
|
|
777
827
|
});
|
|
778
828
|
}
|
|
779
829
|
});
|
|
@@ -906,8 +956,8 @@ function validatePolicy(schema, policy, issues) {
|
|
|
906
956
|
}
|
|
907
957
|
const collectedLocales = collectSchemaLocales(schema);
|
|
908
958
|
const registeredLocales = /* @__PURE__ */ new Set([
|
|
909
|
-
...
|
|
910
|
-
...
|
|
959
|
+
...collectedLocales.defaultLocale === void 0 ? [] : [collectedLocales.defaultLocale],
|
|
960
|
+
...collectedLocales.supportedLocales
|
|
911
961
|
]);
|
|
912
962
|
for (const locale of collectedLocales.translationLocales) {
|
|
913
963
|
if (registeredLocales.has(locale)) continue;
|
|
@@ -921,23 +971,27 @@ function validatePolicy(schema, policy, issues) {
|
|
|
921
971
|
}
|
|
922
972
|
}
|
|
923
973
|
if (policy.allowedLocales !== void 0) {
|
|
974
|
+
const allowedLocales = new Set(policy.allowedLocales.map(canonicalLocaleOrRaw));
|
|
924
975
|
const pathsByLocale = /* @__PURE__ */ new Map();
|
|
925
|
-
if (
|
|
976
|
+
if (collectedLocales.defaultLocale !== void 0)
|
|
977
|
+
pathsByLocale.set(collectedLocales.defaultLocale, ["defaultLocale"]);
|
|
926
978
|
schema.supportedLocales?.forEach((locale, index) => {
|
|
927
|
-
|
|
979
|
+
const canonical = canonicalLocaleOrRaw(locale);
|
|
980
|
+
pathsByLocale.set(canonical, [...pathsByLocale.get(canonical) ?? [], `supportedLocales[${index}]`]);
|
|
928
981
|
});
|
|
929
982
|
for (const [locale, paths] of collectedLocales.translationLocalePaths) {
|
|
930
983
|
pathsByLocale.set(locale, [...pathsByLocale.get(locale) ?? [], ...paths]);
|
|
931
984
|
}
|
|
932
985
|
for (const [locale, paths] of pathsByLocale) {
|
|
933
|
-
if (!
|
|
986
|
+
if (!allowedLocales.has(locale)) {
|
|
934
987
|
for (const path of paths) {
|
|
935
988
|
issue(issues, path, "disallowed_locale", `Locale ${locale} is not allowed by the form policy.`);
|
|
936
989
|
}
|
|
937
990
|
}
|
|
938
991
|
}
|
|
939
|
-
for (const
|
|
940
|
-
|
|
992
|
+
for (const rawLocale of policy.requiredLocales ?? []) {
|
|
993
|
+
const locale = canonicalLocaleOrRaw(rawLocale);
|
|
994
|
+
if (!allowedLocales.has(locale)) {
|
|
941
995
|
issue(
|
|
942
996
|
issues,
|
|
943
997
|
"policy.requiredLocales",
|
|
@@ -950,7 +1004,8 @@ function validatePolicy(schema, policy, issues) {
|
|
|
950
1004
|
if (policy.maxLocales !== void 0 && collectedLocales.allUniqueLocales.size > policy.maxLocales) {
|
|
951
1005
|
issue(issues, "supportedLocales", "max_locales_exceeded", `At most ${policy.maxLocales} locales are allowed.`);
|
|
952
1006
|
}
|
|
953
|
-
for (const locale of policy.requiredLocales ?? [])
|
|
1007
|
+
for (const locale of policy.requiredLocales ?? [])
|
|
1008
|
+
addRequiredTranslationIssues(schema, canonicalLocaleOrRaw(locale), issues);
|
|
954
1009
|
if (policy.maxSchemaBytes !== void 0) {
|
|
955
1010
|
try {
|
|
956
1011
|
const byteLength = new TextEncoder().encode(JSON.stringify(schema)).byteLength;
|
|
@@ -985,8 +1040,12 @@ function validateFormSchema(input, options = {}) {
|
|
|
985
1040
|
);
|
|
986
1041
|
}
|
|
987
1042
|
}
|
|
988
|
-
if (input.defaultLocale !== void 0
|
|
989
|
-
|
|
1043
|
+
if (input.defaultLocale !== void 0) {
|
|
1044
|
+
if (!isNonEmptyString(input.defaultLocale)) {
|
|
1045
|
+
issue(issues, "defaultLocale", "invalid_locale", "Expected a non-empty default locale.");
|
|
1046
|
+
} else if (normalizeLocale(input.defaultLocale) === null) {
|
|
1047
|
+
issue(issues, "defaultLocale", "invalid_locale", "Expected a valid BCP 47 locale.");
|
|
1048
|
+
}
|
|
990
1049
|
}
|
|
991
1050
|
if (input.supportedLocales !== void 0) {
|
|
992
1051
|
if (!Array.isArray(input.supportedLocales) || input.supportedLocales.length === 0) {
|
|
@@ -996,10 +1055,15 @@ function validateFormSchema(input, options = {}) {
|
|
|
996
1055
|
input.supportedLocales.forEach((locale, index) => {
|
|
997
1056
|
if (!isNonEmptyString(locale)) {
|
|
998
1057
|
issue(issues, `supportedLocales[${index}]`, "invalid_locale", "Expected a non-empty locale.");
|
|
999
|
-
} else if (locales.has(locale)) {
|
|
1000
|
-
issue(issues, `supportedLocales[${index}]`, "duplicate_locale", "Locales must be unique.");
|
|
1001
1058
|
} else {
|
|
1002
|
-
|
|
1059
|
+
const normalized = normalizeLocale(locale);
|
|
1060
|
+
if (normalized === null) {
|
|
1061
|
+
issue(issues, `supportedLocales[${index}]`, "invalid_locale", "Expected a valid BCP 47 locale.");
|
|
1062
|
+
} else if (locales.has(normalized)) {
|
|
1063
|
+
issue(issues, `supportedLocales[${index}]`, "duplicate_locale", "Locales must be unique.");
|
|
1064
|
+
} else {
|
|
1065
|
+
locales.add(normalized);
|
|
1066
|
+
}
|
|
1003
1067
|
}
|
|
1004
1068
|
});
|
|
1005
1069
|
}
|
|
@@ -2268,8 +2332,15 @@ function withTranslationMetadata(node, locale, property, metadata) {
|
|
|
2268
2332
|
}
|
|
2269
2333
|
};
|
|
2270
2334
|
}
|
|
2271
|
-
function
|
|
2272
|
-
const
|
|
2335
|
+
function localeRecordEntry2(record, locale) {
|
|
2336
|
+
const normalizedLocale = normalizeLocale(locale) ?? locale;
|
|
2337
|
+
for (const [candidate, value] of Object.entries(record ?? {})) {
|
|
2338
|
+
if ((normalizeLocale(candidate) ?? candidate) === normalizedLocale) return value;
|
|
2339
|
+
}
|
|
2340
|
+
return void 0;
|
|
2341
|
+
}
|
|
2342
|
+
function createSlot(kind, nodeId, property, locale, sourceText, existingText, nodeMetadata, existingTranslationMetadata, options = {}, parentId) {
|
|
2343
|
+
const path = kind === "form" ? `form.${property}` : kind === "option" ? `fields.${parentId ?? ""}.options.${nodeId}.${property}` : `${kind}s.${nodeId}.${property}`;
|
|
2273
2344
|
const manual = options.isManualTranslation?.(existingTranslationMetadata, { path, locale }) ?? isManualTranslationMetadata(existingTranslationMetadata);
|
|
2274
2345
|
const normalizedMetadata = existingTranslationMetadata === void 0 ? void 0 : options.normalizeMetadata === void 0 ? existingTranslationMetadata : { ...options.normalizeMetadata(existingTranslationMetadata, sourceText) };
|
|
2275
2346
|
const status = getTranslationStatusWithManualOverride(sourceText, existingText, normalizedMetadata, manual);
|
|
@@ -2289,7 +2360,10 @@ function createSlot(kind, nodeId, property, locale, sourceText, existingText, no
|
|
|
2289
2360
|
};
|
|
2290
2361
|
}
|
|
2291
2362
|
function translationSlots(schema, locale, options = {}) {
|
|
2363
|
+
locale = normalizeLocale(locale) ?? locale;
|
|
2292
2364
|
const descriptors = [];
|
|
2365
|
+
const schemaTranslation = localeRecordEntry2(schema.translations, locale);
|
|
2366
|
+
const schemaTranslationMetadata = localeRecordEntry2(schema.translationMetadata, locale);
|
|
2293
2367
|
const addFormSlot = (property, sourceText) => {
|
|
2294
2368
|
const slot = createSlot(
|
|
2295
2369
|
"form",
|
|
@@ -2297,17 +2371,17 @@ function translationSlots(schema, locale, options = {}) {
|
|
|
2297
2371
|
property,
|
|
2298
2372
|
locale,
|
|
2299
2373
|
sourceText,
|
|
2300
|
-
|
|
2374
|
+
schemaTranslation?.[property],
|
|
2301
2375
|
schema.metadata,
|
|
2302
|
-
|
|
2376
|
+
schemaTranslationMetadata?.[property],
|
|
2303
2377
|
options
|
|
2304
2378
|
);
|
|
2305
2379
|
descriptors.push({
|
|
2306
2380
|
slot,
|
|
2307
|
-
manual: options.isManualTranslation?.(
|
|
2381
|
+
manual: options.isManualTranslation?.(schemaTranslationMetadata?.[property], {
|
|
2308
2382
|
path: slot.path ?? "",
|
|
2309
2383
|
locale
|
|
2310
|
-
}) ?? isManualTranslationMetadata(
|
|
2384
|
+
}) ?? isManualTranslationMetadata(schemaTranslationMetadata?.[property]),
|
|
2311
2385
|
apply: (current, value, metadata) => withTranslationMetadata(
|
|
2312
2386
|
{ ...current, translations: mergeLocalizedText(current.translations, locale, property, value) },
|
|
2313
2387
|
locale,
|
|
@@ -2320,6 +2394,8 @@ function translationSlots(schema, locale, options = {}) {
|
|
|
2320
2394
|
if (schema.description !== void 0) addFormSlot("description", schema.description);
|
|
2321
2395
|
if (schema.completionMessage !== void 0) addFormSlot("completionMessage", schema.completionMessage);
|
|
2322
2396
|
schema.fields.forEach((field, fieldIndex) => {
|
|
2397
|
+
const fieldTranslation = localeRecordEntry2(field.translations, locale);
|
|
2398
|
+
const fieldTranslationMetadata = localeRecordEntry2(field.translationMetadata, locale);
|
|
2323
2399
|
const addFieldSlot = (property, sourceText) => {
|
|
2324
2400
|
const slot = createSlot(
|
|
2325
2401
|
"field",
|
|
@@ -2327,17 +2403,17 @@ function translationSlots(schema, locale, options = {}) {
|
|
|
2327
2403
|
property,
|
|
2328
2404
|
locale,
|
|
2329
2405
|
sourceText,
|
|
2330
|
-
|
|
2406
|
+
fieldTranslation?.[property],
|
|
2331
2407
|
field.metadata,
|
|
2332
|
-
|
|
2408
|
+
fieldTranslationMetadata?.[property],
|
|
2333
2409
|
options
|
|
2334
2410
|
);
|
|
2335
2411
|
descriptors.push({
|
|
2336
2412
|
slot,
|
|
2337
|
-
manual: options.isManualTranslation?.(
|
|
2413
|
+
manual: options.isManualTranslation?.(fieldTranslationMetadata?.[property], {
|
|
2338
2414
|
path: slot.path ?? "",
|
|
2339
2415
|
locale
|
|
2340
|
-
}) ?? isManualTranslationMetadata(
|
|
2416
|
+
}) ?? isManualTranslationMetadata(fieldTranslationMetadata?.[property]),
|
|
2341
2417
|
apply: (current, value, metadata) => ({
|
|
2342
2418
|
...current,
|
|
2343
2419
|
fields: current.fields.map(
|
|
@@ -2358,23 +2434,26 @@ function translationSlots(schema, locale, options = {}) {
|
|
|
2358
2434
|
if (field.description !== void 0) addFieldSlot("description", field.description);
|
|
2359
2435
|
if ("options" in field) {
|
|
2360
2436
|
field.options.forEach((option, optionIndex) => {
|
|
2437
|
+
const optionTranslation = localeRecordEntry2(option.translations, locale);
|
|
2438
|
+
const optionTranslationMetadata = localeRecordEntry2(option.translationMetadata, locale);
|
|
2361
2439
|
const slot = createSlot(
|
|
2362
2440
|
"option",
|
|
2363
2441
|
option.id,
|
|
2364
2442
|
"label",
|
|
2365
2443
|
locale,
|
|
2366
2444
|
option.label,
|
|
2367
|
-
|
|
2445
|
+
optionTranslation,
|
|
2368
2446
|
option.metadata,
|
|
2369
|
-
|
|
2370
|
-
options
|
|
2447
|
+
optionTranslationMetadata?.label,
|
|
2448
|
+
options,
|
|
2449
|
+
field.id
|
|
2371
2450
|
);
|
|
2372
2451
|
descriptors.push({
|
|
2373
2452
|
slot,
|
|
2374
|
-
manual: options.isManualTranslation?.(
|
|
2453
|
+
manual: options.isManualTranslation?.(optionTranslationMetadata?.label, {
|
|
2375
2454
|
path: slot.path ?? "",
|
|
2376
2455
|
locale
|
|
2377
|
-
}) ?? isManualTranslationMetadata(
|
|
2456
|
+
}) ?? isManualTranslationMetadata(optionTranslationMetadata?.label),
|
|
2378
2457
|
apply: (current, value, metadata) => ({
|
|
2379
2458
|
...current,
|
|
2380
2459
|
fields: current.fields.map((candidate, candidateIndex) => {
|
|
@@ -2400,6 +2479,8 @@ function translationSlots(schema, locale, options = {}) {
|
|
|
2400
2479
|
}
|
|
2401
2480
|
});
|
|
2402
2481
|
schema.pages?.forEach((page, pageIndex) => {
|
|
2482
|
+
const pageTranslation = localeRecordEntry2(page.translations, locale);
|
|
2483
|
+
const pageTranslationMetadata = localeRecordEntry2(page.translationMetadata, locale);
|
|
2403
2484
|
const addPageSlot = (property, sourceText) => {
|
|
2404
2485
|
const slot = createSlot(
|
|
2405
2486
|
"page",
|
|
@@ -2407,17 +2488,17 @@ function translationSlots(schema, locale, options = {}) {
|
|
|
2407
2488
|
property,
|
|
2408
2489
|
locale,
|
|
2409
2490
|
sourceText,
|
|
2410
|
-
|
|
2491
|
+
pageTranslation?.[property],
|
|
2411
2492
|
page.metadata,
|
|
2412
|
-
|
|
2493
|
+
pageTranslationMetadata?.[property],
|
|
2413
2494
|
options
|
|
2414
2495
|
);
|
|
2415
2496
|
descriptors.push({
|
|
2416
2497
|
slot,
|
|
2417
|
-
manual: options.isManualTranslation?.(
|
|
2498
|
+
manual: options.isManualTranslation?.(pageTranslationMetadata?.[property], {
|
|
2418
2499
|
path: slot.path ?? "",
|
|
2419
2500
|
locale
|
|
2420
|
-
}) ?? isManualTranslationMetadata(
|
|
2501
|
+
}) ?? isManualTranslationMetadata(pageTranslationMetadata?.[property]),
|
|
2421
2502
|
apply: (current, value, metadata) => ({
|
|
2422
2503
|
...current,
|
|
2423
2504
|
...current.pages === void 0 ? {} : {
|
|
@@ -2505,7 +2586,7 @@ var migrateSchemaTranslationMetadata = (schema, migratorOrOptions) => {
|
|
|
2505
2586
|
(locale, property) => ({
|
|
2506
2587
|
locale,
|
|
2507
2588
|
defaultLocale,
|
|
2508
|
-
path: property
|
|
2589
|
+
path: `form.${property}`,
|
|
2509
2590
|
property,
|
|
2510
2591
|
nodeKind: "form"
|
|
2511
2592
|
}),
|
|
@@ -2604,8 +2685,11 @@ function collectTranslationSlots(schema, locale) {
|
|
|
2604
2685
|
return translationSlots(schema, locale).map((descriptor) => descriptor.slot);
|
|
2605
2686
|
}
|
|
2606
2687
|
function resolveLocalizedSchema(schema, targetLocale) {
|
|
2607
|
-
if (targetLocale === void 0 || targetLocale.length === 0
|
|
2608
|
-
const
|
|
2688
|
+
if (targetLocale === void 0 || targetLocale.length === 0) return schema;
|
|
2689
|
+
const normalizedTargetLocale = normalizeLocale(targetLocale) ?? targetLocale;
|
|
2690
|
+
const defaultLocale = schema.defaultLocale === void 0 ? void 0 : normalizeLocale(schema.defaultLocale);
|
|
2691
|
+
if (normalizedTargetLocale === defaultLocale || targetLocale === schema.defaultLocale) return schema;
|
|
2692
|
+
const formTranslation = localeRecordEntry2(schema.translations, normalizedTargetLocale);
|
|
2609
2693
|
const completionMessage = formTranslation?.completionMessage ?? schema.completionMessage;
|
|
2610
2694
|
return {
|
|
2611
2695
|
...schema,
|
|
@@ -2613,7 +2697,7 @@ function resolveLocalizedSchema(schema, targetLocale) {
|
|
|
2613
2697
|
...(formTranslation?.description ?? schema.description) === void 0 ? {} : { description: formTranslation?.description ?? schema.description },
|
|
2614
2698
|
...completionMessage === void 0 ? {} : { completionMessage },
|
|
2615
2699
|
fields: schema.fields.map((field) => {
|
|
2616
|
-
const translation = field.translations
|
|
2700
|
+
const translation = localeRecordEntry2(field.translations, normalizedTargetLocale);
|
|
2617
2701
|
const localized = {
|
|
2618
2702
|
...field,
|
|
2619
2703
|
title: translation?.title ?? field.title,
|
|
@@ -2624,13 +2708,13 @@ function resolveLocalizedSchema(schema, targetLocale) {
|
|
|
2624
2708
|
...localized,
|
|
2625
2709
|
options: field.options.map((option) => ({
|
|
2626
2710
|
...option,
|
|
2627
|
-
label: option.translations
|
|
2711
|
+
label: localeRecordEntry2(option.translations, normalizedTargetLocale) ?? option.label
|
|
2628
2712
|
}))
|
|
2629
2713
|
};
|
|
2630
2714
|
}),
|
|
2631
2715
|
...schema.pages === void 0 ? {} : {
|
|
2632
2716
|
pages: schema.pages.map((page) => {
|
|
2633
|
-
const translation = page.translations
|
|
2717
|
+
const translation = localeRecordEntry2(page.translations, normalizedTargetLocale);
|
|
2634
2718
|
const title = translation?.title ?? page.title;
|
|
2635
2719
|
const description = translation?.description ?? page.description;
|
|
2636
2720
|
return {
|
|
@@ -2644,8 +2728,13 @@ function resolveLocalizedSchema(schema, targetLocale) {
|
|
|
2644
2728
|
}
|
|
2645
2729
|
async function populateSchemaTranslations(schema, targetLocales, adapter, options = {}) {
|
|
2646
2730
|
assertValidFormSchema(schema);
|
|
2647
|
-
const
|
|
2648
|
-
const
|
|
2731
|
+
const defaultLocale = schema.defaultLocale === void 0 ? void 0 : normalizeLocale(schema.defaultLocale) ?? schema.defaultLocale;
|
|
2732
|
+
const locales = [
|
|
2733
|
+
...new Set(
|
|
2734
|
+
targetLocales.map((locale) => normalizeLocale(locale) ?? locale.trim()).filter((locale) => locale.length > 0 && locale !== defaultLocale)
|
|
2735
|
+
)
|
|
2736
|
+
];
|
|
2737
|
+
const allowedLocales = options.policy?.allowedLocales?.map((locale) => normalizeLocale(locale) ?? locale);
|
|
2649
2738
|
const collectedLocales = collectSchemaLocales(schema);
|
|
2650
2739
|
const disallowedLocale = [...collectedLocales.allUniqueLocales, ...locales].find(
|
|
2651
2740
|
(locale) => allowedLocales !== void 0 && !allowedLocales.includes(locale)
|
|
@@ -2687,7 +2776,7 @@ async function populateSchemaTranslations(schema, targetLocales, adapter, option
|
|
|
2687
2776
|
adapter,
|
|
2688
2777
|
selected.map((descriptor) => descriptor.slot.sourceText),
|
|
2689
2778
|
locale,
|
|
2690
|
-
|
|
2779
|
+
defaultLocale
|
|
2691
2780
|
);
|
|
2692
2781
|
if (translated.length !== selected.length) {
|
|
2693
2782
|
throw new Error(`Translation adapter returned ${translated.length} texts for ${selected.length} inputs.`);
|
|
@@ -2696,7 +2785,7 @@ async function populateSchemaTranslations(schema, targetLocales, adapter, option
|
|
|
2696
2785
|
const translatedText = translated[index];
|
|
2697
2786
|
if (translatedText === void 0) throw new Error("Translation adapter returned an unexpected result.");
|
|
2698
2787
|
const metadata = options.createMetadata?.(descriptor.slot, translatedText) ?? {
|
|
2699
|
-
sourceLocale:
|
|
2788
|
+
sourceLocale: defaultLocale ?? "",
|
|
2700
2789
|
sourceTextHash: computeSourceTextHash(descriptor.slot.sourceText),
|
|
2701
2790
|
translationSource: "automatic",
|
|
2702
2791
|
translatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
@@ -2707,8 +2796,8 @@ async function populateSchemaTranslations(schema, targetLocales, adapter, option
|
|
|
2707
2796
|
}
|
|
2708
2797
|
const supportedLocales = [
|
|
2709
2798
|
.../* @__PURE__ */ new Set([
|
|
2710
|
-
...
|
|
2711
|
-
...schema.supportedLocales ?? [],
|
|
2799
|
+
...defaultLocale === void 0 ? [] : [defaultLocale],
|
|
2800
|
+
...(schema.supportedLocales ?? []).map((locale) => normalizeLocale(locale) ?? locale),
|
|
2712
2801
|
...locales
|
|
2713
2802
|
])
|
|
2714
2803
|
];
|
|
@@ -3044,6 +3133,7 @@ function assertVersionMutable(status) {
|
|
|
3044
3133
|
matchesSubmissionFilter,
|
|
3045
3134
|
matchesSubmissionPageFilters,
|
|
3046
3135
|
migrateSchemaTranslationMetadata,
|
|
3136
|
+
normalizeLocale,
|
|
3047
3137
|
normalizeSubmissionPageSize,
|
|
3048
3138
|
pipeResponsesToCsvStream,
|
|
3049
3139
|
populateSchemaTranslations,
|
package/dist/index.d.cts
CHANGED
|
@@ -657,6 +657,14 @@ interface CreateSubmissionOptions extends ExtensibleNode {
|
|
|
657
657
|
}
|
|
658
658
|
declare function createSubmission(schema: FormSchema, values: FormValues, options: CreateSubmissionOptions): FormSubmission;
|
|
659
659
|
|
|
660
|
+
/**
|
|
661
|
+
* Normalizes a locale string to its BCP 47 canonical form.
|
|
662
|
+
*
|
|
663
|
+
* Underscore-separated locale tags are accepted for compatibility with common
|
|
664
|
+
* platform and user-input conventions. Invalid tags return null.
|
|
665
|
+
*/
|
|
666
|
+
declare const normalizeLocale: (rawLocale: string) => string | null;
|
|
667
|
+
|
|
660
668
|
interface TranslationSlot {
|
|
661
669
|
readonly kind: "form" | "page" | "field" | "option";
|
|
662
670
|
readonly nodeId: string;
|
|
@@ -768,4 +776,4 @@ declare function calculatePageVisibility(schema: FormSchema, currentAnswers: Rea
|
|
|
768
776
|
declare function calculateFieldVisibility(schema: FormSchema, currentAnswers: Readonly<Record<string, unknown>>): Readonly<Record<string, boolean>>;
|
|
769
777
|
declare function selectVisibleAnswers(schema: FormSchema, currentAnswers: FormValues): FormValues;
|
|
770
778
|
|
|
771
|
-
export { type AccumulatorReport, type AccumulatorResponse, type AccumulatorSkipReason, type AnswerValidationResult, type AsyncTranslationAdapter, type BaseField, type BaseFieldConstraintRule, type BuilderTranslationKey, type CanonicalTranslationMetadata, type CheckboxField, type CheckboxQuestionAggregate, type ChoiceDistributionEntry, type ChoiceFieldConstraintRule, type ChoiceOption, type ChoiceQuestionAggregate, type CloneVersionOptions, type CollectedLocales, type ConditionOperator, type ConditionValue, type CreateSubmissionOptions, type CrossTabulationResult, type CsvColumnContext, type CsvColumnDef, type CsvExportOptions, DEFAULT_FIELD_TYPE_DEFINITIONS, type DeleteDraftOptions, type DisplayCondition, type DisplayConditionGroup, type DisplayRule, type ExtensibleNode, type FieldConstraintRule, type FieldDisplayCondition, type FieldOption, type FieldType, type FieldTypeDefinition, type FormAnalytics, type FormEvent, type FormEventType, type FormField, type FormPage, type FormPolicy, type FormResponse, type FormSchema, type FormStorageAdapter, type FormSubmission, type FormSubmissionSettings, type FormValue, type FormValues, type FormVersionRecord, type FormVersionState, type FormVersionStatus, type JsonValue, type LegacyTranslationMetadata, type LocalizedText, type MigrateSchemaTranslationMetadataOptions, type MultiSelectField, type NodeWritableStream, type NumberField, type NumberQuestionAggregate, type NumericSummary, type OptionAggregate, type PagedSubmissionStorageAdapter, type PaginationIteratorOptions, type PopulateTranslationOptions, type PopulateTranslationsOptions, type PublishDraftOptions, type PublishDraftResult, type Question, type QuestionAggregate, type QuestionType, type RatingField, type RatingFieldConstraintRule, type ResponseAccumulator, type ResponseAccumulatorOptions, type Result, type SanitizeSchemaOptions, type SchemaIssue, type SchemaStructureIssue, type SchemaStructureIssueType, type SchemaTranslations, type SchemaValidationResult, type SelectField, type StorageAdapter, type StorageCommitError, type StreamCsvOptions, type SubmissionCursorValue, type SubmissionFilter, type SubmissionPage, type SubmissionPageQueryOptions, type SubmissionQueryOptions, type TextAnswerCursorValue, type TextAnswerItem, type TextAnswerPage, type TextAnswerPageQueryOptions, type TextField, type TextFieldConstraintRule, type TextQuestionAggregate, type TranslationAdapter, type TranslationMetadataMigrator, type TranslationMigrationContext, type TranslationReport, type TranslationSlot, type TranslationStatus, type ValidateFormSchemaOptions, type ValidationCode, type ValidationError, type ValidationIssue, type VersionTransitionError, type VersionTransitionEvent, type VersionTransitionPlan, type VersionedFormStorageAdapter, type WebhookConfig, type WebhookDispatchResult, aggregateResponses, assertValidFormSchema, assertVersionMutable, calculateChoiceDistribution, calculateCrossTabulation, calculateFieldVisibility, calculateNumericSummary, calculatePageVisibility, cloneVersionToDraft, collectSchemaLocales, collectTranslationSlots, computeSourceTextHash, createCloneTransitionPlan, createDeleteDraftTransitionPlan, createPublishTransitionPlan, createResponseAccumulator, createSubmission, decodeSubmissionCursor, decodeTextAnswerCursor, deleteDraft, dispatchWebhook, encodeSubmissionCursor, encodeTextAnswerCursor, escapeCsvCell, exportResponsesToCsv, exportResponsesToCsvStream, getTranslationStatus, isDisplayConditionGroupSatisfied, isDisplayConditionSatisfied, isManualTranslationMetadata, isQuestionVisible, iterateSubmissionPages, jsonValuesEqual, matchesSubmissionFilter, matchesSubmissionPageFilters, migrateSchemaTranslationMetadata, normalizeSubmissionPageSize, pipeResponsesToCsvStream, populateSchemaTranslations, publishDraft, removeLocaleFromSchema, resolveFormTranslation, resolveLocalizedSchema, sanitizeSchema, selectVisibleAnswers, transformFieldType, validateAnswers, validateFormSchema, validatePageAnswers, validateSchemaStructure };
|
|
779
|
+
export { type AccumulatorReport, type AccumulatorResponse, type AccumulatorSkipReason, type AnswerValidationResult, type AsyncTranslationAdapter, type BaseField, type BaseFieldConstraintRule, type BuilderTranslationKey, type CanonicalTranslationMetadata, type CheckboxField, type CheckboxQuestionAggregate, type ChoiceDistributionEntry, type ChoiceFieldConstraintRule, type ChoiceOption, type ChoiceQuestionAggregate, type CloneVersionOptions, type CollectedLocales, type ConditionOperator, type ConditionValue, type CreateSubmissionOptions, type CrossTabulationResult, type CsvColumnContext, type CsvColumnDef, type CsvExportOptions, DEFAULT_FIELD_TYPE_DEFINITIONS, type DeleteDraftOptions, type DisplayCondition, type DisplayConditionGroup, type DisplayRule, type ExtensibleNode, type FieldConstraintRule, type FieldDisplayCondition, type FieldOption, type FieldType, type FieldTypeDefinition, type FormAnalytics, type FormEvent, type FormEventType, type FormField, type FormPage, type FormPolicy, type FormResponse, type FormSchema, type FormStorageAdapter, type FormSubmission, type FormSubmissionSettings, type FormValue, type FormValues, type FormVersionRecord, type FormVersionState, type FormVersionStatus, type JsonValue, type LegacyTranslationMetadata, type LocalizedText, type MigrateSchemaTranslationMetadataOptions, type MultiSelectField, type NodeWritableStream, type NumberField, type NumberQuestionAggregate, type NumericSummary, type OptionAggregate, type PagedSubmissionStorageAdapter, type PaginationIteratorOptions, type PopulateTranslationOptions, type PopulateTranslationsOptions, type PublishDraftOptions, type PublishDraftResult, type Question, type QuestionAggregate, type QuestionType, type RatingField, type RatingFieldConstraintRule, type ResponseAccumulator, type ResponseAccumulatorOptions, type Result, type SanitizeSchemaOptions, type SchemaIssue, type SchemaStructureIssue, type SchemaStructureIssueType, type SchemaTranslations, type SchemaValidationResult, type SelectField, type StorageAdapter, type StorageCommitError, type StreamCsvOptions, type SubmissionCursorValue, type SubmissionFilter, type SubmissionPage, type SubmissionPageQueryOptions, type SubmissionQueryOptions, type TextAnswerCursorValue, type TextAnswerItem, type TextAnswerPage, type TextAnswerPageQueryOptions, type TextField, type TextFieldConstraintRule, type TextQuestionAggregate, type TranslationAdapter, type TranslationMetadataMigrator, type TranslationMigrationContext, type TranslationReport, type TranslationSlot, type TranslationStatus, type ValidateFormSchemaOptions, type ValidationCode, type ValidationError, type ValidationIssue, type VersionTransitionError, type VersionTransitionEvent, type VersionTransitionPlan, type VersionedFormStorageAdapter, type WebhookConfig, type WebhookDispatchResult, aggregateResponses, assertValidFormSchema, assertVersionMutable, calculateChoiceDistribution, calculateCrossTabulation, calculateFieldVisibility, calculateNumericSummary, calculatePageVisibility, cloneVersionToDraft, collectSchemaLocales, collectTranslationSlots, computeSourceTextHash, createCloneTransitionPlan, createDeleteDraftTransitionPlan, createPublishTransitionPlan, createResponseAccumulator, createSubmission, decodeSubmissionCursor, decodeTextAnswerCursor, deleteDraft, dispatchWebhook, encodeSubmissionCursor, encodeTextAnswerCursor, escapeCsvCell, exportResponsesToCsv, exportResponsesToCsvStream, getTranslationStatus, isDisplayConditionGroupSatisfied, isDisplayConditionSatisfied, isManualTranslationMetadata, isQuestionVisible, iterateSubmissionPages, jsonValuesEqual, matchesSubmissionFilter, matchesSubmissionPageFilters, migrateSchemaTranslationMetadata, normalizeLocale, normalizeSubmissionPageSize, pipeResponsesToCsvStream, populateSchemaTranslations, publishDraft, removeLocaleFromSchema, resolveFormTranslation, resolveLocalizedSchema, sanitizeSchema, selectVisibleAnswers, transformFieldType, validateAnswers, validateFormSchema, validatePageAnswers, validateSchemaStructure };
|
package/dist/index.d.ts
CHANGED
|
@@ -657,6 +657,14 @@ interface CreateSubmissionOptions extends ExtensibleNode {
|
|
|
657
657
|
}
|
|
658
658
|
declare function createSubmission(schema: FormSchema, values: FormValues, options: CreateSubmissionOptions): FormSubmission;
|
|
659
659
|
|
|
660
|
+
/**
|
|
661
|
+
* Normalizes a locale string to its BCP 47 canonical form.
|
|
662
|
+
*
|
|
663
|
+
* Underscore-separated locale tags are accepted for compatibility with common
|
|
664
|
+
* platform and user-input conventions. Invalid tags return null.
|
|
665
|
+
*/
|
|
666
|
+
declare const normalizeLocale: (rawLocale: string) => string | null;
|
|
667
|
+
|
|
660
668
|
interface TranslationSlot {
|
|
661
669
|
readonly kind: "form" | "page" | "field" | "option";
|
|
662
670
|
readonly nodeId: string;
|
|
@@ -768,4 +776,4 @@ declare function calculatePageVisibility(schema: FormSchema, currentAnswers: Rea
|
|
|
768
776
|
declare function calculateFieldVisibility(schema: FormSchema, currentAnswers: Readonly<Record<string, unknown>>): Readonly<Record<string, boolean>>;
|
|
769
777
|
declare function selectVisibleAnswers(schema: FormSchema, currentAnswers: FormValues): FormValues;
|
|
770
778
|
|
|
771
|
-
export { type AccumulatorReport, type AccumulatorResponse, type AccumulatorSkipReason, type AnswerValidationResult, type AsyncTranslationAdapter, type BaseField, type BaseFieldConstraintRule, type BuilderTranslationKey, type CanonicalTranslationMetadata, type CheckboxField, type CheckboxQuestionAggregate, type ChoiceDistributionEntry, type ChoiceFieldConstraintRule, type ChoiceOption, type ChoiceQuestionAggregate, type CloneVersionOptions, type CollectedLocales, type ConditionOperator, type ConditionValue, type CreateSubmissionOptions, type CrossTabulationResult, type CsvColumnContext, type CsvColumnDef, type CsvExportOptions, DEFAULT_FIELD_TYPE_DEFINITIONS, type DeleteDraftOptions, type DisplayCondition, type DisplayConditionGroup, type DisplayRule, type ExtensibleNode, type FieldConstraintRule, type FieldDisplayCondition, type FieldOption, type FieldType, type FieldTypeDefinition, type FormAnalytics, type FormEvent, type FormEventType, type FormField, type FormPage, type FormPolicy, type FormResponse, type FormSchema, type FormStorageAdapter, type FormSubmission, type FormSubmissionSettings, type FormValue, type FormValues, type FormVersionRecord, type FormVersionState, type FormVersionStatus, type JsonValue, type LegacyTranslationMetadata, type LocalizedText, type MigrateSchemaTranslationMetadataOptions, type MultiSelectField, type NodeWritableStream, type NumberField, type NumberQuestionAggregate, type NumericSummary, type OptionAggregate, type PagedSubmissionStorageAdapter, type PaginationIteratorOptions, type PopulateTranslationOptions, type PopulateTranslationsOptions, type PublishDraftOptions, type PublishDraftResult, type Question, type QuestionAggregate, type QuestionType, type RatingField, type RatingFieldConstraintRule, type ResponseAccumulator, type ResponseAccumulatorOptions, type Result, type SanitizeSchemaOptions, type SchemaIssue, type SchemaStructureIssue, type SchemaStructureIssueType, type SchemaTranslations, type SchemaValidationResult, type SelectField, type StorageAdapter, type StorageCommitError, type StreamCsvOptions, type SubmissionCursorValue, type SubmissionFilter, type SubmissionPage, type SubmissionPageQueryOptions, type SubmissionQueryOptions, type TextAnswerCursorValue, type TextAnswerItem, type TextAnswerPage, type TextAnswerPageQueryOptions, type TextField, type TextFieldConstraintRule, type TextQuestionAggregate, type TranslationAdapter, type TranslationMetadataMigrator, type TranslationMigrationContext, type TranslationReport, type TranslationSlot, type TranslationStatus, type ValidateFormSchemaOptions, type ValidationCode, type ValidationError, type ValidationIssue, type VersionTransitionError, type VersionTransitionEvent, type VersionTransitionPlan, type VersionedFormStorageAdapter, type WebhookConfig, type WebhookDispatchResult, aggregateResponses, assertValidFormSchema, assertVersionMutable, calculateChoiceDistribution, calculateCrossTabulation, calculateFieldVisibility, calculateNumericSummary, calculatePageVisibility, cloneVersionToDraft, collectSchemaLocales, collectTranslationSlots, computeSourceTextHash, createCloneTransitionPlan, createDeleteDraftTransitionPlan, createPublishTransitionPlan, createResponseAccumulator, createSubmission, decodeSubmissionCursor, decodeTextAnswerCursor, deleteDraft, dispatchWebhook, encodeSubmissionCursor, encodeTextAnswerCursor, escapeCsvCell, exportResponsesToCsv, exportResponsesToCsvStream, getTranslationStatus, isDisplayConditionGroupSatisfied, isDisplayConditionSatisfied, isManualTranslationMetadata, isQuestionVisible, iterateSubmissionPages, jsonValuesEqual, matchesSubmissionFilter, matchesSubmissionPageFilters, migrateSchemaTranslationMetadata, normalizeSubmissionPageSize, pipeResponsesToCsvStream, populateSchemaTranslations, publishDraft, removeLocaleFromSchema, resolveFormTranslation, resolveLocalizedSchema, sanitizeSchema, selectVisibleAnswers, transformFieldType, validateAnswers, validateFormSchema, validatePageAnswers, validateSchemaStructure };
|
|
779
|
+
export { type AccumulatorReport, type AccumulatorResponse, type AccumulatorSkipReason, type AnswerValidationResult, type AsyncTranslationAdapter, type BaseField, type BaseFieldConstraintRule, type BuilderTranslationKey, type CanonicalTranslationMetadata, type CheckboxField, type CheckboxQuestionAggregate, type ChoiceDistributionEntry, type ChoiceFieldConstraintRule, type ChoiceOption, type ChoiceQuestionAggregate, type CloneVersionOptions, type CollectedLocales, type ConditionOperator, type ConditionValue, type CreateSubmissionOptions, type CrossTabulationResult, type CsvColumnContext, type CsvColumnDef, type CsvExportOptions, DEFAULT_FIELD_TYPE_DEFINITIONS, type DeleteDraftOptions, type DisplayCondition, type DisplayConditionGroup, type DisplayRule, type ExtensibleNode, type FieldConstraintRule, type FieldDisplayCondition, type FieldOption, type FieldType, type FieldTypeDefinition, type FormAnalytics, type FormEvent, type FormEventType, type FormField, type FormPage, type FormPolicy, type FormResponse, type FormSchema, type FormStorageAdapter, type FormSubmission, type FormSubmissionSettings, type FormValue, type FormValues, type FormVersionRecord, type FormVersionState, type FormVersionStatus, type JsonValue, type LegacyTranslationMetadata, type LocalizedText, type MigrateSchemaTranslationMetadataOptions, type MultiSelectField, type NodeWritableStream, type NumberField, type NumberQuestionAggregate, type NumericSummary, type OptionAggregate, type PagedSubmissionStorageAdapter, type PaginationIteratorOptions, type PopulateTranslationOptions, type PopulateTranslationsOptions, type PublishDraftOptions, type PublishDraftResult, type Question, type QuestionAggregate, type QuestionType, type RatingField, type RatingFieldConstraintRule, type ResponseAccumulator, type ResponseAccumulatorOptions, type Result, type SanitizeSchemaOptions, type SchemaIssue, type SchemaStructureIssue, type SchemaStructureIssueType, type SchemaTranslations, type SchemaValidationResult, type SelectField, type StorageAdapter, type StorageCommitError, type StreamCsvOptions, type SubmissionCursorValue, type SubmissionFilter, type SubmissionPage, type SubmissionPageQueryOptions, type SubmissionQueryOptions, type TextAnswerCursorValue, type TextAnswerItem, type TextAnswerPage, type TextAnswerPageQueryOptions, type TextField, type TextFieldConstraintRule, type TextQuestionAggregate, type TranslationAdapter, type TranslationMetadataMigrator, type TranslationMigrationContext, type TranslationReport, type TranslationSlot, type TranslationStatus, type ValidateFormSchemaOptions, type ValidationCode, type ValidationError, type ValidationIssue, type VersionTransitionError, type VersionTransitionEvent, type VersionTransitionPlan, type VersionedFormStorageAdapter, type WebhookConfig, type WebhookDispatchResult, aggregateResponses, assertValidFormSchema, assertVersionMutable, calculateChoiceDistribution, calculateCrossTabulation, calculateFieldVisibility, calculateNumericSummary, calculatePageVisibility, cloneVersionToDraft, collectSchemaLocales, collectTranslationSlots, computeSourceTextHash, createCloneTransitionPlan, createDeleteDraftTransitionPlan, createPublishTransitionPlan, createResponseAccumulator, createSubmission, decodeSubmissionCursor, decodeTextAnswerCursor, deleteDraft, dispatchWebhook, encodeSubmissionCursor, encodeTextAnswerCursor, escapeCsvCell, exportResponsesToCsv, exportResponsesToCsvStream, getTranslationStatus, isDisplayConditionGroupSatisfied, isDisplayConditionSatisfied, isManualTranslationMetadata, isQuestionVisible, iterateSubmissionPages, jsonValuesEqual, matchesSubmissionFilter, matchesSubmissionPageFilters, migrateSchemaTranslationMetadata, normalizeLocale, normalizeSubmissionPageSize, pipeResponsesToCsvStream, populateSchemaTranslations, publishDraft, removeLocaleFromSchema, resolveFormTranslation, resolveLocalizedSchema, sanitizeSchema, selectVisibleAnswers, transformFieldType, validateAnswers, validateFormSchema, validatePageAnswers, validateSchemaStructure };
|
package/dist/index.js
CHANGED
|
@@ -1,8 +1,24 @@
|
|
|
1
|
+
// src/locale.ts
|
|
2
|
+
var normalizeLocale = (rawLocale) => {
|
|
3
|
+
if (!rawLocale || typeof rawLocale !== "string") return null;
|
|
4
|
+
const trimmed = rawLocale.trim().replace(/_/gu, "-");
|
|
5
|
+
if (trimmed.length === 0) return null;
|
|
6
|
+
try {
|
|
7
|
+
return Intl.getCanonicalLocales(trimmed)[0] ?? null;
|
|
8
|
+
} catch {
|
|
9
|
+
return null;
|
|
10
|
+
}
|
|
11
|
+
};
|
|
12
|
+
function canonicalLocaleOrRaw(rawLocale) {
|
|
13
|
+
return normalizeLocale(rawLocale) ?? rawLocale;
|
|
14
|
+
}
|
|
15
|
+
|
|
1
16
|
// src/policy.ts
|
|
2
17
|
function collectRecordKeys(value, path, pathsByLocale) {
|
|
3
|
-
for (const
|
|
18
|
+
for (const rawLocale of Object.keys(value ?? {})) {
|
|
19
|
+
const locale = canonicalLocaleOrRaw(rawLocale);
|
|
4
20
|
const paths = pathsByLocale.get(locale) ?? [];
|
|
5
|
-
paths.push(`${path}.${
|
|
21
|
+
paths.push(`${path}.${rawLocale}`);
|
|
6
22
|
pathsByLocale.set(locale, paths);
|
|
7
23
|
}
|
|
8
24
|
}
|
|
@@ -33,13 +49,15 @@ function collectSchemaLocales(schema) {
|
|
|
33
49
|
});
|
|
34
50
|
const translationLocales = new Set(pathsByLocale.keys());
|
|
35
51
|
const allUniqueLocales = /* @__PURE__ */ new Set([
|
|
36
|
-
...schema.defaultLocale === void 0 ? [] : [schema.defaultLocale],
|
|
37
|
-
...schema.supportedLocales ?? [],
|
|
52
|
+
...schema.defaultLocale === void 0 ? [] : [canonicalLocaleOrRaw(schema.defaultLocale)],
|
|
53
|
+
...(schema.supportedLocales ?? []).map(canonicalLocaleOrRaw),
|
|
38
54
|
...translationLocales
|
|
39
55
|
]);
|
|
56
|
+
const defaultLocale = schema.defaultLocale === void 0 ? void 0 : canonicalLocaleOrRaw(schema.defaultLocale);
|
|
57
|
+
const supportedLocales = (schema.supportedLocales ?? []).map(canonicalLocaleOrRaw);
|
|
40
58
|
return {
|
|
41
|
-
...
|
|
42
|
-
supportedLocales
|
|
59
|
+
...defaultLocale === void 0 ? {} : { defaultLocale },
|
|
60
|
+
supportedLocales,
|
|
43
61
|
translationLocales,
|
|
44
62
|
allUniqueLocales,
|
|
45
63
|
translationLocalePaths: pathsByLocale
|
|
@@ -62,7 +80,12 @@ function displayRuleSourceIds(field) {
|
|
|
62
80
|
}
|
|
63
81
|
function registeredEntries(value, registeredLocales) {
|
|
64
82
|
if (value === void 0) return void 0;
|
|
65
|
-
const entries =
|
|
83
|
+
const entries = [];
|
|
84
|
+
for (const [rawLocale, entry] of Object.entries(value)) {
|
|
85
|
+
const locale = normalizeLocale(rawLocale);
|
|
86
|
+
if (locale === null || !registeredLocales.has(locale)) continue;
|
|
87
|
+
entries.push([locale, entry]);
|
|
88
|
+
}
|
|
66
89
|
return entries.length === 0 ? void 0 : Object.fromEntries(entries);
|
|
67
90
|
}
|
|
68
91
|
function sanitizeNodeLocales(node, registeredLocales) {
|
|
@@ -100,6 +123,16 @@ function sanitizePageLocales(page, registeredLocales) {
|
|
|
100
123
|
const translations = registeredEntries(page.translations, registeredLocales);
|
|
101
124
|
return { ...base, ...translations === void 0 ? {} : { translations } };
|
|
102
125
|
}
|
|
126
|
+
function normalizedLocaleList(locales) {
|
|
127
|
+
return [
|
|
128
|
+
...new Set(
|
|
129
|
+
locales.flatMap((locale) => {
|
|
130
|
+
const normalized = normalizeLocale(locale);
|
|
131
|
+
return normalized === null ? [] : [normalized];
|
|
132
|
+
})
|
|
133
|
+
)
|
|
134
|
+
];
|
|
135
|
+
}
|
|
103
136
|
function sanitizeFieldConstraints(field, policy) {
|
|
104
137
|
const constraint = policy?.fieldConstraints?.[field.type];
|
|
105
138
|
if (constraint === void 0) return field;
|
|
@@ -214,10 +247,9 @@ function validateSchemaStructure(schema) {
|
|
|
214
247
|
}
|
|
215
248
|
function sanitizeSchema(schema, options = {}) {
|
|
216
249
|
const existingQuestionIds = new Set(schema.fields.map((field) => field.id));
|
|
217
|
-
const
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
]);
|
|
250
|
+
const defaultLocale = schema.defaultLocale === void 0 ? null : normalizeLocale(schema.defaultLocale);
|
|
251
|
+
const supportedLocales = schema.supportedLocales === void 0 ? void 0 : normalizedLocaleList(schema.supportedLocales);
|
|
252
|
+
const registeredLocales = /* @__PURE__ */ new Set([...defaultLocale === null ? [] : [defaultLocale], ...supportedLocales ?? []]);
|
|
221
253
|
const cyclic = cyclicQuestionIds(schema.fields);
|
|
222
254
|
const sanitizedFields = schema.fields.map((sourceField) => {
|
|
223
255
|
const field = sanitizeFieldConstraints(sanitizeFieldLocales(sourceField, registeredLocales), options.policy);
|
|
@@ -235,10 +267,17 @@ function sanitizeSchema(schema, options = {}) {
|
|
|
235
267
|
return sanitized;
|
|
236
268
|
});
|
|
237
269
|
const localizedSchema = sanitizeNodeLocales(schema, registeredLocales);
|
|
238
|
-
const {
|
|
270
|
+
const {
|
|
271
|
+
defaultLocale: _defaultLocale,
|
|
272
|
+
supportedLocales: _supportedLocales,
|
|
273
|
+
translations: _translations,
|
|
274
|
+
...schemaWithoutLocaleContent
|
|
275
|
+
} = localizedSchema;
|
|
239
276
|
const translations = registeredEntries(schema.translations, registeredLocales);
|
|
240
277
|
const base = {
|
|
241
278
|
...schemaWithoutLocaleContent,
|
|
279
|
+
...defaultLocale === null ? {} : { defaultLocale },
|
|
280
|
+
...supportedLocales === void 0 ? {} : { supportedLocales },
|
|
242
281
|
...translations === void 0 ? {} : { translations },
|
|
243
282
|
fields: sanitizedFields
|
|
244
283
|
};
|
|
@@ -291,6 +330,12 @@ function isRecord(value) {
|
|
|
291
330
|
function isNonEmptyString(value) {
|
|
292
331
|
return typeof value === "string" && value.trim().length > 0;
|
|
293
332
|
}
|
|
333
|
+
function localeRecordEntry(record, locale) {
|
|
334
|
+
for (const [candidate, value] of Object.entries(record ?? {})) {
|
|
335
|
+
if (canonicalLocaleOrRaw(candidate) === locale) return value;
|
|
336
|
+
}
|
|
337
|
+
return void 0;
|
|
338
|
+
}
|
|
294
339
|
function issue(issues, path, code, message) {
|
|
295
340
|
issues.push({ path, code, message });
|
|
296
341
|
}
|
|
@@ -653,51 +698,55 @@ function collectSchemaText(schema) {
|
|
|
653
698
|
return entries;
|
|
654
699
|
}
|
|
655
700
|
function addRequiredTranslationIssues(schema, locale, issues) {
|
|
656
|
-
if (!(schema.supportedLocales ?? []).
|
|
701
|
+
if (!(schema.supportedLocales ?? []).some((candidate) => canonicalLocaleOrRaw(candidate) === locale)) {
|
|
657
702
|
issue(issues, "supportedLocales", "required_locale_missing", `Required locale ${locale} is missing.`);
|
|
658
703
|
}
|
|
659
|
-
if (
|
|
704
|
+
if (schema.defaultLocale !== void 0 && canonicalLocaleOrRaw(schema.defaultLocale) === locale) return;
|
|
705
|
+
const formTranslation = localeRecordEntry(schema.translations, locale);
|
|
660
706
|
const required = [
|
|
661
|
-
{ path: `translations.${locale}.title`, value:
|
|
707
|
+
{ path: `translations.${locale}.title`, value: formTranslation?.title }
|
|
662
708
|
];
|
|
663
709
|
if (schema.description !== void 0)
|
|
664
|
-
required.push({ path: `translations.${locale}.description`, value:
|
|
710
|
+
required.push({ path: `translations.${locale}.description`, value: formTranslation?.description });
|
|
665
711
|
if (schema.completionMessage !== void 0) {
|
|
666
712
|
required.push({
|
|
667
713
|
path: `translations.${locale}.completionMessage`,
|
|
668
|
-
value:
|
|
714
|
+
value: formTranslation?.completionMessage
|
|
669
715
|
});
|
|
670
716
|
}
|
|
671
717
|
schema.fields.forEach((field, fieldIndex) => {
|
|
718
|
+
const fieldTranslation = localeRecordEntry(field.translations, locale);
|
|
672
719
|
required.push({
|
|
673
720
|
path: `fields[${fieldIndex}].translations.${locale}.title`,
|
|
674
|
-
value:
|
|
721
|
+
value: fieldTranslation?.title
|
|
675
722
|
});
|
|
676
723
|
if (field.description !== void 0) {
|
|
677
724
|
required.push({
|
|
678
725
|
path: `fields[${fieldIndex}].translations.${locale}.description`,
|
|
679
|
-
value:
|
|
726
|
+
value: fieldTranslation?.description
|
|
680
727
|
});
|
|
681
728
|
}
|
|
682
729
|
if (!("options" in field)) return;
|
|
683
730
|
field.options.forEach((option, optionIndex) => {
|
|
731
|
+
const optionTranslation = localeRecordEntry(option.translations, locale);
|
|
684
732
|
required.push({
|
|
685
733
|
path: `fields[${fieldIndex}].options[${optionIndex}].translations.${locale}`,
|
|
686
|
-
value:
|
|
734
|
+
value: optionTranslation
|
|
687
735
|
});
|
|
688
736
|
});
|
|
689
737
|
});
|
|
690
738
|
schema.pages?.forEach((page, pageIndex) => {
|
|
739
|
+
const pageTranslation = localeRecordEntry(page.translations, locale);
|
|
691
740
|
if (page.title !== void 0) {
|
|
692
741
|
required.push({
|
|
693
742
|
path: `pages[${pageIndex}].translations.${locale}.title`,
|
|
694
|
-
value:
|
|
743
|
+
value: pageTranslation?.title
|
|
695
744
|
});
|
|
696
745
|
}
|
|
697
746
|
if (page.description !== void 0) {
|
|
698
747
|
required.push({
|
|
699
748
|
path: `pages[${pageIndex}].translations.${locale}.description`,
|
|
700
|
-
value:
|
|
749
|
+
value: pageTranslation?.description
|
|
701
750
|
});
|
|
702
751
|
}
|
|
703
752
|
});
|
|
@@ -830,8 +879,8 @@ function validatePolicy(schema, policy, issues) {
|
|
|
830
879
|
}
|
|
831
880
|
const collectedLocales = collectSchemaLocales(schema);
|
|
832
881
|
const registeredLocales = /* @__PURE__ */ new Set([
|
|
833
|
-
...
|
|
834
|
-
...
|
|
882
|
+
...collectedLocales.defaultLocale === void 0 ? [] : [collectedLocales.defaultLocale],
|
|
883
|
+
...collectedLocales.supportedLocales
|
|
835
884
|
]);
|
|
836
885
|
for (const locale of collectedLocales.translationLocales) {
|
|
837
886
|
if (registeredLocales.has(locale)) continue;
|
|
@@ -845,23 +894,27 @@ function validatePolicy(schema, policy, issues) {
|
|
|
845
894
|
}
|
|
846
895
|
}
|
|
847
896
|
if (policy.allowedLocales !== void 0) {
|
|
897
|
+
const allowedLocales = new Set(policy.allowedLocales.map(canonicalLocaleOrRaw));
|
|
848
898
|
const pathsByLocale = /* @__PURE__ */ new Map();
|
|
849
|
-
if (
|
|
899
|
+
if (collectedLocales.defaultLocale !== void 0)
|
|
900
|
+
pathsByLocale.set(collectedLocales.defaultLocale, ["defaultLocale"]);
|
|
850
901
|
schema.supportedLocales?.forEach((locale, index) => {
|
|
851
|
-
|
|
902
|
+
const canonical = canonicalLocaleOrRaw(locale);
|
|
903
|
+
pathsByLocale.set(canonical, [...pathsByLocale.get(canonical) ?? [], `supportedLocales[${index}]`]);
|
|
852
904
|
});
|
|
853
905
|
for (const [locale, paths] of collectedLocales.translationLocalePaths) {
|
|
854
906
|
pathsByLocale.set(locale, [...pathsByLocale.get(locale) ?? [], ...paths]);
|
|
855
907
|
}
|
|
856
908
|
for (const [locale, paths] of pathsByLocale) {
|
|
857
|
-
if (!
|
|
909
|
+
if (!allowedLocales.has(locale)) {
|
|
858
910
|
for (const path of paths) {
|
|
859
911
|
issue(issues, path, "disallowed_locale", `Locale ${locale} is not allowed by the form policy.`);
|
|
860
912
|
}
|
|
861
913
|
}
|
|
862
914
|
}
|
|
863
|
-
for (const
|
|
864
|
-
|
|
915
|
+
for (const rawLocale of policy.requiredLocales ?? []) {
|
|
916
|
+
const locale = canonicalLocaleOrRaw(rawLocale);
|
|
917
|
+
if (!allowedLocales.has(locale)) {
|
|
865
918
|
issue(
|
|
866
919
|
issues,
|
|
867
920
|
"policy.requiredLocales",
|
|
@@ -874,7 +927,8 @@ function validatePolicy(schema, policy, issues) {
|
|
|
874
927
|
if (policy.maxLocales !== void 0 && collectedLocales.allUniqueLocales.size > policy.maxLocales) {
|
|
875
928
|
issue(issues, "supportedLocales", "max_locales_exceeded", `At most ${policy.maxLocales} locales are allowed.`);
|
|
876
929
|
}
|
|
877
|
-
for (const locale of policy.requiredLocales ?? [])
|
|
930
|
+
for (const locale of policy.requiredLocales ?? [])
|
|
931
|
+
addRequiredTranslationIssues(schema, canonicalLocaleOrRaw(locale), issues);
|
|
878
932
|
if (policy.maxSchemaBytes !== void 0) {
|
|
879
933
|
try {
|
|
880
934
|
const byteLength = new TextEncoder().encode(JSON.stringify(schema)).byteLength;
|
|
@@ -909,8 +963,12 @@ function validateFormSchema(input, options = {}) {
|
|
|
909
963
|
);
|
|
910
964
|
}
|
|
911
965
|
}
|
|
912
|
-
if (input.defaultLocale !== void 0
|
|
913
|
-
|
|
966
|
+
if (input.defaultLocale !== void 0) {
|
|
967
|
+
if (!isNonEmptyString(input.defaultLocale)) {
|
|
968
|
+
issue(issues, "defaultLocale", "invalid_locale", "Expected a non-empty default locale.");
|
|
969
|
+
} else if (normalizeLocale(input.defaultLocale) === null) {
|
|
970
|
+
issue(issues, "defaultLocale", "invalid_locale", "Expected a valid BCP 47 locale.");
|
|
971
|
+
}
|
|
914
972
|
}
|
|
915
973
|
if (input.supportedLocales !== void 0) {
|
|
916
974
|
if (!Array.isArray(input.supportedLocales) || input.supportedLocales.length === 0) {
|
|
@@ -920,10 +978,15 @@ function validateFormSchema(input, options = {}) {
|
|
|
920
978
|
input.supportedLocales.forEach((locale, index) => {
|
|
921
979
|
if (!isNonEmptyString(locale)) {
|
|
922
980
|
issue(issues, `supportedLocales[${index}]`, "invalid_locale", "Expected a non-empty locale.");
|
|
923
|
-
} else if (locales.has(locale)) {
|
|
924
|
-
issue(issues, `supportedLocales[${index}]`, "duplicate_locale", "Locales must be unique.");
|
|
925
981
|
} else {
|
|
926
|
-
|
|
982
|
+
const normalized = normalizeLocale(locale);
|
|
983
|
+
if (normalized === null) {
|
|
984
|
+
issue(issues, `supportedLocales[${index}]`, "invalid_locale", "Expected a valid BCP 47 locale.");
|
|
985
|
+
} else if (locales.has(normalized)) {
|
|
986
|
+
issue(issues, `supportedLocales[${index}]`, "duplicate_locale", "Locales must be unique.");
|
|
987
|
+
} else {
|
|
988
|
+
locales.add(normalized);
|
|
989
|
+
}
|
|
927
990
|
}
|
|
928
991
|
});
|
|
929
992
|
}
|
|
@@ -2192,8 +2255,15 @@ function withTranslationMetadata(node, locale, property, metadata) {
|
|
|
2192
2255
|
}
|
|
2193
2256
|
};
|
|
2194
2257
|
}
|
|
2195
|
-
function
|
|
2196
|
-
const
|
|
2258
|
+
function localeRecordEntry2(record, locale) {
|
|
2259
|
+
const normalizedLocale = normalizeLocale(locale) ?? locale;
|
|
2260
|
+
for (const [candidate, value] of Object.entries(record ?? {})) {
|
|
2261
|
+
if ((normalizeLocale(candidate) ?? candidate) === normalizedLocale) return value;
|
|
2262
|
+
}
|
|
2263
|
+
return void 0;
|
|
2264
|
+
}
|
|
2265
|
+
function createSlot(kind, nodeId, property, locale, sourceText, existingText, nodeMetadata, existingTranslationMetadata, options = {}, parentId) {
|
|
2266
|
+
const path = kind === "form" ? `form.${property}` : kind === "option" ? `fields.${parentId ?? ""}.options.${nodeId}.${property}` : `${kind}s.${nodeId}.${property}`;
|
|
2197
2267
|
const manual = options.isManualTranslation?.(existingTranslationMetadata, { path, locale }) ?? isManualTranslationMetadata(existingTranslationMetadata);
|
|
2198
2268
|
const normalizedMetadata = existingTranslationMetadata === void 0 ? void 0 : options.normalizeMetadata === void 0 ? existingTranslationMetadata : { ...options.normalizeMetadata(existingTranslationMetadata, sourceText) };
|
|
2199
2269
|
const status = getTranslationStatusWithManualOverride(sourceText, existingText, normalizedMetadata, manual);
|
|
@@ -2213,7 +2283,10 @@ function createSlot(kind, nodeId, property, locale, sourceText, existingText, no
|
|
|
2213
2283
|
};
|
|
2214
2284
|
}
|
|
2215
2285
|
function translationSlots(schema, locale, options = {}) {
|
|
2286
|
+
locale = normalizeLocale(locale) ?? locale;
|
|
2216
2287
|
const descriptors = [];
|
|
2288
|
+
const schemaTranslation = localeRecordEntry2(schema.translations, locale);
|
|
2289
|
+
const schemaTranslationMetadata = localeRecordEntry2(schema.translationMetadata, locale);
|
|
2217
2290
|
const addFormSlot = (property, sourceText) => {
|
|
2218
2291
|
const slot = createSlot(
|
|
2219
2292
|
"form",
|
|
@@ -2221,17 +2294,17 @@ function translationSlots(schema, locale, options = {}) {
|
|
|
2221
2294
|
property,
|
|
2222
2295
|
locale,
|
|
2223
2296
|
sourceText,
|
|
2224
|
-
|
|
2297
|
+
schemaTranslation?.[property],
|
|
2225
2298
|
schema.metadata,
|
|
2226
|
-
|
|
2299
|
+
schemaTranslationMetadata?.[property],
|
|
2227
2300
|
options
|
|
2228
2301
|
);
|
|
2229
2302
|
descriptors.push({
|
|
2230
2303
|
slot,
|
|
2231
|
-
manual: options.isManualTranslation?.(
|
|
2304
|
+
manual: options.isManualTranslation?.(schemaTranslationMetadata?.[property], {
|
|
2232
2305
|
path: slot.path ?? "",
|
|
2233
2306
|
locale
|
|
2234
|
-
}) ?? isManualTranslationMetadata(
|
|
2307
|
+
}) ?? isManualTranslationMetadata(schemaTranslationMetadata?.[property]),
|
|
2235
2308
|
apply: (current, value, metadata) => withTranslationMetadata(
|
|
2236
2309
|
{ ...current, translations: mergeLocalizedText(current.translations, locale, property, value) },
|
|
2237
2310
|
locale,
|
|
@@ -2244,6 +2317,8 @@ function translationSlots(schema, locale, options = {}) {
|
|
|
2244
2317
|
if (schema.description !== void 0) addFormSlot("description", schema.description);
|
|
2245
2318
|
if (schema.completionMessage !== void 0) addFormSlot("completionMessage", schema.completionMessage);
|
|
2246
2319
|
schema.fields.forEach((field, fieldIndex) => {
|
|
2320
|
+
const fieldTranslation = localeRecordEntry2(field.translations, locale);
|
|
2321
|
+
const fieldTranslationMetadata = localeRecordEntry2(field.translationMetadata, locale);
|
|
2247
2322
|
const addFieldSlot = (property, sourceText) => {
|
|
2248
2323
|
const slot = createSlot(
|
|
2249
2324
|
"field",
|
|
@@ -2251,17 +2326,17 @@ function translationSlots(schema, locale, options = {}) {
|
|
|
2251
2326
|
property,
|
|
2252
2327
|
locale,
|
|
2253
2328
|
sourceText,
|
|
2254
|
-
|
|
2329
|
+
fieldTranslation?.[property],
|
|
2255
2330
|
field.metadata,
|
|
2256
|
-
|
|
2331
|
+
fieldTranslationMetadata?.[property],
|
|
2257
2332
|
options
|
|
2258
2333
|
);
|
|
2259
2334
|
descriptors.push({
|
|
2260
2335
|
slot,
|
|
2261
|
-
manual: options.isManualTranslation?.(
|
|
2336
|
+
manual: options.isManualTranslation?.(fieldTranslationMetadata?.[property], {
|
|
2262
2337
|
path: slot.path ?? "",
|
|
2263
2338
|
locale
|
|
2264
|
-
}) ?? isManualTranslationMetadata(
|
|
2339
|
+
}) ?? isManualTranslationMetadata(fieldTranslationMetadata?.[property]),
|
|
2265
2340
|
apply: (current, value, metadata) => ({
|
|
2266
2341
|
...current,
|
|
2267
2342
|
fields: current.fields.map(
|
|
@@ -2282,23 +2357,26 @@ function translationSlots(schema, locale, options = {}) {
|
|
|
2282
2357
|
if (field.description !== void 0) addFieldSlot("description", field.description);
|
|
2283
2358
|
if ("options" in field) {
|
|
2284
2359
|
field.options.forEach((option, optionIndex) => {
|
|
2360
|
+
const optionTranslation = localeRecordEntry2(option.translations, locale);
|
|
2361
|
+
const optionTranslationMetadata = localeRecordEntry2(option.translationMetadata, locale);
|
|
2285
2362
|
const slot = createSlot(
|
|
2286
2363
|
"option",
|
|
2287
2364
|
option.id,
|
|
2288
2365
|
"label",
|
|
2289
2366
|
locale,
|
|
2290
2367
|
option.label,
|
|
2291
|
-
|
|
2368
|
+
optionTranslation,
|
|
2292
2369
|
option.metadata,
|
|
2293
|
-
|
|
2294
|
-
options
|
|
2370
|
+
optionTranslationMetadata?.label,
|
|
2371
|
+
options,
|
|
2372
|
+
field.id
|
|
2295
2373
|
);
|
|
2296
2374
|
descriptors.push({
|
|
2297
2375
|
slot,
|
|
2298
|
-
manual: options.isManualTranslation?.(
|
|
2376
|
+
manual: options.isManualTranslation?.(optionTranslationMetadata?.label, {
|
|
2299
2377
|
path: slot.path ?? "",
|
|
2300
2378
|
locale
|
|
2301
|
-
}) ?? isManualTranslationMetadata(
|
|
2379
|
+
}) ?? isManualTranslationMetadata(optionTranslationMetadata?.label),
|
|
2302
2380
|
apply: (current, value, metadata) => ({
|
|
2303
2381
|
...current,
|
|
2304
2382
|
fields: current.fields.map((candidate, candidateIndex) => {
|
|
@@ -2324,6 +2402,8 @@ function translationSlots(schema, locale, options = {}) {
|
|
|
2324
2402
|
}
|
|
2325
2403
|
});
|
|
2326
2404
|
schema.pages?.forEach((page, pageIndex) => {
|
|
2405
|
+
const pageTranslation = localeRecordEntry2(page.translations, locale);
|
|
2406
|
+
const pageTranslationMetadata = localeRecordEntry2(page.translationMetadata, locale);
|
|
2327
2407
|
const addPageSlot = (property, sourceText) => {
|
|
2328
2408
|
const slot = createSlot(
|
|
2329
2409
|
"page",
|
|
@@ -2331,17 +2411,17 @@ function translationSlots(schema, locale, options = {}) {
|
|
|
2331
2411
|
property,
|
|
2332
2412
|
locale,
|
|
2333
2413
|
sourceText,
|
|
2334
|
-
|
|
2414
|
+
pageTranslation?.[property],
|
|
2335
2415
|
page.metadata,
|
|
2336
|
-
|
|
2416
|
+
pageTranslationMetadata?.[property],
|
|
2337
2417
|
options
|
|
2338
2418
|
);
|
|
2339
2419
|
descriptors.push({
|
|
2340
2420
|
slot,
|
|
2341
|
-
manual: options.isManualTranslation?.(
|
|
2421
|
+
manual: options.isManualTranslation?.(pageTranslationMetadata?.[property], {
|
|
2342
2422
|
path: slot.path ?? "",
|
|
2343
2423
|
locale
|
|
2344
|
-
}) ?? isManualTranslationMetadata(
|
|
2424
|
+
}) ?? isManualTranslationMetadata(pageTranslationMetadata?.[property]),
|
|
2345
2425
|
apply: (current, value, metadata) => ({
|
|
2346
2426
|
...current,
|
|
2347
2427
|
...current.pages === void 0 ? {} : {
|
|
@@ -2429,7 +2509,7 @@ var migrateSchemaTranslationMetadata = (schema, migratorOrOptions) => {
|
|
|
2429
2509
|
(locale, property) => ({
|
|
2430
2510
|
locale,
|
|
2431
2511
|
defaultLocale,
|
|
2432
|
-
path: property
|
|
2512
|
+
path: `form.${property}`,
|
|
2433
2513
|
property,
|
|
2434
2514
|
nodeKind: "form"
|
|
2435
2515
|
}),
|
|
@@ -2528,8 +2608,11 @@ function collectTranslationSlots(schema, locale) {
|
|
|
2528
2608
|
return translationSlots(schema, locale).map((descriptor) => descriptor.slot);
|
|
2529
2609
|
}
|
|
2530
2610
|
function resolveLocalizedSchema(schema, targetLocale) {
|
|
2531
|
-
if (targetLocale === void 0 || targetLocale.length === 0
|
|
2532
|
-
const
|
|
2611
|
+
if (targetLocale === void 0 || targetLocale.length === 0) return schema;
|
|
2612
|
+
const normalizedTargetLocale = normalizeLocale(targetLocale) ?? targetLocale;
|
|
2613
|
+
const defaultLocale = schema.defaultLocale === void 0 ? void 0 : normalizeLocale(schema.defaultLocale);
|
|
2614
|
+
if (normalizedTargetLocale === defaultLocale || targetLocale === schema.defaultLocale) return schema;
|
|
2615
|
+
const formTranslation = localeRecordEntry2(schema.translations, normalizedTargetLocale);
|
|
2533
2616
|
const completionMessage = formTranslation?.completionMessage ?? schema.completionMessage;
|
|
2534
2617
|
return {
|
|
2535
2618
|
...schema,
|
|
@@ -2537,7 +2620,7 @@ function resolveLocalizedSchema(schema, targetLocale) {
|
|
|
2537
2620
|
...(formTranslation?.description ?? schema.description) === void 0 ? {} : { description: formTranslation?.description ?? schema.description },
|
|
2538
2621
|
...completionMessage === void 0 ? {} : { completionMessage },
|
|
2539
2622
|
fields: schema.fields.map((field) => {
|
|
2540
|
-
const translation = field.translations
|
|
2623
|
+
const translation = localeRecordEntry2(field.translations, normalizedTargetLocale);
|
|
2541
2624
|
const localized = {
|
|
2542
2625
|
...field,
|
|
2543
2626
|
title: translation?.title ?? field.title,
|
|
@@ -2548,13 +2631,13 @@ function resolveLocalizedSchema(schema, targetLocale) {
|
|
|
2548
2631
|
...localized,
|
|
2549
2632
|
options: field.options.map((option) => ({
|
|
2550
2633
|
...option,
|
|
2551
|
-
label: option.translations
|
|
2634
|
+
label: localeRecordEntry2(option.translations, normalizedTargetLocale) ?? option.label
|
|
2552
2635
|
}))
|
|
2553
2636
|
};
|
|
2554
2637
|
}),
|
|
2555
2638
|
...schema.pages === void 0 ? {} : {
|
|
2556
2639
|
pages: schema.pages.map((page) => {
|
|
2557
|
-
const translation = page.translations
|
|
2640
|
+
const translation = localeRecordEntry2(page.translations, normalizedTargetLocale);
|
|
2558
2641
|
const title = translation?.title ?? page.title;
|
|
2559
2642
|
const description = translation?.description ?? page.description;
|
|
2560
2643
|
return {
|
|
@@ -2568,8 +2651,13 @@ function resolveLocalizedSchema(schema, targetLocale) {
|
|
|
2568
2651
|
}
|
|
2569
2652
|
async function populateSchemaTranslations(schema, targetLocales, adapter, options = {}) {
|
|
2570
2653
|
assertValidFormSchema(schema);
|
|
2571
|
-
const
|
|
2572
|
-
const
|
|
2654
|
+
const defaultLocale = schema.defaultLocale === void 0 ? void 0 : normalizeLocale(schema.defaultLocale) ?? schema.defaultLocale;
|
|
2655
|
+
const locales = [
|
|
2656
|
+
...new Set(
|
|
2657
|
+
targetLocales.map((locale) => normalizeLocale(locale) ?? locale.trim()).filter((locale) => locale.length > 0 && locale !== defaultLocale)
|
|
2658
|
+
)
|
|
2659
|
+
];
|
|
2660
|
+
const allowedLocales = options.policy?.allowedLocales?.map((locale) => normalizeLocale(locale) ?? locale);
|
|
2573
2661
|
const collectedLocales = collectSchemaLocales(schema);
|
|
2574
2662
|
const disallowedLocale = [...collectedLocales.allUniqueLocales, ...locales].find(
|
|
2575
2663
|
(locale) => allowedLocales !== void 0 && !allowedLocales.includes(locale)
|
|
@@ -2611,7 +2699,7 @@ async function populateSchemaTranslations(schema, targetLocales, adapter, option
|
|
|
2611
2699
|
adapter,
|
|
2612
2700
|
selected.map((descriptor) => descriptor.slot.sourceText),
|
|
2613
2701
|
locale,
|
|
2614
|
-
|
|
2702
|
+
defaultLocale
|
|
2615
2703
|
);
|
|
2616
2704
|
if (translated.length !== selected.length) {
|
|
2617
2705
|
throw new Error(`Translation adapter returned ${translated.length} texts for ${selected.length} inputs.`);
|
|
@@ -2620,7 +2708,7 @@ async function populateSchemaTranslations(schema, targetLocales, adapter, option
|
|
|
2620
2708
|
const translatedText = translated[index];
|
|
2621
2709
|
if (translatedText === void 0) throw new Error("Translation adapter returned an unexpected result.");
|
|
2622
2710
|
const metadata = options.createMetadata?.(descriptor.slot, translatedText) ?? {
|
|
2623
|
-
sourceLocale:
|
|
2711
|
+
sourceLocale: defaultLocale ?? "",
|
|
2624
2712
|
sourceTextHash: computeSourceTextHash(descriptor.slot.sourceText),
|
|
2625
2713
|
translationSource: "automatic",
|
|
2626
2714
|
translatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
@@ -2631,8 +2719,8 @@ async function populateSchemaTranslations(schema, targetLocales, adapter, option
|
|
|
2631
2719
|
}
|
|
2632
2720
|
const supportedLocales = [
|
|
2633
2721
|
.../* @__PURE__ */ new Set([
|
|
2634
|
-
...
|
|
2635
|
-
...schema.supportedLocales ?? [],
|
|
2722
|
+
...defaultLocale === void 0 ? [] : [defaultLocale],
|
|
2723
|
+
...(schema.supportedLocales ?? []).map((locale) => normalizeLocale(locale) ?? locale),
|
|
2636
2724
|
...locales
|
|
2637
2725
|
])
|
|
2638
2726
|
];
|
|
@@ -2967,6 +3055,7 @@ export {
|
|
|
2967
3055
|
matchesSubmissionFilter,
|
|
2968
3056
|
matchesSubmissionPageFilters,
|
|
2969
3057
|
migrateSchemaTranslationMetadata,
|
|
3058
|
+
normalizeLocale,
|
|
2970
3059
|
normalizeSubmissionPageSize,
|
|
2971
3060
|
pipeResponsesToCsvStream,
|
|
2972
3061
|
populateSchemaTranslations,
|