intor-translator 1.1.5 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +32 -142
- package/dist/index.cjs +215 -83
- package/dist/index.d.cts +110 -81
- package/dist/index.d.ts +110 -81
- package/dist/index.js +216 -83
- package/package.json +11 -21
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// src/utils/find-message-in-locales.ts
|
|
1
|
+
// src/translators/shared/utils/find-message-in-locales.ts
|
|
2
2
|
var findMessageInLocales = ({
|
|
3
3
|
messages,
|
|
4
4
|
candidateLocales,
|
|
@@ -21,28 +21,50 @@ var findMessageInLocales = ({
|
|
|
21
21
|
}
|
|
22
22
|
};
|
|
23
23
|
|
|
24
|
-
// src/
|
|
25
|
-
var
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
24
|
+
// src/pipeline/hooks/find-message.hook.ts
|
|
25
|
+
var findMessageHook = {
|
|
26
|
+
name: "findMessage",
|
|
27
|
+
order: 200,
|
|
28
|
+
run(ctx) {
|
|
29
|
+
ctx.rawMessage = findMessageInLocales({
|
|
30
|
+
messages: ctx.messages,
|
|
31
|
+
candidateLocales: ctx.candidateLocales,
|
|
32
|
+
key: ctx.key
|
|
33
|
+
});
|
|
34
|
+
}
|
|
29
35
|
};
|
|
30
36
|
|
|
31
|
-
// src/
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
37
|
+
// src/pipeline/utils/make-handler-context.ts
|
|
38
|
+
function makeHandlerContext(ctx) {
|
|
39
|
+
return Object.freeze({
|
|
40
|
+
locale: ctx.locale,
|
|
41
|
+
key: ctx.key,
|
|
42
|
+
replacements: ctx.replacements,
|
|
43
|
+
messages: ctx.messages,
|
|
44
|
+
candidateLocales: ctx.candidateLocales,
|
|
45
|
+
config: ctx.config,
|
|
46
|
+
isLoading: ctx.isLoading,
|
|
47
|
+
rawMessage: ctx.rawMessage,
|
|
48
|
+
formattedMessage: ctx.formattedMessage,
|
|
49
|
+
meta: ctx.meta
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// src/pipeline/hooks/format.hook.ts
|
|
54
|
+
var formatHook = {
|
|
55
|
+
name: "format",
|
|
56
|
+
order: 500,
|
|
57
|
+
run(ctx) {
|
|
58
|
+
const { config, rawMessage } = ctx;
|
|
59
|
+
const { formatHandler } = config.handlers || {};
|
|
60
|
+
if (!formatHandler || rawMessage === void 0) return;
|
|
61
|
+
ctx.formattedMessage = formatHandler(
|
|
62
|
+
makeHandlerContext(ctx)
|
|
63
|
+
);
|
|
64
|
+
}
|
|
43
65
|
};
|
|
44
66
|
|
|
45
|
-
// src/utils/replace-values.ts
|
|
67
|
+
// src/translators/shared/utils/replace-values.ts
|
|
46
68
|
var replaceValues = (message, params) => {
|
|
47
69
|
if (!params || typeof params !== "object" || Object.keys(params).length === 0) {
|
|
48
70
|
return message;
|
|
@@ -61,63 +83,117 @@ var replaceValues = (message, params) => {
|
|
|
61
83
|
return replaced;
|
|
62
84
|
};
|
|
63
85
|
|
|
64
|
-
// src/
|
|
65
|
-
var
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
const { formatHandler, loadingHandler, missingHandler } = handlers || {};
|
|
78
|
-
const candidateLocales = resolveCandidateLocales(locale, fallbackLocales);
|
|
79
|
-
const message = findMessageInLocales({ messages, candidateLocales, key });
|
|
80
|
-
if (isLoading && (loadingHandler || loadingMessage)) {
|
|
81
|
-
if (loadingHandler)
|
|
82
|
-
return loadingHandler({ key, locale, replacements });
|
|
83
|
-
if (loadingMessage) return loadingMessage;
|
|
84
|
-
}
|
|
85
|
-
if (message === void 0) {
|
|
86
|
-
if (missingHandler)
|
|
87
|
-
return missingHandler({ key, locale, replacements });
|
|
88
|
-
if (placeholder) return placeholder;
|
|
89
|
-
return key;
|
|
90
|
-
}
|
|
91
|
-
if (formatHandler) {
|
|
92
|
-
return formatHandler({ message, key, locale, replacements });
|
|
93
|
-
}
|
|
94
|
-
return replacements ? replaceValues(message, replacements) : message;
|
|
86
|
+
// src/pipeline/hooks/interpolate.hook.ts
|
|
87
|
+
var interpolateHook = {
|
|
88
|
+
name: "interpolate",
|
|
89
|
+
order: 600,
|
|
90
|
+
run(ctx) {
|
|
91
|
+
const { rawMessage, formattedMessage, replacements } = ctx;
|
|
92
|
+
const message = formattedMessage ?? rawMessage;
|
|
93
|
+
if (typeof message !== "string" || !replacements) {
|
|
94
|
+
ctx.finalMessage = message;
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
ctx.finalMessage = replaceValues(message, replacements);
|
|
98
|
+
}
|
|
95
99
|
};
|
|
96
100
|
|
|
101
|
+
// src/pipeline/hooks/loading.hook.ts
|
|
102
|
+
var loadingHook = {
|
|
103
|
+
name: "loading",
|
|
104
|
+
order: 300,
|
|
105
|
+
run(ctx) {
|
|
106
|
+
const { config, isLoading } = ctx;
|
|
107
|
+
if (!isLoading) return;
|
|
108
|
+
const { loadingHandler } = config.handlers || {};
|
|
109
|
+
if (loadingHandler) {
|
|
110
|
+
return {
|
|
111
|
+
done: true,
|
|
112
|
+
value: loadingHandler(makeHandlerContext(ctx))
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
const { loadingMessage } = config;
|
|
116
|
+
if (loadingMessage) {
|
|
117
|
+
return { done: true, value: loadingMessage };
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
// src/pipeline/hooks/missing.hook.ts
|
|
123
|
+
var missingHook = {
|
|
124
|
+
name: "missing",
|
|
125
|
+
order: 400,
|
|
126
|
+
run(ctx) {
|
|
127
|
+
const { config, key, rawMessage } = ctx;
|
|
128
|
+
if (rawMessage !== void 0) return;
|
|
129
|
+
const { missingHandler } = config.handlers || {};
|
|
130
|
+
if (missingHandler) {
|
|
131
|
+
return {
|
|
132
|
+
done: true,
|
|
133
|
+
value: missingHandler(makeHandlerContext(ctx))
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
const { placeholder } = config;
|
|
137
|
+
if (placeholder) {
|
|
138
|
+
return { done: true, value: placeholder };
|
|
139
|
+
}
|
|
140
|
+
return { done: true, value: key };
|
|
141
|
+
}
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
// src/translators/shared/utils/resolve-candidate-locales.ts
|
|
145
|
+
var resolveCandidateLocales = (locale, fallbackLocalesMap) => {
|
|
146
|
+
const fallbacks = fallbackLocalesMap?.[locale] || [];
|
|
147
|
+
const filteredFallbacks = fallbacks.filter((l) => l !== locale);
|
|
148
|
+
return [locale, ...filteredFallbacks];
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
// src/pipeline/hooks/resolve-locales.hook.ts
|
|
152
|
+
var resolveLocalesHook = {
|
|
153
|
+
name: "resolveLocales",
|
|
154
|
+
order: 100,
|
|
155
|
+
run(ctx) {
|
|
156
|
+
ctx.candidateLocales = resolveCandidateLocales(
|
|
157
|
+
ctx.locale,
|
|
158
|
+
ctx.config.fallbackLocales
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
// src/pipeline/hooks/index.ts
|
|
164
|
+
var DEFAULT_HOOKS = [
|
|
165
|
+
resolveLocalesHook,
|
|
166
|
+
findMessageHook,
|
|
167
|
+
loadingHook,
|
|
168
|
+
missingHook,
|
|
169
|
+
formatHook,
|
|
170
|
+
interpolateHook
|
|
171
|
+
];
|
|
172
|
+
|
|
97
173
|
// src/translators/base-translator/base-translator.ts
|
|
98
174
|
var BaseTranslator = class {
|
|
99
175
|
/** Current messages for translation */
|
|
100
|
-
|
|
176
|
+
_messages;
|
|
101
177
|
/** Current active locale */
|
|
102
|
-
|
|
178
|
+
_locale;
|
|
103
179
|
/** Current loading state */
|
|
104
|
-
|
|
180
|
+
_isLoading;
|
|
105
181
|
constructor(options) {
|
|
106
|
-
this.
|
|
107
|
-
this.
|
|
108
|
-
this.
|
|
182
|
+
this._messages = options.messages ?? {};
|
|
183
|
+
this._locale = options.locale;
|
|
184
|
+
this._isLoading = options.isLoading ?? false;
|
|
109
185
|
}
|
|
110
186
|
/** Get messages. */
|
|
111
187
|
get messages() {
|
|
112
|
-
return this.
|
|
188
|
+
return this._messages;
|
|
113
189
|
}
|
|
114
190
|
/** Get the current active locale. */
|
|
115
191
|
get locale() {
|
|
116
|
-
return this.
|
|
192
|
+
return this._locale;
|
|
117
193
|
}
|
|
118
194
|
/** Get the current loading state. */
|
|
119
195
|
get isLoading() {
|
|
120
|
-
return this.
|
|
196
|
+
return this._isLoading;
|
|
121
197
|
}
|
|
122
198
|
/**
|
|
123
199
|
* Replace messages with new ones.
|
|
@@ -126,7 +202,7 @@ var BaseTranslator = class {
|
|
|
126
202
|
* The type cast bypasses TypeScript restrictions on dynamic messages.
|
|
127
203
|
*/
|
|
128
204
|
setMessages(messages) {
|
|
129
|
-
this.
|
|
205
|
+
this._messages = messages;
|
|
130
206
|
}
|
|
131
207
|
/**
|
|
132
208
|
* Set the active locale.
|
|
@@ -134,30 +210,85 @@ var BaseTranslator = class {
|
|
|
134
210
|
* - Note: Unlike `setMessages`, the locale structure cannot be changed at runtime.
|
|
135
211
|
*/
|
|
136
212
|
setLocale(newLocale) {
|
|
137
|
-
this.
|
|
213
|
+
this._locale = newLocale;
|
|
138
214
|
}
|
|
139
215
|
/** Set the loading state. */
|
|
140
216
|
setLoading(state) {
|
|
141
|
-
this.
|
|
217
|
+
this._isLoading = state;
|
|
142
218
|
}
|
|
143
219
|
};
|
|
144
220
|
|
|
221
|
+
// src/translators/shared/has-key.ts
|
|
222
|
+
var hasKey = ({
|
|
223
|
+
messages,
|
|
224
|
+
locale,
|
|
225
|
+
key,
|
|
226
|
+
targetLocale
|
|
227
|
+
}) => {
|
|
228
|
+
const candidateLocales = resolveCandidateLocales(targetLocale || locale);
|
|
229
|
+
const message = findMessageInLocales({
|
|
230
|
+
messages,
|
|
231
|
+
candidateLocales,
|
|
232
|
+
key
|
|
233
|
+
});
|
|
234
|
+
return !!message;
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
// src/pipeline/run-pipeline.ts
|
|
238
|
+
function runPipeline(ctx, hooks) {
|
|
239
|
+
for (const hook of hooks) {
|
|
240
|
+
const result = hook.run(ctx);
|
|
241
|
+
if (result?.done) {
|
|
242
|
+
return result.value;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
return ctx.finalMessage ?? ctx.rawMessage;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// src/translators/shared/translate.ts
|
|
249
|
+
function translate(options) {
|
|
250
|
+
const ctx = {
|
|
251
|
+
...options,
|
|
252
|
+
config: options.translateConfig,
|
|
253
|
+
candidateLocales: [],
|
|
254
|
+
meta: {}
|
|
255
|
+
};
|
|
256
|
+
return runPipeline(ctx, options.hooks);
|
|
257
|
+
}
|
|
258
|
+
|
|
145
259
|
// src/translators/core-translator/core-translator.ts
|
|
146
260
|
var CoreTranslator = class extends BaseTranslator {
|
|
147
|
-
options
|
|
261
|
+
/** User-provided options including messages, locale, and config. */
|
|
262
|
+
translateConfig;
|
|
263
|
+
/** Active pipeline hooks applied during translation. */
|
|
264
|
+
hooks = [...DEFAULT_HOOKS];
|
|
148
265
|
constructor(options) {
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
266
|
+
const { locale, messages, isLoading, plugins, ...translateConfig } = options;
|
|
267
|
+
super({ locale, messages, isLoading });
|
|
268
|
+
this.translateConfig = translateConfig;
|
|
269
|
+
if (plugins) {
|
|
270
|
+
for (const plugin of plugins) this.use(plugin);
|
|
271
|
+
}
|
|
272
|
+
this.sortHooks();
|
|
273
|
+
}
|
|
274
|
+
/** Sort hooks by order value (lower runs earlier). */
|
|
275
|
+
sortHooks() {
|
|
276
|
+
this.hooks.sort((a, b) => (a.order ?? 0) - (b.order ?? 0));
|
|
277
|
+
}
|
|
278
|
+
/** Register a plugin or a raw pipeline hook. */
|
|
279
|
+
use(plugin) {
|
|
280
|
+
if ("run" in plugin) this.hooks.push(plugin);
|
|
281
|
+
else if ("hook" in plugin && plugin.hook) {
|
|
282
|
+
const hooks = Array.isArray(plugin.hook) ? plugin.hook : [plugin.hook];
|
|
283
|
+
this.hooks.push(...hooks);
|
|
284
|
+
}
|
|
285
|
+
this.sortHooks();
|
|
155
286
|
}
|
|
156
287
|
/** Check if a key exists in the specified locale or current locale. */
|
|
157
288
|
hasKey = (key, targetLocale) => {
|
|
158
289
|
return hasKey({
|
|
159
|
-
|
|
160
|
-
|
|
290
|
+
messages: this._messages,
|
|
291
|
+
locale: this._locale,
|
|
161
292
|
key,
|
|
162
293
|
targetLocale
|
|
163
294
|
});
|
|
@@ -165,17 +296,18 @@ var CoreTranslator = class extends BaseTranslator {
|
|
|
165
296
|
/** Get the translated message for a key, with optional replacements. */
|
|
166
297
|
t = (key, replacements) => {
|
|
167
298
|
return translate({
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
299
|
+
hooks: this.hooks,
|
|
300
|
+
messages: this._messages,
|
|
301
|
+
locale: this._locale,
|
|
302
|
+
isLoading: this._isLoading,
|
|
303
|
+
translateConfig: this.translateConfig,
|
|
172
304
|
key,
|
|
173
305
|
replacements
|
|
174
306
|
});
|
|
175
307
|
};
|
|
176
308
|
};
|
|
177
309
|
|
|
178
|
-
// src/utils/get-full-key.ts
|
|
310
|
+
// src/translators/scope-translator/utils/get-full-key.ts
|
|
179
311
|
var getFullKey = (preKey = "", key = "") => {
|
|
180
312
|
if (!preKey) return key;
|
|
181
313
|
if (!key) return preKey;
|
|
@@ -193,8 +325,8 @@ var ScopeTranslator = class extends CoreTranslator {
|
|
|
193
325
|
hasKey: (key, targetLocale) => {
|
|
194
326
|
const fullKey = getFullKey(preKey, key);
|
|
195
327
|
return hasKey({
|
|
196
|
-
|
|
197
|
-
|
|
328
|
+
messages: this._messages,
|
|
329
|
+
locale: this._locale,
|
|
198
330
|
key: fullKey,
|
|
199
331
|
targetLocale
|
|
200
332
|
});
|
|
@@ -202,10 +334,11 @@ var ScopeTranslator = class extends CoreTranslator {
|
|
|
202
334
|
t: (key, replacements) => {
|
|
203
335
|
const fullKey = getFullKey(preKey, key);
|
|
204
336
|
return translate({
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
337
|
+
hooks: this.hooks,
|
|
338
|
+
messages: this._messages,
|
|
339
|
+
locale: this._locale,
|
|
340
|
+
isLoading: this._isLoading,
|
|
341
|
+
translateConfig: this.translateConfig,
|
|
209
342
|
key: fullKey,
|
|
210
343
|
replacements
|
|
211
344
|
});
|
|
@@ -214,4 +347,4 @@ var ScopeTranslator = class extends CoreTranslator {
|
|
|
214
347
|
}
|
|
215
348
|
};
|
|
216
349
|
|
|
217
|
-
export { ScopeTranslator
|
|
350
|
+
export { ScopeTranslator as Translator };
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "intor-translator",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "A type
|
|
3
|
+
"version": "1.2.0",
|
|
4
|
+
"description": "🤖 A modern, type-safe i18n engine.",
|
|
5
5
|
"author": "Yiming Liao",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"homepage": "https://github.com/yiming-liao/intor-translator#readme",
|
|
@@ -17,21 +17,9 @@
|
|
|
17
17
|
"internationalization",
|
|
18
18
|
"translation",
|
|
19
19
|
"typescript",
|
|
20
|
-
"node",
|
|
21
|
-
"nextjs",
|
|
22
|
-
"react",
|
|
23
|
-
"translator",
|
|
24
|
-
"i18n core",
|
|
25
|
-
"custom messages",
|
|
26
|
-
"rich replacements",
|
|
27
|
-
"async loading",
|
|
28
|
-
"type safe",
|
|
29
20
|
"type-safe",
|
|
30
|
-
"
|
|
31
|
-
"
|
|
32
|
-
"type-safe i18n",
|
|
33
|
-
"type-safe translation",
|
|
34
|
-
"type-safe core"
|
|
21
|
+
"i18n engine",
|
|
22
|
+
"translator"
|
|
35
23
|
],
|
|
36
24
|
"exports": {
|
|
37
25
|
".": {
|
|
@@ -56,7 +44,12 @@
|
|
|
56
44
|
"type": "tsc --noEmit",
|
|
57
45
|
"lint": "eslint",
|
|
58
46
|
"lint:debug": "eslint --debug",
|
|
59
|
-
"knip": "knip --config .config/knip.config.ts"
|
|
47
|
+
"knip": "knip --config .config/knip.config.ts",
|
|
48
|
+
"examples:html": "vite --config examples/html/vite.config.ts"
|
|
49
|
+
},
|
|
50
|
+
"sideEffects": false,
|
|
51
|
+
"engines": {
|
|
52
|
+
"node": ">=16.0.0"
|
|
60
53
|
},
|
|
61
54
|
"dependencies": {},
|
|
62
55
|
"devDependencies": {
|
|
@@ -77,10 +70,7 @@
|
|
|
77
70
|
"tsup": "^8.4.0",
|
|
78
71
|
"typescript": "^5.8.3",
|
|
79
72
|
"typescript-eslint": "^8.46.4",
|
|
73
|
+
"vite": "^7.2.6",
|
|
80
74
|
"vitest": "^4.0.9"
|
|
81
|
-
},
|
|
82
|
-
"sideEffects": false,
|
|
83
|
-
"engines": {
|
|
84
|
-
"node": ">=16.0.0"
|
|
85
75
|
}
|
|
86
76
|
}
|