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 +15 -13
- package/dist/index.cjs +64 -171
- package/dist/index.d.cts +236 -244
- package/dist/index.d.ts +236 -244
- package/dist/index.js +64 -172
- package/package.json +26 -21
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
|
-
|
|
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
|
|
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: {
|
|
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<
|
|
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
|
-
|
|
156
|
-
|
|
157
|
-
|
|
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
|
|
169
|
-
| ----------- |
|
|
170
|
-
| `messages` | `M`
|
|
171
|
-
| `locale` | `
|
|
172
|
-
| `isLoading` | `boolean`
|
|
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:
|
|
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
|
-
|
|
6
|
+
candidateLocales,
|
|
98
7
|
key
|
|
99
8
|
}) => {
|
|
100
|
-
for (const
|
|
101
|
-
const localeMessages = messages[
|
|
102
|
-
if (!localeMessages)
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
const
|
|
106
|
-
|
|
107
|
-
|
|
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
|
|
114
|
-
var
|
|
115
|
-
const fallbacks =
|
|
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
|
-
|
|
133
|
-
|
|
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.
|
|
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
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
const
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
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 (
|
|
207
|
-
return
|
|
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
|
|
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
|
|
237
|
-
*
|
|
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
|
-
/**
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
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
|
-
|
|
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;
|