@nexusts/i18n 0.7.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 +41 -0
- package/dist/index.js +445 -0
- package/dist/index.js.map +13 -0
- package/package.json +31 -0
package/README.md
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# @nexusts/i18n
|
|
2
|
+
|
|
3
|
+
> **NexusTS** — Bun-native fullstack framework
|
|
4
|
+
|
|
5
|
+
## Description
|
|
6
|
+
|
|
7
|
+
Internationalization (Intl-based, pluralization).
|
|
8
|
+
|
|
9
|
+
Locale detection middleware (query → cookie → Accept-Language → default). JSON catalogs. Intl-based formatters: formatDate, formatNumber, formatCurrency. Pluralization with the `|` separator.
|
|
10
|
+
|
|
11
|
+
## Install
|
|
12
|
+
|
|
13
|
+
This module is part of the NexusTS monorepo. Each module is published as its own npm package under the `@nexusts/` scope.
|
|
14
|
+
|
|
15
|
+
Most apps start with just the core:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
bun add @nexusts/core reflect-metadata zod hono
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Then add this module only if you need it:
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
bun add @nexusts/i18n
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Peer dependencies
|
|
28
|
+
|
|
29
|
+
None. This module is fully self-contained.
|
|
30
|
+
|
|
31
|
+
## Usage
|
|
32
|
+
|
|
33
|
+
```typescript
|
|
34
|
+
import { /* public API */ } from "@nexusts/i18n";
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
See the [user guide](../../docs/user-guide/i18n.md) and the [example app](../../examples/) for a working demo.
|
|
38
|
+
|
|
39
|
+
## License
|
|
40
|
+
|
|
41
|
+
MIT — see the root [LICENSE](../../LICENSE).
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,445 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
var __legacyDecorateClassTS = function(decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function")
|
|
5
|
+
r = Reflect.decorate(decorators, target, key, desc);
|
|
6
|
+
else
|
|
7
|
+
for (var i = decorators.length - 1;i >= 0; i--)
|
|
8
|
+
if (d = decorators[i])
|
|
9
|
+
r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
10
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
11
|
+
};
|
|
12
|
+
var __legacyDecorateParamTS = (index, decorator) => (target, key) => decorator(target, key, index);
|
|
13
|
+
var __legacyMetadataTS = (k, v) => {
|
|
14
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function")
|
|
15
|
+
return Reflect.metadata(k, v);
|
|
16
|
+
};
|
|
17
|
+
var __require = import.meta.require;
|
|
18
|
+
|
|
19
|
+
// packages/i18n/src/service.ts
|
|
20
|
+
var I18N_SERVICE_TOKEN = Symbol.for("nexus:I18nService");
|
|
21
|
+
|
|
22
|
+
class I18nService {
|
|
23
|
+
messages = {};
|
|
24
|
+
defaultLocale;
|
|
25
|
+
fallback;
|
|
26
|
+
fallbackToDefault;
|
|
27
|
+
supportedLocales = null;
|
|
28
|
+
pluralRulesCache = new Map;
|
|
29
|
+
dateFormatCache = new Map;
|
|
30
|
+
numberFormatCache = new Map;
|
|
31
|
+
collatorCache = new Map;
|
|
32
|
+
constructor(config = {}) {
|
|
33
|
+
this.defaultLocale = config.defaultLocale ?? "en";
|
|
34
|
+
this.fallback = config.fallback ?? true;
|
|
35
|
+
this.fallbackToDefault = config.fallbackToDefault ?? true;
|
|
36
|
+
if (config.supportedLocales && config.supportedLocales.length > 0) {
|
|
37
|
+
this.supportedLocales = new Set(config.supportedLocales);
|
|
38
|
+
}
|
|
39
|
+
if (config.messages) {
|
|
40
|
+
for (const [locale, dict] of Object.entries(config.messages)) {
|
|
41
|
+
this.addMessages(locale, dict);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
addMessages(locale, dict) {
|
|
46
|
+
const existing = this.messages[locale] ?? {};
|
|
47
|
+
this.messages[locale] = mergeDict(existing, dict);
|
|
48
|
+
}
|
|
49
|
+
setMessages(catalog) {
|
|
50
|
+
this.messages = {};
|
|
51
|
+
for (const [locale, dict] of Object.entries(catalog)) {
|
|
52
|
+
this.addMessages(locale, dict);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
hasMessage(key, locale = this.defaultLocale) {
|
|
56
|
+
return resolveKey(this.messages[locale], key) !== undefined;
|
|
57
|
+
}
|
|
58
|
+
getRaw(key, locale = this.defaultLocale) {
|
|
59
|
+
const value = resolveKey(this.messages[locale], key);
|
|
60
|
+
return typeof value === "string" ? value : undefined;
|
|
61
|
+
}
|
|
62
|
+
t(key, args, locale = this.defaultLocale) {
|
|
63
|
+
const raw = this.lookupRaw(key, locale);
|
|
64
|
+
if (raw === undefined) {
|
|
65
|
+
return key;
|
|
66
|
+
}
|
|
67
|
+
return this.format(raw, args, locale);
|
|
68
|
+
}
|
|
69
|
+
tOr(key, fallback, args, locale = this.defaultLocale) {
|
|
70
|
+
const raw = this.lookupRaw(key, locale);
|
|
71
|
+
if (raw === undefined)
|
|
72
|
+
return this.format(fallback, args, locale);
|
|
73
|
+
return this.format(raw, args, locale);
|
|
74
|
+
}
|
|
75
|
+
tChoice(key, count, args = {}, locale = this.defaultLocale) {
|
|
76
|
+
return this.t(key, { ...args, count }, locale);
|
|
77
|
+
}
|
|
78
|
+
format(template, args = {}, locale = this.defaultLocale) {
|
|
79
|
+
const selected = this.selectPluralForm(template, args, locale);
|
|
80
|
+
return this.interpolate(selected, args);
|
|
81
|
+
}
|
|
82
|
+
pluralCategory(count, locale = this.defaultLocale) {
|
|
83
|
+
const rules = this.getPluralRules(locale);
|
|
84
|
+
return rules.select(count);
|
|
85
|
+
}
|
|
86
|
+
formatDate(date, options = {}, locale) {
|
|
87
|
+
const useLocale = options.locale ?? locale ?? this.defaultLocale;
|
|
88
|
+
const key = `${useLocale}|${stableStringify(options)}`;
|
|
89
|
+
let fmt = this.dateFormatCache.get(key);
|
|
90
|
+
if (!fmt) {
|
|
91
|
+
fmt = new Intl.DateTimeFormat(useLocale, options);
|
|
92
|
+
this.dateFormatCache.set(key, fmt);
|
|
93
|
+
}
|
|
94
|
+
return fmt.format(typeof date === "string" ? new Date(date) : new Date(date));
|
|
95
|
+
}
|
|
96
|
+
formatNumber(value, options = {}, locale) {
|
|
97
|
+
const useLocale = options.locale ?? locale ?? this.defaultLocale;
|
|
98
|
+
const key = `${useLocale}|${stableStringify(options)}`;
|
|
99
|
+
let fmt = this.numberFormatCache.get(key);
|
|
100
|
+
if (!fmt) {
|
|
101
|
+
fmt = new Intl.NumberFormat(useLocale, options);
|
|
102
|
+
this.numberFormatCache.set(key, fmt);
|
|
103
|
+
}
|
|
104
|
+
return fmt.format(value);
|
|
105
|
+
}
|
|
106
|
+
formatCurrency(amount, options, locale) {
|
|
107
|
+
const useLocale = options.locale ?? locale ?? this.defaultLocale;
|
|
108
|
+
const { locale: _omit, ...opts } = options;
|
|
109
|
+
const key = `${useLocale}|${stableStringify(opts)}`;
|
|
110
|
+
let fmt = this.numberFormatCache.get(key);
|
|
111
|
+
if (!fmt) {
|
|
112
|
+
fmt = new Intl.NumberFormat(useLocale, { style: "currency", ...opts });
|
|
113
|
+
this.numberFormatCache.set(key, fmt);
|
|
114
|
+
}
|
|
115
|
+
return fmt.format(amount);
|
|
116
|
+
}
|
|
117
|
+
compare(a, b, locale = this.defaultLocale) {
|
|
118
|
+
let c = this.collatorCache.get(locale);
|
|
119
|
+
if (!c) {
|
|
120
|
+
c = new Intl.Collator(locale);
|
|
121
|
+
this.collatorCache.set(locale, c);
|
|
122
|
+
}
|
|
123
|
+
return c.compare(a, b);
|
|
124
|
+
}
|
|
125
|
+
getDefaultLocale() {
|
|
126
|
+
return this.defaultLocale;
|
|
127
|
+
}
|
|
128
|
+
getLocales() {
|
|
129
|
+
return Object.keys(this.messages);
|
|
130
|
+
}
|
|
131
|
+
isSupported(locale) {
|
|
132
|
+
if (!this.supportedLocales)
|
|
133
|
+
return true;
|
|
134
|
+
return this.supportedLocales.has(locale);
|
|
135
|
+
}
|
|
136
|
+
negotiateLocale(preferred = [], acceptLanguage) {
|
|
137
|
+
const candidates = [...preferred];
|
|
138
|
+
if (acceptLanguage) {
|
|
139
|
+
for (const tag of parseAcceptLanguage(acceptLanguage)) {
|
|
140
|
+
if (!candidates.includes(tag))
|
|
141
|
+
candidates.push(tag);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
for (const cand of candidates) {
|
|
145
|
+
if (this.isSupported(cand) && this.messages[cand])
|
|
146
|
+
return cand;
|
|
147
|
+
if (this.fallback) {
|
|
148
|
+
const lang = cand.split("-")[0];
|
|
149
|
+
if (lang && lang !== cand && this.isSupported(lang) && this.messages[lang]) {
|
|
150
|
+
return lang;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
return this.defaultLocale;
|
|
155
|
+
}
|
|
156
|
+
lookupRaw(key, locale) {
|
|
157
|
+
const v = resolveKey(this.messages[locale], key);
|
|
158
|
+
if (typeof v === "string")
|
|
159
|
+
return v;
|
|
160
|
+
if (this.fallback) {
|
|
161
|
+
const lang = locale.split("-")[0];
|
|
162
|
+
if (lang && lang !== locale) {
|
|
163
|
+
const v2 = resolveKey(this.messages[lang], key);
|
|
164
|
+
if (typeof v2 === "string")
|
|
165
|
+
return v2;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
if (this.fallbackToDefault && locale !== this.defaultLocale) {
|
|
169
|
+
const v3 = resolveKey(this.messages[this.defaultLocale], key);
|
|
170
|
+
if (typeof v3 === "string")
|
|
171
|
+
return v3;
|
|
172
|
+
}
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
selectPluralForm(template, args, locale) {
|
|
176
|
+
if (!template.includes("|"))
|
|
177
|
+
return template;
|
|
178
|
+
const segments = template.split("|");
|
|
179
|
+
if (segments.length === 1)
|
|
180
|
+
return template;
|
|
181
|
+
const count = pickCount(args);
|
|
182
|
+
if (count === undefined) {
|
|
183
|
+
return segments[segments.length - 1];
|
|
184
|
+
}
|
|
185
|
+
const category = this.pluralCategory(count, locale);
|
|
186
|
+
const idx = this.pluralIndex(category, segments.length);
|
|
187
|
+
return segments[idx];
|
|
188
|
+
}
|
|
189
|
+
pluralIndex(category, segmentCount) {
|
|
190
|
+
if (segmentCount <= 1)
|
|
191
|
+
return segmentCount - 1;
|
|
192
|
+
const table = {
|
|
193
|
+
2: ["one", "other"],
|
|
194
|
+
3: ["zero", "one", "other"],
|
|
195
|
+
4: ["zero", "one", "two", "other"],
|
|
196
|
+
5: ["zero", "one", "two", "few", "other"],
|
|
197
|
+
6: ["zero", "one", "two", "few", "many", "other"]
|
|
198
|
+
};
|
|
199
|
+
const cats = table[segmentCount] ?? ["other"];
|
|
200
|
+
const i = cats.indexOf(category);
|
|
201
|
+
if (i < 0)
|
|
202
|
+
return segmentCount - 1;
|
|
203
|
+
return i;
|
|
204
|
+
}
|
|
205
|
+
getPluralRules(locale) {
|
|
206
|
+
let r = this.pluralRulesCache.get(locale);
|
|
207
|
+
if (!r) {
|
|
208
|
+
try {
|
|
209
|
+
r = new Intl.PluralRules(locale);
|
|
210
|
+
} catch {
|
|
211
|
+
r = new Intl.PluralRules(this.defaultLocale);
|
|
212
|
+
}
|
|
213
|
+
this.pluralRulesCache.set(locale, r);
|
|
214
|
+
}
|
|
215
|
+
return r;
|
|
216
|
+
}
|
|
217
|
+
interpolate(template, args) {
|
|
218
|
+
if (Object.keys(args).length === 0)
|
|
219
|
+
return template;
|
|
220
|
+
return template.replace(/:([A-Za-z_][A-Za-z0-9_]*)/g, (match, name) => {
|
|
221
|
+
if (name in args) {
|
|
222
|
+
return String(args[name]);
|
|
223
|
+
}
|
|
224
|
+
return match;
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
function resolveKey(dict, key) {
|
|
229
|
+
if (!dict)
|
|
230
|
+
return;
|
|
231
|
+
const parts = key.split(".");
|
|
232
|
+
let cur = dict;
|
|
233
|
+
for (const p of parts) {
|
|
234
|
+
if (cur && typeof cur === "object" && p in cur) {
|
|
235
|
+
cur = cur[p];
|
|
236
|
+
} else {
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
return cur;
|
|
241
|
+
}
|
|
242
|
+
function mergeDict(target, source) {
|
|
243
|
+
const out = { ...target };
|
|
244
|
+
for (const [k, v] of Object.entries(source)) {
|
|
245
|
+
const existing = out[k];
|
|
246
|
+
if (existing && typeof existing === "object" && !Array.isArray(existing) && v && typeof v === "object" && !Array.isArray(v)) {
|
|
247
|
+
out[k] = mergeDict(existing, v);
|
|
248
|
+
} else {
|
|
249
|
+
out[k] = v;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
return out;
|
|
253
|
+
}
|
|
254
|
+
function pickCount(args) {
|
|
255
|
+
if ("count" in args && typeof args.count === "number")
|
|
256
|
+
return args.count;
|
|
257
|
+
for (const v of Object.values(args)) {
|
|
258
|
+
if (typeof v === "number")
|
|
259
|
+
return v;
|
|
260
|
+
}
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
function parseAcceptLanguage(header) {
|
|
264
|
+
return header.split(",").map((part) => {
|
|
265
|
+
const [tag, qPart] = part.trim().split(";");
|
|
266
|
+
const q = qPart?.startsWith("q=") ? Number(qPart.slice(2)) : 1;
|
|
267
|
+
return { tag: tag?.trim() ?? "", q: isFinite(q) ? q : 1 };
|
|
268
|
+
}).filter((e) => e.tag.length > 0).sort((a, b) => b.q - a.q).map((e) => e.tag);
|
|
269
|
+
}
|
|
270
|
+
function stableStringify(obj) {
|
|
271
|
+
return JSON.stringify(obj, (_k, v) => {
|
|
272
|
+
if (v && typeof v === "object" && !Array.isArray(v)) {
|
|
273
|
+
const sorted = {};
|
|
274
|
+
for (const key of Object.keys(v).sort()) {
|
|
275
|
+
sorted[key] = v[key];
|
|
276
|
+
}
|
|
277
|
+
return sorted;
|
|
278
|
+
}
|
|
279
|
+
return v;
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
// packages/i18n/src/module.ts
|
|
283
|
+
import { Module } from "@nexusts/core/decorators/module.js";
|
|
284
|
+
import { Inject } from "@nexusts/core/decorators/injectable.js";
|
|
285
|
+
import"reflect-metadata";
|
|
286
|
+
|
|
287
|
+
// packages/i18n/src/middleware.ts
|
|
288
|
+
function i18nMiddleware(service, options = {}) {
|
|
289
|
+
const queryKey = options.queryKey ?? "lang";
|
|
290
|
+
const cookieKey = options.cookieKey ?? "lang";
|
|
291
|
+
const defaultLocale = options.defaultLocale ?? service.getDefaultLocale();
|
|
292
|
+
return async (c, next) => {
|
|
293
|
+
const fromQuery = c.req.query(queryKey);
|
|
294
|
+
const fromCookie = getCookie(c, cookieKey);
|
|
295
|
+
const acceptLanguage = c.req.header("accept-language");
|
|
296
|
+
const preferred = [];
|
|
297
|
+
if (fromQuery)
|
|
298
|
+
preferred.push(fromQuery);
|
|
299
|
+
if (fromCookie)
|
|
300
|
+
preferred.push(fromCookie);
|
|
301
|
+
if (acceptLanguage) {
|
|
302
|
+
preferred.push(...parseAcceptLanguage2(acceptLanguage));
|
|
303
|
+
}
|
|
304
|
+
preferred.push(defaultLocale);
|
|
305
|
+
const locale = service.negotiateLocale(preferred, acceptLanguage);
|
|
306
|
+
c.set("locale", locale);
|
|
307
|
+
c.set("i18n", service);
|
|
308
|
+
await next();
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
function getCookie(c, name) {
|
|
312
|
+
const raw = c.req.header("cookie");
|
|
313
|
+
if (!raw)
|
|
314
|
+
return;
|
|
315
|
+
for (const part of raw.split(";")) {
|
|
316
|
+
const [k, ...rest] = part.trim().split("=");
|
|
317
|
+
if (k === name)
|
|
318
|
+
return decodeURIComponent(rest.join("="));
|
|
319
|
+
}
|
|
320
|
+
return;
|
|
321
|
+
}
|
|
322
|
+
function parseAcceptLanguage2(header) {
|
|
323
|
+
return header.split(",").map((part) => {
|
|
324
|
+
const [tag, qPart] = part.trim().split(";");
|
|
325
|
+
const q = qPart?.startsWith("q=") ? Number(qPart.slice(2)) : 1;
|
|
326
|
+
return { tag: tag?.trim() ?? "", q: isFinite(q) ? q : 1 };
|
|
327
|
+
}).filter((e) => e.tag.length > 0).sort((a, b) => b.q - a.q).map((e) => e.tag);
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// packages/i18n/src/module.ts
|
|
331
|
+
class I18nModule {
|
|
332
|
+
static forRoot(config = {}) {
|
|
333
|
+
const fullConfig = {
|
|
334
|
+
defaultLocale: config.defaultLocale ?? "en",
|
|
335
|
+
fallback: config.fallback ?? true,
|
|
336
|
+
fallbackToDefault: config.fallbackToDefault ?? true,
|
|
337
|
+
supportedLocales: config.supportedLocales ?? [],
|
|
338
|
+
messages: config.messages ?? {},
|
|
339
|
+
messagesDir: config.messagesDir ?? "",
|
|
340
|
+
detectQueryKey: config.detectQueryKey ?? "lang",
|
|
341
|
+
detectCookieKey: config.detectCookieKey ?? "lang"
|
|
342
|
+
};
|
|
343
|
+
|
|
344
|
+
class ConfiguredI18nModule {
|
|
345
|
+
service;
|
|
346
|
+
constructor(service) {
|
|
347
|
+
this.service = service;
|
|
348
|
+
}
|
|
349
|
+
middleware() {
|
|
350
|
+
return i18nMiddleware(this.service, {
|
|
351
|
+
queryKey: fullConfig.detectQueryKey,
|
|
352
|
+
cookieKey: fullConfig.detectCookieKey
|
|
353
|
+
});
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
ConfiguredI18nModule = __legacyDecorateClassTS([
|
|
357
|
+
Module({
|
|
358
|
+
providers: [
|
|
359
|
+
{
|
|
360
|
+
provide: I18nService,
|
|
361
|
+
useFactory: () => {
|
|
362
|
+
const svc = new I18nService({
|
|
363
|
+
defaultLocale: fullConfig.defaultLocale,
|
|
364
|
+
fallback: fullConfig.fallback,
|
|
365
|
+
fallbackToDefault: fullConfig.fallbackToDefault,
|
|
366
|
+
supportedLocales: fullConfig.supportedLocales
|
|
367
|
+
});
|
|
368
|
+
if (fullConfig.messages) {
|
|
369
|
+
svc.setMessages(fullConfig.messages);
|
|
370
|
+
}
|
|
371
|
+
if (fullConfig.messagesDir) {
|
|
372
|
+
loadFromDir(svc, fullConfig.messagesDir);
|
|
373
|
+
}
|
|
374
|
+
return svc;
|
|
375
|
+
}
|
|
376
|
+
},
|
|
377
|
+
{ provide: I18N_SERVICE_TOKEN, useExisting: I18nService },
|
|
378
|
+
{ provide: "I18N_CONFIG", useValue: fullConfig }
|
|
379
|
+
],
|
|
380
|
+
exports: [I18nService, I18N_SERVICE_TOKEN, "I18N_CONFIG"]
|
|
381
|
+
}),
|
|
382
|
+
__legacyDecorateParamTS(0, Inject(I18N_SERVICE_TOKEN)),
|
|
383
|
+
__legacyMetadataTS("design:paramtypes", [
|
|
384
|
+
typeof I18nService === "undefined" ? Object : I18nService
|
|
385
|
+
])
|
|
386
|
+
], ConfiguredI18nModule);
|
|
387
|
+
Object.defineProperty(ConfiguredI18nModule, "name", {
|
|
388
|
+
value: "ConfiguredI18nModule"
|
|
389
|
+
});
|
|
390
|
+
ConfiguredI18nModule.install = (app) => {};
|
|
391
|
+
return ConfiguredI18nModule;
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
I18nModule = __legacyDecorateClassTS([
|
|
395
|
+
Module({
|
|
396
|
+
providers: [
|
|
397
|
+
I18nService,
|
|
398
|
+
{ provide: I18N_SERVICE_TOKEN, useExisting: I18nService }
|
|
399
|
+
],
|
|
400
|
+
exports: [I18nService, I18N_SERVICE_TOKEN]
|
|
401
|
+
})
|
|
402
|
+
], I18nModule);
|
|
403
|
+
function loadFromDir(svc, dir) {
|
|
404
|
+
let fs;
|
|
405
|
+
let path;
|
|
406
|
+
try {
|
|
407
|
+
fs = __require("fs");
|
|
408
|
+
path = __require("path");
|
|
409
|
+
} catch {
|
|
410
|
+
throw new Error("I18nModule messagesDir requires a Node-like runtime. " + "On Cloudflare Workers, register messages programmatically " + "via I18nModule.forRoot({ messages: {...} }).");
|
|
411
|
+
}
|
|
412
|
+
const stat = fs.statSync(dir);
|
|
413
|
+
if (!stat.isDirectory()) {
|
|
414
|
+
throw new Error(`I18nModule messagesDir is not a directory: ${dir}`);
|
|
415
|
+
}
|
|
416
|
+
for (const file of fs.readdirSync(dir)) {
|
|
417
|
+
if (!file.endsWith(".json"))
|
|
418
|
+
continue;
|
|
419
|
+
const locale = file.replace(/\.json$/, "");
|
|
420
|
+
const content = fs.readFileSync(path.join(dir, file), "utf8");
|
|
421
|
+
try {
|
|
422
|
+
const dict = JSON.parse(content);
|
|
423
|
+
svc.addMessages(locale, dict);
|
|
424
|
+
} catch (err) {
|
|
425
|
+
throw new Error(`I18nModule: failed to parse ${file}: ${err.message}`);
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
// packages/i18n/src/decorators.ts
|
|
430
|
+
import"reflect-metadata";
|
|
431
|
+
import { createParamDecorator } from "@nexusts/core/decorators/params.js";
|
|
432
|
+
import { PARAM_TYPES } from "@nexusts/core/constants.js";
|
|
433
|
+
function CurrentLocale() {
|
|
434
|
+
return createParamDecorator(PARAM_TYPES.USER, {});
|
|
435
|
+
}
|
|
436
|
+
export {
|
|
437
|
+
i18nMiddleware,
|
|
438
|
+
I18nService,
|
|
439
|
+
I18nModule,
|
|
440
|
+
I18N_SERVICE_TOKEN,
|
|
441
|
+
CurrentLocale
|
|
442
|
+
};
|
|
443
|
+
|
|
444
|
+
//# debugId=A86782B0005846E564756E2164756E21
|
|
445
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/service.ts", "../src/module.ts", "../src/middleware.ts", "../src/decorators.ts"],
|
|
4
|
+
"sourcesContent": [
|
|
5
|
+
"/**\n * `I18nService` — locale-aware translation + formatting.\n *\n * Usage:\n *\n * const i18n = new I18nService({\n * defaultLocale: \"en\",\n * messages: {\n * en: { hello: \"Hello, :name!\" },\n * ko: { hello: \"안녕하세요, :name님!\" },\n * },\n * });\n *\n * i18n.t(\"hello\", { name: \"Alice\" }); // → \"Hello, Alice!\"\n * i18n.t(\"hello\", { name: \"Alice\" }, \"ko\"); // → \"안녕하세요, Alice님!\"\n *\n * The service is registered as a DI singleton by\n * `I18nModule.forRoot(config)`. The Hono middleware (built-in)\n * extracts the locale from the request and stores it in the\n * request context. Controllers can read it via the\n * `@CurrentLocale()` decorator.\n *\n * Message format:\n *\n * \"auth.welcome\": \"Welcome, :name!\" # interpolation\n * \"items.count\": \"no items|:count items\" # pluralization (| separator)\n * \"deep.key\": \"value\" # nested lookup\n *\n * The plural-form separator uses `Intl.PluralRules` to pick the\n * right form. The number of `|` segments is configurable per\n * locale via the `pluralCategories()` helper.\n *\n * Locale fallback chain:\n * - Exact match (`fr-CA`)\n * - Region fallback (`fr`)\n * - Default locale\n */\n\nimport type {\n\tLocale,\n\tMessageCatalog,\n\tMessageDict,\n\tTranslateArgs,\n\tPluralCategory,\n\tCurrencyFormatOptions,\n\tDateFormatOptions,\n\tNumberFormatOptions,\n} from \"./types.js\";\n\nconst DEFAULT_PLURAL_CATEGORIES: PluralCategory[] = [\n\t\"zero\",\n\t\"one\",\n\t\"two\",\n\t\"few\",\n\t\"many\",\n\t\"other\",\n];\n\nexport const I18N_SERVICE_TOKEN = Symbol.for(\"nexus:I18nService\");\n\nexport class I18nService {\n\tprivate messages: MessageCatalog = {};\n\tprivate defaultLocale: Locale;\n\tprivate fallback: boolean;\n\tprivate fallbackToDefault: boolean;\n\tprivate supportedLocales: Set<Locale> | null = null;\n\t/** Cached `Intl.PluralRules` per locale. */\n\tprivate pluralRulesCache = new Map<Locale, Intl.PluralRules>();\n\t/** Cached `Intl.DateTimeFormat` per (locale, options) key. */\n\tprivate dateFormatCache = new Map<string, Intl.DateTimeFormat>();\n\t/** Cached `Intl.NumberFormat` per (locale, options) key. */\n\tprivate numberFormatCache = new Map<string, Intl.NumberFormat>();\n\t/** Cached `Intl.Collator` per locale. */\n\tprivate collatorCache = new Map<Locale, Intl.Collator>();\n\n\tconstructor(config: {\n\t\tdefaultLocale?: Locale;\n\t\tfallback?: boolean;\n\t\tfallbackToDefault?: boolean;\n\t\tsupportedLocales?: Locale[];\n\t\tmessages?: MessageCatalog;\n\t} = {}) {\n\t\tthis.defaultLocale = config.defaultLocale ?? \"en\";\n\t\tthis.fallback = config.fallback ?? true;\n\t\tthis.fallbackToDefault = config.fallbackToDefault ?? true;\n\t\tif (config.supportedLocales && config.supportedLocales.length > 0) {\n\t\t\tthis.supportedLocales = new Set(config.supportedLocales);\n\t\t}\n\t\tif (config.messages) {\n\t\t\tfor (const [locale, dict] of Object.entries(config.messages)) {\n\t\t\t\tthis.addMessages(locale, dict);\n\t\t\t}\n\t\t}\n\t}\n\n\t/* ---------------- messages ---------------- */\n\n\t/**\n\t * Add (or merge) messages for a locale. Nested dicts are merged\n\t * recursively — so you can call `addMessages` multiple times\n\t * for the same locale.\n\t */\n\taddMessages(locale: Locale, dict: MessageDict): void {\n\t\tconst existing = this.messages[locale] ?? {};\n\t\tthis.messages[locale] = mergeDict(existing, dict);\n\t}\n\n\t/** Replace the entire message catalog. */\n\tsetMessages(catalog: MessageCatalog): void {\n\t\tthis.messages = {};\n\t\tfor (const [locale, dict] of Object.entries(catalog)) {\n\t\t\tthis.addMessages(locale, dict);\n\t\t}\n\t}\n\n\t/** True if a message key exists in the given locale. */\n\thasMessage(key: string, locale: Locale = this.defaultLocale): boolean {\n\t\treturn resolveKey(this.messages[locale], key) !== undefined;\n\t}\n\n\t/** Get a raw message string (with no interpolation or pluralization). */\n\tgetRaw(key: string, locale: Locale = this.defaultLocale): string | undefined {\n\t\tconst value = resolveKey(this.messages[locale], key);\n\t\treturn typeof value === \"string\" ? value : undefined;\n\t}\n\n\t/* ---------------- translation ---------------- */\n\n\t/**\n\t * Translate a key.\n\t *\n\t * - `args` interpolates `:name`-style placeholders.\n\t * - If the value contains `|` segments, the appropriate plural\n\t * form is selected based on `args.count` (or the first\n\t * numeric arg).\n\t * - `locale` defaults to the configured default locale. Pass\n\t * an explicit locale to override.\n\t *\n\t * If the key is missing in `locale`, the service falls back to\n\t * the default locale (if `fallbackToDefault` is true) and then\n\t * to the key itself (so the developer sees what's missing).\n\t */\n\tt(\n\t\tkey: string,\n\t\targs?: TranslateArgs,\n\t\tlocale: Locale = this.defaultLocale,\n\t): string {\n\t\tconst raw = this.lookupRaw(key, locale);\n\t\tif (raw === undefined) {\n\t\t\t// Key not found. Return the key itself, optionally\n\t\t\t// bracketed, so the developer can spot it.\n\t\t\treturn key;\n\t\t}\n\t\treturn this.format(raw, args, locale);\n\t}\n\n\t/**\n\t * Translate a key; if not found, return the fallback string\n\t * (no `[]` brackets). Useful for optional UI text.\n\t */\n\ttOr(\n\t\tkey: string,\n\t\tfallback: string,\n\t\targs?: TranslateArgs,\n\t\tlocale: Locale = this.defaultLocale,\n\t): string {\n\t\tconst raw = this.lookupRaw(key, locale);\n\t\tif (raw === undefined) return this.format(fallback, args, locale);\n\t\treturn this.format(raw, args, locale);\n\t}\n\n\t/**\n\t * Translate a key with explicit plural control. The `count`\n\t * argument is used to select the plural form.\n\t */\n\ttChoice(\n\t\tkey: string,\n\t\tcount: number,\n\t\targs: TranslateArgs = {},\n\t\tlocale: Locale = this.defaultLocale,\n\t): string {\n\t\treturn this.t(key, { ...args, count }, locale);\n\t}\n\n\t/**\n\t * Format a raw message template with args (interpolation +\n\t * plural selection). Exposed for users who want to format\n\t * messages outside the catalog.\n\t */\n\tformat(template: string, args: TranslateArgs = {}, locale: Locale = this.defaultLocale): string {\n\t\t// 1. Select plural form (if any)\n\t\tconst selected = this.selectPluralForm(template, args, locale);\n\t\t// 2. Interpolate :name placeholders\n\t\treturn this.interpolate(selected, args);\n\t}\n\n\t/* ---------------- pluralization ---------------- */\n\n\t/**\n\t * Return the plural category for `count` in the given locale.\n\t * Wraps `Intl.PluralRules`.\n\t */\n\tpluralCategory(count: number, locale: Locale = this.defaultLocale): PluralCategory {\n\t\tconst rules = this.getPluralRules(locale);\n\t\treturn rules.select(count) as PluralCategory;\n\t}\n\n\t/* ---------------- formatters ---------------- */\n\n\tformatDate(\n\t\tdate: Date | number | string,\n\t\toptions: DateFormatOptions = {},\n\t\tlocale?: Locale,\n\t): string {\n\t\tconst useLocale = options.locale ?? locale ?? this.defaultLocale;\n\t\tconst key = `${useLocale}|${stableStringify(options)}`;\n\t\tlet fmt = this.dateFormatCache.get(key);\n\t\tif (!fmt) {\n\t\t\tfmt = new Intl.DateTimeFormat(useLocale, options);\n\t\t\tthis.dateFormatCache.set(key, fmt);\n\t\t}\n\t\treturn fmt.format(typeof date === \"string\" ? new Date(date) : new Date(date));\n\t}\n\n\tformatNumber(\n\t\tvalue: number,\n\t\toptions: NumberFormatOptions = {},\n\t\tlocale?: Locale,\n\t): string {\n\t\tconst useLocale = options.locale ?? locale ?? this.defaultLocale;\n\t\tconst key = `${useLocale}|${stableStringify(options)}`;\n\t\tlet fmt = this.numberFormatCache.get(key);\n\t\tif (!fmt) {\n\t\t\tfmt = new Intl.NumberFormat(useLocale, options);\n\t\t\tthis.numberFormatCache.set(key, fmt);\n\t\t}\n\t\treturn fmt.format(value);\n\t}\n\n\tformatCurrency(\n\t\tamount: number,\n\t\toptions: CurrencyFormatOptions,\n\t\tlocale?: Locale,\n\t): string {\n\t\tconst useLocale = options.locale ?? locale ?? this.defaultLocale;\n\t\tconst { locale: _omit, ...opts } = options;\n\t\tconst key = `${useLocale}|${stableStringify(opts)}`;\n\t\tlet fmt = this.numberFormatCache.get(key);\n\t\tif (!fmt) {\n\t\t\tfmt = new Intl.NumberFormat(useLocale, { style: \"currency\", ...opts });\n\t\t\tthis.numberFormatCache.set(key, fmt);\n\t\t}\n\t\treturn fmt.format(amount);\n\t}\n\n\t/**\n\t * Compare two strings using locale-aware ordering.\n\t * Returns negative if a < b, positive if a > b, 0 if equal.\n\t */\n\tcompare(a: string, b: string, locale: Locale = this.defaultLocale): number {\n\t\tlet c = this.collatorCache.get(locale);\n\t\tif (!c) {\n\t\t\tc = new Intl.Collator(locale);\n\t\t\tthis.collatorCache.set(locale, c);\n\t\t}\n\t\treturn c.compare(a, b);\n\t}\n\n\t/* ---------------- locales ---------------- */\n\n\tgetDefaultLocale(): Locale {\n\t\treturn this.defaultLocale;\n\t}\n\n\t/** Locales currently registered (in the catalog). */\n\tgetLocales(): Locale[] {\n\t\treturn Object.keys(this.messages);\n\t}\n\n\t/** True if the locale is in the supported set (or all are allowed). */\n\tisSupported(locale: Locale): boolean {\n\t\tif (!this.supportedLocales) return true;\n\t\treturn this.supportedLocales.has(locale);\n\t}\n\n\t/** Negotiate a locale from a list of candidates. */\n\tnegotiateLocale(\n\t\tpreferred: Locale[] = [],\n\t\tacceptLanguage?: string,\n\t): Locale {\n\t\t// Combine preferred (in order) with Accept-Language (sorted by\n\t\t// quality). Preferred wins because the caller may have already\n\t\t// applied query/cookie priorities.\n\t\tconst candidates: Locale[] = [...preferred];\n\t\tif (acceptLanguage) {\n\t\t\tfor (const tag of parseAcceptLanguage(acceptLanguage)) {\n\t\t\t\tif (!candidates.includes(tag)) candidates.push(tag);\n\t\t\t}\n\t\t}\n\t\tfor (const cand of candidates) {\n\t\t\tif (this.isSupported(cand) && this.messages[cand]) return cand;\n\t\t\tif (this.fallback) {\n\t\t\t\tconst lang = cand.split(\"-\")[0];\n\t\t\t\tif (lang && lang !== cand && this.isSupported(lang) && this.messages[lang]) {\n\t\t\t\t\treturn lang;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn this.defaultLocale;\n\t}\n\n\t/* ---------------- internals ---------------- */\n\n\tprivate lookupRaw(key: string, locale: Locale): string | undefined {\n\t\t// 1. Exact match\n\t\tconst v = resolveKey(this.messages[locale], key);\n\t\tif (typeof v === \"string\") return v;\n\n\t\t// 2. Region fallback\n\t\tif (this.fallback) {\n\t\t\tconst lang = locale.split(\"-\")[0];\n\t\t\tif (lang && lang !== locale) {\n\t\t\t\tconst v2 = resolveKey(this.messages[lang], key);\n\t\t\t\tif (typeof v2 === \"string\") return v2;\n\t\t\t}\n\t\t}\n\n\t\t// 3. Default-locale fallback\n\t\tif (this.fallbackToDefault && locale !== this.defaultLocale) {\n\t\t\tconst v3 = resolveKey(this.messages[this.defaultLocale], key);\n\t\t\tif (typeof v3 === \"string\") return v3;\n\t\t}\n\n\t\treturn undefined;\n\t}\n\n\tprivate selectPluralForm(\n\t\ttemplate: string,\n\t\targs: TranslateArgs,\n\t\tlocale: Locale,\n\t): string {\n\t\tif (!template.includes(\"|\")) return template;\n\t\tconst segments = template.split(\"|\");\n\t\tif (segments.length === 1) return template;\n\n\t\t// Find a count in args\n\t\tconst count = pickCount(args);\n\t\tif (count === undefined) {\n\t\t\t// No count → use the \"other\" form (last segment).\n\t\t\treturn segments[segments.length - 1]!;\n\t\t}\n\n\t\tconst category = this.pluralCategory(count, locale);\n\t\tconst idx = this.pluralIndex(category, segments.length);\n\t\treturn segments[idx]!;\n\t}\n\n\tprivate pluralIndex(category: PluralCategory, segmentCount: number): number {\n\t\t// Map plural category to a segment index. The number of\n\t\t// segments the user provides maps to CLDR categories as\n\t\t// follows (the AdonisJS / i18next convention):\n\t\t// 1 segment → other\n\t\t// 2 segments → one | other\n\t\t// 3 segments → zero | one | other\n\t\t// 4 segments → zero | one | two | other\n\t\t// 5 segments → zero | one | two | few | other\n\t\t// 6 segments → zero | one | two | few | many | other\n\t\t// Categories not in the table (e.g. \"few\" with 3 segments)\n\t\t// fall back to the last segment (\"other\").\n\t\tif (segmentCount <= 1) return segmentCount - 1;\n\t\t// Build a mapping table.\n\t\tconst table: Record<number, PluralCategory[]> = {\n\t\t\t2: [\"one\", \"other\"],\n\t\t\t3: [\"zero\", \"one\", \"other\"],\n\t\t\t4: [\"zero\", \"one\", \"two\", \"other\"],\n\t\t\t5: [\"zero\", \"one\", \"two\", \"few\", \"other\"],\n\t\t\t6: [\"zero\", \"one\", \"two\", \"few\", \"many\", \"other\"],\n\t\t};\n\t\tconst cats = table[segmentCount] ?? [\"other\"];\n\t\tconst i = cats.indexOf(category);\n\t\tif (i < 0) return segmentCount - 1;\n\t\treturn i;\n\t}\n\n\tprivate getPluralRules(locale: Locale): Intl.PluralRules {\n\t\tlet r = this.pluralRulesCache.get(locale);\n\t\tif (!r) {\n\t\t\ttry {\n\t\t\t\tr = new Intl.PluralRules(locale);\n\t\t\t} catch {\n\t\t\t\tr = new Intl.PluralRules(this.defaultLocale);\n\t\t\t}\n\t\t\tthis.pluralRulesCache.set(locale, r);\n\t\t}\n\t\treturn r;\n\t}\n\n\tprivate interpolate(template: string, args: TranslateArgs): string {\n\t\tif (Object.keys(args).length === 0) return template;\n\t\treturn template.replace(/:([A-Za-z_][A-Za-z0-9_]*)/g, (match, name: string) => {\n\t\t\tif (name in args) {\n\t\t\t\treturn String(args[name]);\n\t\t\t}\n\t\t\treturn match;\n\t\t});\n\t}\n}\n\n/* ------------------------------------------------------------------ *\n * Helpers\n * ------------------------------------------------------------------ */\n\nfunction resolveKey(\n\tdict: MessageDict | undefined,\n\tkey: string,\n): string | MessageDict | undefined {\n\tif (!dict) return undefined;\n\tconst parts = key.split(\".\");\n\tlet cur: string | MessageDict | undefined = dict;\n\tfor (const p of parts) {\n\t\tif (cur && typeof cur === \"object\" && p in (cur as MessageDict)) {\n\t\t\tcur = (cur as MessageDict)[p];\n\t\t} else {\n\t\t\treturn undefined;\n\t\t}\n\t}\n\treturn cur;\n}\n\nfunction mergeDict(target: MessageDict, source: MessageDict): MessageDict {\n\tconst out: MessageDict = { ...target };\n\tfor (const [k, v] of Object.entries(source)) {\n\t\tconst existing = out[k];\n\t\tif (\n\t\t\texisting &&\n\t\t\ttypeof existing === \"object\" &&\n\t\t\t!Array.isArray(existing) &&\n\t\t\tv &&\n\t\t\ttypeof v === \"object\" &&\n\t\t\t!Array.isArray(v)\n\t\t) {\n\t\t\tout[k] = mergeDict(existing as MessageDict, v as MessageDict);\n\t\t} else {\n\t\t\tout[k] = v;\n\t\t}\n\t}\n\treturn out;\n}\n\nfunction pickCount(args: TranslateArgs): number | undefined {\n\tif (\"count\" in args && typeof args.count === \"number\") return args.count;\n\tfor (const v of Object.values(args)) {\n\t\tif (typeof v === \"number\") return v;\n\t}\n\treturn undefined;\n}\n\nfunction parseAcceptLanguage(header: string): Locale[] {\n\treturn header\n\t\t.split(\",\")\n\t\t.map((part) => {\n\t\t\tconst [tag, qPart] = part.trim().split(\";\");\n\t\t\tconst q = qPart?.startsWith(\"q=\") ? Number(qPart.slice(2)) : 1;\n\t\t\treturn { tag: tag?.trim() ?? \"\", q: isFinite(q) ? q : 1 };\n\t\t})\n\t\t.filter((e) => e.tag.length > 0)\n\t\t.sort((a, b) => b.q - a.q)\n\t\t.map((e) => e.tag);\n}\n\nfunction stableStringify(obj: object): string {\n\treturn JSON.stringify(obj, (_k, v) => {\n\t\tif (v && typeof v === \"object\" && !Array.isArray(v)) {\n\t\t\tconst sorted: Record<string, unknown> = {};\n\t\t\tfor (const key of Object.keys(v).sort()) {\n\t\t\t\tsorted[key] = (v as Record<string, unknown>)[key];\n\t\t\t}\n\t\t\treturn sorted;\n\t\t}\n\t\treturn v;\n\t});\n}\n",
|
|
6
|
+
"/**\n * `I18nModule` — wires `I18nService` into the DI container.\n *\n * Usage:\n *\n * @Module({\n * imports: [\n * I18nModule.forRoot({\n * defaultLocale: \"en\",\n * messages: {\n * en: { hello: \"Hello, :name!\" },\n * ko: { hello: \"안녕하세요, :name님!\" },\n * },\n * }),\n * ],\n * })\n * class AppModule {}\n *\n * The middleware is also exported (`i18nMiddleware()`) and\n * installed by the module's static `install()` helper:\n *\n * const app = new Application(AppModule);\n * I18nModule.install(app, container); // or via @Module middleware config\n */\n\nimport { Module } from \"@nexusts/core/decorators/module.js\";\nimport { Inject } from \"@nexusts/core/decorators/injectable.js\";\nimport \"reflect-metadata\";\nimport type { Application } from \"@nexusts/core/application.js\";\nimport {\n\tI18nService,\n\tI18N_SERVICE_TOKEN,\n} from \"./service.js\";\nimport { i18nMiddleware } from \"./middleware.js\";\nimport type { I18nConfig, Locale, MessageCatalog } from \"./types.js\";\n\n@Module({\n\tproviders: [\n\t\tI18nService,\n\t\t{ provide: I18N_SERVICE_TOKEN, useExisting: I18nService },\n\t],\n\texports: [I18nService, I18N_SERVICE_TOKEN],\n})\nexport class I18nModule {\n\tstatic forRoot(config: I18nConfig = {}) {\n\t\tconst fullConfig: Required<I18nConfig> = {\n\t\t\tdefaultLocale: config.defaultLocale ?? \"en\",\n\t\t\tfallback: config.fallback ?? true,\n\t\t\tfallbackToDefault: config.fallbackToDefault ?? true,\n\t\t\tsupportedLocales: config.supportedLocales ?? [],\n\t\t\tmessages: config.messages ?? {},\n\t\t\tmessagesDir: config.messagesDir ?? \"\",\n\t\t\tdetectQueryKey: config.detectQueryKey ?? \"lang\",\n\t\t\tdetectCookieKey: config.detectCookieKey ?? \"lang\",\n\t\t};\n\n\t\t@Module({\n\t\t\tproviders: [\n\t\t\t\t{\n\t\t\t\t\tprovide: I18nService,\n\t\t\t\t\tuseFactory: () => {\n\t\t\t\t\t\tconst svc = new I18nService({\n\t\t\t\t\t\t\tdefaultLocale: fullConfig.defaultLocale,\n\t\t\t\t\t\t\tfallback: fullConfig.fallback,\n\t\t\t\t\t\t\tfallbackToDefault: fullConfig.fallbackToDefault,\n\t\t\t\t\t\t\tsupportedLocales: fullConfig.supportedLocales,\n\t\t\t\t\t\t});\n\t\t\t\t\t\tif (fullConfig.messages) {\n\t\t\t\t\t\t\tsvc.setMessages(fullConfig.messages as MessageCatalog);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (fullConfig.messagesDir) {\n\t\t\t\t\t\t\tloadFromDir(svc, fullConfig.messagesDir);\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn svc;\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\t{ provide: I18N_SERVICE_TOKEN, useExisting: I18nService },\n\t\t\t\t{ provide: \"I18N_CONFIG\", useValue: fullConfig },\n\t\t\t],\n\t\t\texports: [I18nService, I18N_SERVICE_TOKEN, \"I18N_CONFIG\"],\n\t\t})\n\t\tclass ConfiguredI18nModule {\n\t\t\tconstructor(@Inject(I18N_SERVICE_TOKEN) readonly service: I18nService) {}\n\n\t\t\t/** Returns a Hono middleware bound to the configured service. */\n\t\t\tmiddleware() {\n\t\t\t\treturn i18nMiddleware(this.service, {\n\t\t\t\t\tqueryKey: fullConfig.detectQueryKey,\n\t\t\t\t\tcookieKey: fullConfig.detectCookieKey,\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t\tObject.defineProperty(ConfiguredI18nModule, \"name\", {\n\t\t\tvalue: \"ConfiguredI18nModule\",\n\t\t});\n\n\t\t// Add static `install` helper.\n\t\t(ConfiguredI18nModule as unknown as { install: (app: Application) => void }).install = (\n\t\t\tapp: Application,\n\t\t) => {\n\t\t\t// Lazy: the module is constructed when the app boots.\n\t\t\t// The user typically accesses the middleware via\n\t\t\t// `app.i18nMiddleware()` instead.\n\t\t\tvoid app;\n\t\t};\n\n\t\treturn ConfiguredI18nModule as unknown as typeof ConfiguredI18nModule & {\n\t\t\treadonly config: typeof fullConfig;\n\t\t};\n\t}\n}\n\n/* ------------------------------------------------------------------ *\n * Loader: messages from disk\n * ------------------------------------------------------------------ */\n\nfunction loadFromDir(svc: I18nService, dir: string): void {\n\t// Lazy-import `node:fs` so this module is edge-safe.\n\tlet fs: typeof import(\"node:fs\");\n\tlet path: typeof import(\"node:path\");\n\ttry {\n\t\tfs = require(\"node:fs\");\n\t\tpath = require(\"node:path\");\n\t} catch {\n\t\tthrow new Error(\n\t\t\t\"I18nModule messagesDir requires a Node-like runtime. \" +\n\t\t\t\t\"On Cloudflare Workers, register messages programmatically \" +\n\t\t\t\t\"via I18nModule.forRoot({ messages: {...} }).\",\n\t\t);\n\t}\n\n\tconst stat = fs.statSync(dir);\n\tif (!stat.isDirectory()) {\n\t\tthrow new Error(`I18nModule messagesDir is not a directory: ${dir}`);\n\t}\n\tfor (const file of fs.readdirSync(dir)) {\n\t\tif (!file.endsWith(\".json\")) continue;\n\t\tconst locale = file.replace(/\\.json$/, \"\");\n\t\tconst content = fs.readFileSync(path.join(dir, file), \"utf8\");\n\t\ttry {\n\t\t\tconst dict = JSON.parse(content);\n\t\t\tsvc.addMessages(locale, dict);\n\t\t} catch (err) {\n\t\t\tthrow new Error(\n\t\t\t\t`I18nModule: failed to parse ${file}: ${(err as Error).message}`,\n\t\t\t);\n\t\t}\n\t}\n}\n",
|
|
7
|
+
"/**\n * Hono middleware that detects the active locale for a request\n * and stores it in the Hono context (`c.var.locale`).\n *\n * Detection priority:\n * 1. Query string: `?lang=ko`\n * 2. Cookie: `lang=ko`\n * 3. `Accept-Language` header\n * 4. Default locale\n *\n * The middleware uses `I18nService.negotiateLocale()` to pick\n * the best locale based on the request headers and the\n * service's registered catalog.\n */\n\nimport type { Context, MiddlewareHandler, Next } from \"hono\";\nimport type { I18nService } from \"./service.js\";\nimport type { DetectOptions, Locale } from \"./types.js\";\n\n/** Augment Hono's `c.var` with the active locale. */\ndeclare module \"hono\" {\n\tinterface ContextVariableMap {\n\t\tlocale?: Locale;\n\t\ti18n?: I18nService;\n\t}\n}\n\nexport function i18nMiddleware(\n\tservice: I18nService,\n\toptions: DetectOptions = {},\n): MiddlewareHandler {\n\tconst queryKey = options.queryKey ?? \"lang\";\n\tconst cookieKey = options.cookieKey ?? \"lang\";\n\tconst defaultLocale = options.defaultLocale ?? service.getDefaultLocale();\n\n\treturn async (c: Context, next: Next) => {\n\t\tconst fromQuery = c.req.query(queryKey);\n\t\tconst fromCookie = getCookie(c, cookieKey);\n\t\tconst acceptLanguage = c.req.header(\"accept-language\");\n\n\t\tconst preferred: Locale[] = [];\n\t\tif (fromQuery) preferred.push(fromQuery);\n\t\tif (fromCookie) preferred.push(fromCookie);\n\t\tif (acceptLanguage) {\n\t\t\tpreferred.push(...parseAcceptLanguage(acceptLanguage));\n\t\t}\n\t\tpreferred.push(defaultLocale);\n\n\t\tconst locale = service.negotiateLocale(preferred, acceptLanguage);\n\t\tc.set(\"locale\", locale);\n\t\tc.set(\"i18n\", service);\n\t\tawait next();\n\t};\n}\n\n/* ------------------------------------------------------------------ *\n * Helpers\n * ------------------------------------------------------------------ */\n\nfunction getCookie(c: Context, name: string): string | undefined {\n\t// Hono parses cookies lazily via `c.req.header(\"cookie\")`. We\n\t// parse a small subset here; for production apps with many\n\t// cookies, prefer `hono/cookie`.\n\tconst raw = c.req.header(\"cookie\");\n\tif (!raw) return undefined;\n\tfor (const part of raw.split(\";\")) {\n\t\tconst [k, ...rest] = part.trim().split(\"=\");\n\t\tif (k === name) return decodeURIComponent(rest.join(\"=\"));\n\t}\n\treturn undefined;\n}\n\nfunction parseAcceptLanguage(header: string): Locale[] {\n\treturn header\n\t\t.split(\",\")\n\t\t.map((part) => {\n\t\t\tconst [tag, qPart] = part.trim().split(\";\");\n\t\t\tconst q = qPart?.startsWith(\"q=\") ? Number(qPart.slice(2)) : 1;\n\t\t\treturn { tag: tag?.trim() ?? \"\", q: isFinite(q) ? q : 1 };\n\t\t})\n\t\t.filter((e) => e.tag.length > 0)\n\t\t.sort((a, b) => b.q - a.q)\n\t\t.map((e) => e.tag);\n}\n",
|
|
8
|
+
"/**\n * `@CurrentLocale()` — controller parameter decorator that\n * injects the active locale string (e.g. `\"en\"`, `\"ko\"`).\n *\n * Requires that `i18nMiddleware()` has been installed upstream.\n *\n * @Get('/')\n * index(@CurrentLocale() locale: string) {\n * return { locale };\n * }\n */\n\nimport \"reflect-metadata\";\nimport { createParamDecorator } from \"@nexusts/core/decorators/params.js\";\nimport { PARAM_TYPES } from \"@nexusts/core/constants.js\";\nimport type { Locale } from \"./types.js\";\n\nexport function CurrentLocale(): ParameterDecorator {\n\treturn createParamDecorator(PARAM_TYPES.USER, {} as never) as ParameterDecorator;\n}\n"
|
|
9
|
+
],
|
|
10
|
+
"mappings": "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",
|
|
11
|
+
"debugId": "A86782B0005846E564756E2164756E21",
|
|
12
|
+
"names": []
|
|
13
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@nexusts/i18n",
|
|
3
|
+
"version": "0.7.0",
|
|
4
|
+
"description": "Internationalization (Intl-based, pluralization)",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"module": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"import": "./dist/index.js"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"dist",
|
|
17
|
+
"README.md"
|
|
18
|
+
],
|
|
19
|
+
"scripts": {
|
|
20
|
+
"build": "bun run ../../build.ts"
|
|
21
|
+
},
|
|
22
|
+
"keywords": [
|
|
23
|
+
"nexusts",
|
|
24
|
+
"framework",
|
|
25
|
+
"bun"
|
|
26
|
+
],
|
|
27
|
+
"license": "MIT",
|
|
28
|
+
"dependencies": {
|
|
29
|
+
"@nexusts/core": "^0.7.0"
|
|
30
|
+
}
|
|
31
|
+
}
|