intor-translator 1.1.5 → 1.2.1

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,8 @@
1
- // src/utils/find-message-in-locales.ts
1
+ import { rura } from 'rura';
2
+
3
+ // src/pipeline/hooks/find-message.ts
4
+
5
+ // src/translators/shared/utils/find-message-in-locales.ts
2
6
  var findMessageInLocales = ({
3
7
  messages,
4
8
  candidateLocales,
@@ -21,28 +25,50 @@ var findMessageInLocales = ({
21
25
  }
22
26
  };
23
27
 
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];
29
- };
28
+ // src/pipeline/hooks/find-message.ts
29
+ var findMessage = rura.createHook(
30
+ "findMessage",
31
+ (ctx) => {
32
+ ctx.rawMessage = findMessageInLocales({
33
+ messages: ctx.messages,
34
+ candidateLocales: ctx.candidateLocales,
35
+ key: ctx.key
36
+ });
37
+ },
38
+ 200
39
+ );
30
40
 
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;
43
- };
41
+ // src/pipeline/utils/make-handler-context.ts
42
+ function makeHandlerContext(ctx) {
43
+ return Object.freeze({
44
+ locale: ctx.locale,
45
+ key: ctx.key,
46
+ replacements: ctx.replacements,
47
+ messages: ctx.messages,
48
+ candidateLocales: ctx.candidateLocales,
49
+ config: ctx.config,
50
+ isLoading: ctx.isLoading,
51
+ rawMessage: ctx.rawMessage,
52
+ formattedMessage: ctx.formattedMessage,
53
+ meta: ctx.meta
54
+ });
55
+ }
44
56
 
