intor-translator 1.0.15 → 1.1.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 CHANGED
@@ -88,7 +88,7 @@ const translator = new Translator({
88
88
  fallbackLocales: { en: ["zh"] }, // Use zh if message not found in en
89
89
  placeholder: "Content unavailable", // Shown if key is missing in all locales
90
90
  handlers: {
91
- formatMessage: ({ locale, message }) =>
91
+ formatHandler: ({ locale, message }) =>
92
92
  locale === "zh" ? `${message}。` : `${message}.`, // Auto punctuation per locale
93
93
  },
94
94
  });
@@ -110,7 +110,7 @@ import { Translator, FormatMessage } from "intor-translator";
110
110
  import { IntlMessageFormat } from "intl-messageformat";
111
111
 
112
112
  // Create a custom handler
113
- const formatMessage: FormatMessage = ({ message, locale, replacements }) => {
113
+ const formatHandler: FormatMessage = ({ message, locale, replacements }) => {
114
114
  const formatter = new IntlMessageFormat(message, locale);
115
115
  return formatter.format(replacements);
116
116
  };
@@ -126,7 +126,7 @@ const messages = {
126
126
  const translator = new Translator({
127
127
  locale: "en",
128
128
  messages,
129
- handlers: { formatMessage },
129
+ handlers: { formatHandler },
130
130
  });
131
131
 
132
132
  translator.t("notification", { name: "John", count: 0 }); // -> John has no messages.
@@ -141,7 +141,7 @@ translator.t("notification", { name: "John", count: 5 }); // -> John has 5 messa
141
141
 
142
142
  | Option | Type | Description |
143
143
  | ----------------- | ------------------------------------- | ------------------------------------------------------------------------ |
144
- | `messages` | `Readonly<LocaleNamespaceMessages>` | Translation messages grouped by locale and namespace |
144
+ | `messages` | `Readonly<LocaleMessages>` | Translation messages grouped by locale and namespace |
145
145
  | `locale` | `string` | Active locale key |
146
146
  | `fallbackLocales` | `Record<Locale, Locale[]>` (optional) | Locales to fallback to when a key is missing |
147
147
  | `placeholder` | `string` (optional) | Message to display when a key is missing in all locales |
@@ -152,9 +152,11 @@ translator.t("notification", { name: "John", count: 5 }); // -> John has 5 messa
152
152
 
153
153
  ```ts
154
154
  type TranslateHandlers = {
155
- formatMessage?: (ctx: TranslateContext & { message: string }) => unknown;
156
- onLoading?: (ctx: TranslateContext) => unknown;
157
- onMissing?: (ctx: TranslateContext) => unknown;
155
+ formatHandler?: (
156
+ ctx: TranslateHandlerContext & { message: string },
157
+ ) => unknown;
158
+ LoadingHandler?: (ctx: TranslateHandlerContext) => unknown;
159
+ MissingHandler?: (ctx: TranslateHandlerContext) => unknown;
158
160
  };
159
161
  ```
160
162
 
@@ -165,11 +167,11 @@ type TranslateHandlers = {
165
167
 
166
168
  ### Instance Properties
167
169
 
168
- | Property | Type | Description |
169
- | ----------- | -------------- | ------------------------------------------ |
170
- | `messages` | `M` | Current messages object |
171
- | `locale` | `LocaleKey<M>` | Currently active locale |
172
- | `isLoading` | `boolean` | Whether the translator is in loading state |
170
+ | Property | Type | Description |
171
+ | ----------- | ----------- | ------------------------------------------ |
172
+ | `messages` | `M` | Current messages object |
173
+ | `locale` | `Locale<M>` | Currently active locale |
174
+ | `isLoading` | `boolean` | Whether the translator is in loading state |
173
175
 
174
176
  ---
175
177
 
@@ -178,7 +180,7 @@ type TranslateHandlers = {
178
180
  | Method | Signature | Description |
179
181
  | ------------- | ------------------------------------------------- | ------------------------------------------------------------------- |
180
182
  | `setMessages` | `(messages: M) => void` | Replaces the current message set |
181
- | `setLocale` | `(locale: LocaleKey<M>) => boolean` | Sets a new locale and returns whether it changed |
183
+ | `setLocale` | `(locale: Locale<M>) => boolean` | Sets a new locale and returns whether it changed |
182
184
  | `setLoading` | `(state: boolean) => void` | Sets the loading state manually |
183
185
  | `hasKey` | `(key, targetLocale?) => boolean` | Checks whether the given key exists in the target or current locale |
184
186
  | `t` | `<Result = string>(key, replacements?) => Result` | Translates a key with optional replacements |
package/dist/index.cjs CHANGED
@@ -1,118 +1,31 @@
1
1
  'use strict';
2
2
 
3
- // src/cache/cache.ts
4
- var Cache = class {
5
- constructor(maxSize = 100, ttl = 1e3 * 60 * 5) {
6
- this.cache = /* @__PURE__ */ new Map();
7
- this.maxSize = maxSize;
8
- this.ttl = ttl;
9
- }
10
- // Clean up expired cache entries
11
- cleanUp() {
12
- const now = Date.now();
13
- this.cache.forEach((entry, key) => {
14
- if (now - entry.timestamp > this.ttl) {
15
- this.cache.delete(key);
16
- }
17
- });
18
- }
19
- // Get cache data
20
- get(key) {
21
- this.cleanUp();
22
- const entry = this.cache.get(key);
23
- if (entry) {
24
- entry.timestamp = Date.now();
25
- return entry.value;
26
- }
27
- return void 0;
28
- }
29
- // Set cache data
30
- set(key, value) {
31
- this.cleanUp();
32
- if (this.cache.size >= this.maxSize) {
33
- const oldestKey = this.cache.keys().next().value;
34
- if (oldestKey) this.cache.delete(oldestKey);
35
- }
36
- this.cache.set(key, { value, timestamp: Date.now() });
37
- }
38
- // Check if a key exists in the cache
39
- has(key) {
40
- this.cleanUp();
41
- return this.cache.has(key);
42
- }
43
- // Clear all cache
44
- clear() {
45
- this.cache.clear();
46
- }
47
- };
48
-
49
- // src/cache/message-key-cache.ts
50
- var MESSAGE_KEY_CACHE_MAX_SIZE = 100;
51
- var MESSAGE_KEY_CACHE_EXPIRES_TIME = 1e3 * 60 * 5;
52
- var messageKeyCache;
53
- var getMessageKeyCache = () => {
54
- if (typeof window !== "undefined" && !messageKeyCache) {
55
- messageKeyCache = new Cache(
56
- MESSAGE_KEY_CACHE_MAX_SIZE,
57
- MESSAGE_KEY_CACHE_EXPIRES_TIME
58
- );
59
- }
60
- return messageKeyCache;
61
- };
62
- var clearMessageKeyCache = () => {
63
- if (messageKeyCache) {
64
- messageKeyCache.clear();
65
- messageKeyCache = void 0;
66
- }
67
- };
68
-
69
- // src/utils/get-value-by-key.ts
70
- var getValueByKey = (locale, messages, key, useCache = true) => {
71
- const cache = getMessageKeyCache();
72
- useCache = Boolean(useCache && cache);
73
- const cacheKey = `${key}`;
74
- const currentLocale = cache?.get("locale");
75
- if (currentLocale !== locale) {
76
- cache?.clear();
77
- cache?.set("locale", locale);
78
- }
79
- if (useCache && cache?.has(cacheKey)) {
80
- return cache?.get(cacheKey);
81
- }
82
- const value = key.split(".").reduce((acc, key2) => {
83
- if (acc && typeof acc === "object" && key2 in acc) {
84
- return acc[key2];
85
- }
86
- return void 0;
87
- }, messages);
88
- if (useCache && value !== void 0) {
89
- cache?.set(cacheKey, value);
90
- }
91
- return value;
92
- };
93
-
94
3
  // src/utils/find-message-in-locales.ts
95
4
  var findMessageInLocales = ({
96
5
  messages,
97
- localesToTry,
6
+ candidateLocales,
98
7
  key
99
8
  }) => {
100
- for (const loc of localesToTry) {
101
- const localeMessages = messages[loc];
102
- if (!localeMessages) {
103
- continue;
104
- }
105
- const candidate = getValueByKey(loc, localeMessages, key);
106
- if (typeof candidate === "string") {
107
- return candidate;
9
+ for (const locale of candidateLocales) {
10
+ const localeMessages = messages[locale];
11
+ if (!localeMessages) continue;
12
+ let candidate = localeMessages;
13
+ const keys = key.split(".");
14
+ for (const k of keys) {
15
+ if (candidate && typeof candidate === "object" && k in candidate) {
16
+ candidate = candidate[k];
17
+ } else {
18
+ candidate = void 0;
19
+ break;
20
+ }
108
21
  }
22
+ if (typeof candidate === "string") return candidate;
109
23
  }
110
- return void 0;
111
24
  };
112
25
 
113
- // src/utils/resolve-locales-to-try.ts
114
- var resolveLocalesToTry = (locale, fallbackLocales) => {
115
- const fallbacks = fallbackLocales?.[locale] || [];
26
+ // src/utils/resolve-candidate-locales.ts
27
+ var resolveCandidateLocales = (locale, fallbackLocalesMap) => {
28
+ const fallbacks = fallbackLocalesMap?.[locale] || [];
116
29
  const filteredFallbacks = fallbacks.filter((l) => l !== locale);
117
30
  return [locale, ...filteredFallbacks];
118
31
  };
@@ -129,11 +42,9 @@ var hasKey = ({
129
42
  if (!messages) {
130
43
  throw new Error("[intor-translator] 'messages' is required");
131
44
  }
132
- if (!locale) {
133
- throw new Error("[intor-translator] 'locale' is required");
134
- }
135
- const localesToTry = resolveLocalesToTry(targetLocale || locale);
136
- return findMessageInLocales({ messages, localesToTry, key }) ? true : false;
45
+ const candidateLocales = resolveCandidateLocales(targetLocale || locale);
46
+ const message = findMessageInLocales({ messages, candidateLocales, key });
47
+ return !!message;
137
48
  };
138
49
 
139
50
  // src/utils/replace-values.ts
@@ -141,7 +52,7 @@ var replaceValues = (message, params) => {
141
52
  if (!params || typeof params !== "object" || Object.keys(params).length === 0) {
142
53
  return message;
143
54
  }
144
- const replaced = message.replace(/{([^}]+)}/g, (match, key) => {
55
+ const replaced = message.replaceAll(/{([^}]+)}/g, (match, key) => {
145
56
  const keys = key.split(".");
146
57
  let value = params;
147
58
  for (const k of keys) {
@@ -166,85 +77,61 @@ var translate = ({
166
77
  }) => {
167
78
  const messages = messagesRef.current;
168
79
  const locale = localeRef.current;
80
+ const isLoading = isLoadingRef.current;
169
81
  if (!messages) {
170
82
  throw new Error("[intor-translator] 'messages' is required");
171
83
  }
172
- if (!locale) {
173
- throw new Error("[intor-translator] 'locale' is required");
174
- }
175
- const isLoading = isLoadingRef.current;
176
- const {
177
- fallbackLocales,
178
- loadingMessage,
179
- placeholder,
180
- handlers = {}
181
- } = translateConfig;
182
- const { formatMessage, onLoading, onMissing } = handlers;
183
- const localesToTry = resolveLocalesToTry(locale, fallbackLocales);
184
- const message = findMessageInLocales({ messages, localesToTry, key });
185
- if (isLoading) {
186
- if (onLoading) {
187
- return onLoading({
188
- key,
189
- locale,
190
- replacements
191
- });
192
- }
193
- if (loadingMessage) {
194
- return loadingMessage;
195
- }
196
- }
197
- if (message === void 0 || message === null) {
198
- if (onMissing) {
199
- return onMissing({ key, locale, replacements });
200
- }
201
- if (placeholder !== void 0 && placeholder !== null) {
202
- return placeholder;
203
- }
84
+ const { fallbackLocales, loadingMessage, placeholder, handlers } = translateConfig;
85
+ const { formatHandler, loadingHandler, missingHandler } = handlers || {};
86
+ const candidateLocales = resolveCandidateLocales(locale, fallbackLocales);
87
+ const message = findMessageInLocales({ messages, candidateLocales, key });
88
+ if (isLoading && (loadingHandler || loadingMessage)) {
89
+ if (loadingHandler)
90
+ return loadingHandler({ key, locale, replacements });
91
+ if (loadingMessage) return loadingMessage;
92
+ }
93
+ if (message === void 0) {
94
+ if (missingHandler)
95
+ return missingHandler({ key, locale, replacements });
96
+ if (placeholder) return placeholder;
204
97
  return key;
205
98
  }
206
- if (formatMessage) {
207
- return formatMessage({ message, key, locale, replacements });
208
- } else {
209
- return replacements ? replaceValues(message, replacements) : message;
99
+ if (formatHandler) {
100
+ return formatHandler({ message, key, locale, replacements });
210
101
  }
102
+ return replacements ? replaceValues(message, replacements) : message;
211
103
  };
212
104
 
213
105
  // src/translators/base-translator/base-translator.ts
214
106
  var BaseTranslator = class {
215
107
  constructor(options) {
108
+ /** Current messages for translation, updatable at runtime */
216
109
  this.messagesRef = { current: void 0 };
217
- /** Check if a key exists in the specified locale or current locale. */
218
- this.hasKey = (key, targetLocale) => {
219
- return hasKey({
220
- messagesRef: this.messagesRef,
221
- localeRef: this.localeRef,
222
- key,
223
- targetLocale
224
- });
225
- };
226
110
  this.messagesRef = { current: options.messages };
227
111
  this.localeRef = { current: options.locale };
228
112
  }
229
- /** Get all message data. */
113
+ /** Get messages. */
230
114
  get messages() {
231
115
  return this.messagesRef.current;
232
116
  }
117
+ /** Get the current active locale. */
118
+ get locale() {
119
+ return this.localeRef.current;
120
+ }
233
121
  /**
234
122
  * Replace messages with new ones.
235
123
  *
236
- * Note: This allows runtime setting of messages even if M is inferred as `never` (uninitialized).
237
- * Type cast is used to bypass TypeScript restrictions on dynamic messages.
124
+ * - Note: This allows runtime setting of messages even if `M` is inferred as `never`.
125
+ * The type cast bypasses TypeScript restrictions on dynamic messages.
238
126
  */
239
127
  setMessages(messages) {
240
128
  this.messagesRef.current = messages;
241
- clearMessageKeyCache();
242
129
  }
243
- /** Get the current active locale. */
244
- get locale() {
245
- return this.localeRef.current;
246
- }
247
- /** Change the active locale. */
130
+ /**
131
+ * Set the active locale.
132
+ *
133
+ * - Note: Unlike `setMessages`, the locale structure cannot be changed at runtime.
134
+ */
248
135
  setLocale(newLocale) {
249
136
  this.localeRef.current = newLocale;
250
137
  }
@@ -253,8 +140,17 @@ var BaseTranslator = class {
253
140
  // src/translators/core-translator/core-translator.ts
254
141
  var CoreTranslator = class extends BaseTranslator {
255
142
  constructor(options) {
256
- super(options);
143
+ super({ locale: options.locale, messages: options.messages });
257
144
  this.isLoadingRef = { current: false };
145
+ /** Check if a key exists in the specified locale or current locale. */
146
+ this.hasKey = (key, targetLocale) => {
147
+ return hasKey({
148
+ messagesRef: this.messagesRef,
149
+ localeRef: this.localeRef,
150
+ key,
151
+ targetLocale
152
+ });
153
+ };
258
154
  this.t = (key, replacements) => {
259
155
  return translate({
260
156
  messagesRef: this.messagesRef,
@@ -279,12 +175,8 @@ var CoreTranslator = class extends BaseTranslator {
279
175
 
280
176
  // src/utils/get-full-key.ts
281
177
  var getFullKey = (preKey = "", key = "") => {
282
- if (!preKey) {
283
- return key;
284
- }
285
- if (!key) {
286
- return preKey;
287
- }
178
+ if (!preKey) return key;
179
+ if (!key) return preKey;
288
180
  return `${preKey}.${key}`;
289
181
  };
290
182
 
@@ -319,4 +211,5 @@ var ScopeTranslator = class extends CoreTranslator {
319
211
  }
320
212
  };
321
213
 
214
+ exports.ScopeTranslator = ScopeTranslator;
322
215
  exports.Translator = ScopeTranslator;