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/dist/index.js
CHANGED
|
@@ -1,116 +1,29 @@
|
|
|
1
|
-
// src/cache/cache.ts
|
|
2
|
-
var Cache = class {
|
|
3
|
-
constructor(maxSize = 100, ttl = 1e3 * 60 * 5) {
|
|
4
|
-
this.cache = /* @__PURE__ */ new Map();
|
|
5
|
-
this.maxSize = maxSize;
|
|
6
|
-
this.ttl = ttl;
|
|
7
|
-
}
|
|
8
|
-
// Clean up expired cache entries
|
|
9
|
-
cleanUp() {
|
|
10
|
-
const now = Date.now();
|
|
11
|
-
this.cache.forEach((entry, key) => {
|
|
12
|
-
if (now - entry.timestamp > this.ttl) {
|
|
13
|
-
this.cache.delete(key);
|
|
14
|
-
}
|
|
15
|
-
});
|
|
16
|
-
}
|
|
17
|
-
// Get cache data
|
|
18
|
-
get(key) {
|
|
19
|
-
this.cleanUp();
|
|
20
|
-
const entry = this.cache.get(key);
|
|
21
|
-
if (entry) {
|
|
22
|
-
entry.timestamp = Date.now();
|
|
23
|
-
return entry.value;
|
|
24
|
-
}
|
|
25
|
-
return void 0;
|
|
26
|
-
}
|
|
27
|
-
// Set cache data
|
|
28
|
-
set(key, value) {
|
|
29
|
-
this.cleanUp();
|
|
30
|
-
if (this.cache.size >= this.maxSize) {
|
|
31
|
-
const oldestKey = this.cache.keys().next().value;
|
|
32
|
-
if (oldestKey) this.cache.delete(oldestKey);
|
|
33
|
-
}
|
|
34
|
-
this.cache.set(key, { value, timestamp: Date.now() });
|
|
35
|
-
}
|
|
36
|
-
// Check if a key exists in the cache
|
|
37
|
-
has(key) {
|
|
38
|
-
this.cleanUp();
|
|
39
|
-
return this.cache.has(key);
|
|
40
|
-
}
|
|
41
|
-
// Clear all cache
|
|
42
|
-
clear() {
|
|
43
|
-
this.cache.clear();
|
|
44
|
-
}
|
|
45
|
-
};
|
|
46
|
-
|
|
47
|
-
// src/cache/message-key-cache.ts
|
|
48
|
-
var MESSAGE_KEY_CACHE_MAX_SIZE = 100;
|
|
49
|
-
var MESSAGE_KEY_CACHE_EXPIRES_TIME = 1e3 * 60 * 5;
|
|
50
|
-
var messageKeyCache;
|
|
51
|
-
var getMessageKeyCache = () => {
|
|
52
|
-
if (typeof window !== "undefined" && !messageKeyCache) {
|
|
53
|
-
messageKeyCache = new Cache(
|
|
54
|
-
MESSAGE_KEY_CACHE_MAX_SIZE,
|
|
55
|
-
MESSAGE_KEY_CACHE_EXPIRES_TIME
|
|
56
|
-
);
|
|
57
|
-
}
|
|
58
|
-
return messageKeyCache;
|
|
59
|
-
};
|
|
60
|
-
var clearMessageKeyCache = () => {
|
|
61
|
-
if (messageKeyCache) {
|
|
62
|
-
messageKeyCache.clear();
|
|
63
|
-
messageKeyCache = void 0;
|
|
64
|
-
}
|
|
65
|
-
};
|
|
66
|
-
|
|
67
|
-
// src/utils/get-value-by-key.ts
|
|
68
|
-
var getValueByKey = (locale, messages, key, useCache = true) => {
|
|
69
|
-
const cache = getMessageKeyCache();
|
|
70
|
-
useCache = Boolean(useCache && cache);
|
|
71
|
-
const cacheKey = `${key}`;
|
|
72
|
-
const currentLocale = cache?.get("locale");
|
|
73
|
-
if (currentLocale !== locale) {
|
|
74
|
-
cache?.clear();
|
|
75
|
-
cache?.set("locale", locale);
|
|
76
|
-
}
|
|
77
|
-
if (useCache && cache?.has(cacheKey)) {
|
|
78
|
-
return cache?.get(cacheKey);
|
|
79
|
-
}
|
|
80
|
-
const value = key.split(".").reduce((acc, key2) => {
|
|
81
|
-
if (acc && typeof acc === "object" && key2 in acc) {
|
|
82
|
-
return acc[key2];
|
|
83
|
-
}
|
|
84
|
-
return void 0;
|
|
85
|
-
}, messages);
|
|
86
|
-
if (useCache && value !== void 0) {
|
|
87
|
-
cache?.set(cacheKey, value);
|
|
88
|
-
}
|
|
89
|
-
return value;
|
|
90
|
-
};
|
|
91
|
-
|
|
92
1
|
// src/utils/find-message-in-locales.ts
|
|
93
2
|
var findMessageInLocales = ({
|
|
94
3
|
messages,
|
|
95
|
-
|
|
4
|
+
candidateLocales,
|
|
96
5
|
key
|
|
97
6
|
}) => {
|
|
98
|
-
for (const
|
|
99
|
-
const localeMessages = messages[
|
|
100
|
-
if (!localeMessages)
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
const
|
|
104
|
-
|
|
105
|
-
|
|
7
|
+
for (const locale of candidateLocales) {
|
|
8
|
+
const localeMessages = messages[locale];
|
|
9
|
+
if (!localeMessages) continue;
|
|
10
|
+
let candidate = localeMessages;
|
|
11
|
+
const keys = key.split(".");
|
|
12
|
+
for (const k of keys) {
|
|
13
|
+
if (candidate && typeof candidate === "object" && k in candidate) {
|
|
14
|
+
candidate = candidate[k];
|
|
15
|
+
} else {
|
|
16
|
+
candidate = void 0;
|
|
17
|
+
break;
|
|
18
|
+
}
|
|
106
19
|
}
|
|
20
|
+
if (typeof candidate === "string") return candidate;
|
|
107
21
|
}
|
|
108
|
-
return void 0;
|
|
109
22
|
};
|
|
110
23
|
|
|
111
|
-
// src/utils/resolve-locales
|
|
112
|
-
var
|
|
113
|
-
const fallbacks =
|
|
24
|
+
// src/utils/resolve-candidate-locales.ts
|
|
25
|
+
var resolveCandidateLocales = (locale, fallbackLocalesMap) => {
|
|
26
|
+
const fallbacks = fallbackLocalesMap?.[locale] || [];
|
|
114
27
|
const filteredFallbacks = fallbacks.filter((l) => l !== locale);
|
|
115
28
|
return [locale, ...filteredFallbacks];
|
|
116
29
|
};
|
|
@@ -127,11 +40,9 @@ var hasKey = ({
|
|
|
127
40
|
if (!messages) {
|
|
128
41
|
throw new Error("[intor-translator] 'messages' is required");
|
|
129
42
|
}
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
const localesToTry = resolveLocalesToTry(targetLocale || locale);
|
|
134
|
-
return findMessageInLocales({ messages, localesToTry, key }) ? true : false;
|
|
43
|
+
const candidateLocales = resolveCandidateLocales(targetLocale || locale);
|
|
44
|
+
const message = findMessageInLocales({ messages, candidateLocales, key });
|
|
45
|
+
return !!message;
|
|
135
46
|
};
|
|
136
47
|
|
|
137
48
|
// src/utils/replace-values.ts
|
|
@@ -139,7 +50,7 @@ var replaceValues = (message, params) => {
|
|
|
139
50
|
if (!params || typeof params !== "object" || Object.keys(params).length === 0) {
|
|
140
51
|
return message;
|
|
141
52
|
}
|
|
142
|
-
const replaced = message.
|
|
53
|
+
const replaced = message.replaceAll(/{([^}]+)}/g, (match, key) => {
|
|
143
54
|
const keys = key.split(".");
|
|
144
55
|
let value = params;
|
|
145
56
|
for (const k of keys) {
|
|
@@ -164,85 +75,61 @@ var translate = ({
|
|
|
164
75
|
}) => {
|
|
165
76
|
const messages = messagesRef.current;
|
|
166
77
|
const locale = localeRef.current;
|
|
78
|
+
const isLoading = isLoadingRef.current;
|
|
167
79
|
if (!messages) {
|
|
168
80
|
throw new Error("[intor-translator] 'messages' is required");
|
|
169
81
|
}
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
const
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
if (isLoading) {
|
|
184
|
-
if (onLoading) {
|
|
185
|
-
return onLoading({
|
|
186
|
-
key,
|
|
187
|
-
locale,
|
|
188
|
-
replacements
|
|
189
|
-
});
|
|
190
|
-
}
|
|
191
|
-
if (loadingMessage) {
|
|
192
|
-
return loadingMessage;
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
if (message === void 0 || message === null) {
|
|
196
|
-
if (onMissing) {
|
|
197
|
-
return onMissing({ key, locale, replacements });
|
|
198
|
-
}
|
|
199
|
-
if (placeholder !== void 0 && placeholder !== null) {
|
|
200
|
-
return placeholder;
|
|
201
|
-
}
|
|
82
|
+
const { fallbackLocales, loadingMessage, placeholder, handlers } = translateConfig;
|
|
83
|
+
const { formatHandler, loadingHandler, missingHandler } = handlers || {};
|
|
84
|
+
const candidateLocales = resolveCandidateLocales(locale, fallbackLocales);
|
|
85
|
+
const message = findMessageInLocales({ messages, candidateLocales, key });
|
|
86
|
+
if (isLoading && (loadingHandler || loadingMessage)) {
|
|
87
|
+
if (loadingHandler)
|
|
88
|
+
return loadingHandler({ key, locale, replacements });
|
|
89
|
+
if (loadingMessage) return loadingMessage;
|
|
90
|
+
}
|
|
91
|
+
if (message === void 0) {
|
|
92
|
+
if (missingHandler)
|
|
93
|
+
return missingHandler({ key, locale, replacements });
|
|
94
|
+
if (placeholder) return placeholder;
|
|
202
95
|
return key;
|
|
203
96
|
}
|
|
204
|
-
if (
|
|
205
|
-
return
|
|
206
|
-
} else {
|
|
207
|
-
return replacements ? replaceValues(message, replacements) : message;
|
|
97
|
+
if (formatHandler) {
|
|
98
|
+
return formatHandler({ message, key, locale, replacements });
|
|
208
99
|
}
|
|
100
|
+
return replacements ? replaceValues(message, replacements) : message;
|
|
209
101
|
};
|
|
210
102
|
|
|
211
103
|
// src/translators/base-translator/base-translator.ts
|
|
212
104
|
var BaseTranslator = class {
|
|
213
105
|
constructor(options) {
|
|
106
|
+
/** Current messages for translation, updatable at runtime */
|
|
214
107
|
this.messagesRef = { current: void 0 };
|
|
215
|
-
/** Check if a key exists in the specified locale or current locale. */
|
|
216
|
-
this.hasKey = (key, targetLocale) => {
|
|
217
|
-
return hasKey({
|
|
218
|
-
messagesRef: this.messagesRef,
|
|
219
|
-
localeRef: this.localeRef,
|
|
220
|
-
key,
|
|
221
|
-
targetLocale
|
|
222
|
-
});
|
|
223
|
-
};
|
|
224
108
|
this.messagesRef = { current: options.messages };
|
|
225
109
|
this.localeRef = { current: options.locale };
|
|
226
110
|
}
|
|
227
|
-
/** Get
|
|
111
|
+
/** Get messages. */
|
|
228
112
|
get messages() {
|
|
229
113
|
return this.messagesRef.current;
|
|
230
114
|
}
|
|
115
|
+
/** Get the current active locale. */
|
|
116
|
+
get locale() {
|
|
117
|
+
return this.localeRef.current;
|
|
118
|
+
}
|
|
231
119
|
/**
|
|
232
120
|
* Replace messages with new ones.
|
|
233
121
|
*
|
|
234
|
-
* Note: This allows runtime setting of messages even if M is inferred as `never
|
|
235
|
-
*
|
|
122
|
+
* - Note: This allows runtime setting of messages even if `M` is inferred as `never`.
|
|
123
|
+
* The type cast bypasses TypeScript restrictions on dynamic messages.
|
|
236
124
|
*/
|
|
237
125
|
setMessages(messages) {
|
|
238
126
|
this.messagesRef.current = messages;
|
|
239
|
-
clearMessageKeyCache();
|
|
240
127
|
}
|
|
241
|
-
/**
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
128
|
+
/**
|
|
129
|
+
* Set the active locale.
|
|
130
|
+
*
|
|
131
|
+
* - Note: Unlike `setMessages`, the locale structure cannot be changed at runtime.
|
|
132
|
+
*/
|
|
246
133
|
setLocale(newLocale) {
|
|
247
134
|
this.localeRef.current = newLocale;
|
|
248
135
|
}
|
|
@@ -251,8 +138,17 @@ var BaseTranslator = class {
|
|
|
251
138
|
// src/translators/core-translator/core-translator.ts
|
|
252
139
|
var CoreTranslator = class extends BaseTranslator {
|
|
253
140
|
constructor(options) {
|
|
254
|
-
super(options);
|
|
141
|
+
super({ locale: options.locale, messages: options.messages });
|
|
255
142
|
this.isLoadingRef = { current: false };
|
|
143
|
+
/** Check if a key exists in the specified locale or current locale. */
|
|
144
|
+
this.hasKey = (key, targetLocale) => {
|
|
145
|
+
return hasKey({
|
|
146
|
+
messagesRef: this.messagesRef,
|
|
147
|
+
localeRef: this.localeRef,
|
|
148
|
+
key,
|
|
149
|
+
targetLocale
|
|
150
|
+
});
|
|
151
|
+
};
|
|
256
152
|
this.t = (key, replacements) => {
|
|
257
153
|
return translate({
|
|
258
154
|
messagesRef: this.messagesRef,
|
|
@@ -277,12 +173,8 @@ var CoreTranslator = class extends BaseTranslator {
|
|
|
277
173
|
|
|
278
174
|
// src/utils/get-full-key.ts
|
|
279
175
|
var getFullKey = (preKey = "", key = "") => {
|
|
280
|
-
if (!preKey)
|
|
281
|
-
|
|
282
|
-
}
|
|
283
|
-
if (!key) {
|
|
284
|
-
return preKey;
|
|
285
|
-
}
|
|
176
|
+
if (!preKey) return key;
|
|
177
|
+
if (!key) return preKey;
|
|
286
178
|
return `${preKey}.${key}`;
|
|
287
179
|
};
|
|
288
180
|
|
|
@@ -317,4 +209,4 @@ var ScopeTranslator = class extends CoreTranslator {
|
|
|
317
209
|
}
|
|
318
210
|
};
|
|
319
211
|
|
|
320
|
-
export { ScopeTranslator as Translator };
|
|
212
|
+
export { ScopeTranslator, ScopeTranslator as Translator };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "intor-translator",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.1.0",
|
|
4
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.",
|
|
5
5
|
"author": "Yiming Liao",
|
|
6
6
|
"license": "MIT",
|
|
@@ -51,42 +51,47 @@
|
|
|
51
51
|
"type": "module",
|
|
52
52
|
"scripts": {
|
|
53
53
|
"build": "tsup",
|
|
54
|
+
"prepublishOnly": "yarn build",
|
|
55
|
+
"test": "vitest",
|
|
56
|
+
"type": "tsc --noEmit",
|
|
54
57
|
"lint": "eslint",
|
|
55
|
-
"
|
|
56
|
-
"
|
|
57
|
-
"test:coverage": "jest --coverage && open coverage/lcov-report/index.html",
|
|
58
|
-
"prepublishOnly": "yarn build"
|
|
59
|
-
},
|
|
60
|
-
"sideEffects": false,
|
|
61
|
-
"engines": {
|
|
62
|
-
"node": ">=16.0.0"
|
|
58
|
+
"lint:debug": "eslint --debug",
|
|
59
|
+
"knip": "knip --config .config/knip.config.ts"
|
|
63
60
|
},
|
|
61
|
+
"dependencies": {},
|
|
64
62
|
"devDependencies": {
|
|
65
|
-
"@eslint/
|
|
63
|
+
"@eslint/eslintrc": "^3.3.1",
|
|
66
64
|
"@testing-library/dom": "^10.4.0",
|
|
67
65
|
"@testing-library/jest-dom": "^6.6.3",
|
|
68
66
|
"@testing-library/react": "^16.3.0",
|
|
69
|
-
"@types/
|
|
67
|
+
"@types/node": "^24.10.1",
|
|
70
68
|
"@types/react": "^19.1.4",
|
|
71
69
|
"@types/react-dom": "^19.1.5",
|
|
72
|
-
"
|
|
73
|
-
"eslint
|
|
74
|
-
"eslint-
|
|
75
|
-
"eslint-
|
|
76
|
-
"eslint-plugin-
|
|
70
|
+
"@vitest/coverage-v8": "4.0.9",
|
|
71
|
+
"eslint": "^9.39.1",
|
|
72
|
+
"eslint-config-prettier": "^10.1.8",
|
|
73
|
+
"eslint-import-resolver-typescript": "^4.4.4",
|
|
74
|
+
"eslint-plugin-import": "^2.32.0",
|
|
75
|
+
"eslint-plugin-jsx-a11y": "^6.10.2",
|
|
76
|
+
"eslint-plugin-prettier": "^5.5.4",
|
|
77
|
+
"eslint-plugin-unicorn": "^62.0.0",
|
|
78
|
+
"eslint-plugin-unused-imports": "^4.3.0",
|
|
77
79
|
"globals": "^16.1.0",
|
|
78
80
|
"intl-messageformat": "^10.7.16",
|
|
79
|
-
"
|
|
80
|
-
"jest-environment-jsdom": "^29.7.0",
|
|
81
|
-
"jest-fetch-mock": "^3.0.3",
|
|
81
|
+
"knip": "^5.69.1",
|
|
82
82
|
"next": "^15.3.2",
|
|
83
83
|
"prettier": "^3.5.3",
|
|
84
84
|
"react": "^19.1.0",
|
|
85
85
|
"react-dom": "^19.1.0",
|
|
86
|
-
"ts-jest": "^29.3.2",
|
|
87
86
|
"ts-node": "^10.9.2",
|
|
87
|
+
"tsd": "^0.33.0",
|
|
88
88
|
"tsup": "^8.4.0",
|
|
89
89
|
"typescript": "^5.8.3",
|
|
90
|
-
"typescript-eslint": "^8.
|
|
90
|
+
"typescript-eslint": "^8.46.4",
|
|
91
|
+
"vitest": "^4.0.9"
|
|
92
|
+
},
|
|
93
|
+
"sideEffects": false,
|
|
94
|
+
"engines": {
|
|
95
|
+
"node": ">=16.0.0"
|
|
91
96
|
}
|
|
92
97
|
}
|