45
- // src/utils/replace-values.ts
57
+ // src/pipeline/hooks/format.ts
58
+ var format = rura.createHook(
59
+ "format",
60
+ (ctx) => {
61
+ const { config, rawMessage } = ctx;
62
+ const { formatHandler } = config.handlers || {};
63
+ if (!formatHandler || rawMessage === void 0) return;
64
+ ctx.formattedMessage = formatHandler(
65
+ makeHandlerContext(ctx)
66
+ );
67
+ },
68
+ 500
69
+ );
70
+
71
+ // src/translators/shared/utils/replace-values.ts
46
72
  var replaceValues = (message, params) => {
47
73
  if (!params || typeof params !== "object" || Object.keys(params).length === 0) {
48
74
  return message;
@@ -61,63 +87,113 @@ var replaceValues = (message, params) => {
61
87
  return replaced;
62
88
  };
63
89
 
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;
90
+ // src/pipeline/hooks/interpolate.ts
91
+ var interpolate = rura.createHook(
92
+ "interpolate",
93
+ (ctx) => {
94
+ const { rawMessage, formattedMessage, replacements } = ctx;
95
+ const message = formattedMessage ?? rawMessage;
96
+ if (typeof message !== "string" || !replacements) {
97
+ ctx.finalMessage = message;
98
+ return;
99
+ }
100
+ ctx.finalMessage = replaceValues(message, replacements);
101
+ },
102
+ 600
103
+ );
104
+ var loading = rura.createHook(
105
+ "loading",
106
+ (ctx) => {
107
+ const { config, isLoading } = ctx;
108
+ if (!isLoading) return;
109
+ const { loadingHandler } = config.handlers || {};
110
+ if (loadingHandler) {
111
+ return {
112
+ early: true,
113
+ output: loadingHandler(makeHandlerContext(ctx))
114
+ };
115
+ }
116
+ const { loadingMessage } = config;
117
+ if (loadingMessage) {
118
+ return { early: true, output: loadingMessage };
119
+ }
120
+ },
121
+ 300
122
+ );
123
+ var missing = rura.createHook(
124
+ "missing",
125
+ (ctx) => {
126
+ const { config, key, rawMessage } = ctx;
127
+ if (rawMessage !== void 0) return;
128
+ const { missingHandler } = config.handlers || {};
129
+ if (missingHandler) {
130
+ return {
131
+ early: true,
132
+ output: missingHandler(makeHandlerContext(ctx))
133
+ };
134
+ }
135
+ const { placeholder } = config;
136
+ if (placeholder) {
137
+ return { early: true, output: placeholder };
138
+ }
139
+ return { early: true, output: key };
140
+ },
141
+ 400
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];
95
149
  };
96
150
 
151
+ // src/pipeline/hooks/resolve-locales.ts
152
+ var resolveLocales = rura.createHook(
153
+ "resolveLocales",
154
+ (ctx) => {
155
+ ctx.candidateLocales = resolveCandidateLocales(
156
+ ctx.locale,
157
+ ctx.config.fallbackLocales
158
+ );
159
+ },
160
+ 100
161
+ );
162
+
163
+ // src/pipeline/index.ts
164
+ var DEFAULT_HOOKS = [
165
+ resolveLocales,
166
+ findMessage,
167
+ loading,
168
+ missing,
169
+ format,
170
+ interpolate
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,74 @@ 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
+ function translate(options) {
237
+ const context = {
238
+ ...options,
239
+ config: options.translateConfig,
240
+ candidateLocales: [],
241
+ meta: {}
242
+ };
243
+ const { early, ctx, output } = rura.run(context, options.hooks);
244
+ if (early === true) return output;
245
+ return ctx.finalMessage;
246
+ }
247
+
145
248
  // src/translators/core-translator/core-translator.ts
146
249
  var CoreTranslator = class extends BaseTranslator {
147
- options;
250
+ /** User-provided options including messages, locale, and config. */
251
+ translateConfig;
252
+ /** Active pipeline hooks applied during translation. */
253
+ hooks = [...DEFAULT_HOOKS];
148
254
  constructor(options) {
149
- super({
150
- locale: options.locale,
151
- messages: options.messages,
152
- isLoading: options.isLoading
153
- });
154
- this.options = options;
255
+ const { locale, messages, isLoading, plugins, ...translateConfig } = options;
256
+ super({ locale, messages, isLoading });
257
+ this.translateConfig = translateConfig;
258
+ if (plugins) {
259
+ for (const plugin of plugins) this.use(plugin);
260
+ }
261
+ this.sortHooks();
262
+ }
263
+ /** Sort hooks by order value (lower runs earlier). */
264
+ sortHooks() {
265
+ this.hooks.sort((a, b) => (a.order ?? 0) - (b.order ?? 0));
266
+ }
267
+ /** Register a plugin or a raw pipeline hook. */
268
+ use(plugin) {
269
+ if ("run" in plugin) this.hooks.push(plugin);
270
+ else if ("hook" in plugin && plugin.hook) {
271
+ const hooks = Array.isArray(plugin.hook) ? plugin.hook : [plugin.hook];
272
+ this.hooks.push(...hooks);
273
+ }
274
+ this.sortHooks();
155
275
  }
156
276
  /** Check if a key exists in the specified locale or current locale. */
157
277
  hasKey = (key, targetLocale) => {
158
278
  return hasKey({
159
- messagesRef: this.messagesRef,
160
- localeRef: this.localeRef,
279
+ messages: this._messages,
280
+ locale: this._locale,
161
281
  key,
162
282
  targetLocale
163
283
  });
@@ -165,17 +285,18 @@ var CoreTranslator = class extends BaseTranslator {
165
285
  /** Get the translated message for a key, with optional replacements. */
166
286
  t = (key, replacements) => {
167
287
  return translate({
168
- messagesRef: this.messagesRef,
169
- localeRef: this.localeRef,
170
- isLoadingRef: this.isLoadingRef,
171
- translateConfig: this.options,
288
+ hooks: this.hooks,
289
+ messages: this._messages,
290
+ locale: this._locale,
291
+ isLoading: this._isLoading,
292
+ translateConfig: this.translateConfig,
172
293
  key,
173
294
  replacements
174
295
  });
175
296
  };
176
297
  };
177
298
 
178
- // src/utils/get-full-key.ts
299
+ // src/translators/scope-translator/utils/get-full-key.ts
179
300
  var getFullKey = (preKey = "", key = "") => {
180
301
  if (!preKey) return key;
181
302
  if (!key) return preKey;
@@ -193,8 +314,8 @@ var ScopeTranslator = class extends CoreTranslator {
193
314
  hasKey: (key, targetLocale) => {
194
315
  const fullKey = getFullKey(preKey, key);
195
316
  return hasKey({
196
- messagesRef: this.messagesRef,
197
- localeRef: this.localeRef,
317
+ messages: this._messages,
318
+ locale: this._locale,
198
319
  key: fullKey,
199
320
  targetLocale
200
321
  });
@@ -202,10 +323,11 @@ var ScopeTranslator = class extends CoreTranslator {
202
323
  t: (key, replacements) => {
203
324
  const fullKey = getFullKey(preKey, key);
204
325
  return translate({
205
- messagesRef: this.messagesRef,
206
- localeRef: this.localeRef,
207
- isLoadingRef: this.isLoadingRef,
208
- translateConfig: this.options,
326
+ hooks: this.hooks,
327
+ messages: this._messages,
328
+ locale: this._locale,
329
+ isLoading: this._isLoading,
330
+ translateConfig: this.translateConfig,
209
331
  key: fullKey,
210
332
  replacements
211
333
  });
@@ -214,4 +336,4 @@ var ScopeTranslator = class extends CoreTranslator {
214
336
  }
215
337
  };
216
338
 
217
- export { ScopeTranslator, ScopeTranslator as Translator };
339
+ 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.1",
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,12 +44,19 @@
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"
53
+ },
54
+ "dependencies": {
55
+ "rura": "1.0.7"
60
56
  },
61
- "dependencies": {},
62
57
  "devDependencies": {
63
58
  "@types/node": "^24.10.1",
64
- "@vitest/coverage-v8": "4.0.9",
59
+ "@vitest/coverage-v8": "4.0.15",
65
60
  "eslint": "^9.39.1",
66
61
  "eslint-config-prettier": "^10.1.8",
67
62
  "eslint-import-resolver-typescript": "^4.4.4",
@@ -77,10 +72,7 @@
77
72
  "tsup": "^8.4.0",
78
73
  "typescript": "^5.8.3",
79
74
  "typescript-eslint": "^8.46.4",
75
+ "vite": "^7.2.6",
80
76
  "vitest": "^4.0.9"
81
- },
82
- "sideEffects": false,
83
- "engines": {
84
- "node": ">=16.0.0"
85
77
  }
86
78
  }