@logixjs/i18n 1.0.2-beta.0 → 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/I18n.cjs +13 -127
- package/dist/I18n.cjs.map +1 -1
- package/dist/I18n.d.cts +15 -7
- package/dist/I18n.d.ts +15 -7
- package/dist/I18n.js +1 -3
- package/dist/Token.cjs +42 -36
- package/dist/Token.cjs.map +1 -1
- package/dist/Token.d.cts +6 -7
- package/dist/Token.d.ts +6 -7
- package/dist/Token.js +1 -4
- package/dist/{chunk-4NKGIEWG.js → chunk-DQVWWU7T.js} +14 -20
- package/dist/chunk-DQVWWU7T.js.map +1 -0
- package/dist/chunk-LGNB43KG.js +123 -0
- package/dist/chunk-LGNB43KG.js.map +1 -0
- package/dist/index.cjs +128 -178
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -4
- package/dist/index.d.ts +2 -4
- package/dist/index.js +2 -14
- package/package.json +29 -22
- package/LICENSE +0 -201
- package/dist/I18nModule.cjs +0 -88
- package/dist/I18nModule.cjs.map +0 -1
- package/dist/I18nModule.d.cts +0 -5
- package/dist/I18nModule.d.ts +0 -5
- package/dist/I18nModule.js +0 -9
- package/dist/I18nModule.js.map +0 -1
- package/dist/chunk-4NKGIEWG.js.map +0 -1
- package/dist/chunk-FDPDEDU7.js +0 -1
- package/dist/chunk-FDPDEDU7.js.map +0 -1
- package/dist/chunk-LW6LHDDL.js +0 -1
- package/dist/chunk-LW6LHDDL.js.map +0 -1
- package/dist/chunk-NWWL4MNH.js +0 -116
- package/dist/chunk-NWWL4MNH.js.map +0 -1
- package/dist/chunk-X2U6SCIR.js +0 -45
- package/dist/chunk-X2U6SCIR.js.map +0 -1
|
@@ -1,7 +1,3 @@
|
|
|
1
|
-
import {
|
|
2
|
-
token
|
|
3
|
-
} from "./chunk-NWWL4MNH.js";
|
|
4
|
-
|
|
5
1
|
// src/internal/driver/i18n.ts
|
|
6
2
|
import {
|
|
7
3
|
Duration,
|
|
@@ -84,22 +80,22 @@ var makeI18nService = (driver) => Effect.gen(function* () {
|
|
|
84
80
|
yield* update({ init: "ready", language });
|
|
85
81
|
}
|
|
86
82
|
});
|
|
87
|
-
const fallback = (
|
|
88
|
-
const
|
|
83
|
+
const fallback = (message, hints) => hints?.fallback ?? message.key;
|
|
84
|
+
const render = (message, hints) => {
|
|
89
85
|
if (currentSnapshot.init !== "ready") {
|
|
90
|
-
return fallback(
|
|
86
|
+
return fallback(message, hints);
|
|
91
87
|
}
|
|
92
88
|
try {
|
|
93
|
-
return driver.t(key,
|
|
89
|
+
return driver.t(message.key, message.params);
|
|
94
90
|
} catch {
|
|
95
|
-
return fallback(
|
|
91
|
+
return fallback(message, hints);
|
|
96
92
|
}
|
|
97
93
|
};
|
|
98
|
-
const
|
|
94
|
+
const renderReady = (message, hints, timeoutMs) => Effect.gen(function* () {
|
|
99
95
|
const cap = timeoutMs ?? 5e3;
|
|
100
96
|
const snap0 = yield* SubscriptionRef.get(snapshotRef);
|
|
101
|
-
if (snap0.init === "ready") return
|
|
102
|
-
if (snap0.init === "failed") return fallback(
|
|
97
|
+
if (snap0.init === "ready") return render(message, hints);
|
|
98
|
+
if (snap0.init === "failed") return fallback(message, hints);
|
|
103
99
|
const wait = Stream.filter(SubscriptionRef.changes(snapshotRef), (s) => s.init !== "pending").pipe(
|
|
104
100
|
Stream.runHead,
|
|
105
101
|
Effect.timeoutOption(Duration.millis(cap)),
|
|
@@ -109,21 +105,19 @@ var makeI18nService = (driver) => Effect.gen(function* () {
|
|
|
109
105
|
const snap1 = yield* SubscriptionRef.get(snapshotRef);
|
|
110
106
|
if (snap1.init !== "pending") {
|
|
111
107
|
yield* Fiber.interrupt(fiber);
|
|
112
|
-
return snap1.init === "ready" ?
|
|
108
|
+
return snap1.init === "ready" ? render(message, hints) : fallback(message, hints);
|
|
113
109
|
}
|
|
114
110
|
const outcome = yield* Fiber.join(fiber);
|
|
115
111
|
return Option.match(outcome, {
|
|
116
|
-
onNone: () => fallback(
|
|
117
|
-
onSome: (snap) => snap.init === "ready" ?
|
|
112
|
+
onNone: () => fallback(message, hints),
|
|
113
|
+
onSome: (snap) => snap.init === "ready" ? render(message, hints) : fallback(message, hints)
|
|
118
114
|
});
|
|
119
115
|
});
|
|
120
116
|
return {
|
|
121
|
-
instance: driver,
|
|
122
117
|
snapshot: snapshotRef,
|
|
123
|
-
token,
|
|
124
118
|
changeLanguage,
|
|
125
|
-
|
|
126
|
-
|
|
119
|
+
render,
|
|
120
|
+
renderReady
|
|
127
121
|
};
|
|
128
122
|
});
|
|
129
123
|
var I18n = {
|
|
@@ -135,4 +129,4 @@ export {
|
|
|
135
129
|
I18nTag,
|
|
136
130
|
I18n
|
|
137
131
|
};
|
|
138
|
-
//# sourceMappingURL=chunk-
|
|
132
|
+
//# sourceMappingURL=chunk-DQVWWU7T.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/internal/driver/i18n.ts"],"sourcesContent":["import {\n Duration,\n Effect,\n Fiber,\n Layer,\n ManagedRuntime,\n Option,\n ServiceMap,\n Scope,\n Schema,\n Stream,\n SubscriptionRef,\n} from 'effect'\n\nimport type { I18nMessageToken, I18nTokenParamsInput } from '../token/token.js'\n\nexport type { I18nTokenParamsInput } from '../token/token.js'\n\n/**\n * Low-level driver contract.\n *\n * Package-level canonical consumption remains service-first:\n * callers should read `services.i18n` or `I18nTag`, rather than coupling to driver lifecycle details directly.\n */\nexport type I18nDriver = {\n readonly language: string\n readonly isInitialized?: boolean\n readonly t: (key: string, input?: unknown) => string\n readonly changeLanguage: (language: string) => Promise<unknown> | unknown\n readonly on: (event: 'initialized' | 'languageChanged', handler: (...args: any[]) => void) => unknown\n readonly off: (event: 'initialized' | 'languageChanged', handler: (...args: any[]) => void) => unknown\n}\n\nexport type I18nInitState = 'pending' | 'ready' | 'failed'\n\nexport type I18nSnapshot = {\n readonly language: string\n readonly init: I18nInitState\n readonly seq: number\n}\n\nexport const I18nSnapshotSchema = Schema.Struct({\n language: Schema.String,\n init: Schema.Literals(['pending', 'ready', 'failed']),\n seq: Schema.Number,\n})\n\nexport type I18nRenderHints = {\n readonly fallback?: string\n}\n\nexport type I18nService = {\n readonly snapshot: SubscriptionRef.SubscriptionRef<I18nSnapshot>\n readonly changeLanguage: (language: string) => Effect.Effect<void, never, never>\n readonly render: (token: I18nMessageToken, hints?: I18nRenderHints) => string\n readonly renderReady: (\n token: I18nMessageToken,\n hints?: I18nRenderHints,\n timeoutMs?: number,\n ) => Effect.Effect<string, never, never>\n}\n\nexport class I18nTag extends ServiceMap.Service<I18nTag, I18nService>()('@logixjs/i18n/I18n') {}\n\nconst asNonEmptyString = (value: unknown): string | undefined =>\n typeof value === 'string' && value.length > 0 ? value : undefined\n\nconst makeI18nService = (driver: I18nDriver): Effect.Effect<I18nService, never, Scope.Scope> =>\n Effect.gen(function* () {\n const init: I18nInitState = driver.isInitialized ? 'ready' : 'pending'\n let currentSnapshot: I18nSnapshot = {\n language: driver.language,\n init,\n seq: 0,\n }\n const snapshotRef = yield* SubscriptionRef.make<I18nSnapshot>(currentSnapshot)\n\n const update = (patch: Partial<Pick<I18nSnapshot, 'language' | 'init'>>): Effect.Effect<void> =>\n SubscriptionRef.update(snapshotRef, (prev) => {\n const next: I18nSnapshot = {\n language: patch.language ?? prev.language,\n init: patch.init ?? prev.init,\n seq: prev.seq + 1,\n }\n currentSnapshot = next\n return next\n })\n\n const pushSnapshot = (patch: Partial<Pick<I18nSnapshot, 'language' | 'init'>>): void => {\n const next: I18nSnapshot = {\n language: patch.language ?? currentSnapshot.language,\n init: patch.init ?? currentSnapshot.init,\n seq: currentSnapshot.seq + 1,\n }\n currentSnapshot = next\n void Effect.runPromise(SubscriptionRef.set(snapshotRef, next).pipe(Effect.catchCause(() => Effect.void)))\n }\n\n const onInitialized = (): void => {\n pushSnapshot({ init: 'ready', language: driver.language })\n }\n\n const onLanguageChanged = (lang: unknown): void => {\n pushSnapshot({\n init: 'ready',\n language: asNonEmptyString(lang) ?? driver.language,\n })\n }\n\n yield* Effect.sync(() => {\n driver.on('initialized', onInitialized)\n driver.on('languageChanged', onLanguageChanged)\n })\n\n const scope = yield* Effect.scope\n yield* Scope.addFinalizer(\n scope,\n Effect.sync(() => {\n driver.off('initialized', onInitialized)\n driver.off('languageChanged', onLanguageChanged)\n }),\n )\n\n /**\n * Canonical runtime-facing lifecycle wiring:\n * - driver events update the shared snapshot\n * - consumers stay on the service contract (`render`, `renderReady`, `snapshot`)\n */\n const changeLanguage = (language: string): Effect.Effect<void, never, never> =>\n Effect.gen(function* () {\n yield* update({ language, init: 'pending' })\n const exit = yield* Effect.exit(\n Effect.tryPromise({\n try: () => Promise.resolve(driver.changeLanguage(language)),\n catch: () => undefined,\n }),\n )\n if (exit._tag === 'Failure') {\n yield* update({ init: 'failed' })\n } else {\n yield* update({ init: 'ready', language })\n }\n })\n\n const fallback = (message: I18nMessageToken, hints?: I18nRenderHints): string => hints?.fallback ?? message.key\n\n const render = (message: I18nMessageToken, hints?: I18nRenderHints): string => {\n if (currentSnapshot.init !== 'ready') {\n return fallback(message, hints)\n }\n try {\n return driver.t(message.key, message.params as I18nTokenParamsInput | undefined)\n } catch {\n return fallback(message, hints)\n }\n }\n\n const renderReady = (\n message: I18nMessageToken,\n hints?: I18nRenderHints,\n timeoutMs?: number,\n ): Effect.Effect<string, never, never> =>\n Effect.gen(function* () {\n const cap = timeoutMs ?? 5000\n const snap0 = yield* SubscriptionRef.get(snapshotRef)\n if (snap0.init === 'ready') return render(message, hints)\n if (snap0.init === 'failed') return fallback(message, hints)\n\n const wait = Stream.filter(SubscriptionRef.changes(snapshotRef), (s) => s.init !== 'pending').pipe(\n Stream.runHead,\n Effect.timeoutOption(Duration.millis(cap)),\n Effect.map((maybe) => (Option.isSome(maybe) ? maybe.value : Option.none())),\n )\n\n const fiber = yield* wait.pipe(Effect.forkChild)\n\n const snap1 = yield* SubscriptionRef.get(snapshotRef)\n if (snap1.init !== 'pending') {\n yield* Fiber.interrupt(fiber)\n return snap1.init === 'ready' ? render(message, hints) : fallback(message, hints)\n }\n\n const outcome = yield* Fiber.join(fiber)\n return Option.match(outcome, {\n onNone: () => fallback(message, hints),\n onSome: (snap) => (snap.init === 'ready' ? render(message, hints) : fallback(message, hints)),\n })\n })\n\n return {\n snapshot: snapshotRef,\n changeLanguage,\n render,\n renderReady,\n } as const\n })\n\nexport const I18n = {\n layer: (driver: I18nDriver): Layer.Layer<I18nTag, never, never> => Layer.effect(I18nTag, makeI18nService(driver)),\n} as const\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
// src/internal/token/token.ts
|
|
2
|
+
var InvalidI18nMessageTokenError = class extends Error {
|
|
3
|
+
constructor(reason, details, fix) {
|
|
4
|
+
super(`[InvalidI18nMessageTokenError] reason=${reason}`);
|
|
5
|
+
this.reason = reason;
|
|
6
|
+
this.details = details;
|
|
7
|
+
this.fix = fix;
|
|
8
|
+
this.name = "InvalidI18nMessageTokenError";
|
|
9
|
+
}
|
|
10
|
+
};
|
|
11
|
+
var TOKEN_BUDGET = {
|
|
12
|
+
keyMaxLen: 96,
|
|
13
|
+
paramKeyMaxCount: 8,
|
|
14
|
+
paramValueStringMaxLen: 96
|
|
15
|
+
};
|
|
16
|
+
var LANGUAGE_FROZEN_KEYS = /* @__PURE__ */ new Set(["lng", "lngs"]);
|
|
17
|
+
var DANGEROUS_PARAM_KEYS = /* @__PURE__ */ new Set(["__proto__", "prototype", "constructor"]);
|
|
18
|
+
var LEGACY_RENDER_FALLBACK_KEY = `default${"Value"}`;
|
|
19
|
+
var RESERVED_RENDER_FALLBACK_KEYS = /* @__PURE__ */ new Set([LEGACY_RENDER_FALLBACK_KEY]);
|
|
20
|
+
var isJsonPrimitive = (value) => value === null || typeof value === "boolean" || typeof value === "number" || typeof value === "string";
|
|
21
|
+
var invalidToken = (reason, details, fix) => {
|
|
22
|
+
throw new InvalidI18nMessageTokenError(reason, details, fix);
|
|
23
|
+
};
|
|
24
|
+
var canonicalizeTokenParams = (params) => {
|
|
25
|
+
if (!params) return void 0;
|
|
26
|
+
if (typeof params !== "object" || params === null || Array.isArray(params)) {
|
|
27
|
+
invalidToken("paramValueInvalid", { field: "params", actual: typeof params }, [
|
|
28
|
+
"\u8BF7\u4F20\u5165 plain object \u4F5C\u4E3A params\uFF08Record<string, JsonPrimitive>\uFF09\u3002",
|
|
29
|
+
"\u4E0D\u8981\u4F20\u5165\u6570\u7EC4/\u51FD\u6570/\u7C7B\u5B9E\u4F8B\u7B49\u4E0D\u53EF\u5E8F\u5217\u5316\u503C\u3002"
|
|
30
|
+
]);
|
|
31
|
+
}
|
|
32
|
+
const entries = Object.entries(params).filter((p) => p[1] !== void 0);
|
|
33
|
+
if (entries.length === 0) return void 0;
|
|
34
|
+
if (entries.length > TOKEN_BUDGET.paramKeyMaxCount) {
|
|
35
|
+
invalidToken(
|
|
36
|
+
"tooManyParams",
|
|
37
|
+
{
|
|
38
|
+
field: "params",
|
|
39
|
+
max: TOKEN_BUDGET.paramKeyMaxCount,
|
|
40
|
+
actual: entries.length
|
|
41
|
+
},
|
|
42
|
+
[
|
|
43
|
+
`\u51CF\u5C11 params \u952E\u6570\u91CF\uFF08\u5EFA\u8BAE \u2264 ${TOKEN_BUDGET.paramKeyMaxCount}\uFF09\u3002`,
|
|
44
|
+
"\u907F\u514D\u628A\u5927\u5BF9\u8C61\u6216\u5C55\u793A\u515C\u5E95\u6587\u6848\u585E\u8FDB semantic token\u3002"
|
|
45
|
+
]
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
for (const [k, v] of entries) {
|
|
49
|
+
if (RESERVED_RENDER_FALLBACK_KEYS.has(k)) {
|
|
50
|
+
invalidToken("renderFallbackReserved", { field: `params.${k}`, key: k }, [
|
|
51
|
+
"\u4E0D\u8981\u628A\u65E7\u7684\u5C55\u793A\u515C\u5E95\u5B57\u6BB5\u653E\u8FDB semantic token params\u3002",
|
|
52
|
+
"\u8BF7\u628A\u515C\u5E95\u6587\u6848\u6539\u653E\u5230 render/renderReady \u7684 hints.fallback\u3002"
|
|
53
|
+
]);
|
|
54
|
+
}
|
|
55
|
+
if (DANGEROUS_PARAM_KEYS.has(k) || k.length === 0) {
|
|
56
|
+
invalidToken("paramKeyInvalid", { field: `params.${k}`, key: k }, [
|
|
57
|
+
"\u8BF7\u4F7F\u7528\u666E\u901A\u5B57\u6BB5\u540D\u4F5C\u4E3A params key\uFF0C\u907F\u514D __proto__/constructor/prototype \u7B49\u5371\u9669\u952E\u3002",
|
|
58
|
+
"\u5982\u9700\u4F20\u9012\u590D\u6742\u7ED3\u6784\uFF0C\u8BF7\u5148\u5728\u5C55\u793A\u8FB9\u754C\u8F6C\u6362\u4E3A\u5B57\u7B26\u4E32\u3002"
|
|
59
|
+
]);
|
|
60
|
+
}
|
|
61
|
+
if (LANGUAGE_FROZEN_KEYS.has(k)) {
|
|
62
|
+
invalidToken("languageFrozen", { field: `params.${k}`, key: k }, [
|
|
63
|
+
"\u4E0D\u8981\u5728 token params \u4E2D\u4F20\u5165 lng/lngs \u7B49\u8BED\u8A00\u51BB\u7ED3\u5B57\u6BB5\u3002",
|
|
64
|
+
"\u8BED\u8A00\u7531\u5916\u90E8 i18n \u5B9E\u4F8B\u51B3\u5B9A\uFF1Btoken \u53EA\u8868\u8FBE\u201C\u8981\u7FFB\u8BD1\u4EC0\u4E48\u201D\u3002"
|
|
65
|
+
]);
|
|
66
|
+
}
|
|
67
|
+
if (!isJsonPrimitive(v)) {
|
|
68
|
+
invalidToken("paramValueInvalid", { field: `params.${k}`, actual: typeof v }, [
|
|
69
|
+
"params value \u53EA\u5141\u8BB8 JsonPrimitive\uFF08null/boolean/number/string\uFF09\u3002",
|
|
70
|
+
"\u4E0D\u8981\u4F20\u5165\u5BF9\u8C61/\u6570\u7EC4/\u51FD\u6570\uFF1B\u9700\u8981\u65F6\u8BF7\u5728\u5C55\u793A\u8FB9\u754C\u5148\u683C\u5F0F\u5316\u6210\u5B57\u7B26\u4E32\u3002"
|
|
71
|
+
]);
|
|
72
|
+
}
|
|
73
|
+
if (typeof v === "number" && !Number.isFinite(v)) {
|
|
74
|
+
invalidToken("numberNotJsonSafe", { field: `params.${k}`, value: String(v) }, [
|
|
75
|
+
"\u4E0D\u8981\u5728 token params \u4E2D\u4F20\u5165 NaN/Infinity\u3002",
|
|
76
|
+
"\u8BF7\u5148\u628A\u8BE5\u6570\u503C\u8F6C\u6362\u4E3A\u53EF JSON \u5316\u7684 number \u6216 string\u3002"
|
|
77
|
+
]);
|
|
78
|
+
}
|
|
79
|
+
if (typeof v === "string" && v.length > TOKEN_BUDGET.paramValueStringMaxLen) {
|
|
80
|
+
invalidToken(
|
|
81
|
+
"paramValueTooLong",
|
|
82
|
+
{
|
|
83
|
+
field: `params.${k}`,
|
|
84
|
+
maxLen: TOKEN_BUDGET.paramValueStringMaxLen,
|
|
85
|
+
actualLen: v.length
|
|
86
|
+
},
|
|
87
|
+
[
|
|
88
|
+
`\u7F29\u77ED\u5B57\u7B26\u4E32\u503C\u957F\u5EA6\uFF08\u5EFA\u8BAE \u2264 ${TOKEN_BUDGET.paramValueStringMaxLen}\uFF09\u3002`,
|
|
89
|
+
"\u628A\u957F\u6587\u672C\u7559\u5728\u5C55\u793A\u8FB9\u754C\uFF0C\u4E0D\u8981\u653E\u8FDB semantic token\u3002"
|
|
90
|
+
]
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
entries.sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0);
|
|
95
|
+
const out = {};
|
|
96
|
+
for (const [k, v] of entries) {
|
|
97
|
+
out[k] = v;
|
|
98
|
+
}
|
|
99
|
+
return out;
|
|
100
|
+
};
|
|
101
|
+
var token = (key, params) => {
|
|
102
|
+
if (key.length > TOKEN_BUDGET.keyMaxLen) {
|
|
103
|
+
invalidToken("keyTooLong", { field: "key", maxLen: TOKEN_BUDGET.keyMaxLen, actualLen: key.length }, [
|
|
104
|
+
`\u7F29\u77ED key\uFF08\u5EFA\u8BAE \u2264 ${TOKEN_BUDGET.keyMaxLen}\uFF09\u3002`,
|
|
105
|
+
"\u5982\u679C key \u8FC7\u957F\uFF0C\u5EFA\u8BAE\u6539\u4E3A\u201C\u7A33\u5B9A key + \u5C11\u91CF semantic params\u201D\u3002"
|
|
106
|
+
]);
|
|
107
|
+
}
|
|
108
|
+
const canon = canonicalizeTokenParams(params);
|
|
109
|
+
return canon ? {
|
|
110
|
+
_tag: "i18n",
|
|
111
|
+
key,
|
|
112
|
+
params: canon
|
|
113
|
+
} : {
|
|
114
|
+
_tag: "i18n",
|
|
115
|
+
key
|
|
116
|
+
};
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
export {
|
|
120
|
+
InvalidI18nMessageTokenError,
|
|
121
|
+
token
|
|
122
|
+
};
|
|
123
|
+
//# sourceMappingURL=chunk-LGNB43KG.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/internal/token/token.ts"],"sourcesContent":["export type JsonPrimitive = null | boolean | number | string\n\nexport type I18nTokenParams = Readonly<Record<string, JsonPrimitive>>\nexport type I18nTokenParamsInput = Readonly<Record<string, JsonPrimitive | undefined>>\n\nexport type I18nMessageToken = {\n readonly _tag: 'i18n'\n readonly key: string\n readonly params?: I18nTokenParams\n}\n\nexport type InvalidI18nMessageTokenReason =\n | 'keyTooLong'\n | 'tooManyParams'\n | 'paramKeyInvalid'\n | 'paramValueInvalid'\n | 'paramValueTooLong'\n | 'numberNotJsonSafe'\n | 'languageFrozen'\n | 'renderFallbackReserved'\n\nexport class InvalidI18nMessageTokenError extends Error {\n readonly name = 'InvalidI18nMessageTokenError'\n\n constructor(\n readonly reason: InvalidI18nMessageTokenReason,\n readonly details: unknown,\n readonly fix: ReadonlyArray<string>,\n ) {\n super(`[InvalidI18nMessageTokenError] reason=${reason}`)\n }\n}\n\nconst TOKEN_BUDGET = {\n keyMaxLen: 96,\n paramKeyMaxCount: 8,\n paramValueStringMaxLen: 96,\n} as const\n\nconst LANGUAGE_FROZEN_KEYS = new Set(['lng', 'lngs'])\nconst DANGEROUS_PARAM_KEYS = new Set(['__proto__', 'prototype', 'constructor'])\nconst LEGACY_RENDER_FALLBACK_KEY = `default${'Value'}`\nconst RESERVED_RENDER_FALLBACK_KEYS = new Set([LEGACY_RENDER_FALLBACK_KEY])\n\nconst isJsonPrimitive = (value: unknown): value is JsonPrimitive =>\n value === null || typeof value === 'boolean' || typeof value === 'number' || typeof value === 'string'\n\nconst invalidToken = (reason: InvalidI18nMessageTokenReason, details: unknown, fix: ReadonlyArray<string>): never => {\n throw new InvalidI18nMessageTokenError(reason, details, fix)\n}\n\nexport const canonicalizeTokenParams = (params: I18nTokenParamsInput | undefined): I18nTokenParams | undefined => {\n if (!params) return undefined\n if (typeof params !== 'object' || params === null || Array.isArray(params)) {\n invalidToken('paramValueInvalid', { field: 'params', actual: typeof params }, [\n '请传入 plain object 作为 params(Record<string, JsonPrimitive>)。',\n '不要传入数组/函数/类实例等不可序列化值。',\n ])\n }\n\n const entries = Object.entries(params).filter((p): p is [string, JsonPrimitive] => p[1] !== undefined)\n\n if (entries.length === 0) return undefined\n\n if (entries.length > TOKEN_BUDGET.paramKeyMaxCount) {\n invalidToken(\n 'tooManyParams',\n {\n field: 'params',\n max: TOKEN_BUDGET.paramKeyMaxCount,\n actual: entries.length,\n },\n [\n `减少 params 键数量(建议 ≤ ${TOKEN_BUDGET.paramKeyMaxCount})。`,\n '避免把大对象或展示兜底文案塞进 semantic token。',\n ],\n )\n }\n\n for (const [k, v] of entries) {\n if (RESERVED_RENDER_FALLBACK_KEYS.has(k)) {\n invalidToken('renderFallbackReserved', { field: `params.${k}`, key: k }, [\n '不要把旧的展示兜底字段放进 semantic token params。',\n '请把兜底文案改放到 render/renderReady 的 hints.fallback。',\n ])\n }\n\n if (DANGEROUS_PARAM_KEYS.has(k) || k.length === 0) {\n invalidToken('paramKeyInvalid', { field: `params.${k}`, key: k }, [\n '请使用普通字段名作为 params key,避免 __proto__/constructor/prototype 等危险键。',\n '如需传递复杂结构,请先在展示边界转换为字符串。',\n ])\n }\n\n if (LANGUAGE_FROZEN_KEYS.has(k)) {\n invalidToken('languageFrozen', { field: `params.${k}`, key: k }, [\n '不要在 token params 中传入 lng/lngs 等语言冻结字段。',\n '语言由外部 i18n 实例决定;token 只表达“要翻译什么”。',\n ])\n }\n\n if (!isJsonPrimitive(v)) {\n invalidToken('paramValueInvalid', { field: `params.${k}`, actual: typeof v }, [\n 'params value 只允许 JsonPrimitive(null/boolean/number/string)。',\n '不要传入对象/数组/函数;需要时请在展示边界先格式化成字符串。',\n ])\n }\n\n if (typeof v === 'number' && !Number.isFinite(v)) {\n invalidToken('numberNotJsonSafe', { field: `params.${k}`, value: String(v) }, [\n '不要在 token params 中传入 NaN/Infinity。',\n '请先把该数值转换为可 JSON 化的 number 或 string。',\n ])\n }\n\n if (typeof v === 'string' && v.length > TOKEN_BUDGET.paramValueStringMaxLen) {\n invalidToken(\n 'paramValueTooLong',\n {\n field: `params.${k}`,\n maxLen: TOKEN_BUDGET.paramValueStringMaxLen,\n actualLen: v.length,\n },\n [\n `缩短字符串值长度(建议 ≤ ${TOKEN_BUDGET.paramValueStringMaxLen})。`,\n '把长文本留在展示边界,不要放进 semantic token。',\n ],\n )\n }\n }\n\n entries.sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0))\n\n const out: Record<string, JsonPrimitive> = {}\n for (const [k, v] of entries) {\n out[k] = v\n }\n return out\n}\n\nexport const token = (key: string, params?: I18nTokenParamsInput): I18nMessageToken => {\n if (key.length > TOKEN_BUDGET.keyMaxLen) {\n invalidToken('keyTooLong', { field: 'key', maxLen: TOKEN_BUDGET.keyMaxLen, actualLen: key.length }, [\n `缩短 key(建议 ≤ ${TOKEN_BUDGET.keyMaxLen})。`,\n '如果 key 过长,建议改为“稳定 key + 少量 semantic params”。',\n ])\n }\n\n const canon = canonicalizeTokenParams(params)\n return canon\n ? {\n _tag: 'i18n',\n key,\n params: canon,\n }\n : {\n _tag: 'i18n',\n key,\n }\n}\n"],"mappings":"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","names":[]}
|
package/dist/index.cjs
CHANGED
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __create = Object.create;
|
|
3
2
|
var __defProp = Object.defineProperty;
|
|
4
3
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
4
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
-
var __getProtoOf = Object.getPrototypeOf;
|
|
7
5
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
6
|
var __export = (target, all) => {
|
|
9
7
|
for (var name in all)
|
|
@@ -17,143 +15,19 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
17
15
|
}
|
|
18
16
|
return to;
|
|
19
17
|
};
|
|
20
|
-
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
-
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
-
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
-
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
-
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
-
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
-
mod
|
|
27
|
-
));
|
|
28
18
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
19
|
|
|
30
20
|
// src/index.ts
|
|
31
21
|
var index_exports = {};
|
|
32
22
|
__export(index_exports, {
|
|
33
23
|
I18n: () => I18n,
|
|
34
|
-
I18nModule: () => I18nModule,
|
|
35
|
-
I18nSnapshotSchema: () => I18nSnapshotSchema,
|
|
36
24
|
I18nTag: () => I18nTag,
|
|
37
|
-
InvalidI18nMessageTokenError: () => InvalidI18nMessageTokenError,
|
|
38
|
-
canonicalizeTokenOptions: () => canonicalizeTokenOptions,
|
|
39
25
|
token: () => token
|
|
40
26
|
});
|
|
41
27
|
module.exports = __toCommonJS(index_exports);
|
|
42
28
|
|
|
43
29
|
// src/internal/driver/i18n.ts
|
|
44
30
|
var import_effect = require("effect");
|
|
45
|
-
|
|
46
|
-
// src/internal/token/token.ts
|
|
47
|
-
var InvalidI18nMessageTokenError = class extends Error {
|
|
48
|
-
constructor(reason, details, fix) {
|
|
49
|
-
super(`[InvalidI18nMessageTokenError] reason=${reason}`);
|
|
50
|
-
this.reason = reason;
|
|
51
|
-
this.details = details;
|
|
52
|
-
this.fix = fix;
|
|
53
|
-
this.name = "InvalidI18nMessageTokenError";
|
|
54
|
-
}
|
|
55
|
-
};
|
|
56
|
-
var TOKEN_BUDGET = {
|
|
57
|
-
keyMaxLen: 96,
|
|
58
|
-
optionKeyMaxCount: 8,
|
|
59
|
-
optionValueStringMaxLen: 96
|
|
60
|
-
};
|
|
61
|
-
var LANGUAGE_FROZEN_KEYS = /* @__PURE__ */ new Set(["lng", "lngs"]);
|
|
62
|
-
var DANGEROUS_OPTION_KEYS = /* @__PURE__ */ new Set(["__proto__", "prototype", "constructor"]);
|
|
63
|
-
var isJsonPrimitive = (value) => value === null || typeof value === "boolean" || typeof value === "number" || typeof value === "string";
|
|
64
|
-
var invalidToken = (reason, details, fix) => {
|
|
65
|
-
throw new InvalidI18nMessageTokenError(reason, details, fix);
|
|
66
|
-
};
|
|
67
|
-
var canonicalizeTokenOptions = (options) => {
|
|
68
|
-
if (!options) return void 0;
|
|
69
|
-
if (typeof options !== "object" || options === null || Array.isArray(options)) {
|
|
70
|
-
invalidToken("optionValueInvalid", { field: "options", actual: typeof options }, [
|
|
71
|
-
"\u8BF7\u4F20\u5165 plain object \u4F5C\u4E3A options\uFF08Record<string, JsonPrimitive>\uFF09\u3002",
|
|
72
|
-
"\u4E0D\u8981\u4F20\u5165\u6570\u7EC4/\u51FD\u6570/\u7C7B\u5B9E\u4F8B\u7B49\u4E0D\u53EF\u5E8F\u5217\u5316\u503C\u3002"
|
|
73
|
-
]);
|
|
74
|
-
}
|
|
75
|
-
const entries = Object.entries(options).filter((p) => p[1] !== void 0);
|
|
76
|
-
if (entries.length === 0) return void 0;
|
|
77
|
-
if (entries.length > TOKEN_BUDGET.optionKeyMaxCount) {
|
|
78
|
-
invalidToken(
|
|
79
|
-
"tooManyOptions",
|
|
80
|
-
{
|
|
81
|
-
field: "options",
|
|
82
|
-
max: TOKEN_BUDGET.optionKeyMaxCount,
|
|
83
|
-
actual: entries.length
|
|
84
|
-
},
|
|
85
|
-
[
|
|
86
|
-
`\u51CF\u5C11 options \u952E\u6570\u91CF\uFF08\u5EFA\u8BAE \u2264 ${TOKEN_BUDGET.optionKeyMaxCount}\uFF09\u3002`,
|
|
87
|
-
"\u628A\u8F83\u957F\u7684\u4FE1\u606F\u632A\u5230 key \u6216 defaultValue\uFF1B\u907F\u514D\u628A\u5927\u5BF9\u8C61\u585E\u8FDB token\u3002"
|
|
88
|
-
]
|
|
89
|
-
);
|
|
90
|
-
}
|
|
91
|
-
for (const [k, v] of entries) {
|
|
92
|
-
if (DANGEROUS_OPTION_KEYS.has(k) || k.length === 0) {
|
|
93
|
-
invalidToken("optionKeyInvalid", { field: `options.${k}`, key: k }, [
|
|
94
|
-
"\u8BF7\u4F7F\u7528\u666E\u901A\u5B57\u6BB5\u540D\u4F5C\u4E3A options key\uFF08\u907F\u514D __proto__/constructor/prototype \u7B49\u5371\u9669\u952E\uFF09\u3002",
|
|
95
|
-
"\u5982\u9700\u4F20\u9012\u590D\u6742\u7ED3\u6784\uFF0C\u8BF7\u5148\u5728\u5C55\u793A\u8FB9\u754C\u8F6C\u6362\u4E3A\u5B57\u7B26\u4E32\u3002"
|
|
96
|
-
]);
|
|
97
|
-
}
|
|
98
|
-
if (LANGUAGE_FROZEN_KEYS.has(k)) {
|
|
99
|
-
invalidToken("languageFrozen", { field: `options.${k}`, key: k }, [
|
|
100
|
-
"\u4E0D\u8981\u5728 token options \u4E2D\u4F20\u5165 lng/lngs \u7B49\u8BED\u8A00\u51BB\u7ED3\u5B57\u6BB5\u3002",
|
|
101
|
-
"\u8BED\u8A00\u7531\u5916\u90E8 i18n \u5B9E\u4F8B\u51B3\u5B9A\uFF1Btoken \u53EA\u8868\u8FBE\u201C\u8981\u7FFB\u8BD1\u4EC0\u4E48\u201D\u3002"
|
|
102
|
-
]);
|
|
103
|
-
}
|
|
104
|
-
if (!isJsonPrimitive(v)) {
|
|
105
|
-
invalidToken("optionValueInvalid", { field: `options.${k}`, actual: typeof v }, [
|
|
106
|
-
"options value \u53EA\u5141\u8BB8 JsonPrimitive\uFF08null/boolean/number/string\uFF09\u3002",
|
|
107
|
-
"\u4E0D\u8981\u4F20\u5165\u5BF9\u8C61/\u6570\u7EC4/\u51FD\u6570\uFF1B\u9700\u8981\u65F6\u8BF7\u5728\u5C55\u793A\u8FB9\u754C\u5148\u683C\u5F0F\u5316\u6210\u5B57\u7B26\u4E32\u3002"
|
|
108
|
-
]);
|
|
109
|
-
}
|
|
110
|
-
if (typeof v === "number" && !Number.isFinite(v)) {
|
|
111
|
-
invalidToken("numberNotJsonSafe", { field: `options.${k}`, value: String(v) }, [
|
|
112
|
-
"\u4E0D\u8981\u5728 token options \u4E2D\u4F20\u5165 NaN/Infinity\u3002",
|
|
113
|
-
"\u8BF7\u5148\u628A\u8BE5\u6570\u503C\u8F6C\u6362\u4E3A\u53EF JSON \u5316\u7684 number \u6216 string\u3002"
|
|
114
|
-
]);
|
|
115
|
-
}
|
|
116
|
-
if (typeof v === "string" && v.length > TOKEN_BUDGET.optionValueStringMaxLen) {
|
|
117
|
-
invalidToken(
|
|
118
|
-
"optionValueTooLong",
|
|
119
|
-
{
|
|
120
|
-
field: `options.${k}`,
|
|
121
|
-
maxLen: TOKEN_BUDGET.optionValueStringMaxLen,
|
|
122
|
-
actualLen: v.length
|
|
123
|
-
},
|
|
124
|
-
[
|
|
125
|
-
`\u7F29\u77ED\u5B57\u7B26\u4E32\u503C\u957F\u5EA6\uFF08\u5EFA\u8BAE \u2264 ${TOKEN_BUDGET.optionValueStringMaxLen}\uFF09\u3002`,
|
|
126
|
-
"\u628A\u957F\u6587\u672C\u79FB\u5230 defaultValue \u6216\u76F4\u63A5\u5728\u5C55\u793A\u8FB9\u754C\u751F\u6210\u6700\u7EC8\u5B57\u7B26\u4E32\u3002"
|
|
127
|
-
]
|
|
128
|
-
);
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
entries.sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0);
|
|
132
|
-
const out = {};
|
|
133
|
-
for (const [k, v] of entries) {
|
|
134
|
-
out[k] = v;
|
|
135
|
-
}
|
|
136
|
-
return out;
|
|
137
|
-
};
|
|
138
|
-
var token = (key, options) => {
|
|
139
|
-
if (key.length > TOKEN_BUDGET.keyMaxLen) {
|
|
140
|
-
invalidToken("keyTooLong", { field: "key", maxLen: TOKEN_BUDGET.keyMaxLen, actualLen: key.length }, [
|
|
141
|
-
`\u7F29\u77ED key\uFF08\u5EFA\u8BAE \u2264 ${TOKEN_BUDGET.keyMaxLen}\uFF09\u3002`,
|
|
142
|
-
"\u5982\u679C key \u8FC7\u957F\uFF0C\u5EFA\u8BAE\u6539\u4E3A\u201C\u7A33\u5B9A key + \u53D8\u91CF options/defaultValue\u201D\u3002"
|
|
143
|
-
]);
|
|
144
|
-
}
|
|
145
|
-
const canon = canonicalizeTokenOptions(options);
|
|
146
|
-
return canon ? {
|
|
147
|
-
_tag: "i18n",
|
|
148
|
-
key,
|
|
149
|
-
options: canon
|
|
150
|
-
} : {
|
|
151
|
-
_tag: "i18n",
|
|
152
|
-
key
|
|
153
|
-
};
|
|
154
|
-
};
|
|
155
|
-
|
|
156
|
-
// src/internal/driver/i18n.ts
|
|
157
31
|
var I18nSnapshotSchema = import_effect.Schema.Struct({
|
|
158
32
|
language: import_effect.Schema.String,
|
|
159
33
|
init: import_effect.Schema.Literals(["pending", "ready", "failed"]),
|
|
@@ -223,22 +97,22 @@ var makeI18nService = (driver) => import_effect.Effect.gen(function* () {
|
|
|
223
97
|
yield* update({ init: "ready", language });
|
|
224
98
|
}
|
|
225
99
|
});
|
|
226
|
-
const fallback = (
|
|
227
|
-
const
|
|
100
|
+
const fallback = (message, hints) => hints?.fallback ?? message.key;
|
|
101
|
+
const render = (message, hints) => {
|
|
228
102
|
if (currentSnapshot.init !== "ready") {
|
|
229
|
-
return fallback(
|
|
103
|
+
return fallback(message, hints);
|
|
230
104
|
}
|
|
231
105
|
try {
|
|
232
|
-
return driver.t(key,
|
|
106
|
+
return driver.t(message.key, message.params);
|
|
233
107
|
} catch {
|
|
234
|
-
return fallback(
|
|
108
|
+
return fallback(message, hints);
|
|
235
109
|
}
|
|
236
110
|
};
|
|
237
|
-
const
|
|
111
|
+
const renderReady = (message, hints, timeoutMs) => import_effect.Effect.gen(function* () {
|
|
238
112
|
const cap = timeoutMs ?? 5e3;
|
|
239
113
|
const snap0 = yield* import_effect.SubscriptionRef.get(snapshotRef);
|
|
240
|
-
if (snap0.init === "ready") return
|
|
241
|
-
if (snap0.init === "failed") return fallback(
|
|
114
|
+
if (snap0.init === "ready") return render(message, hints);
|
|
115
|
+
if (snap0.init === "failed") return fallback(message, hints);
|
|
242
116
|
const wait = import_effect.Stream.filter(import_effect.SubscriptionRef.changes(snapshotRef), (s) => s.init !== "pending").pipe(
|
|
243
117
|
import_effect.Stream.runHead,
|
|
244
118
|
import_effect.Effect.timeoutOption(import_effect.Duration.millis(cap)),
|
|
@@ -248,70 +122,146 @@ var makeI18nService = (driver) => import_effect.Effect.gen(function* () {
|
|
|
248
122
|
const snap1 = yield* import_effect.SubscriptionRef.get(snapshotRef);
|
|
249
123
|
if (snap1.init !== "pending") {
|
|
250
124
|
yield* import_effect.Fiber.interrupt(fiber);
|
|
251
|
-
return snap1.init === "ready" ?
|
|
125
|
+
return snap1.init === "ready" ? render(message, hints) : fallback(message, hints);
|
|
252
126
|
}
|
|
253
127
|
const outcome = yield* import_effect.Fiber.join(fiber);
|
|
254
128
|
return import_effect.Option.match(outcome, {
|
|
255
|
-
onNone: () => fallback(
|
|
256
|
-
onSome: (snap) => snap.init === "ready" ?
|
|
129
|
+
onNone: () => fallback(message, hints),
|
|
130
|
+
onSome: (snap) => snap.init === "ready" ? render(message, hints) : fallback(message, hints)
|
|
257
131
|
});
|
|
258
132
|
});
|
|
259
133
|
return {
|
|
260
|
-
instance: driver,
|
|
261
134
|
snapshot: snapshotRef,
|
|
262
|
-
token,
|
|
263
135
|
changeLanguage,
|
|
264
|
-
|
|
265
|
-
|
|
136
|
+
render,
|
|
137
|
+
renderReady
|
|
266
138
|
};
|
|
267
139
|
});
|
|
268
140
|
var I18n = {
|
|
269
141
|
layer: (driver) => import_effect.Layer.effect(I18nTag, makeI18nService(driver))
|
|
270
142
|
};
|
|
271
143
|
|
|
272
|
-
// src/internal/
|
|
273
|
-
var
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
144
|
+
// src/internal/token/token.ts
|
|
145
|
+
var InvalidI18nMessageTokenError = class extends Error {
|
|
146
|
+
constructor(reason, details, fix) {
|
|
147
|
+
super(`[InvalidI18nMessageTokenError] reason=${reason}`);
|
|
148
|
+
this.reason = reason;
|
|
149
|
+
this.details = details;
|
|
150
|
+
this.fix = fix;
|
|
151
|
+
this.name = "InvalidI18nMessageTokenError";
|
|
279
152
|
}
|
|
280
|
-
}
|
|
281
|
-
var
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
153
|
+
};
|
|
154
|
+
var TOKEN_BUDGET = {
|
|
155
|
+
keyMaxLen: 96,
|
|
156
|
+
paramKeyMaxCount: 8,
|
|
157
|
+
paramValueStringMaxLen: 96
|
|
158
|
+
};
|
|
159
|
+
var LANGUAGE_FROZEN_KEYS = /* @__PURE__ */ new Set(["lng", "lngs"]);
|
|
160
|
+
var DANGEROUS_PARAM_KEYS = /* @__PURE__ */ new Set(["__proto__", "prototype", "constructor"]);
|
|
161
|
+
var LEGACY_RENDER_FALLBACK_KEY = `default${"Value"}`;
|
|
162
|
+
var RESERVED_RENDER_FALLBACK_KEYS = /* @__PURE__ */ new Set([LEGACY_RENDER_FALLBACK_KEY]);
|
|
163
|
+
var isJsonPrimitive = (value) => value === null || typeof value === "boolean" || typeof value === "number" || typeof value === "string";
|
|
164
|
+
var invalidToken = (reason, details, fix) => {
|
|
165
|
+
throw new InvalidI18nMessageTokenError(reason, details, fix);
|
|
166
|
+
};
|
|
167
|
+
var canonicalizeTokenParams = (params) => {
|
|
168
|
+
if (!params) return void 0;
|
|
169
|
+
if (typeof params !== "object" || params === null || Array.isArray(params)) {
|
|
170
|
+
invalidToken("paramValueInvalid", { field: "params", actual: typeof params }, [
|
|
171
|
+
"\u8BF7\u4F20\u5165 plain object \u4F5C\u4E3A params\uFF08Record<string, JsonPrimitive>\uFF09\u3002",
|
|
172
|
+
"\u4E0D\u8981\u4F20\u5165\u6570\u7EC4/\u51FD\u6570/\u7C7B\u5B9E\u4F8B\u7B49\u4E0D\u53EF\u5E8F\u5217\u5316\u503C\u3002"
|
|
173
|
+
]);
|
|
174
|
+
}
|
|
175
|
+
const entries = Object.entries(params).filter((p) => p[1] !== void 0);
|
|
176
|
+
if (entries.length === 0) return void 0;
|
|
177
|
+
if (entries.length > TOKEN_BUDGET.paramKeyMaxCount) {
|
|
178
|
+
invalidToken(
|
|
179
|
+
"tooManyParams",
|
|
180
|
+
{
|
|
181
|
+
field: "params",
|
|
182
|
+
max: TOKEN_BUDGET.paramKeyMaxCount,
|
|
183
|
+
actual: entries.length
|
|
184
|
+
},
|
|
185
|
+
[
|
|
186
|
+
`\u51CF\u5C11 params \u952E\u6570\u91CF\uFF08\u5EFA\u8BAE \u2264 ${TOKEN_BUDGET.paramKeyMaxCount}\uFF09\u3002`,
|
|
187
|
+
"\u907F\u514D\u628A\u5927\u5BF9\u8C61\u6216\u5C55\u793A\u515C\u5E95\u6587\u6848\u585E\u8FDB semantic token\u3002"
|
|
188
|
+
]
|
|
299
189
|
);
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
190
|
+
}
|
|
191
|
+
for (const [k, v] of entries) {
|
|
192
|
+
if (RESERVED_RENDER_FALLBACK_KEYS.has(k)) {
|
|
193
|
+
invalidToken("renderFallbackReserved", { field: `params.${k}`, key: k }, [
|
|
194
|
+
"\u4E0D\u8981\u628A\u65E7\u7684\u5C55\u793A\u515C\u5E95\u5B57\u6BB5\u653E\u8FDB semantic token params\u3002",
|
|
195
|
+
"\u8BF7\u628A\u515C\u5E95\u6587\u6848\u6539\u653E\u5230 render/renderReady \u7684 hints.fallback\u3002"
|
|
196
|
+
]);
|
|
197
|
+
}
|
|
198
|
+
if (DANGEROUS_PARAM_KEYS.has(k) || k.length === 0) {
|
|
199
|
+
invalidToken("paramKeyInvalid", { field: `params.${k}`, key: k }, [
|
|
200
|
+
"\u8BF7\u4F7F\u7528\u666E\u901A\u5B57\u6BB5\u540D\u4F5C\u4E3A params key\uFF0C\u907F\u514D __proto__/constructor/prototype \u7B49\u5371\u9669\u952E\u3002",
|
|
201
|
+
"\u5982\u9700\u4F20\u9012\u590D\u6742\u7ED3\u6784\uFF0C\u8BF7\u5148\u5728\u5C55\u793A\u8FB9\u754C\u8F6C\u6362\u4E3A\u5B57\u7B26\u4E32\u3002"
|
|
202
|
+
]);
|
|
203
|
+
}
|
|
204
|
+
if (LANGUAGE_FROZEN_KEYS.has(k)) {
|
|
205
|
+
invalidToken("languageFrozen", { field: `params.${k}`, key: k }, [
|
|
206
|
+
"\u4E0D\u8981\u5728 token params \u4E2D\u4F20\u5165 lng/lngs \u7B49\u8BED\u8A00\u51BB\u7ED3\u5B57\u6BB5\u3002",
|
|
207
|
+
"\u8BED\u8A00\u7531\u5916\u90E8 i18n \u5B9E\u4F8B\u51B3\u5B9A\uFF1Btoken \u53EA\u8868\u8FBE\u201C\u8981\u7FFB\u8BD1\u4EC0\u4E48\u201D\u3002"
|
|
208
|
+
]);
|
|
209
|
+
}
|
|
210
|
+
if (!isJsonPrimitive(v)) {
|
|
211
|
+
invalidToken("paramValueInvalid", { field: `params.${k}`, actual: typeof v }, [
|
|
212
|
+
"params value \u53EA\u5141\u8BB8 JsonPrimitive\uFF08null/boolean/number/string\uFF09\u3002",
|
|
213
|
+
"\u4E0D\u8981\u4F20\u5165\u5BF9\u8C61/\u6570\u7EC4/\u51FD\u6570\uFF1B\u9700\u8981\u65F6\u8BF7\u5728\u5C55\u793A\u8FB9\u754C\u5148\u683C\u5F0F\u5316\u6210\u5B57\u7B26\u4E32\u3002"
|
|
214
|
+
]);
|
|
215
|
+
}
|
|
216
|
+
if (typeof v === "number" && !Number.isFinite(v)) {
|
|
217
|
+
invalidToken("numberNotJsonSafe", { field: `params.${k}`, value: String(v) }, [
|
|
218
|
+
"\u4E0D\u8981\u5728 token params \u4E2D\u4F20\u5165 NaN/Infinity\u3002",
|
|
219
|
+
"\u8BF7\u5148\u628A\u8BE5\u6570\u503C\u8F6C\u6362\u4E3A\u53EF JSON \u5316\u7684 number \u6216 string\u3002"
|
|
220
|
+
]);
|
|
221
|
+
}
|
|
222
|
+
if (typeof v === "string" && v.length > TOKEN_BUDGET.paramValueStringMaxLen) {
|
|
223
|
+
invalidToken(
|
|
224
|
+
"paramValueTooLong",
|
|
225
|
+
{
|
|
226
|
+
field: `params.${k}`,
|
|
227
|
+
maxLen: TOKEN_BUDGET.paramValueStringMaxLen,
|
|
228
|
+
actualLen: v.length
|
|
229
|
+
},
|
|
230
|
+
[
|
|
231
|
+
`\u7F29\u77ED\u5B57\u7B26\u4E32\u503C\u957F\u5EA6\uFF08\u5EFA\u8BAE \u2264 ${TOKEN_BUDGET.paramValueStringMaxLen}\uFF09\u3002`,
|
|
232
|
+
"\u628A\u957F\u6587\u672C\u7559\u5728\u5C55\u793A\u8FB9\u754C\uFF0C\u4E0D\u8981\u653E\u8FDB semantic token\u3002"
|
|
233
|
+
]
|
|
234
|
+
);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
entries.sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0);
|
|
238
|
+
const out = {};
|
|
239
|
+
for (const [k, v] of entries) {
|
|
240
|
+
out[k] = v;
|
|
241
|
+
}
|
|
242
|
+
return out;
|
|
243
|
+
};
|
|
244
|
+
var token = (key, params) => {
|
|
245
|
+
if (key.length > TOKEN_BUDGET.keyMaxLen) {
|
|
246
|
+
invalidToken("keyTooLong", { field: "key", maxLen: TOKEN_BUDGET.keyMaxLen, actualLen: key.length }, [
|
|
247
|
+
`\u7F29\u77ED key\uFF08\u5EFA\u8BAE \u2264 ${TOKEN_BUDGET.keyMaxLen}\uFF09\u3002`,
|
|
248
|
+
"\u5982\u679C key \u8FC7\u957F\uFF0C\u5EFA\u8BAE\u6539\u4E3A\u201C\u7A33\u5B9A key + \u5C11\u91CF semantic params\u201D\u3002"
|
|
249
|
+
]);
|
|
250
|
+
}
|
|
251
|
+
const canon = canonicalizeTokenParams(params);
|
|
252
|
+
return canon ? {
|
|
253
|
+
_tag: "i18n",
|
|
254
|
+
key,
|
|
255
|
+
params: canon
|
|
256
|
+
} : {
|
|
257
|
+
_tag: "i18n",
|
|
258
|
+
key
|
|
259
|
+
};
|
|
260
|
+
};
|
|
307
261
|
// Annotate the CommonJS export names for ESM import in node:
|
|
308
262
|
0 && (module.exports = {
|
|
309
263
|
I18n,
|
|
310
|
-
I18nModule,
|
|
311
|
-
I18nSnapshotSchema,
|
|
312
264
|
I18nTag,
|
|
313
|
-
InvalidI18nMessageTokenError,
|
|
314
|
-
canonicalizeTokenOptions,
|
|
315
265
|
token
|
|
316
266
|
});
|
|
317
267
|
//# sourceMappingURL=index.cjs.map
|