@gravito/cosmos 2.0.0 → 3.0.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/.turbo/turbo-build.log +20 -0
- package/.turbo/turbo-test$colon$ci.log +35 -0
- package/.turbo/turbo-test$colon$coverage.log +35 -0
- package/.turbo/turbo-test.log +27 -0
- package/.turbo/turbo-typecheck.log +2 -0
- package/CHANGELOG.md +14 -0
- package/build.ts +35 -0
- package/dist/index.cjs +309 -0
- package/dist/index.d.cts +274 -0
- package/dist/index.d.ts +274 -0
- package/dist/index.js +140 -31
- package/package.json +18 -6
- package/src/I18nService.ts +95 -16
- package/src/index.ts +32 -6
- package/tests/manager.test.ts +0 -1
- package/tests/service.test.ts +5 -0
- package/dist/src/index.js +0 -173
package/dist/index.js
CHANGED
|
@@ -1,40 +1,87 @@
|
|
|
1
1
|
// src/I18nService.ts
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
2
|
+
var I18nInstance = class _I18nInstance {
|
|
3
|
+
/**
|
|
4
|
+
* Create a new I18nInstance.
|
|
5
|
+
*
|
|
6
|
+
* @param manager - The I18nManager instance.
|
|
7
|
+
* @param initialLocale - The initial locale for this instance.
|
|
8
|
+
*/
|
|
5
9
|
constructor(manager, initialLocale) {
|
|
6
10
|
this.manager = manager;
|
|
7
11
|
this._locale = initialLocale;
|
|
8
12
|
}
|
|
13
|
+
_locale;
|
|
9
14
|
get locale() {
|
|
10
15
|
return this._locale;
|
|
11
16
|
}
|
|
12
17
|
set locale(value) {
|
|
13
18
|
this.setLocale(value);
|
|
14
19
|
}
|
|
20
|
+
/**
|
|
21
|
+
* Set the current locale.
|
|
22
|
+
*
|
|
23
|
+
* @param locale - The locale to set.
|
|
24
|
+
*/
|
|
15
25
|
setLocale(locale) {
|
|
16
26
|
if (this.manager.getConfig().supportedLocales.includes(locale)) {
|
|
17
27
|
this._locale = locale;
|
|
18
28
|
}
|
|
19
29
|
}
|
|
30
|
+
/**
|
|
31
|
+
* Get the current locale.
|
|
32
|
+
*
|
|
33
|
+
* @returns The current locale string.
|
|
34
|
+
*/
|
|
20
35
|
getLocale() {
|
|
21
36
|
return this._locale;
|
|
22
37
|
}
|
|
38
|
+
/**
|
|
39
|
+
* Translate a key.
|
|
40
|
+
*
|
|
41
|
+
* @param key - The translation key (e.g., 'messages.welcome').
|
|
42
|
+
* @param replacements - Optional replacements for parameters in the translation string.
|
|
43
|
+
* @returns The translated string, or the key if not found.
|
|
44
|
+
*/
|
|
23
45
|
t(key, replacements) {
|
|
24
46
|
return this.manager.translate(this._locale, key, replacements);
|
|
25
47
|
}
|
|
48
|
+
/**
|
|
49
|
+
* Check if a translation key exists.
|
|
50
|
+
*
|
|
51
|
+
* @param key - The translation key to check.
|
|
52
|
+
* @returns True if the key exists, false otherwise.
|
|
53
|
+
*/
|
|
26
54
|
has(key) {
|
|
27
55
|
return this.t(key) !== key;
|
|
28
56
|
}
|
|
57
|
+
/**
|
|
58
|
+
* Clone the current instance with a potentially new locale.
|
|
59
|
+
*
|
|
60
|
+
* @param locale - Optional new locale for the cloned instance.
|
|
61
|
+
* @returns A new I18nInstance.
|
|
62
|
+
*/
|
|
29
63
|
clone(locale) {
|
|
30
|
-
return new
|
|
64
|
+
return new _I18nInstance(this.manager, locale || this._locale);
|
|
31
65
|
}
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
66
|
+
/**
|
|
67
|
+
* Get the I18n configuration.
|
|
68
|
+
*/
|
|
69
|
+
getConfig() {
|
|
70
|
+
return this.manager.getConfig();
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Get the translations.
|
|
74
|
+
*/
|
|
75
|
+
get translations() {
|
|
76
|
+
return this.manager.translations;
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
var I18nManager = class {
|
|
80
|
+
/**
|
|
81
|
+
* Create a new I18nManager.
|
|
82
|
+
*
|
|
83
|
+
* @param config - The I18n configuration.
|
|
84
|
+
*/
|
|
38
85
|
constructor(config) {
|
|
39
86
|
this.config = config;
|
|
40
87
|
if (config.translations) {
|
|
@@ -42,36 +89,84 @@ class I18nManager {
|
|
|
42
89
|
}
|
|
43
90
|
this.globalInstance = new I18nInstance(this, config.defaultLocale);
|
|
44
91
|
}
|
|
92
|
+
translations = {};
|
|
93
|
+
// Default instance for global usage (e.g. CLI or background jobs)
|
|
94
|
+
globalInstance;
|
|
95
|
+
// --- I18nService Implementation (Delegates to global instance) ---
|
|
45
96
|
get locale() {
|
|
46
97
|
return this.globalInstance.locale;
|
|
47
98
|
}
|
|
48
99
|
set locale(value) {
|
|
49
100
|
this.globalInstance.locale = value;
|
|
50
101
|
}
|
|
102
|
+
/**
|
|
103
|
+
* Set the global locale.
|
|
104
|
+
*
|
|
105
|
+
* @param locale - The locale to set.
|
|
106
|
+
*/
|
|
51
107
|
setLocale(locale) {
|
|
52
108
|
this.globalInstance.setLocale(locale);
|
|
53
109
|
}
|
|
110
|
+
/**
|
|
111
|
+
* Get the global locale.
|
|
112
|
+
*
|
|
113
|
+
* @returns The global locale string.
|
|
114
|
+
*/
|
|
54
115
|
getLocale() {
|
|
55
116
|
return this.globalInstance.getLocale();
|
|
56
117
|
}
|
|
118
|
+
/**
|
|
119
|
+
* Translate a key using the global locale.
|
|
120
|
+
*
|
|
121
|
+
* @param key - The translation key.
|
|
122
|
+
* @param replacements - Optional replacements.
|
|
123
|
+
* @returns The translated string.
|
|
124
|
+
*/
|
|
57
125
|
t(key, replacements) {
|
|
58
126
|
return this.globalInstance.t(key, replacements);
|
|
59
127
|
}
|
|
128
|
+
/**
|
|
129
|
+
* Check if a translation key exists in the global locale.
|
|
130
|
+
*
|
|
131
|
+
* @param key - The translation key.
|
|
132
|
+
* @returns True if found.
|
|
133
|
+
*/
|
|
60
134
|
has(key) {
|
|
61
135
|
return this.globalInstance.has(key);
|
|
62
136
|
}
|
|
137
|
+
/**
|
|
138
|
+
* Clone the global instance.
|
|
139
|
+
*
|
|
140
|
+
* @param locale - Optional locale for the clone.
|
|
141
|
+
* @returns A new I18nInstance.
|
|
142
|
+
*/
|
|
63
143
|
clone(locale) {
|
|
64
144
|
return new I18nInstance(this, locale || this.config.defaultLocale);
|
|
65
145
|
}
|
|
146
|
+
// --- Manager Internal API ---
|
|
147
|
+
/**
|
|
148
|
+
* Get the I18n configuration.
|
|
149
|
+
*
|
|
150
|
+
* @returns The configuration object.
|
|
151
|
+
*/
|
|
66
152
|
getConfig() {
|
|
67
153
|
return this.config;
|
|
68
154
|
}
|
|
155
|
+
/**
|
|
156
|
+
* Add a resource bundle for a specific locale.
|
|
157
|
+
*
|
|
158
|
+
* @param locale - The locale string.
|
|
159
|
+
* @param translations - The translations object.
|
|
160
|
+
*/
|
|
69
161
|
addResource(locale, translations) {
|
|
70
162
|
this.translations[locale] = {
|
|
71
163
|
...this.translations[locale] || {},
|
|
72
164
|
...translations
|
|
73
165
|
};
|
|
74
166
|
}
|
|
167
|
+
/**
|
|
168
|
+
* Internal translation logic used by instances
|
|
169
|
+
*/
|
|
75
170
|
translate(locale, key, replacements) {
|
|
76
171
|
const keys = key.split(".");
|
|
77
172
|
let value = this.translations[locale];
|
|
@@ -79,33 +174,34 @@ class I18nManager {
|
|
|
79
174
|
if (value && typeof value === "object" && k in value) {
|
|
80
175
|
value = value[k];
|
|
81
176
|
} else {
|
|
82
|
-
value =
|
|
177
|
+
value = void 0;
|
|
83
178
|
break;
|
|
84
179
|
}
|
|
85
180
|
}
|
|
86
|
-
if (value ===
|
|
181
|
+
if (value === void 0 && locale !== this.config.defaultLocale) {
|
|
87
182
|
let fallbackValue = this.translations[this.config.defaultLocale];
|
|
88
183
|
for (const k of keys) {
|
|
89
184
|
if (fallbackValue && typeof fallbackValue === "object" && k in fallbackValue) {
|
|
90
185
|
fallbackValue = fallbackValue[k];
|
|
91
186
|
} else {
|
|
92
|
-
fallbackValue =
|
|
187
|
+
fallbackValue = void 0;
|
|
93
188
|
break;
|
|
94
189
|
}
|
|
95
190
|
}
|
|
96
191
|
value = fallbackValue;
|
|
97
192
|
}
|
|
98
|
-
if (value ===
|
|
193
|
+
if (value === void 0 || typeof value !== "string") {
|
|
99
194
|
return key;
|
|
100
195
|
}
|
|
101
|
-
if (replacements) {
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
}
|
|
196
|
+
if (replacements && Object.keys(replacements).length > 0) {
|
|
197
|
+
value = value.replace(REPLACEMENT_REGEX, (match, key2) => {
|
|
198
|
+
return replacements[key2] !== void 0 ? String(replacements[key2]) : match;
|
|
199
|
+
});
|
|
105
200
|
}
|
|
106
201
|
return value;
|
|
107
202
|
}
|
|
108
|
-
}
|
|
203
|
+
};
|
|
204
|
+
var REPLACEMENT_REGEX = /:([a-zA-Z0-9_]+)/g;
|
|
109
205
|
var localeMiddleware = (i18nManager) => {
|
|
110
206
|
return async (c, next) => {
|
|
111
207
|
let locale = c.req.param("locale") || c.req.query("lang");
|
|
@@ -117,12 +213,13 @@ var localeMiddleware = (i18nManager) => {
|
|
|
117
213
|
}
|
|
118
214
|
const i18n = i18nManager.clone(locale);
|
|
119
215
|
c.set("i18n", i18n);
|
|
120
|
-
await next();
|
|
216
|
+
return await next();
|
|
121
217
|
};
|
|
122
218
|
};
|
|
219
|
+
|
|
123
220
|
// src/loader.ts
|
|
124
|
-
import { readdir, readFile } from "
|
|
125
|
-
import { join, parse } from "
|
|
221
|
+
import { readdir, readFile } from "fs/promises";
|
|
222
|
+
import { join, parse } from "path";
|
|
126
223
|
async function loadTranslations(directory) {
|
|
127
224
|
const translations = {};
|
|
128
225
|
try {
|
|
@@ -140,29 +237,41 @@ async function loadTranslations(directory) {
|
|
|
140
237
|
}
|
|
141
238
|
}
|
|
142
239
|
} catch (_e) {
|
|
143
|
-
console.warn(
|
|
240
|
+
console.warn(
|
|
241
|
+
`[Orbit-I18n] Could not load translations from ${directory}. Directory might not exist.`
|
|
242
|
+
);
|
|
144
243
|
}
|
|
145
244
|
return translations;
|
|
146
245
|
}
|
|
147
246
|
|
|
148
247
|
// src/index.ts
|
|
149
|
-
|
|
150
|
-
|
|
248
|
+
var OrbitCosmos = class {
|
|
249
|
+
/**
|
|
250
|
+
* Create a new OrbitCosmos instance.
|
|
251
|
+
* @param config - The i18n configuration options.
|
|
252
|
+
*/
|
|
151
253
|
constructor(config) {
|
|
152
254
|
this.config = config;
|
|
153
255
|
}
|
|
256
|
+
/**
|
|
257
|
+
* Install the i18n service into PlanetCore.
|
|
258
|
+
* Registers the I18nManager and sets up the locale middleware.
|
|
259
|
+
*
|
|
260
|
+
* @param core - The PlanetCore instance.
|
|
261
|
+
*/
|
|
154
262
|
install(core) {
|
|
155
263
|
const i18nManager = new I18nManager(this.config);
|
|
264
|
+
core.container.instance("i18n", i18nManager);
|
|
156
265
|
core.adapter.use("*", localeMiddleware(i18nManager));
|
|
157
266
|
core.logger.info(`[OrbitCosmos] I18n initialized with locale: ${this.config.defaultLocale}`);
|
|
158
267
|
}
|
|
159
|
-
}
|
|
268
|
+
};
|
|
160
269
|
var I18nOrbit = OrbitCosmos;
|
|
161
270
|
export {
|
|
162
|
-
|
|
163
|
-
loadTranslations,
|
|
164
|
-
OrbitCosmos,
|
|
165
|
-
I18nOrbit,
|
|
271
|
+
I18nInstance,
|
|
166
272
|
I18nManager,
|
|
167
|
-
|
|
273
|
+
I18nOrbit,
|
|
274
|
+
OrbitCosmos,
|
|
275
|
+
loadTranslations,
|
|
276
|
+
localeMiddleware
|
|
168
277
|
};
|
package/package.json
CHANGED
|
@@ -1,11 +1,21 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gravito/cosmos",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.1",
|
|
4
4
|
"description": "Internationalization orbit for Gravito framework",
|
|
5
|
-
"main": "dist/index.
|
|
6
|
-
"
|
|
5
|
+
"main": "./dist/index.cjs",
|
|
6
|
+
"module": "./dist/index.js",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"sideEffects": false,
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"exports": {
|
|
11
|
+
".": {
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"import": "./dist/index.js",
|
|
14
|
+
"require": "./dist/index.cjs"
|
|
15
|
+
}
|
|
16
|
+
},
|
|
7
17
|
"scripts": {
|
|
8
|
-
"build": "bun build
|
|
18
|
+
"build": "bun run build.ts",
|
|
9
19
|
"test": "bun test",
|
|
10
20
|
"test:coverage": "bun test --coverage --coverage-threshold=80",
|
|
11
21
|
"test:ci": "bun test --coverage --coverage-threshold=80",
|
|
@@ -24,7 +34,9 @@
|
|
|
24
34
|
"@gravito/photon": "workspace:*"
|
|
25
35
|
},
|
|
26
36
|
"devDependencies": {
|
|
27
|
-
"bun-types": "latest"
|
|
37
|
+
"bun-types": "latest",
|
|
38
|
+
"tsup": "^8.5.1",
|
|
39
|
+
"typescript": "^5.9.3"
|
|
28
40
|
},
|
|
29
41
|
"publishConfig": {
|
|
30
42
|
"access": "public"
|
|
@@ -35,4 +47,4 @@
|
|
|
35
47
|
"url": "git+https://github.com/gravito-framework/gravito.git",
|
|
36
48
|
"directory": "packages/cosmos"
|
|
37
49
|
}
|
|
38
|
-
}
|
|
50
|
+
}
|
package/src/I18nService.ts
CHANGED
|
@@ -1,24 +1,84 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { GravitoMiddleware } from '@gravito/core'
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* A map of translations where keys are translation keys and values
|
|
5
|
+
* are either the translated string or a nested map for grouping.
|
|
6
|
+
*
|
|
7
|
+
* @public
|
|
8
|
+
* @since 3.0.0
|
|
9
|
+
*/
|
|
3
10
|
export type TranslationMap = {
|
|
4
11
|
[key: string]: string | TranslationMap
|
|
5
12
|
}
|
|
6
13
|
|
|
14
|
+
/**
|
|
15
|
+
* Configuration for the I18n service.
|
|
16
|
+
*
|
|
17
|
+
* @public
|
|
18
|
+
* @since 3.0.0
|
|
19
|
+
*/
|
|
7
20
|
export interface I18nConfig {
|
|
21
|
+
/** The fallback locale to use when the requested one is not found. */
|
|
8
22
|
defaultLocale: string
|
|
23
|
+
/** List of locales officially supported by the application. */
|
|
9
24
|
supportedLocales: string[]
|
|
10
|
-
|
|
11
|
-
|
|
25
|
+
/**
|
|
26
|
+
* Optional record of translations indexed by locale.
|
|
27
|
+
* Keys are locale strings (e.g., 'en', 'zh-TW').
|
|
28
|
+
*/
|
|
12
29
|
translations?: Record<string, TranslationMap>
|
|
13
30
|
}
|
|
14
31
|
|
|
32
|
+
/**
|
|
33
|
+
* Interface for the I18n service providing translation capabilities.
|
|
34
|
+
*
|
|
35
|
+
* It allows for setting and getting the current locale, translating strings
|
|
36
|
+
* with optional replacements, and checking for key existence.
|
|
37
|
+
*
|
|
38
|
+
* @public
|
|
39
|
+
* @since 3.0.0
|
|
40
|
+
*/
|
|
15
41
|
export interface I18nService {
|
|
42
|
+
/** The current active locale. */
|
|
16
43
|
locale: string
|
|
44
|
+
/**
|
|
45
|
+
* Set the active locale for this service instance.
|
|
46
|
+
*
|
|
47
|
+
* @param locale - Valid locale string from supportedLocales.
|
|
48
|
+
*/
|
|
17
49
|
setLocale(locale: string): void
|
|
50
|
+
/**
|
|
51
|
+
* Get the current active locale.
|
|
52
|
+
*
|
|
53
|
+
* @returns Current locale string.
|
|
54
|
+
*/
|
|
18
55
|
getLocale(): string
|
|
56
|
+
/**
|
|
57
|
+
* Translate a key into the current locale.
|
|
58
|
+
*
|
|
59
|
+
* @param key - The translation key (e.g., 'auth.login_success').
|
|
60
|
+
* @param replacements - Optional placeholders in the format `:key` replaced by values.
|
|
61
|
+
* @returns The translated string, or the key itself if not found.
|
|
62
|
+
*
|
|
63
|
+
* @example
|
|
64
|
+
* ```typescript
|
|
65
|
+
* i18n.t('messages.hello', { name: 'John' }); // "Hello John"
|
|
66
|
+
* ```
|
|
67
|
+
*/
|
|
19
68
|
t(key: string, replacements?: Record<string, string | number>): string
|
|
69
|
+
/**
|
|
70
|
+
* Check if a translation key exists for the current locale.
|
|
71
|
+
*
|
|
72
|
+
* @param key - The key to check.
|
|
73
|
+
* @returns True if the key exists, false otherwise.
|
|
74
|
+
*/
|
|
20
75
|
has(key: string): boolean
|
|
21
|
-
|
|
76
|
+
/**
|
|
77
|
+
* Create a new request-scoped instance of the I18n service.
|
|
78
|
+
*
|
|
79
|
+
* @param locale - Optional initial locale for the new instance.
|
|
80
|
+
* @returns A new I18nService instance.
|
|
81
|
+
*/
|
|
22
82
|
clone(locale?: string): I18nService
|
|
23
83
|
}
|
|
24
84
|
|
|
@@ -36,7 +96,7 @@ export class I18nInstance implements I18nService {
|
|
|
36
96
|
* @param initialLocale - The initial locale for this instance.
|
|
37
97
|
*/
|
|
38
98
|
constructor(
|
|
39
|
-
|
|
99
|
+
public readonly manager: I18nManager,
|
|
40
100
|
initialLocale: string
|
|
41
101
|
) {
|
|
42
102
|
this._locale = initialLocale
|
|
@@ -100,6 +160,20 @@ export class I18nInstance implements I18nService {
|
|
|
100
160
|
clone(locale?: string): I18nService {
|
|
101
161
|
return new I18nInstance(this.manager, locale || this._locale)
|
|
102
162
|
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Get the I18n configuration.
|
|
166
|
+
*/
|
|
167
|
+
getConfig(): I18nConfig {
|
|
168
|
+
return this.manager.getConfig()
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Get the translations.
|
|
173
|
+
*/
|
|
174
|
+
get translations(): Record<string, TranslationMap> {
|
|
175
|
+
return this.manager.translations
|
|
176
|
+
}
|
|
103
177
|
}
|
|
104
178
|
|
|
105
179
|
/**
|
|
@@ -107,7 +181,7 @@ export class I18nInstance implements I18nService {
|
|
|
107
181
|
* Holds shared configuration and translation resources
|
|
108
182
|
*/
|
|
109
183
|
export class I18nManager implements I18nService {
|
|
110
|
-
|
|
184
|
+
public translations: Record<string, TranslationMap> = {}
|
|
111
185
|
// Default instance for global usage (e.g. CLI or background jobs)
|
|
112
186
|
private globalInstance: I18nInstance
|
|
113
187
|
|
|
@@ -211,12 +285,12 @@ export class I18nManager implements I18nService {
|
|
|
211
285
|
*/
|
|
212
286
|
translate(locale: string, key: string, replacements?: Record<string, string | number>): string {
|
|
213
287
|
const keys = key.split('.')
|
|
214
|
-
let value:
|
|
288
|
+
let value: string | TranslationMap | undefined = this.translations[locale]
|
|
215
289
|
|
|
216
290
|
// 1. Try current locale
|
|
217
291
|
for (const k of keys) {
|
|
218
292
|
if (value && typeof value === 'object' && k in value) {
|
|
219
|
-
value = value[k]
|
|
293
|
+
value = (value as TranslationMap)[k]
|
|
220
294
|
} else {
|
|
221
295
|
value = undefined
|
|
222
296
|
break
|
|
@@ -225,10 +299,11 @@ export class I18nManager implements I18nService {
|
|
|
225
299
|
|
|
226
300
|
// 2. If not found, try fallback (defaultLocale)
|
|
227
301
|
if (value === undefined && locale !== this.config.defaultLocale) {
|
|
228
|
-
let fallbackValue:
|
|
302
|
+
let fallbackValue: string | TranslationMap | undefined =
|
|
303
|
+
this.translations[this.config.defaultLocale]
|
|
229
304
|
for (const k of keys) {
|
|
230
305
|
if (fallbackValue && typeof fallbackValue === 'object' && k in fallbackValue) {
|
|
231
|
-
fallbackValue = fallbackValue[k]
|
|
306
|
+
fallbackValue = (fallbackValue as TranslationMap)[k]
|
|
232
307
|
} else {
|
|
233
308
|
fallbackValue = undefined
|
|
234
309
|
break
|
|
@@ -242,16 +317,20 @@ export class I18nManager implements I18nService {
|
|
|
242
317
|
}
|
|
243
318
|
|
|
244
319
|
// 3. Replacements
|
|
245
|
-
if (replacements) {
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
320
|
+
if (replacements && Object.keys(replacements).length > 0) {
|
|
321
|
+
value = value.replace(REPLACEMENT_REGEX, (match, key) => {
|
|
322
|
+
return (replacements as Record<string, unknown>)[key] !== undefined
|
|
323
|
+
? String((replacements as Record<string, unknown>)[key])
|
|
324
|
+
: match
|
|
325
|
+
})
|
|
249
326
|
}
|
|
250
327
|
|
|
251
328
|
return value
|
|
252
329
|
}
|
|
253
330
|
}
|
|
254
331
|
|
|
332
|
+
const REPLACEMENT_REGEX = /:([a-zA-Z0-9_]+)/g
|
|
333
|
+
|
|
255
334
|
/**
|
|
256
335
|
* Locale Middleware
|
|
257
336
|
*
|
|
@@ -259,7 +338,7 @@ export class I18nManager implements I18nService {
|
|
|
259
338
|
* 1. Route Parameter (e.g. /:locale/foo) - Recommended for SEO
|
|
260
339
|
* 2. Header (Accept-Language) - Recommended for APIs
|
|
261
340
|
*/
|
|
262
|
-
export const localeMiddleware = (i18nManager: I18nService):
|
|
341
|
+
export const localeMiddleware = (i18nManager: I18nService): GravitoMiddleware => {
|
|
263
342
|
return async (c, next) => {
|
|
264
343
|
// Determine initial locale
|
|
265
344
|
// Priority: 1. Route Param 2. Query ?lang= 3. Header 4. Default
|
|
@@ -279,6 +358,6 @@ export const localeMiddleware = (i18nManager: I18nService): MiddlewareHandler =>
|
|
|
279
358
|
// Inject into context
|
|
280
359
|
c.set('i18n', i18n)
|
|
281
360
|
|
|
282
|
-
await next()
|
|
361
|
+
return await next()
|
|
283
362
|
}
|
|
284
363
|
}
|
package/src/index.ts
CHANGED
|
@@ -2,24 +2,48 @@ import type { GravitoMiddleware, GravitoOrbit, PlanetCore } from '@gravito/core'
|
|
|
2
2
|
import { type I18nConfig, I18nManager, type I18nService, localeMiddleware } from './I18nService'
|
|
3
3
|
|
|
4
4
|
declare module '@gravito/core' {
|
|
5
|
-
interface
|
|
5
|
+
interface GravitoVariables {
|
|
6
|
+
/** I18n service for translations */
|
|
6
7
|
i18n: I18nService
|
|
7
8
|
}
|
|
8
9
|
}
|
|
9
10
|
|
|
10
11
|
/**
|
|
11
|
-
* OrbitCosmos
|
|
12
|
+
* OrbitCosmos provides internationalization (i18n) support for Gravito.
|
|
13
|
+
* It manages translations, locale switching, and provides a middleware for request-scoped locale detection.
|
|
12
14
|
*
|
|
13
|
-
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```typescript
|
|
17
|
+
* const cosmos = new OrbitCosmos({
|
|
18
|
+
* defaultLocale: 'en',
|
|
19
|
+
* supportedLocales: ['en', 'zh-TW'],
|
|
20
|
+
* translations: {
|
|
21
|
+
* en: { welcome: 'Welcome!' },
|
|
22
|
+
* 'zh-TW': { welcome: '歡迎!' }
|
|
23
|
+
* }
|
|
24
|
+
* });
|
|
25
|
+
* core.addOrbit(cosmos);
|
|
26
|
+
* ```
|
|
27
|
+
* @public
|
|
14
28
|
*/
|
|
15
29
|
export class OrbitCosmos implements GravitoOrbit {
|
|
30
|
+
/**
|
|
31
|
+
* Create a new OrbitCosmos instance.
|
|
32
|
+
* @param config - The i18n configuration options.
|
|
33
|
+
*/
|
|
16
34
|
constructor(private config: I18nConfig) {}
|
|
17
35
|
|
|
36
|
+
/**
|
|
37
|
+
* Install the i18n service into PlanetCore.
|
|
38
|
+
* Registers the I18nManager and sets up the locale middleware.
|
|
39
|
+
*
|
|
40
|
+
* @param core - The PlanetCore instance.
|
|
41
|
+
*/
|
|
18
42
|
install(core: PlanetCore): void {
|
|
19
43
|
const i18nManager = new I18nManager(this.config)
|
|
20
44
|
|
|
21
|
-
// Register globally
|
|
22
|
-
|
|
45
|
+
// Register globally for CLI/Jobs using the container
|
|
46
|
+
core.container.instance('i18n', i18nManager)
|
|
23
47
|
|
|
24
48
|
// Inject locale middleware into every request
|
|
25
49
|
// This middleware handles cloning the i18n instance per request
|
|
@@ -29,7 +53,9 @@ export class OrbitCosmos implements GravitoOrbit {
|
|
|
29
53
|
}
|
|
30
54
|
}
|
|
31
55
|
|
|
32
|
-
/**
|
|
56
|
+
/**
|
|
57
|
+
* @deprecated Use OrbitCosmos instead. Will be removed in v4.0.0.
|
|
58
|
+
*/
|
|
33
59
|
export const I18nOrbit = OrbitCosmos
|
|
34
60
|
|
|
35
61
|
export * from './I18nService'
|
package/tests/manager.test.ts
CHANGED
package/tests/service.test.ts
CHANGED
|
@@ -90,8 +90,12 @@ describe('localeMiddleware', () => {
|
|
|
90
90
|
|
|
91
91
|
describe('OrbitCosmos', () => {
|
|
92
92
|
it('installs locale middleware and logs initialization', () => {
|
|
93
|
+
const instances = new Map<string, unknown>()
|
|
93
94
|
const core = {
|
|
94
95
|
adapter: { use: jest.fn() },
|
|
96
|
+
container: {
|
|
97
|
+
instance: (key: string, value: unknown) => instances.set(key, value),
|
|
98
|
+
},
|
|
95
99
|
logger: { info: jest.fn() },
|
|
96
100
|
}
|
|
97
101
|
const orbit = new OrbitCosmos({
|
|
@@ -104,6 +108,7 @@ describe('OrbitCosmos', () => {
|
|
|
104
108
|
|
|
105
109
|
expect(core.adapter.use).toHaveBeenCalledWith('*', expect.any(Function))
|
|
106
110
|
expect(core.logger.info).toHaveBeenCalledWith('[OrbitCosmos] I18n initialized with locale: en')
|
|
111
|
+
expect(instances.get('i18n')).toBeDefined()
|
|
107
112
|
expect(I18nOrbit).toBe(OrbitCosmos)
|
|
108
113
|
})
|
|
109
114
|
})
|