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/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/utils/resolve-candidate-locales.ts
25
- var resolveCandidateLocales = (locale, fallbackLocalesMap) => {
26
- const fallbacks = fallbackLocalesMap?.[locale] || [];
27
- const filteredFallbacks = fallbacks.filter((l) => l !== locale);
28
- return [locale, ...filteredFallbacks];
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/translator-methods/has-key/has-key.ts
32
- var hasKey = ({
33
- messagesRef,
34
- localeRef,
35
- key,
36
- targetLocale
37
- }) => {
38
- const messages = messagesRef.current;
39
- const locale = localeRef.current;
40
- const candidateLocales = resolveCandidateLocales(targetLocale || locale);
41
- const message = findMessageInLocales({ messages, candidateLocales, key });
42
- return !!message;
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/translator-methods/translate/translate.ts
65
- var translate = ({
66
- messagesRef,
67
- localeRef,
68
- isLoadingRef,
69
- translateConfig,
70
- key,
71
- replacements
72
- }) => {
73
- const messages = messagesRef.current;
74
- const locale = localeRef.current;
75
- const isLoading = isLoadingRef.current;
76
- const { fallbackLocales, loadingMessage, placeholder, handlers } = translateConfig;
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
- messagesRef;
176
+ _messages;
101
177
  /** Current active locale */
102
- localeRef;
178
+ _locale;
103
179
  /** Current loading state */
104
- isLoadingRef;
180
+ _isLoading;
105
181
  constructor(options) {
106
- this.messagesRef = { current: options.messages ?? {} };
107
- this.localeRef = { current: options.locale };
108
- this.isLoadingRef = { current: options.isLoading ?? false };
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.messagesRef.current;
188
+ return this._messages;
113
189
  }
114
190
  /** Get the current active locale. */
115
191
  get locale() {
116
- return this.localeRef.current;
192
+ return this._locale;
117
193
  }
118
194
  /** Get the current loading state. */
119
195
  get isLoading() {
120
- return this.isLoadingRef.current;
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.messagesRef.current = messages;
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.localeRef.current = newLocale;
213
+ this._locale = newLocale;
138
214
  }
139
215
  /** Set the loading state. */
140
216
  setLoading(state) {
141
- this.isLoadingRef.current = state;
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
- super({
150
- locale: options.locale,
151
- messages: options.messages,
152
- isLoading: options.isLoading
153
- });
154
- this.options = options;
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
- messagesRef: this.messagesRef,
160
- localeRef: this.localeRef,
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
- messagesRef: this.messagesRef,
169
- localeRef: this.localeRef,
170
- isLoadingRef: this.isLoadingRef,
171
- translateConfig: this.options,
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
- messagesRef: this.messagesRef,
197
- localeRef: this.localeRef,
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
- messagesRef: this.messagesRef,
206
- localeRef: this.localeRef,
207
- isLoadingRef: this.isLoadingRef,
208
- translateConfig: this.options,
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, ScopeTranslator as Translator };
350
+ export { ScopeTranslator as Translator };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "intor-translator",
3
- "version": "1.1.5",
4
- "description": "A type safe translator that knows what to say and how to handle the rest. Supports custom messages, rich replacements, and async loading.",
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
- "type safety",
31
- "type-safe translator",
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
  }