@pactor-app/i18n 0.0.0-stage → 1.55.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/LICENSE +21 -0
- package/README.md +118 -2
- package/dist/chunk-3YCHXWF6.js +1032 -0
- package/dist/index.cjs +1075 -0
- package/dist/index.d.cts +258 -0
- package/dist/index.d.ts +258 -0
- package/dist/index.js +40 -0
- package/dist/node.cjs +1354 -0
- package/dist/node.d.cts +99 -0
- package/dist/node.d.ts +99 -0
- package/dist/node.js +315 -0
- package/package.json +75 -3
package/dist/node.cjs
ADDED
|
@@ -0,0 +1,1354 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/node.ts
|
|
21
|
+
var node_exports = {};
|
|
22
|
+
__export(node_exports, {
|
|
23
|
+
ApiMessageProvider: () => ApiMessageProvider,
|
|
24
|
+
I18nError: () => I18nError,
|
|
25
|
+
MISSING_TRANSLATION_TEXT: () => MISSING_TRANSLATION_TEXT,
|
|
26
|
+
TranslatableFieldRegistry: () => TranslatableFieldRegistry,
|
|
27
|
+
YamlMessageProvider: () => YamlMessageProvider,
|
|
28
|
+
assertMessages: () => assertMessages,
|
|
29
|
+
assertNoCrossNamespaceKeyConflicts: () => assertNoCrossNamespaceKeyConflicts,
|
|
30
|
+
createDefaultTranslatableFieldRegistry: () => createDefaultTranslatableFieldRegistry,
|
|
31
|
+
escapeJsonPointerSegment: () => escapeJsonPointerSegment,
|
|
32
|
+
getDefaultTranslatableFieldRegistry: () => getDefaultTranslatableFieldRegistry,
|
|
33
|
+
isI18nError: () => isI18nError,
|
|
34
|
+
isValidMessageKey: () => isValidMessageKey,
|
|
35
|
+
localizePageDsl: () => localizePageDsl,
|
|
36
|
+
localizeYamlResource: () => localizeYamlResource,
|
|
37
|
+
normalizeLocaleTag: () => normalizeLocaleTag,
|
|
38
|
+
parseAcceptLanguage: () => parseAcceptLanguage,
|
|
39
|
+
parseJsonPointer: () => parseJsonPointer,
|
|
40
|
+
parsePageYamlStrict: () => parsePageYamlStrict,
|
|
41
|
+
resolveJsonPointer: () => resolveJsonPointer,
|
|
42
|
+
resolveRequestLocale: () => resolveRequestLocale,
|
|
43
|
+
validateBundleSetEnvelope: () => validateBundleSetEnvelope,
|
|
44
|
+
validateManifest: () => validateManifest
|
|
45
|
+
});
|
|
46
|
+
module.exports = __toCommonJS(node_exports);
|
|
47
|
+
|
|
48
|
+
// src/errors.ts
|
|
49
|
+
var I18nError = class extends Error {
|
|
50
|
+
constructor(code, message, details) {
|
|
51
|
+
super(message);
|
|
52
|
+
this.code = code;
|
|
53
|
+
this.details = details;
|
|
54
|
+
this.name = "I18nError";
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
function isI18nError(error, code) {
|
|
58
|
+
return error instanceof I18nError && (code === void 0 || error.code === code);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// src/locale-resolver.ts
|
|
62
|
+
var LOCALE_TAG_RE = /^[a-zA-Z]{2,3}(?:-[a-zA-Z]{4})?(?:-(?:[a-zA-Z]{2}|\d{3}))?$/;
|
|
63
|
+
function normalizeLocaleTag(value) {
|
|
64
|
+
const trimmed = value.trim().replace(/_/g, "-");
|
|
65
|
+
if (!LOCALE_TAG_RE.test(trimmed)) {
|
|
66
|
+
return void 0;
|
|
67
|
+
}
|
|
68
|
+
const parts = trimmed.split("-");
|
|
69
|
+
let result = parts[0].toLowerCase();
|
|
70
|
+
for (let i = 1; i < parts.length; i += 1) {
|
|
71
|
+
const part = parts[i];
|
|
72
|
+
result += "-" + (part.length === 4 ? part[0].toUpperCase() + part.slice(1).toLowerCase() : part.toUpperCase());
|
|
73
|
+
}
|
|
74
|
+
return result;
|
|
75
|
+
}
|
|
76
|
+
function parseAcceptLanguage(header) {
|
|
77
|
+
if (header === void 0) {
|
|
78
|
+
return [];
|
|
79
|
+
}
|
|
80
|
+
const entries = [];
|
|
81
|
+
const parts = header.split(",");
|
|
82
|
+
for (let order = 0; order < parts.length; order += 1) {
|
|
83
|
+
const segments = parts[order].split(";");
|
|
84
|
+
const rawTag = segments[0].trim();
|
|
85
|
+
if (rawTag === "") {
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
let q = 1;
|
|
89
|
+
let validQ = true;
|
|
90
|
+
for (const segment of segments.slice(1)) {
|
|
91
|
+
const [name, rawValue] = segment.split("=");
|
|
92
|
+
if (name?.trim().toLowerCase() !== "q") {
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
const parsed = Number(rawValue?.trim());
|
|
96
|
+
if (!Number.isFinite(parsed) || parsed < 0 || parsed > 1) {
|
|
97
|
+
validQ = false;
|
|
98
|
+
} else {
|
|
99
|
+
q = parsed;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
if (!validQ || q === 0) {
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
if (rawTag === "*") {
|
|
106
|
+
entries.push({ tag: "*", q, order });
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
const tag = normalizeLocaleTag(rawTag);
|
|
110
|
+
if (tag === void 0) {
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
entries.push({ tag, q, order });
|
|
114
|
+
}
|
|
115
|
+
entries.sort((a, b) => b.q - a.q || a.order - b.order);
|
|
116
|
+
return entries.map(({ tag, q }) => ({ tag, q }));
|
|
117
|
+
}
|
|
118
|
+
function configError(message) {
|
|
119
|
+
throw new I18nError("INVALID_LOCALE_CONFIG", message);
|
|
120
|
+
}
|
|
121
|
+
function prepareConfig(config) {
|
|
122
|
+
if (!Array.isArray(config.supportedLocales) || config.supportedLocales.length === 0) {
|
|
123
|
+
configError("supportedLocales \u5FC5\u987B\u4E3A\u975E\u7A7A\u6570\u7EC4");
|
|
124
|
+
}
|
|
125
|
+
const supported = /* @__PURE__ */ new Map();
|
|
126
|
+
for (const raw of config.supportedLocales) {
|
|
127
|
+
const normalized = typeof raw === "string" ? normalizeLocaleTag(raw) : void 0;
|
|
128
|
+
if (normalized === void 0) {
|
|
129
|
+
configError(`supportedLocales \u542B\u975E\u6CD5\u8BED\u8A00\u6807\u8BC6: ${String(raw)}`);
|
|
130
|
+
}
|
|
131
|
+
if (supported.has(normalized)) {
|
|
132
|
+
configError(`supportedLocales \u542B\u91CD\u590D\u8BED\u8A00\uFF08\u89C4\u8303\u5316\u540E\uFF09: ${normalized}`);
|
|
133
|
+
}
|
|
134
|
+
supported.set(normalized, normalized);
|
|
135
|
+
}
|
|
136
|
+
const defaultNormalized = typeof config.defaultLocale === "string" ? normalizeLocaleTag(config.defaultLocale) : void 0;
|
|
137
|
+
if (defaultNormalized === void 0 || !supported.has(defaultNormalized)) {
|
|
138
|
+
configError(`defaultLocale \u5FC5\u987B\u5C5E\u4E8E supportedLocales: ${String(config.defaultLocale)}`);
|
|
139
|
+
}
|
|
140
|
+
const defaultLocale = supported.get(defaultNormalized);
|
|
141
|
+
const strategy = config.defaultStrategy ?? "system";
|
|
142
|
+
if (strategy !== "system" && strategy !== "application") {
|
|
143
|
+
configError(`\u975E\u6CD5 defaultStrategy: ${String(config.defaultStrategy)}`);
|
|
144
|
+
}
|
|
145
|
+
const aliases = /* @__PURE__ */ new Map();
|
|
146
|
+
for (const [rawFrom, rawTo] of Object.entries(config.aliases ?? {})) {
|
|
147
|
+
const from = normalizeLocaleTag(rawFrom);
|
|
148
|
+
const to = normalizeLocaleTag(rawTo);
|
|
149
|
+
if (from === void 0 || to === void 0) {
|
|
150
|
+
configError(`\u522B\u540D\u542B\u975E\u6CD5\u8BED\u8A00\u6807\u8BC6: ${rawFrom} \u2192 ${rawTo}`);
|
|
151
|
+
}
|
|
152
|
+
if (supported.has(from)) {
|
|
153
|
+
configError(`\u522B\u540D\u4E0E\u5DF2\u652F\u6301\u8BED\u8A00\u51B2\u7A81: ${from}`);
|
|
154
|
+
}
|
|
155
|
+
if (aliases.has(from)) {
|
|
156
|
+
configError(`\u522B\u540D\u91CD\u590D\u5B9A\u4E49\uFF08\u89C4\u8303\u5316\u540E\uFF09: ${from}`);
|
|
157
|
+
}
|
|
158
|
+
if (from === to) {
|
|
159
|
+
configError(`\u522B\u540D\u81EA\u6620\u5C04: ${from}`);
|
|
160
|
+
}
|
|
161
|
+
aliases.set(from, to);
|
|
162
|
+
}
|
|
163
|
+
for (const from of aliases.keys()) {
|
|
164
|
+
if (applyAliases(from, aliases) === void 0) {
|
|
165
|
+
configError(`\u522B\u540D\u76EE\u6807\u65E0\u6CD5\u89E3\u6790\u5230\u652F\u6301\u8BED\u8A00\u6216\u5B58\u5728\u73AF: ${from}`);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
const resolveAliasTarget = (value) => {
|
|
169
|
+
const terminal = applyAliases(value, aliases);
|
|
170
|
+
return terminal !== void 0 && supported.has(terminal) ? terminal : void 0;
|
|
171
|
+
};
|
|
172
|
+
for (const [from, to] of aliases) {
|
|
173
|
+
if (resolveAliasTarget(to) === void 0) {
|
|
174
|
+
configError(`\u522B\u540D\u76EE\u6807\u4E0D\u5728\u652F\u6301\u5217\u8868: ${from} \u2192 ${to}`);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
const fallbackLocales = /* @__PURE__ */ new Map();
|
|
178
|
+
for (const [rawFrom, rawList] of Object.entries(config.fallbackLocales ?? {})) {
|
|
179
|
+
const from = normalizeLocaleTag(rawFrom);
|
|
180
|
+
if (from === void 0 || !Array.isArray(rawList)) {
|
|
181
|
+
configError(`fallbackLocales \u542B\u975E\u6CD5\u914D\u7F6E: ${rawFrom}`);
|
|
182
|
+
}
|
|
183
|
+
const list = rawList.map((item) => {
|
|
184
|
+
const normalized = typeof item === "string" ? normalizeLocaleTag(item) : void 0;
|
|
185
|
+
if (normalized === void 0) {
|
|
186
|
+
configError(`fallbackLocales \u542B\u975E\u6CD5\u8BED\u8A00\u6807\u8BC6: ${String(item)}`);
|
|
187
|
+
}
|
|
188
|
+
return normalized;
|
|
189
|
+
});
|
|
190
|
+
fallbackLocales.set(from, list);
|
|
191
|
+
}
|
|
192
|
+
return { supported, defaultLocale, strategy, aliases, fallbackLocales };
|
|
193
|
+
}
|
|
194
|
+
function applyAliases(tag, aliases) {
|
|
195
|
+
const visited = /* @__PURE__ */ new Set([tag]);
|
|
196
|
+
let current = tag;
|
|
197
|
+
while (aliases.has(current)) {
|
|
198
|
+
const next = aliases.get(current);
|
|
199
|
+
if (visited.has(next)) {
|
|
200
|
+
return void 0;
|
|
201
|
+
}
|
|
202
|
+
visited.add(next);
|
|
203
|
+
current = next;
|
|
204
|
+
}
|
|
205
|
+
return current;
|
|
206
|
+
}
|
|
207
|
+
function matchExactOrAlias(tag, config) {
|
|
208
|
+
const terminal = applyAliases(tag, config.aliases);
|
|
209
|
+
if (terminal === void 0) {
|
|
210
|
+
return void 0;
|
|
211
|
+
}
|
|
212
|
+
return config.supported.get(terminal);
|
|
213
|
+
}
|
|
214
|
+
function matchForNegotiation(tag, config) {
|
|
215
|
+
const direct = matchExactOrAlias(tag, config);
|
|
216
|
+
if (direct !== void 0) {
|
|
217
|
+
return direct;
|
|
218
|
+
}
|
|
219
|
+
for (const fallback of config.fallbackLocales.get(tag) ?? []) {
|
|
220
|
+
const matched = matchExactOrAlias(fallback, config);
|
|
221
|
+
if (matched !== void 0) {
|
|
222
|
+
return matched;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
return void 0;
|
|
226
|
+
}
|
|
227
|
+
function invalidLocale(value, source) {
|
|
228
|
+
throw new I18nError("INVALID_LOCALE", `${source} \u8BED\u8A00\u6807\u8BC6\u975E\u6CD5: ${value}`, {
|
|
229
|
+
value,
|
|
230
|
+
source
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
function resolveRequestLocale(input, config) {
|
|
234
|
+
const cfg = prepareConfig(config);
|
|
235
|
+
let queryValue;
|
|
236
|
+
if (Array.isArray(input.queryLocale)) {
|
|
237
|
+
if (input.queryLocale.length > 1) {
|
|
238
|
+
throw new I18nError("DUPLICATE_LOCALE_PARAM", "locale \u67E5\u8BE2\u53C2\u6570\u91CD\u590D", {
|
|
239
|
+
values: [...input.queryLocale]
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
queryValue = input.queryLocale[0];
|
|
243
|
+
} else {
|
|
244
|
+
queryValue = input.queryLocale;
|
|
245
|
+
}
|
|
246
|
+
const pathNorm = input.pathLocale !== void 0 ? normalizeLocaleTag(input.pathLocale) ?? invalidLocale(input.pathLocale, "url-path") : void 0;
|
|
247
|
+
const queryNorm = queryValue !== void 0 ? normalizeLocaleTag(queryValue) ?? invalidLocale(queryValue, "url-query") : void 0;
|
|
248
|
+
if (pathNorm !== void 0 && queryNorm !== void 0) {
|
|
249
|
+
const pathResolved = applyAliases(pathNorm, cfg.aliases);
|
|
250
|
+
const queryResolved = applyAliases(queryNorm, cfg.aliases);
|
|
251
|
+
if (pathResolved !== queryResolved) {
|
|
252
|
+
throw new I18nError("LOCALE_CONFLICT", "\u8DEF\u5F84\u4E0E\u67E5\u8BE2\u53C2\u6570\u6307\u5B9A\u7684\u8BED\u8A00\u51B2\u7A81", {
|
|
253
|
+
pathLocale: pathNorm,
|
|
254
|
+
queryLocale: queryNorm
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
const explicit = pathNorm ?? queryNorm;
|
|
259
|
+
if (explicit !== void 0) {
|
|
260
|
+
const matched = matchExactOrAlias(explicit, cfg);
|
|
261
|
+
if (matched === void 0) {
|
|
262
|
+
throw new I18nError("UNSUPPORTED_LOCALE", `\u4E0D\u652F\u6301\u7684\u8BED\u8A00: ${explicit}`, {
|
|
263
|
+
value: explicit,
|
|
264
|
+
supportedLocales: [...cfg.supported.values()]
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
return {
|
|
268
|
+
locale: matched,
|
|
269
|
+
source: pathNorm !== void 0 ? "url-path" : "url-query",
|
|
270
|
+
explicit: true
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
if (input.cookieLocale !== void 0) {
|
|
274
|
+
const cookieNorm = normalizeLocaleTag(input.cookieLocale);
|
|
275
|
+
if (cookieNorm !== void 0) {
|
|
276
|
+
const matched = matchExactOrAlias(cookieNorm, cfg);
|
|
277
|
+
if (matched !== void 0) {
|
|
278
|
+
return { locale: matched, source: "cookie", explicit: true };
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
if (cfg.strategy === "system") {
|
|
283
|
+
for (const entry of parseAcceptLanguage(input.acceptLanguage)) {
|
|
284
|
+
if (entry.tag === "*") {
|
|
285
|
+
return { locale: cfg.defaultLocale, source: "accept-language", explicit: false };
|
|
286
|
+
}
|
|
287
|
+
const matched = matchForNegotiation(entry.tag, cfg);
|
|
288
|
+
if (matched !== void 0) {
|
|
289
|
+
return { locale: matched, source: "accept-language", explicit: false };
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
return { locale: cfg.defaultLocale, source: "application-default", explicit: false };
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// src/bundle-validation.ts
|
|
297
|
+
var MESSAGE_KEY_RE = /^[^\s.]+(?:\.[^\s.]+)*$/;
|
|
298
|
+
function isValidMessageKey(key) {
|
|
299
|
+
return MESSAGE_KEY_RE.test(key);
|
|
300
|
+
}
|
|
301
|
+
function isPlainObject(value) {
|
|
302
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
303
|
+
}
|
|
304
|
+
function assertMessages(value, context, code) {
|
|
305
|
+
if (!isPlainObject(value)) {
|
|
306
|
+
throw new I18nError(code, `${context}: \u8BED\u8A00\u5305\u5FC5\u987B\u662F\u6241\u5E73\u952E\u503C\u5BF9\u8C61`);
|
|
307
|
+
}
|
|
308
|
+
for (const [key, entry] of Object.entries(value)) {
|
|
309
|
+
if (!isValidMessageKey(key)) {
|
|
310
|
+
throw new I18nError(code, `${context}: \u975E\u6CD5\u8BCD\u6761\u952E\u540D: ${key}`, { key });
|
|
311
|
+
}
|
|
312
|
+
if (typeof entry !== "string") {
|
|
313
|
+
throw new I18nError(code, `${context}: \u8BCD\u6761\u503C\u5FC5\u987B\u662F\u5B57\u7B26\u4E32: ${key}`, {
|
|
314
|
+
key,
|
|
315
|
+
valueType: Array.isArray(entry) ? "array" : typeof entry
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
return value;
|
|
320
|
+
}
|
|
321
|
+
function assertNoCrossNamespaceKeyConflicts(bundles, code) {
|
|
322
|
+
const owners = /* @__PURE__ */ new Map();
|
|
323
|
+
for (const [namespace, messages] of Object.entries(bundles)) {
|
|
324
|
+
for (const key of Object.keys(messages)) {
|
|
325
|
+
const list = owners.get(key);
|
|
326
|
+
if (list === void 0) {
|
|
327
|
+
owners.set(key, [namespace]);
|
|
328
|
+
} else {
|
|
329
|
+
list.push(namespace);
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
const conflicts = [...owners.entries()].filter(([, list]) => list.length > 1);
|
|
334
|
+
if (conflicts.length > 0) {
|
|
335
|
+
const [key, namespaces] = conflicts[0];
|
|
336
|
+
throw new I18nError(
|
|
337
|
+
code,
|
|
338
|
+
`\u4E0D\u540C namespace \u5B9A\u4E49\u4E86\u76F8\u540C\u5B8C\u6574 key: ${key}\uFF08${[...namespaces].sort().join(", ")}\uFF09`,
|
|
339
|
+
{ key, namespaces: [...namespaces].sort() }
|
|
340
|
+
);
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
function validateBundleSetEnvelope(data, expected) {
|
|
344
|
+
if (!isPlainObject(data)) {
|
|
345
|
+
throw new I18nError("MESSAGE_PROTOCOL_ERROR", "\u8BED\u8A00\u5305\u54CD\u5E94\u5FC5\u987B\u662F JSON \u5BF9\u8C61 envelope");
|
|
346
|
+
}
|
|
347
|
+
for (const field of ["appId", "locale", "release"]) {
|
|
348
|
+
if (typeof data[field] !== "string" || data[field] === "") {
|
|
349
|
+
throw new I18nError("MESSAGE_PROTOCOL_ERROR", `envelope \u7F3A\u5C11\u5B57\u6BB5 ${field}`);
|
|
350
|
+
}
|
|
351
|
+
if (data[field] !== expected[field]) {
|
|
352
|
+
throw new I18nError(
|
|
353
|
+
"MESSAGE_PROTOCOL_ERROR",
|
|
354
|
+
`envelope.${field} \u4E0E\u8BF7\u6C42\u4E0D\u4E00\u81F4: ${String(data[field])} !== ${expected[field]}`,
|
|
355
|
+
{ field, actual: data[field], expected: expected[field] }
|
|
356
|
+
);
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
if (data["format"] !== "simple-v1") {
|
|
360
|
+
throw new I18nError("MESSAGE_PROTOCOL_ERROR", `\u4E0D\u652F\u6301\u7684\u8BED\u8A00\u5305 format: ${String(data["format"])}`, {
|
|
361
|
+
format: data["format"]
|
|
362
|
+
});
|
|
363
|
+
}
|
|
364
|
+
const rawBundles = data["bundles"];
|
|
365
|
+
if (!isPlainObject(rawBundles)) {
|
|
366
|
+
throw new I18nError("MESSAGE_PROTOCOL_ERROR", "envelope.bundles \u5FC5\u987B\u662F\u5BF9\u8C61");
|
|
367
|
+
}
|
|
368
|
+
const bundles = {};
|
|
369
|
+
for (const [namespace, rawBundle] of Object.entries(rawBundles)) {
|
|
370
|
+
if (!isPlainObject(rawBundle)) {
|
|
371
|
+
throw new I18nError(
|
|
372
|
+
"MESSAGE_PROTOCOL_ERROR",
|
|
373
|
+
`envelope.bundles.${namespace} \u5FC5\u987B\u662F\u5BF9\u8C61\uFF08\u8BED\u8A00\u5305 API \u4E0D\u8FD4\u56DE YAML \u5B57\u7B26\u4E32\uFF09`,
|
|
374
|
+
{ namespace }
|
|
375
|
+
);
|
|
376
|
+
}
|
|
377
|
+
if (typeof rawBundle["version"] !== "string" || rawBundle["version"] === "") {
|
|
378
|
+
throw new I18nError("MESSAGE_PROTOCOL_ERROR", `envelope.bundles.${namespace} \u7F3A\u5C11 version`, {
|
|
379
|
+
namespace
|
|
380
|
+
});
|
|
381
|
+
}
|
|
382
|
+
bundles[namespace] = {
|
|
383
|
+
version: rawBundle["version"],
|
|
384
|
+
messages: assertMessages(
|
|
385
|
+
rawBundle["messages"],
|
|
386
|
+
`envelope.bundles.${namespace}`,
|
|
387
|
+
"MESSAGE_PROTOCOL_ERROR"
|
|
388
|
+
)
|
|
389
|
+
};
|
|
390
|
+
}
|
|
391
|
+
for (const namespace of expected.namespaces) {
|
|
392
|
+
if (!(namespace in bundles)) {
|
|
393
|
+
throw new I18nError("NAMESPACE_NOT_FOUND", `\u8BED\u8A00\u5305\u54CD\u5E94\u7F3A\u5931 namespace: ${namespace}`, {
|
|
394
|
+
namespace
|
|
395
|
+
});
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
assertNoCrossNamespaceKeyConflicts(
|
|
399
|
+
Object.fromEntries(Object.entries(bundles).map(([ns, b]) => [ns, b.messages])),
|
|
400
|
+
"MESSAGE_PROTOCOL_ERROR"
|
|
401
|
+
);
|
|
402
|
+
return {
|
|
403
|
+
appId: expected.appId,
|
|
404
|
+
locale: expected.locale,
|
|
405
|
+
release: expected.release,
|
|
406
|
+
format: "simple-v1",
|
|
407
|
+
bundles
|
|
408
|
+
};
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
// src/api-message-provider.ts
|
|
412
|
+
function cacheKeyOf(input) {
|
|
413
|
+
return [
|
|
414
|
+
input.appId,
|
|
415
|
+
input.tenantId ?? "",
|
|
416
|
+
input.locale,
|
|
417
|
+
[...input.namespaces].sort().join(","),
|
|
418
|
+
input.release
|
|
419
|
+
].join("\n");
|
|
420
|
+
}
|
|
421
|
+
function buildUrl(endpoint, input) {
|
|
422
|
+
const params = [
|
|
423
|
+
["appId", input.appId],
|
|
424
|
+
["locale", input.locale],
|
|
425
|
+
["namespaces", input.namespaces.join(",")],
|
|
426
|
+
["release", input.release]
|
|
427
|
+
];
|
|
428
|
+
if (input.tenantId !== void 0) {
|
|
429
|
+
params.push(["tenantId", input.tenantId]);
|
|
430
|
+
}
|
|
431
|
+
const query = params.map(([k, v]) => `${k}=${encodeURIComponent(v)}`).join("&");
|
|
432
|
+
return `${endpoint}${endpoint.includes("?") ? "&" : "?"}${query}`;
|
|
433
|
+
}
|
|
434
|
+
var JSON_CONTENT_TYPE_RE = /^\s*application\/json\s*(?:;|$)/i;
|
|
435
|
+
var ApiMessageProvider = class {
|
|
436
|
+
constructor(options) {
|
|
437
|
+
this.cache = /* @__PURE__ */ new Map();
|
|
438
|
+
this.options = options;
|
|
439
|
+
}
|
|
440
|
+
/** MessageProvider 契约实现 */
|
|
441
|
+
async load(input) {
|
|
442
|
+
const result = await this.loadWithCacheInfo(input);
|
|
443
|
+
return result.bundleSet;
|
|
444
|
+
}
|
|
445
|
+
/** 加载并返回缓存信息(ETag / notModified) */
|
|
446
|
+
async loadWithCacheInfo(input) {
|
|
447
|
+
const key = cacheKeyOf(input);
|
|
448
|
+
const cached = this.cache.get(key);
|
|
449
|
+
const etag = input.etag ?? cached?.etag;
|
|
450
|
+
const headers = { ...this.options.headers };
|
|
451
|
+
if (etag !== void 0) {
|
|
452
|
+
headers["If-None-Match"] = etag;
|
|
453
|
+
}
|
|
454
|
+
let response;
|
|
455
|
+
try {
|
|
456
|
+
response = await this.options.fetch(buildUrl(this.options.endpoint, input), { headers });
|
|
457
|
+
} catch (error) {
|
|
458
|
+
throw new I18nError("MESSAGE_FETCH_FAILED", "\u8BED\u8A00\u5305\u8BF7\u6C42\u5931\u8D25", {
|
|
459
|
+
cause: error instanceof Error ? error.message : String(error)
|
|
460
|
+
});
|
|
461
|
+
}
|
|
462
|
+
if (response.status === 304) {
|
|
463
|
+
if (cached !== void 0) {
|
|
464
|
+
return { bundleSet: cached.bundleSet, etag: cached.etag, notModified: true };
|
|
465
|
+
}
|
|
466
|
+
throw new I18nError("MESSAGE_PROTOCOL_ERROR", "\u6536\u5230 304 \u4F46\u6CA1\u6709\u53EF\u590D\u7528\u7684\u5DF2\u6821\u9A8C\u7F13\u5B58\u5305");
|
|
467
|
+
}
|
|
468
|
+
if (response.status !== 200) {
|
|
469
|
+
throw new I18nError("MESSAGE_FETCH_FAILED", `\u8BED\u8A00\u5305\u63A5\u53E3\u8FD4\u56DE HTTP ${response.status}`, {
|
|
470
|
+
status: response.status
|
|
471
|
+
});
|
|
472
|
+
}
|
|
473
|
+
const contentType = response.headers.get("content-type");
|
|
474
|
+
if (contentType === null || !JSON_CONTENT_TYPE_RE.test(contentType)) {
|
|
475
|
+
throw new I18nError(
|
|
476
|
+
"MESSAGE_PROTOCOL_ERROR",
|
|
477
|
+
`\u8BED\u8A00\u5305\u63A5\u53E3 Content-Type \u5FC5\u987B\u662F application/json\uFF0C\u5B9E\u9645: ${contentType ?? "(\u7F3A\u5931)"}`,
|
|
478
|
+
{ contentType }
|
|
479
|
+
);
|
|
480
|
+
}
|
|
481
|
+
const text = await response.text();
|
|
482
|
+
let data;
|
|
483
|
+
try {
|
|
484
|
+
data = JSON.parse(text);
|
|
485
|
+
} catch {
|
|
486
|
+
throw new I18nError(
|
|
487
|
+
"MESSAGE_PROTOCOL_ERROR",
|
|
488
|
+
"\u8BED\u8A00\u5305\u54CD\u5E94\u4E0D\u662F\u5408\u6CD5 JSON\uFF08\u534F\u8BAE\u4E0D\u8FD4\u56DE YAML \u539F\u6587\u6216\u5185\u5D4C YAML \u5B57\u7B26\u4E32\uFF09"
|
|
489
|
+
);
|
|
490
|
+
}
|
|
491
|
+
const bundleSet = validateBundleSetEnvelope(data, input);
|
|
492
|
+
const newEtag = response.headers.get("etag") ?? void 0;
|
|
493
|
+
this.cache.set(key, { etag: newEtag, bundleSet });
|
|
494
|
+
return { bundleSet, etag: newEtag, notModified: false };
|
|
495
|
+
}
|
|
496
|
+
};
|
|
497
|
+
|
|
498
|
+
// src/translatable-field-registry.ts
|
|
499
|
+
var SEGMENT_RE = /^[^.[\]]+(?:\[\*\])?$/;
|
|
500
|
+
function assertValidFieldPath(path, root) {
|
|
501
|
+
const segments = typeof path === "string" ? path.split(".") : [];
|
|
502
|
+
if (segments[0] !== root || segments.length < 2) {
|
|
503
|
+
throw new I18nError(
|
|
504
|
+
"INVALID_FIELD_PATH",
|
|
505
|
+
`\u53EF\u7FFB\u8BD1\u5B57\u6BB5\u8DEF\u5F84\u5FC5\u987B\u4EE5 ${root}.<field> \u5F00\u5934: ${String(path)}`,
|
|
506
|
+
{ path }
|
|
507
|
+
);
|
|
508
|
+
}
|
|
509
|
+
for (const segment of segments) {
|
|
510
|
+
if (!SEGMENT_RE.test(segment)) {
|
|
511
|
+
throw new I18nError("INVALID_FIELD_PATH", `\u975E\u6CD5\u53EF\u7FFB\u8BD1\u5B57\u6BB5\u8DEF\u5F84: ${path}`, { path });
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
function assertName(name, kind) {
|
|
516
|
+
if (typeof name !== "string" || name === "") {
|
|
517
|
+
throw new I18nError("INVALID_FIELD_PATH", `${kind}\u540D\u5FC5\u987B\u662F\u975E\u7A7A\u5B57\u7B26\u4E32`, { name });
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
var TranslatableFieldRegistry = class {
|
|
521
|
+
constructor() {
|
|
522
|
+
this.componentFields = /* @__PURE__ */ new Map();
|
|
523
|
+
this.actionFields = /* @__PURE__ */ new Map();
|
|
524
|
+
}
|
|
525
|
+
/** 注册组件 type 的可翻译字段路径(可重复调用,累积合并) */
|
|
526
|
+
register(componentType, fields) {
|
|
527
|
+
assertName(componentType, "\u7EC4\u4EF6 type");
|
|
528
|
+
const list = typeof fields === "string" ? [fields] : fields;
|
|
529
|
+
let set = this.componentFields.get(componentType);
|
|
530
|
+
if (set === void 0) {
|
|
531
|
+
set = /* @__PURE__ */ new Set();
|
|
532
|
+
this.componentFields.set(componentType, set);
|
|
533
|
+
}
|
|
534
|
+
for (const field of list) {
|
|
535
|
+
assertValidFieldPath(field, "props");
|
|
536
|
+
set.add(field);
|
|
537
|
+
}
|
|
538
|
+
return this;
|
|
539
|
+
}
|
|
540
|
+
/** 注册动作名的可翻译参数字段路径(如 message 动作的 `params.text`) */
|
|
541
|
+
registerAction(actionName, fields) {
|
|
542
|
+
assertName(actionName, "\u52A8\u4F5C");
|
|
543
|
+
const list = typeof fields === "string" ? [fields] : fields;
|
|
544
|
+
let set = this.actionFields.get(actionName);
|
|
545
|
+
if (set === void 0) {
|
|
546
|
+
set = /* @__PURE__ */ new Set();
|
|
547
|
+
this.actionFields.set(actionName, set);
|
|
548
|
+
}
|
|
549
|
+
for (const field of list) {
|
|
550
|
+
assertValidFieldPath(field, "params");
|
|
551
|
+
set.add(field);
|
|
552
|
+
}
|
|
553
|
+
return this;
|
|
554
|
+
}
|
|
555
|
+
/**
|
|
556
|
+
* 判断组件字段是否已注册。`canonicalPath` 为规范化路径
|
|
557
|
+
* (数组下标段写作 `[*]`,如 `props.columns[*].title`)。
|
|
558
|
+
*/
|
|
559
|
+
isTranslatable(componentType, canonicalPath) {
|
|
560
|
+
if (componentType === void 0) {
|
|
561
|
+
return false;
|
|
562
|
+
}
|
|
563
|
+
return this.componentFields.get(componentType)?.has(canonicalPath) ?? false;
|
|
564
|
+
}
|
|
565
|
+
/** 判断动作参数字段是否已注册 */
|
|
566
|
+
isTranslatableActionField(actionName, canonicalPath) {
|
|
567
|
+
return this.actionFields.get(actionName)?.has(canonicalPath) ?? false;
|
|
568
|
+
}
|
|
569
|
+
/** 已注册组件 type 列表(诊断/调试用) */
|
|
570
|
+
registeredComponents() {
|
|
571
|
+
return [...this.componentFields.keys()];
|
|
572
|
+
}
|
|
573
|
+
/** 已注册动作名列表(诊断/调试用) */
|
|
574
|
+
registeredActions() {
|
|
575
|
+
return [...this.actionFields.keys()];
|
|
576
|
+
}
|
|
577
|
+
};
|
|
578
|
+
function createDefaultTranslatableFieldRegistry() {
|
|
579
|
+
return new TranslatableFieldRegistry().register("Button", ["props.text", "props.label", "props.description", "props.ariaLabel"]).register("Text", ["props.text", "props.description", "props.ariaLabel"]).register("Input", [
|
|
580
|
+
"props.text",
|
|
581
|
+
"props.label",
|
|
582
|
+
"props.placeholder",
|
|
583
|
+
"props.description",
|
|
584
|
+
"props.ariaLabel"
|
|
585
|
+
]).register("Table", [
|
|
586
|
+
"props.label",
|
|
587
|
+
"props.description",
|
|
588
|
+
"props.ariaLabel",
|
|
589
|
+
"props.columns[*].title"
|
|
590
|
+
]).register("Select", [
|
|
591
|
+
"props.label",
|
|
592
|
+
"props.placeholder",
|
|
593
|
+
"props.description",
|
|
594
|
+
"props.ariaLabel",
|
|
595
|
+
"props.options[*].label"
|
|
596
|
+
]).registerAction("message", ["params.text"]).registerAction("confirm", ["params.title", "params.content"]);
|
|
597
|
+
}
|
|
598
|
+
var defaultRegistry;
|
|
599
|
+
function getDefaultTranslatableFieldRegistry() {
|
|
600
|
+
defaultRegistry ?? (defaultRegistry = createDefaultTranslatableFieldRegistry());
|
|
601
|
+
return defaultRegistry;
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
// src/json-pointer.ts
|
|
605
|
+
function isPlainObject2(value) {
|
|
606
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
607
|
+
}
|
|
608
|
+
var ARRAY_INDEX_RE = /^(0|[1-9]\d*)$/;
|
|
609
|
+
function escapeJsonPointerSegment(segment) {
|
|
610
|
+
return segment.replace(/~/g, "~0").replace(/\//g, "~1");
|
|
611
|
+
}
|
|
612
|
+
function parseJsonPointer(pointer) {
|
|
613
|
+
if (pointer === "") {
|
|
614
|
+
return [];
|
|
615
|
+
}
|
|
616
|
+
if (typeof pointer !== "string" || !pointer.startsWith("/")) {
|
|
617
|
+
throw new I18nError("BINDING_INVALID", `bindings \u952E\u5FC5\u987B\u662F JSON Pointer\uFF08/ \u5F00\u5934\u6216\u7A7A\u4E32\uFF09: ${String(pointer)}`, {
|
|
618
|
+
pointer
|
|
619
|
+
});
|
|
620
|
+
}
|
|
621
|
+
return pointer.slice(1).split("/").map((raw) => {
|
|
622
|
+
if (/~(?![01])/.test(raw)) {
|
|
623
|
+
throw new I18nError("BINDING_INVALID", `JSON Pointer \u542B\u975E\u6CD5\u8F6C\u4E49: ${pointer}`, { pointer });
|
|
624
|
+
}
|
|
625
|
+
return raw.replace(/~1/g, "/").replace(/~0/g, "~");
|
|
626
|
+
});
|
|
627
|
+
}
|
|
628
|
+
function missing(pointer) {
|
|
629
|
+
throw new I18nError("BINDING_INVALID", `bindings \u76EE\u6807\u4E0D\u5B58\u5728: ${pointer}`, { pointer });
|
|
630
|
+
}
|
|
631
|
+
function parseArrayIndex(segment, pointer) {
|
|
632
|
+
if (!ARRAY_INDEX_RE.test(segment)) {
|
|
633
|
+
throw new I18nError(
|
|
634
|
+
"BINDING_INVALID",
|
|
635
|
+
`bindings \u6570\u7EC4\u8DEF\u5F84\u5FC5\u987B\u662F\u5177\u4F53\u4E0B\u6807\uFF08\u4E0D\u652F\u6301\u901A\u914D\u7B26\u4E0E -\uFF09: ${pointer}`,
|
|
636
|
+
{ pointer, segment }
|
|
637
|
+
);
|
|
638
|
+
}
|
|
639
|
+
return Number(segment);
|
|
640
|
+
}
|
|
641
|
+
function childAt(container, segment, pointer) {
|
|
642
|
+
if (Array.isArray(container)) {
|
|
643
|
+
const index = parseArrayIndex(segment, pointer);
|
|
644
|
+
if (index >= container.length) {
|
|
645
|
+
missing(pointer);
|
|
646
|
+
}
|
|
647
|
+
return container[index];
|
|
648
|
+
}
|
|
649
|
+
if (isPlainObject2(container)) {
|
|
650
|
+
if (!Object.prototype.hasOwnProperty.call(container, segment)) {
|
|
651
|
+
missing(pointer);
|
|
652
|
+
}
|
|
653
|
+
return container[segment];
|
|
654
|
+
}
|
|
655
|
+
missing(pointer);
|
|
656
|
+
}
|
|
657
|
+
function resolveJsonPointer(root, segments, pointer) {
|
|
658
|
+
if (segments.length === 0) {
|
|
659
|
+
throw new I18nError("BINDING_INVALID", "bindings \u4E0D\u80FD\u6307\u5411\u6574\u4E2A\u6587\u6863\uFF08\u76EE\u6807\u5FC5\u987B\u4E3A\u5B57\u7B26\u4E32\u5B57\u6BB5\uFF09", {
|
|
660
|
+
pointer
|
|
661
|
+
});
|
|
662
|
+
}
|
|
663
|
+
let current = root;
|
|
664
|
+
for (let i = 0; i < segments.length - 1; i += 1) {
|
|
665
|
+
current = childAt(current, segments[i], pointer);
|
|
666
|
+
}
|
|
667
|
+
const last = segments[segments.length - 1];
|
|
668
|
+
if (Array.isArray(current)) {
|
|
669
|
+
const index = parseArrayIndex(last, pointer);
|
|
670
|
+
if (index >= current.length) {
|
|
671
|
+
missing(pointer);
|
|
672
|
+
}
|
|
673
|
+
return { parent: current, key: index, value: current[index] };
|
|
674
|
+
}
|
|
675
|
+
if (isPlainObject2(current)) {
|
|
676
|
+
if (!Object.prototype.hasOwnProperty.call(current, last)) {
|
|
677
|
+
missing(pointer);
|
|
678
|
+
}
|
|
679
|
+
return { parent: current, key: last, value: current[last] };
|
|
680
|
+
}
|
|
681
|
+
missing(pointer);
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
// src/dsl-localizer.ts
|
|
685
|
+
var import_core = require("@pactor-app/core");
|
|
686
|
+
var MISSING_TRANSLATION_TEXT = "[missing translation]";
|
|
687
|
+
var FORBIDDEN_BINDING_ROOTS = /* @__PURE__ */ new Set(["state", "dataSources", "requires", "meta", "i18n"]);
|
|
688
|
+
function isPlainObject3(value) {
|
|
689
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
690
|
+
}
|
|
691
|
+
function deepClone(value) {
|
|
692
|
+
if (Array.isArray(value)) {
|
|
693
|
+
return value.map((item) => deepClone(item));
|
|
694
|
+
}
|
|
695
|
+
if (isPlainObject3(value)) {
|
|
696
|
+
const out = {};
|
|
697
|
+
for (const [key, child] of Object.entries(value)) {
|
|
698
|
+
out[key] = deepClone(child);
|
|
699
|
+
}
|
|
700
|
+
return out;
|
|
701
|
+
}
|
|
702
|
+
return value;
|
|
703
|
+
}
|
|
704
|
+
function lookupKey(ctx, key) {
|
|
705
|
+
for (let i = 0; i < ctx.bundleChain.length; i += 1) {
|
|
706
|
+
const set = ctx.bundleChain[i];
|
|
707
|
+
for (const bundle of Object.values(set.bundles)) {
|
|
708
|
+
const value = bundle.messages[key];
|
|
709
|
+
if (typeof value === "string") {
|
|
710
|
+
const resolution = i === 0 ? "current" : set.locale === ctx.defaultLocale ? "default-locale" : "fallback-locale";
|
|
711
|
+
return { found: true, value, resolution };
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
return { found: false };
|
|
716
|
+
}
|
|
717
|
+
function lookupInline(ctx, messages) {
|
|
718
|
+
for (const loc of ctx.localeChain) {
|
|
719
|
+
const value = messages[loc];
|
|
720
|
+
if (typeof value === "string") {
|
|
721
|
+
const resolution = loc === ctx.locale ? "current" : loc === ctx.defaultLocale ? "default-locale" : "fallback-locale";
|
|
722
|
+
return { found: true, value, resolution };
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
return { found: false };
|
|
726
|
+
}
|
|
727
|
+
function assertDescriptor(value, path) {
|
|
728
|
+
if (!isPlainObject3(value)) {
|
|
729
|
+
throw new I18nError("I18N_DESCRIPTOR_INVALID", `\u7FFB\u8BD1\u63CF\u8FF0\u5FC5\u987B\u662F\u5BF9\u8C61: ${path}`, { path });
|
|
730
|
+
}
|
|
731
|
+
const hasKey = typeof value["key"] === "string";
|
|
732
|
+
const messages = value["messages"];
|
|
733
|
+
const hasMessages = isPlainObject3(messages) && Object.values(messages).every((v) => typeof v === "string");
|
|
734
|
+
if (hasKey === hasMessages) {
|
|
735
|
+
throw new I18nError(
|
|
736
|
+
"I18N_DESCRIPTOR_INVALID",
|
|
737
|
+
`\u7FFB\u8BD1\u63CF\u8FF0\u5FC5\u987B\u4E14\u53EA\u80FD\u5305\u542B key \u6216 messages \u4E4B\u4E00: ${path}`,
|
|
738
|
+
{ path }
|
|
739
|
+
);
|
|
740
|
+
}
|
|
741
|
+
if (value["params"] !== void 0 && !isPlainObject3(value["params"])) {
|
|
742
|
+
throw new I18nError("I18N_DESCRIPTOR_INVALID", `\u7FFB\u8BD1\u63CF\u8FF0 params \u5FC5\u987B\u662F\u5BF9\u8C61: ${path}`, { path });
|
|
743
|
+
}
|
|
744
|
+
if (value["fallback"] !== void 0 && typeof value["fallback"] !== "string") {
|
|
745
|
+
throw new I18nError("I18N_DESCRIPTOR_INVALID", `\u7FFB\u8BD1\u63CF\u8FF0 fallback \u5FC5\u987B\u662F\u5B57\u7B26\u4E32: ${path}`, {
|
|
746
|
+
path
|
|
747
|
+
});
|
|
748
|
+
}
|
|
749
|
+
return value;
|
|
750
|
+
}
|
|
751
|
+
function assertSafeTemplate(template, path) {
|
|
752
|
+
if (template.includes("${")) {
|
|
753
|
+
throw new I18nError("UNSAFE_MESSAGE", `\u8BCD\u6761\u6216 fallback \u542B\u5B57\u9762\u91CF \${\uFF0C\u62D2\u7EDD\u8F6C\u6362: ${path}`, {
|
|
754
|
+
path
|
|
755
|
+
});
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
var PARAM_PLACEHOLDER_RE = /\{\{(\w+)\}\}/g;
|
|
759
|
+
function interpolateTemplate(ctx, template, params, path) {
|
|
760
|
+
return template.replace(PARAM_PLACEHOLDER_RE, (raw, name) => {
|
|
761
|
+
if (Object.prototype.hasOwnProperty.call(params, name)) {
|
|
762
|
+
const value = params[name];
|
|
763
|
+
return value === void 0 || value === null ? "" : String(value);
|
|
764
|
+
}
|
|
765
|
+
ctx.onDiagnostic?.({
|
|
766
|
+
type: "missing-param",
|
|
767
|
+
path,
|
|
768
|
+
locale: ctx.locale,
|
|
769
|
+
param: name,
|
|
770
|
+
resolution: "placeholder"
|
|
771
|
+
});
|
|
772
|
+
return raw;
|
|
773
|
+
});
|
|
774
|
+
}
|
|
775
|
+
function convertDescriptor(ctx, desc, implicitFallback, path) {
|
|
776
|
+
const key = "key" in desc ? desc.key : void 0;
|
|
777
|
+
const hit = key !== void 0 ? lookupKey(ctx, key) : lookupInline(ctx, desc.messages);
|
|
778
|
+
let template;
|
|
779
|
+
if (hit.found) {
|
|
780
|
+
template = hit.value;
|
|
781
|
+
if (hit.resolution !== "current") {
|
|
782
|
+
ctx.onDiagnostic?.({
|
|
783
|
+
type: "missing-translation",
|
|
784
|
+
path,
|
|
785
|
+
locale: ctx.locale,
|
|
786
|
+
key,
|
|
787
|
+
resolution: hit.resolution
|
|
788
|
+
});
|
|
789
|
+
}
|
|
790
|
+
} else if (typeof desc.fallback === "string") {
|
|
791
|
+
template = desc.fallback;
|
|
792
|
+
ctx.onDiagnostic?.({
|
|
793
|
+
type: "missing-translation",
|
|
794
|
+
path,
|
|
795
|
+
locale: ctx.locale,
|
|
796
|
+
key,
|
|
797
|
+
resolution: "fallback"
|
|
798
|
+
});
|
|
799
|
+
} else if (implicitFallback !== void 0) {
|
|
800
|
+
template = implicitFallback;
|
|
801
|
+
ctx.onDiagnostic?.({
|
|
802
|
+
type: "missing-translation",
|
|
803
|
+
path,
|
|
804
|
+
locale: ctx.locale,
|
|
805
|
+
key,
|
|
806
|
+
resolution: "implicit-fallback"
|
|
807
|
+
});
|
|
808
|
+
} else if (key !== void 0) {
|
|
809
|
+
template = key;
|
|
810
|
+
ctx.onDiagnostic?.({
|
|
811
|
+
type: "missing-translation",
|
|
812
|
+
path,
|
|
813
|
+
locale: ctx.locale,
|
|
814
|
+
key,
|
|
815
|
+
resolution: "key"
|
|
816
|
+
});
|
|
817
|
+
} else {
|
|
818
|
+
template = MISSING_TRANSLATION_TEXT;
|
|
819
|
+
ctx.onDiagnostic?.({
|
|
820
|
+
type: "missing-translation",
|
|
821
|
+
path,
|
|
822
|
+
locale: ctx.locale,
|
|
823
|
+
resolution: "missing-text"
|
|
824
|
+
});
|
|
825
|
+
}
|
|
826
|
+
assertSafeTemplate(template, path);
|
|
827
|
+
const params = desc.params;
|
|
828
|
+
if (params !== void 0) {
|
|
829
|
+
const hasRuntimeParam = Object.values(params).some(
|
|
830
|
+
(value) => typeof value === "string" && value.includes("${")
|
|
831
|
+
);
|
|
832
|
+
if (hasRuntimeParam) {
|
|
833
|
+
return { $localizedText: { template, params: { ...params } } };
|
|
834
|
+
}
|
|
835
|
+
const result = interpolateTemplate(ctx, template, params, path);
|
|
836
|
+
assertSafeTemplate(result, path);
|
|
837
|
+
return result;
|
|
838
|
+
}
|
|
839
|
+
return template;
|
|
840
|
+
}
|
|
841
|
+
function walkValue(ctx, value, canonical, display, isTranslatable) {
|
|
842
|
+
if (Array.isArray(value)) {
|
|
843
|
+
for (let i = 0; i < value.length; i += 1) {
|
|
844
|
+
value[i] = walkValue(ctx, value[i], `${canonical}[*]`, `${display}/${i}`, isTranslatable);
|
|
845
|
+
}
|
|
846
|
+
return value;
|
|
847
|
+
}
|
|
848
|
+
if (isPlainObject3(value)) {
|
|
849
|
+
if ("$i18n" in value) {
|
|
850
|
+
if (!isTranslatable(canonical)) {
|
|
851
|
+
throw new I18nError(
|
|
852
|
+
"UNREGISTERED_I18N_FIELD",
|
|
853
|
+
`\u672A\u6CE8\u518C\u7684\u53EF\u7FFB\u8BD1\u5B57\u6BB5\u51FA\u73B0 $i18n\uFF08\u4E0D\u9759\u9ED8\u5FFD\u7565\uFF09: ${display}`,
|
|
854
|
+
{ path: display, field: canonical }
|
|
855
|
+
);
|
|
856
|
+
}
|
|
857
|
+
return convertDescriptor(ctx, assertDescriptor(value["$i18n"], display), void 0, display);
|
|
858
|
+
}
|
|
859
|
+
for (const key of Object.keys(value)) {
|
|
860
|
+
value[key] = walkValue(
|
|
861
|
+
ctx,
|
|
862
|
+
value[key],
|
|
863
|
+
`${canonical}.${key}`,
|
|
864
|
+
`${display}/${escapeJsonPointerSegment(key)}`,
|
|
865
|
+
isTranslatable
|
|
866
|
+
);
|
|
867
|
+
}
|
|
868
|
+
return value;
|
|
869
|
+
}
|
|
870
|
+
return value;
|
|
871
|
+
}
|
|
872
|
+
function walkAction(ctx, action, display) {
|
|
873
|
+
if (!isPlainObject3(action)) {
|
|
874
|
+
return;
|
|
875
|
+
}
|
|
876
|
+
const name = typeof action["action"] === "string" ? action["action"] : void 0;
|
|
877
|
+
if (action["params"] !== void 0) {
|
|
878
|
+
action["params"] = walkValue(
|
|
879
|
+
ctx,
|
|
880
|
+
action["params"],
|
|
881
|
+
"params",
|
|
882
|
+
`${display}/params`,
|
|
883
|
+
(p) => name !== void 0 ? ctx.registry.isTranslatableActionField(name, p) : false
|
|
884
|
+
);
|
|
885
|
+
}
|
|
886
|
+
}
|
|
887
|
+
function walkActions(ctx, value, display) {
|
|
888
|
+
if (Array.isArray(value)) {
|
|
889
|
+
value.forEach((action, index) => walkAction(ctx, action, `${display}/${index}`));
|
|
890
|
+
} else {
|
|
891
|
+
walkAction(ctx, value, display);
|
|
892
|
+
}
|
|
893
|
+
}
|
|
894
|
+
function resolveNodeType(ctx, node) {
|
|
895
|
+
if (typeof node["type"] === "string") {
|
|
896
|
+
return node["type"];
|
|
897
|
+
}
|
|
898
|
+
if (typeof node["component"] === "string") {
|
|
899
|
+
const def = ctx.namedComponents?.[node["component"]];
|
|
900
|
+
if (isPlainObject3(def) && !(0, import_core.isDslSourceRef)(def) && typeof def["type"] === "string") {
|
|
901
|
+
return def["type"];
|
|
902
|
+
}
|
|
903
|
+
}
|
|
904
|
+
return void 0;
|
|
905
|
+
}
|
|
906
|
+
function walkNode(ctx, node, display) {
|
|
907
|
+
if (!isPlainObject3(node)) {
|
|
908
|
+
return;
|
|
909
|
+
}
|
|
910
|
+
if ((0, import_core.isDslSourceRef)(node)) {
|
|
911
|
+
if (node.placeholder !== void 0) {
|
|
912
|
+
walkNode(ctx, node.placeholder, `${display}/placeholder`);
|
|
913
|
+
}
|
|
914
|
+
return;
|
|
915
|
+
}
|
|
916
|
+
const type = resolveNodeType(ctx, node);
|
|
917
|
+
if (node["props"] !== void 0) {
|
|
918
|
+
node["props"] = walkValue(
|
|
919
|
+
ctx,
|
|
920
|
+
node["props"],
|
|
921
|
+
"props",
|
|
922
|
+
`${display}/props`,
|
|
923
|
+
(p) => ctx.registry.isTranslatable(type, p)
|
|
924
|
+
);
|
|
925
|
+
}
|
|
926
|
+
if (isPlainObject3(node["events"])) {
|
|
927
|
+
for (const [event, actions] of Object.entries(node["events"])) {
|
|
928
|
+
walkActions(ctx, actions, `${display}/events/${escapeJsonPointerSegment(event)}`);
|
|
929
|
+
}
|
|
930
|
+
}
|
|
931
|
+
if (Array.isArray(node["children"])) {
|
|
932
|
+
node["children"].forEach((child, index) => walkNode(ctx, child, `${display}/children/${index}`));
|
|
933
|
+
} else if (node["children"] !== void 0) {
|
|
934
|
+
walkNode(ctx, node["children"], `${display}/children`);
|
|
935
|
+
}
|
|
936
|
+
}
|
|
937
|
+
function assertBindingAllowed(segments, pointer) {
|
|
938
|
+
const root = segments[0];
|
|
939
|
+
if (root !== void 0 && FORBIDDEN_BINDING_ROOTS.has(root)) {
|
|
940
|
+
throw new I18nError("BINDING_INVALID", `bindings \u7981\u6B62\u7ED1\u5B9A\u8BE5\u5B57\u6BB5: ${pointer}`, { pointer });
|
|
941
|
+
}
|
|
942
|
+
if (root === "page" && segments[1] === "id") {
|
|
943
|
+
throw new I18nError("BINDING_INVALID", `bindings \u7981\u6B62\u7ED1\u5B9A\u9875\u9762\u6807\u8BC6: ${pointer}`, { pointer });
|
|
944
|
+
}
|
|
945
|
+
if (segments[segments.length - 1] === "permission") {
|
|
946
|
+
throw new I18nError("BINDING_INVALID", `bindings \u7981\u6B62\u7ED1\u5B9A\u6743\u9650\u5B57\u6BB5: ${pointer}`, { pointer });
|
|
947
|
+
}
|
|
948
|
+
}
|
|
949
|
+
function applyBindings(ctx, root, bindings) {
|
|
950
|
+
const seen = /* @__PURE__ */ new Map();
|
|
951
|
+
for (const [pointer, rawDesc] of Object.entries(bindings)) {
|
|
952
|
+
const segments = parseJsonPointer(pointer);
|
|
953
|
+
const canonical = segments.map(escapeJsonPointerSegment).join("/");
|
|
954
|
+
const previous = seen.get(canonical);
|
|
955
|
+
if (previous !== void 0) {
|
|
956
|
+
throw new I18nError("BINDING_INVALID", `bindings \u8DEF\u5F84\u91CD\u590D: ${previous} \u4E0E ${pointer}`, {
|
|
957
|
+
pointer,
|
|
958
|
+
previous
|
|
959
|
+
});
|
|
960
|
+
}
|
|
961
|
+
seen.set(canonical, pointer);
|
|
962
|
+
assertBindingAllowed(segments, pointer);
|
|
963
|
+
const desc = assertDescriptor(rawDesc, pointer);
|
|
964
|
+
const resolved = resolveJsonPointer(root, segments, pointer);
|
|
965
|
+
if (isPlainObject3(resolved.value) && "$i18n" in resolved.value) {
|
|
966
|
+
throw new I18nError(
|
|
967
|
+
"BINDING_CONFLICT",
|
|
968
|
+
`\u540C\u4E00\u5B57\u6BB5\u88AB $i18n \u4E0E bindings \u91CD\u590D\u58F0\u660E: ${pointer}`,
|
|
969
|
+
{ pointer }
|
|
970
|
+
);
|
|
971
|
+
}
|
|
972
|
+
if (typeof resolved.value !== "string") {
|
|
973
|
+
throw new I18nError("BINDING_INVALID", `bindings \u76EE\u6807\u5FC5\u987B\u662F\u5B57\u7B26\u4E32: ${pointer}`, {
|
|
974
|
+
pointer,
|
|
975
|
+
valueType: Array.isArray(resolved.value) ? "array" : typeof resolved.value
|
|
976
|
+
});
|
|
977
|
+
}
|
|
978
|
+
const converted = convertDescriptor(ctx, desc, resolved.value, pointer);
|
|
979
|
+
resolved.parent[resolved.key] = converted;
|
|
980
|
+
}
|
|
981
|
+
}
|
|
982
|
+
function dedupeLocales(locales) {
|
|
983
|
+
return [...new Set(locales)];
|
|
984
|
+
}
|
|
985
|
+
function assertNoResidualI18n(value, display) {
|
|
986
|
+
if (Array.isArray(value)) {
|
|
987
|
+
value.forEach((item, index) => assertNoResidualI18n(item, `${display}/${index}`));
|
|
988
|
+
return;
|
|
989
|
+
}
|
|
990
|
+
if (isPlainObject3(value)) {
|
|
991
|
+
if ("$i18n" in value) {
|
|
992
|
+
throw new I18nError(
|
|
993
|
+
"UNREGISTERED_I18N_FIELD",
|
|
994
|
+
`$i18n \u51FA\u73B0\u5728\u4E0D\u53EF\u7FFB\u8BD1\u7684\u4F4D\u7F6E\uFF08\u4E0D\u9759\u9ED8\u900F\u4F20\uFF09: ${display}`,
|
|
995
|
+
{ path: display }
|
|
996
|
+
);
|
|
997
|
+
}
|
|
998
|
+
for (const [key, child] of Object.entries(value)) {
|
|
999
|
+
assertNoResidualI18n(child, `${display}/${escapeJsonPointerSegment(key)}`);
|
|
1000
|
+
}
|
|
1001
|
+
}
|
|
1002
|
+
}
|
|
1003
|
+
function localizePageDsl(input) {
|
|
1004
|
+
const { locale, defaultLocale } = input;
|
|
1005
|
+
if (typeof locale !== "string" || locale === "") {
|
|
1006
|
+
throw new I18nError("INVALID_LOCALE", "localizePageDsl \u7F3A\u5C11\u6709\u6548 locale");
|
|
1007
|
+
}
|
|
1008
|
+
if (typeof defaultLocale !== "string" || defaultLocale === "") {
|
|
1009
|
+
throw new I18nError("INVALID_LOCALE", "localizePageDsl \u7F3A\u5C11\u6709\u6548 defaultLocale");
|
|
1010
|
+
}
|
|
1011
|
+
const page = deepClone(input.page);
|
|
1012
|
+
const fallbacks = input.languageBundles.fallbacks ?? [];
|
|
1013
|
+
const bundleChain = [input.languageBundles.current, ...fallbacks];
|
|
1014
|
+
const localeChain = dedupeLocales([locale, ...fallbacks.map((set) => set.locale), defaultLocale]);
|
|
1015
|
+
const ctx = {
|
|
1016
|
+
locale,
|
|
1017
|
+
defaultLocale,
|
|
1018
|
+
localeChain,
|
|
1019
|
+
bundleChain,
|
|
1020
|
+
registry: input.registry ?? getDefaultTranslatableFieldRegistry(),
|
|
1021
|
+
namedComponents: isPlainObject3(page.components) ? page.components : void 0,
|
|
1022
|
+
onDiagnostic: input.onDiagnostic
|
|
1023
|
+
};
|
|
1024
|
+
const authorI18n = page.i18n;
|
|
1025
|
+
if (authorI18n !== void 0) {
|
|
1026
|
+
if (isPlainObject3(authorI18n.bindings)) {
|
|
1027
|
+
applyBindings(ctx, page, authorI18n.bindings);
|
|
1028
|
+
}
|
|
1029
|
+
delete page.i18n;
|
|
1030
|
+
}
|
|
1031
|
+
if (page.body !== void 0) {
|
|
1032
|
+
walkNode(ctx, page.body, "/body");
|
|
1033
|
+
}
|
|
1034
|
+
if (page.children !== void 0) {
|
|
1035
|
+
if (Array.isArray(page.children)) {
|
|
1036
|
+
page.children.forEach((child, index) => walkNode(ctx, child, `/children/${index}`));
|
|
1037
|
+
} else {
|
|
1038
|
+
walkNode(ctx, page.children, "/children");
|
|
1039
|
+
}
|
|
1040
|
+
}
|
|
1041
|
+
if (isPlainObject3(page.components)) {
|
|
1042
|
+
for (const [name, def] of Object.entries(page.components)) {
|
|
1043
|
+
walkNode(ctx, def, `/components/${escapeJsonPointerSegment(name)}`);
|
|
1044
|
+
}
|
|
1045
|
+
}
|
|
1046
|
+
if (isPlainObject3(page.actions)) {
|
|
1047
|
+
for (const [name, actions] of Object.entries(page.actions)) {
|
|
1048
|
+
walkActions(ctx, actions, `/actions/${escapeJsonPointerSegment(name)}`);
|
|
1049
|
+
}
|
|
1050
|
+
}
|
|
1051
|
+
if (isPlainObject3(page.lifecycle)) {
|
|
1052
|
+
for (const [event, actions] of Object.entries(page.lifecycle)) {
|
|
1053
|
+
walkActions(ctx, actions, `/lifecycle/${escapeJsonPointerSegment(event)}`);
|
|
1054
|
+
}
|
|
1055
|
+
}
|
|
1056
|
+
assertNoResidualI18n(page, "");
|
|
1057
|
+
return page;
|
|
1058
|
+
}
|
|
1059
|
+
|
|
1060
|
+
// src/yaml-message-provider.ts
|
|
1061
|
+
var import_yaml = require("yaml");
|
|
1062
|
+
var DEFAULT_MAX_FILE_BYTES = 1e6;
|
|
1063
|
+
var DEFAULT_MAX_ALIAS_COUNT = 100;
|
|
1064
|
+
function isPlainObject4(value) {
|
|
1065
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1066
|
+
}
|
|
1067
|
+
function isSafeRelativePath(path) {
|
|
1068
|
+
const normalized = path.replace(/\\/g, "/");
|
|
1069
|
+
return normalized !== "" && !normalized.startsWith("/") && !/^[A-Za-z]:\//.test(normalized) && !normalized.split("/").includes("..");
|
|
1070
|
+
}
|
|
1071
|
+
function validateManifest(manifest) {
|
|
1072
|
+
if (!isPlainObject4(manifest)) {
|
|
1073
|
+
throw new I18nError("MANIFEST_INVALID", "\u8BED\u8A00\u5305\u6E05\u5355\u5FC5\u987B\u662F\u5BF9\u8C61");
|
|
1074
|
+
}
|
|
1075
|
+
if (typeof manifest["appId"] !== "string" || manifest["appId"] === "") {
|
|
1076
|
+
throw new I18nError("MANIFEST_INVALID", "\u6E05\u5355\u7F3A\u5C11 appId");
|
|
1077
|
+
}
|
|
1078
|
+
if (typeof manifest["release"] !== "string" || manifest["release"] === "") {
|
|
1079
|
+
throw new I18nError("MANIFEST_INVALID", "\u6E05\u5355\u7F3A\u5C11 release");
|
|
1080
|
+
}
|
|
1081
|
+
const locales = manifest["locales"];
|
|
1082
|
+
if (!isPlainObject4(locales)) {
|
|
1083
|
+
throw new I18nError("MANIFEST_INVALID", "\u6E05\u5355\u7F3A\u5C11 locales");
|
|
1084
|
+
}
|
|
1085
|
+
for (const [locale, namespaces] of Object.entries(locales)) {
|
|
1086
|
+
if (!isPlainObject4(namespaces)) {
|
|
1087
|
+
throw new I18nError("MANIFEST_INVALID", `\u6E05\u5355 locales.${locale} \u5FC5\u987B\u662F\u5BF9\u8C61`);
|
|
1088
|
+
}
|
|
1089
|
+
for (const [namespace, entry] of Object.entries(namespaces)) {
|
|
1090
|
+
if (!isPlainObject4(entry) || typeof entry["path"] !== "string" || typeof entry["version"] !== "string") {
|
|
1091
|
+
throw new I18nError("MANIFEST_INVALID", `\u6E05\u5355\u6761\u76EE\u7F3A\u5C11 path/version: ${locale}/${namespace}`);
|
|
1092
|
+
}
|
|
1093
|
+
if (!isSafeRelativePath(entry["path"])) {
|
|
1094
|
+
throw new I18nError("MANIFEST_INVALID", `\u6E05\u5355\u6761\u76EE\u8DEF\u5F84\u5FC5\u987B\u662F\u5B89\u5168\u76F8\u5BF9\u8DEF\u5F84: ${entry["path"]}`, {
|
|
1095
|
+
locale,
|
|
1096
|
+
namespace
|
|
1097
|
+
});
|
|
1098
|
+
}
|
|
1099
|
+
}
|
|
1100
|
+
}
|
|
1101
|
+
return manifest;
|
|
1102
|
+
}
|
|
1103
|
+
function byteLength(text) {
|
|
1104
|
+
let bytes = 0;
|
|
1105
|
+
for (let i = 0; i < text.length; i += 1) {
|
|
1106
|
+
const code = text.charCodeAt(i);
|
|
1107
|
+
if (code < 128) {
|
|
1108
|
+
bytes += 1;
|
|
1109
|
+
} else if (code < 2048) {
|
|
1110
|
+
bytes += 2;
|
|
1111
|
+
} else if (code >= 55296 && code <= 56319 && i + 1 < text.length) {
|
|
1112
|
+
bytes += 4;
|
|
1113
|
+
i += 1;
|
|
1114
|
+
} else {
|
|
1115
|
+
bytes += 3;
|
|
1116
|
+
}
|
|
1117
|
+
}
|
|
1118
|
+
return bytes;
|
|
1119
|
+
}
|
|
1120
|
+
function hasExplicitTag(node) {
|
|
1121
|
+
if ((0, import_yaml.isPair)(node)) {
|
|
1122
|
+
return hasExplicitTag(node.key) || hasExplicitTag(node.value);
|
|
1123
|
+
}
|
|
1124
|
+
if ((0, import_yaml.isMap)(node) || (0, import_yaml.isSeq)(node)) {
|
|
1125
|
+
return node.tag !== void 0 || node.items.some(hasExplicitTag);
|
|
1126
|
+
}
|
|
1127
|
+
if ((0, import_yaml.isScalar)(node)) {
|
|
1128
|
+
return node.tag !== void 0;
|
|
1129
|
+
}
|
|
1130
|
+
return false;
|
|
1131
|
+
}
|
|
1132
|
+
var YamlMessageProvider = class {
|
|
1133
|
+
constructor(options) {
|
|
1134
|
+
this.manifest = validateManifest(options.manifest);
|
|
1135
|
+
this.readFile = options.readFile;
|
|
1136
|
+
this.maxFileBytes = options.maxFileBytes ?? DEFAULT_MAX_FILE_BYTES;
|
|
1137
|
+
this.maxAliasCount = options.maxAliasCount ?? DEFAULT_MAX_ALIAS_COUNT;
|
|
1138
|
+
}
|
|
1139
|
+
async load(input) {
|
|
1140
|
+
if (input.appId !== this.manifest.appId || input.release !== this.manifest.release) {
|
|
1141
|
+
throw new I18nError("MANIFEST_MISMATCH", "\u8BF7\u6C42 appId/release \u4E0E\u8BED\u8A00\u5305\u6E05\u5355\u4E0D\u4E00\u81F4", {
|
|
1142
|
+
appId: input.appId,
|
|
1143
|
+
release: input.release,
|
|
1144
|
+
manifestAppId: this.manifest.appId,
|
|
1145
|
+
manifestRelease: this.manifest.release
|
|
1146
|
+
});
|
|
1147
|
+
}
|
|
1148
|
+
const localeEntries = this.manifest.locales[input.locale];
|
|
1149
|
+
if (localeEntries === void 0) {
|
|
1150
|
+
throw new I18nError("NAMESPACE_NOT_FOUND", `\u6E05\u5355\u4E0D\u542B\u8BED\u8A00: ${input.locale}`, {
|
|
1151
|
+
locale: input.locale
|
|
1152
|
+
});
|
|
1153
|
+
}
|
|
1154
|
+
const bundles = {};
|
|
1155
|
+
for (const namespace of input.namespaces) {
|
|
1156
|
+
const entry = localeEntries[namespace];
|
|
1157
|
+
if (entry === void 0) {
|
|
1158
|
+
throw new I18nError(
|
|
1159
|
+
"NAMESPACE_NOT_FOUND",
|
|
1160
|
+
`\u6E05\u5355\u4E0D\u542B namespace: ${input.locale}/${namespace}`,
|
|
1161
|
+
{ locale: input.locale, namespace }
|
|
1162
|
+
);
|
|
1163
|
+
}
|
|
1164
|
+
bundles[namespace] = {
|
|
1165
|
+
version: entry.version,
|
|
1166
|
+
messages: await this.loadBundle(entry.path, namespace)
|
|
1167
|
+
};
|
|
1168
|
+
}
|
|
1169
|
+
assertNoCrossNamespaceKeyConflicts(
|
|
1170
|
+
Object.fromEntries(Object.entries(bundles).map(([ns, b]) => [ns, b.messages])),
|
|
1171
|
+
"MESSAGE_BUNDLE_INVALID"
|
|
1172
|
+
);
|
|
1173
|
+
return {
|
|
1174
|
+
appId: this.manifest.appId,
|
|
1175
|
+
locale: input.locale,
|
|
1176
|
+
release: this.manifest.release,
|
|
1177
|
+
format: "simple-v1",
|
|
1178
|
+
bundles
|
|
1179
|
+
};
|
|
1180
|
+
}
|
|
1181
|
+
/** 读取并严格解析单个语言包 YAML 文件 */
|
|
1182
|
+
async loadBundle(path, namespace) {
|
|
1183
|
+
let text;
|
|
1184
|
+
try {
|
|
1185
|
+
text = await this.readFile(path);
|
|
1186
|
+
} catch (error) {
|
|
1187
|
+
throw new I18nError("MESSAGE_FETCH_FAILED", `\u8BED\u8A00\u5305\u6587\u4EF6\u8BFB\u53D6\u5931\u8D25: ${path}`, {
|
|
1188
|
+
path,
|
|
1189
|
+
cause: error instanceof Error ? error.message : String(error)
|
|
1190
|
+
});
|
|
1191
|
+
}
|
|
1192
|
+
const context = `\u8BED\u8A00\u5305 ${namespace}\uFF08${path}\uFF09`;
|
|
1193
|
+
if (byteLength(text) > this.maxFileBytes) {
|
|
1194
|
+
throw new I18nError("MESSAGE_BUNDLE_INVALID", `${context}: \u8D85\u51FA\u5927\u5C0F\u9650\u5236 ${this.maxFileBytes} \u5B57\u8282`, {
|
|
1195
|
+
path,
|
|
1196
|
+
maxFileBytes: this.maxFileBytes
|
|
1197
|
+
});
|
|
1198
|
+
}
|
|
1199
|
+
const doc = (0, import_yaml.parseDocument)(text, {
|
|
1200
|
+
schema: "core",
|
|
1201
|
+
uniqueKeys: true,
|
|
1202
|
+
strict: true
|
|
1203
|
+
});
|
|
1204
|
+
if (doc.errors.length > 0) {
|
|
1205
|
+
throw new I18nError(
|
|
1206
|
+
"MESSAGE_BUNDLE_INVALID",
|
|
1207
|
+
`${context}: YAML \u89E3\u6790\u5931\u8D25: ${doc.errors.map((e) => e.message).join("; ")}`,
|
|
1208
|
+
{ path }
|
|
1209
|
+
);
|
|
1210
|
+
}
|
|
1211
|
+
if (hasExplicitTag(doc.contents)) {
|
|
1212
|
+
throw new I18nError("MESSAGE_BUNDLE_INVALID", `${context}: \u4E0D\u5141\u8BB8\u81EA\u5B9A\u4E49\u6807\u7B7E`, { path });
|
|
1213
|
+
}
|
|
1214
|
+
let data;
|
|
1215
|
+
try {
|
|
1216
|
+
data = doc.toJS({ maxAliasCount: this.maxAliasCount });
|
|
1217
|
+
} catch (error) {
|
|
1218
|
+
throw new I18nError(
|
|
1219
|
+
"MESSAGE_BUNDLE_INVALID",
|
|
1220
|
+
`${context}: ${error instanceof Error ? error.message : String(error)}`,
|
|
1221
|
+
{ path }
|
|
1222
|
+
);
|
|
1223
|
+
}
|
|
1224
|
+
return assertMessages(data, context, "MESSAGE_BUNDLE_INVALID");
|
|
1225
|
+
}
|
|
1226
|
+
};
|
|
1227
|
+
|
|
1228
|
+
// src/localize-yaml-resource.ts
|
|
1229
|
+
var import_yaml2 = require("yaml");
|
|
1230
|
+
var import_schema = require("@pactor-app/schema");
|
|
1231
|
+
function parsePageYamlStrict(text, options) {
|
|
1232
|
+
const doc = (0, import_yaml2.parseDocument)(text, {
|
|
1233
|
+
schema: "core",
|
|
1234
|
+
uniqueKeys: true,
|
|
1235
|
+
strict: true
|
|
1236
|
+
});
|
|
1237
|
+
if (doc.errors.length > 0) {
|
|
1238
|
+
throw new I18nError(
|
|
1239
|
+
"PAGE_YAML_INVALID",
|
|
1240
|
+
`\u9875\u9762 YAML \u89E3\u6790\u5931\u8D25: ${doc.errors.map((e) => e.message).join("; ")}`
|
|
1241
|
+
);
|
|
1242
|
+
}
|
|
1243
|
+
if (hasExplicitTag(doc.contents)) {
|
|
1244
|
+
throw new I18nError("PAGE_YAML_INVALID", "\u9875\u9762 YAML \u4E0D\u5141\u8BB8\u81EA\u5B9A\u4E49\u6807\u7B7E");
|
|
1245
|
+
}
|
|
1246
|
+
try {
|
|
1247
|
+
return doc.toJS({ maxAliasCount: options?.maxAliasCount ?? 100 });
|
|
1248
|
+
} catch (error) {
|
|
1249
|
+
throw new I18nError(
|
|
1250
|
+
"PAGE_YAML_INVALID",
|
|
1251
|
+
`\u9875\u9762 YAML \u65E0\u6548: ${error instanceof Error ? error.message : String(error)}`
|
|
1252
|
+
);
|
|
1253
|
+
}
|
|
1254
|
+
}
|
|
1255
|
+
function emptyBundleSet(appId, locale, release) {
|
|
1256
|
+
return { appId, locale, release, format: "simple-v1", bundles: {} };
|
|
1257
|
+
}
|
|
1258
|
+
async function localizeYamlResource(input) {
|
|
1259
|
+
let text;
|
|
1260
|
+
try {
|
|
1261
|
+
text = await input.loadText();
|
|
1262
|
+
} catch (error) {
|
|
1263
|
+
throw new I18nError("PAGE_LOAD_FAILED", "\u9875\u9762\u8D44\u6E90\u52A0\u8F7D\u5931\u8D25", {
|
|
1264
|
+
cause: error instanceof Error ? error.message : String(error)
|
|
1265
|
+
});
|
|
1266
|
+
}
|
|
1267
|
+
const parse = input.parseYaml ?? ((value) => parsePageYamlStrict(value));
|
|
1268
|
+
let document;
|
|
1269
|
+
try {
|
|
1270
|
+
document = parse(text);
|
|
1271
|
+
} catch (error) {
|
|
1272
|
+
if (isI18nError(error)) {
|
|
1273
|
+
throw error;
|
|
1274
|
+
}
|
|
1275
|
+
throw new I18nError(
|
|
1276
|
+
"PAGE_YAML_INVALID",
|
|
1277
|
+
`\u9875\u9762 YAML \u89E3\u6790\u5931\u8D25: ${error instanceof Error ? error.message : String(error)}`
|
|
1278
|
+
);
|
|
1279
|
+
}
|
|
1280
|
+
const author = (0, import_schema.validateAuthorPageDsl)(document);
|
|
1281
|
+
if (!author.valid) {
|
|
1282
|
+
throw new I18nError("PAGE_DSL_INVALID", "\u4F5C\u8005\u6001\u9875\u9762\u6821\u9A8C\u5931\u8D25", { errors: author.errors });
|
|
1283
|
+
}
|
|
1284
|
+
const pruned = input.prune ? input.prune(document) : document;
|
|
1285
|
+
const namespaces = pruned.i18n?.namespaces ?? [];
|
|
1286
|
+
const chain = [.../* @__PURE__ */ new Set([input.locale, ...input.fallbackLocales ?? [], input.defaultLocale])];
|
|
1287
|
+
let current;
|
|
1288
|
+
let fallbacks;
|
|
1289
|
+
if (namespaces.length === 0) {
|
|
1290
|
+
[current, ...fallbacks] = chain.map((loc) => emptyBundleSet(input.appId, loc, input.release));
|
|
1291
|
+
} else {
|
|
1292
|
+
const loaded = await Promise.all(
|
|
1293
|
+
chain.map(
|
|
1294
|
+
(loc) => input.provider.load({
|
|
1295
|
+
appId: input.appId,
|
|
1296
|
+
tenantId: input.tenantId,
|
|
1297
|
+
locale: loc,
|
|
1298
|
+
namespaces,
|
|
1299
|
+
release: input.release
|
|
1300
|
+
})
|
|
1301
|
+
)
|
|
1302
|
+
);
|
|
1303
|
+
[current, ...fallbacks] = loaded;
|
|
1304
|
+
}
|
|
1305
|
+
const schema = localizePageDsl({
|
|
1306
|
+
page: pruned,
|
|
1307
|
+
locale: input.locale,
|
|
1308
|
+
defaultLocale: input.defaultLocale,
|
|
1309
|
+
languageBundles: { current, fallbacks },
|
|
1310
|
+
registry: input.registry,
|
|
1311
|
+
onDiagnostic: input.onDiagnostic
|
|
1312
|
+
});
|
|
1313
|
+
const localized = (0, import_schema.validateLocalizedPageDsl)(schema);
|
|
1314
|
+
if (!localized.valid) {
|
|
1315
|
+
throw new I18nError("PAGE_DSL_INVALID", "\u4E0B\u53D1\u6001\u9875\u9762\u6821\u9A8C\u5931\u8D25", { errors: localized.errors });
|
|
1316
|
+
}
|
|
1317
|
+
const bundleVersions = Object.fromEntries(
|
|
1318
|
+
Object.entries(current.bundles).map(([namespace, bundle]) => [namespace, bundle.version])
|
|
1319
|
+
);
|
|
1320
|
+
const localization = {
|
|
1321
|
+
locale: input.locale,
|
|
1322
|
+
defaultLocale: input.defaultLocale,
|
|
1323
|
+
release: input.release,
|
|
1324
|
+
bundleVersions,
|
|
1325
|
+
...input.source !== void 0 ? { source: input.source } : {},
|
|
1326
|
+
...input.explicit !== void 0 ? { explicit: input.explicit } : {}
|
|
1327
|
+
};
|
|
1328
|
+
return { schema, localization };
|
|
1329
|
+
}
|
|
1330
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
1331
|
+
0 && (module.exports = {
|
|
1332
|
+
ApiMessageProvider,
|
|
1333
|
+
I18nError,
|
|
1334
|
+
MISSING_TRANSLATION_TEXT,
|
|
1335
|
+
TranslatableFieldRegistry,
|
|
1336
|
+
YamlMessageProvider,
|
|
1337
|
+
assertMessages,
|
|
1338
|
+
assertNoCrossNamespaceKeyConflicts,
|
|
1339
|
+
createDefaultTranslatableFieldRegistry,
|
|
1340
|
+
escapeJsonPointerSegment,
|
|
1341
|
+
getDefaultTranslatableFieldRegistry,
|
|
1342
|
+
isI18nError,
|
|
1343
|
+
isValidMessageKey,
|
|
1344
|
+
localizePageDsl,
|
|
1345
|
+
localizeYamlResource,
|
|
1346
|
+
normalizeLocaleTag,
|
|
1347
|
+
parseAcceptLanguage,
|
|
1348
|
+
parseJsonPointer,
|
|
1349
|
+
parsePageYamlStrict,
|
|
1350
|
+
resolveJsonPointer,
|
|
1351
|
+
resolveRequestLocale,
|
|
1352
|
+
validateBundleSetEnvelope,
|
|
1353
|
+
validateManifest
|
|
1354
|
+
});
|