@gravito/cosmos 1.0.0-alpha.2 → 1.0.0-alpha.5
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.zh-TW.md +46 -0
- package/dist/index.js +65 -24
- package/dist/src/index.js +49 -1
- package/ion/src/index.js +2249 -0
- package/package.json +2 -2
- package/signal/src/index.js +101809 -0
- package/src/I18nService.ts +175 -34
- package/src/index.ts +7 -14
package/src/I18nService.ts
CHANGED
|
@@ -14,17 +14,28 @@ export interface I18nService {
|
|
|
14
14
|
getLocale(): string
|
|
15
15
|
t(key: string, replacements?: Record<string, string | number>): string
|
|
16
16
|
has(key: string): boolean
|
|
17
|
+
// Create a request-scoped instance
|
|
18
|
+
clone(locale?: string): I18nService
|
|
17
19
|
}
|
|
18
20
|
|
|
19
|
-
|
|
21
|
+
/**
|
|
22
|
+
* Request-scoped I18n Instance
|
|
23
|
+
* Holds the state (locale) for a single request, but shares the heavy resources (translations)
|
|
24
|
+
*/
|
|
25
|
+
export class I18nInstance implements I18nService {
|
|
20
26
|
private _locale: string
|
|
21
|
-
private translations: Record<string, Record<string, string>> = {}
|
|
22
27
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
+
/**
|
|
29
|
+
* Create a new I18nInstance.
|
|
30
|
+
*
|
|
31
|
+
* @param manager - The I18nManager instance.
|
|
32
|
+
* @param initialLocale - The initial locale for this instance.
|
|
33
|
+
*/
|
|
34
|
+
constructor(
|
|
35
|
+
private manager: I18nManager,
|
|
36
|
+
initialLocale: string
|
|
37
|
+
) {
|
|
38
|
+
this._locale = initialLocale
|
|
28
39
|
}
|
|
29
40
|
|
|
30
41
|
get locale(): string {
|
|
@@ -35,18 +46,154 @@ export class I18nManager implements I18nService {
|
|
|
35
46
|
this.setLocale(value)
|
|
36
47
|
}
|
|
37
48
|
|
|
49
|
+
/**
|
|
50
|
+
* Set the current locale.
|
|
51
|
+
*
|
|
52
|
+
* @param locale - The locale to set.
|
|
53
|
+
*/
|
|
38
54
|
setLocale(locale: string) {
|
|
39
|
-
if (this.
|
|
55
|
+
if (this.manager.getConfig().supportedLocales.includes(locale)) {
|
|
40
56
|
this._locale = locale
|
|
41
57
|
}
|
|
42
58
|
}
|
|
43
59
|
|
|
60
|
+
/**
|
|
61
|
+
* Get the current locale.
|
|
62
|
+
*
|
|
63
|
+
* @returns The current locale string.
|
|
64
|
+
*/
|
|
44
65
|
getLocale(): string {
|
|
45
66
|
return this._locale
|
|
46
67
|
}
|
|
47
68
|
|
|
48
69
|
/**
|
|
49
|
-
*
|
|
70
|
+
* Translate a key.
|
|
71
|
+
*
|
|
72
|
+
* @param key - The translation key (e.g., 'messages.welcome').
|
|
73
|
+
* @param replacements - Optional replacements for parameters in the translation string.
|
|
74
|
+
* @returns The translated string, or the key if not found.
|
|
75
|
+
*/
|
|
76
|
+
t(key: string, replacements?: Record<string, string | number>): string {
|
|
77
|
+
return this.manager.translate(this._locale, key, replacements)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Check if a translation key exists.
|
|
82
|
+
*
|
|
83
|
+
* @param key - The translation key to check.
|
|
84
|
+
* @returns True if the key exists, false otherwise.
|
|
85
|
+
*/
|
|
86
|
+
has(key: string): boolean {
|
|
87
|
+
return this.t(key) !== key
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Clone the current instance with a potentially new locale.
|
|
92
|
+
*
|
|
93
|
+
* @param locale - Optional new locale for the cloned instance.
|
|
94
|
+
* @returns A new I18nInstance.
|
|
95
|
+
*/
|
|
96
|
+
clone(locale?: string): I18nService {
|
|
97
|
+
return new I18nInstance(this.manager, locale || this._locale)
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Global I18n Manager
|
|
103
|
+
* Holds shared configuration and translation resources
|
|
104
|
+
*/
|
|
105
|
+
export class I18nManager implements I18nService {
|
|
106
|
+
private translations: Record<string, Record<string, string>> = {}
|
|
107
|
+
// Default instance for global usage (e.g. CLI or background jobs)
|
|
108
|
+
private globalInstance: I18nInstance
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Create a new I18nManager.
|
|
112
|
+
*
|
|
113
|
+
* @param config - The I18n configuration.
|
|
114
|
+
*/
|
|
115
|
+
constructor(private config: I18nConfig) {
|
|
116
|
+
if (config.translations) {
|
|
117
|
+
this.translations = config.translations
|
|
118
|
+
}
|
|
119
|
+
this.globalInstance = new I18nInstance(this, config.defaultLocale)
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// --- I18nService Implementation (Delegates to global instance) ---
|
|
123
|
+
|
|
124
|
+
get locale(): string {
|
|
125
|
+
return this.globalInstance.locale
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
set locale(value: string) {
|
|
129
|
+
this.globalInstance.locale = value
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Set the global locale.
|
|
134
|
+
*
|
|
135
|
+
* @param locale - The locale to set.
|
|
136
|
+
*/
|
|
137
|
+
setLocale(locale: string): void {
|
|
138
|
+
this.globalInstance.setLocale(locale)
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Get the global locale.
|
|
143
|
+
*
|
|
144
|
+
* @returns The global locale string.
|
|
145
|
+
*/
|
|
146
|
+
getLocale(): string {
|
|
147
|
+
return this.globalInstance.getLocale()
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Translate a key using the global locale.
|
|
152
|
+
*
|
|
153
|
+
* @param key - The translation key.
|
|
154
|
+
* @param replacements - Optional replacements.
|
|
155
|
+
* @returns The translated string.
|
|
156
|
+
*/
|
|
157
|
+
t(key: string, replacements?: Record<string, string | number>): string {
|
|
158
|
+
return this.globalInstance.t(key, replacements)
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Check if a translation key exists in the global locale.
|
|
163
|
+
*
|
|
164
|
+
* @param key - The translation key.
|
|
165
|
+
* @returns True if found.
|
|
166
|
+
*/
|
|
167
|
+
has(key: string): boolean {
|
|
168
|
+
return this.globalInstance.has(key)
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Clone the global instance.
|
|
173
|
+
*
|
|
174
|
+
* @param locale - Optional locale for the clone.
|
|
175
|
+
* @returns A new I18nInstance.
|
|
176
|
+
*/
|
|
177
|
+
clone(locale?: string): I18nService {
|
|
178
|
+
return new I18nInstance(this, locale || this.config.defaultLocale)
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// --- Manager Internal API ---
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Get the I18n configuration.
|
|
185
|
+
*
|
|
186
|
+
* @returns The configuration object.
|
|
187
|
+
*/
|
|
188
|
+
getConfig(): I18nConfig {
|
|
189
|
+
return this.config
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Add a resource bundle for a specific locale.
|
|
194
|
+
*
|
|
195
|
+
* @param locale - The locale string.
|
|
196
|
+
* @param translations - The translations object.
|
|
50
197
|
*/
|
|
51
198
|
addResource(locale: string, translations: Record<string, string>) {
|
|
52
199
|
this.translations[locale] = {
|
|
@@ -56,16 +203,11 @@ export class I18nManager implements I18nService {
|
|
|
56
203
|
}
|
|
57
204
|
|
|
58
205
|
/**
|
|
59
|
-
*
|
|
60
|
-
* t('messages.welcome', { name: 'Carl' })
|
|
61
|
-
* Supports nested keys via dot notation: t('auth.errors.invalid')
|
|
206
|
+
* Internal translation logic used by instances
|
|
62
207
|
*/
|
|
63
|
-
|
|
208
|
+
translate(locale: string, key: string, replacements?: Record<string, string | number>): string {
|
|
64
209
|
const keys = key.split('.')
|
|
65
|
-
let value: any = this.translations[
|
|
66
|
-
|
|
67
|
-
// Fallback to default locale if not found in current locale?
|
|
68
|
-
// Implementation: Try current locale, then fallback.
|
|
210
|
+
let value: any = this.translations[locale]
|
|
69
211
|
|
|
70
212
|
// 1. Try current locale
|
|
71
213
|
for (const k of keys) {
|
|
@@ -78,7 +220,7 @@ export class I18nManager implements I18nService {
|
|
|
78
220
|
}
|
|
79
221
|
|
|
80
222
|
// 2. If not found, try fallback (defaultLocale)
|
|
81
|
-
if (value === undefined &&
|
|
223
|
+
if (value === undefined && locale !== this.config.defaultLocale) {
|
|
82
224
|
let fallbackValue: any = this.translations[this.config.defaultLocale]
|
|
83
225
|
for (const k of keys) {
|
|
84
226
|
if (fallbackValue && typeof fallbackValue === 'object' && k in fallbackValue) {
|
|
@@ -104,11 +246,6 @@ export class I18nManager implements I18nService {
|
|
|
104
246
|
|
|
105
247
|
return value
|
|
106
248
|
}
|
|
107
|
-
|
|
108
|
-
has(key: string): boolean {
|
|
109
|
-
// Simplistic check
|
|
110
|
-
return this.t(key) !== key
|
|
111
|
-
}
|
|
112
249
|
}
|
|
113
250
|
|
|
114
251
|
/**
|
|
@@ -118,21 +255,25 @@ export class I18nManager implements I18nService {
|
|
|
118
255
|
* 1. Route Parameter (e.g. /:locale/foo) - Recommended for SEO
|
|
119
256
|
* 2. Header (Accept-Language) - Recommended for APIs
|
|
120
257
|
*/
|
|
121
|
-
export const localeMiddleware = (
|
|
258
|
+
export const localeMiddleware = (i18nManager: I18nService): MiddlewareHandler => {
|
|
122
259
|
return async (c, next) => {
|
|
123
|
-
//
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
260
|
+
// Determine initial locale
|
|
261
|
+
// Priority: 1. Route Param 2. Query ?lang= 3. Header 4. Default
|
|
262
|
+
let locale = c.req.param('locale') || c.req.query('lang')
|
|
263
|
+
|
|
264
|
+
if (!locale) {
|
|
265
|
+
const acceptLang = c.req.header('Accept-Language')
|
|
266
|
+
if (acceptLang) {
|
|
267
|
+
// Simple extraction: 'en-US,en;q=0.9' -> 'en-US'
|
|
268
|
+
locale = acceptLang.split(',')[0]?.trim()
|
|
269
|
+
}
|
|
127
270
|
}
|
|
128
271
|
|
|
129
|
-
//
|
|
130
|
-
|
|
272
|
+
// Clone a request-scoped instance
|
|
273
|
+
const i18n = i18nManager.clone(locale)
|
|
131
274
|
|
|
132
|
-
//
|
|
133
|
-
|
|
134
|
-
// For Inertia, we might want to share it as a prop.
|
|
135
|
-
// This part depends on how the user sets up their detailed pipeline, but setting it in 'c' is the start.
|
|
275
|
+
// Inject into context
|
|
276
|
+
c.set('i18n', i18n)
|
|
136
277
|
|
|
137
278
|
await next()
|
|
138
279
|
}
|
package/src/index.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { GravitoOrbit, PlanetCore } from 'gravito-core'
|
|
2
|
-
import { type I18nConfig, I18nManager, type I18nService } from './I18nService'
|
|
2
|
+
import { type I18nConfig, I18nManager, type I18nService, localeMiddleware } from './I18nService'
|
|
3
3
|
|
|
4
4
|
declare module 'gravito-core' {
|
|
5
5
|
interface Variables {
|
|
@@ -11,21 +11,14 @@ export class I18nOrbit implements GravitoOrbit {
|
|
|
11
11
|
constructor(private config: I18nConfig) {}
|
|
12
12
|
|
|
13
13
|
install(core: PlanetCore): void {
|
|
14
|
-
const
|
|
14
|
+
const i18nManager = new I18nManager(this.config)
|
|
15
15
|
|
|
16
|
-
// Register globally if needed
|
|
17
|
-
//
|
|
18
|
-
// Ideally we attach it to the core instance or inject it into every request.
|
|
16
|
+
// Register globally if needed (for CLI/Jobs)
|
|
17
|
+
// core.services.set('i18n', i18nManager);
|
|
19
18
|
|
|
20
|
-
// Inject into every request
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
await next()
|
|
24
|
-
})
|
|
25
|
-
|
|
26
|
-
// Register a helper if using Orbit View (View Rendering)
|
|
27
|
-
// We can check if 'view' exists or we can register a global view helper if that API exists.
|
|
28
|
-
// For now, context injection is sufficient.
|
|
19
|
+
// Inject locale middleware into every request
|
|
20
|
+
// This middleware handles cloning the i18n instance per request
|
|
21
|
+
core.adapter.use('*', localeMiddleware(i18nManager) as any)
|
|
29
22
|
|
|
30
23
|
core.logger.info(`I18n Orbit initialized with locale: ${this.config.defaultLocale}`)
|
|
31
24
|
}
|