@gravito/cosmos 1.0.0-alpha.2 → 1.0.0-alpha.3

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.
@@ -14,17 +14,22 @@ 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
- export class I18nManager implements I18nService {
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
- constructor(private config: I18nConfig) {
24
- this._locale = config.defaultLocale
25
- if (config.translations) {
26
- this.translations = config.translations
27
- }
28
+ constructor(
29
+ private manager: I18nManager,
30
+ initialLocale: string
31
+ ) {
32
+ this._locale = initialLocale
28
33
  }
29
34
 
30
35
  get locale(): string {
@@ -36,7 +41,7 @@ export class I18nManager implements I18nService {
36
41
  }
37
42
 
38
43
  setLocale(locale: string) {
39
- if (this.config.supportedLocales.includes(locale)) {
44
+ if (this.manager.getConfig().supportedLocales.includes(locale)) {
40
45
  this._locale = locale
41
46
  }
42
47
  }
@@ -45,9 +50,71 @@ export class I18nManager implements I18nService {
45
50
  return this._locale
46
51
  }
47
52
 
48
- /**
49
- * Add translations for a locale
50
- */
53
+ t(key: string, replacements?: Record<string, string | number>): string {
54
+ return this.manager.translate(this._locale, key, replacements)
55
+ }
56
+
57
+ has(key: string): boolean {
58
+ return this.t(key) !== key
59
+ }
60
+
61
+ clone(locale?: string): I18nService {
62
+ return new I18nInstance(this.manager, locale || this._locale)
63
+ }
64
+ }
65
+
66
+ /**
67
+ * Global I18n Manager
68
+ * Holds shared configuration and translation resources
69
+ */
70
+ export class I18nManager implements I18nService {
71
+ private translations: Record<string, Record<string, string>> = {}
72
+ // Default instance for global usage (e.g. CLI or background jobs)
73
+ private globalInstance: I18nInstance
74
+
75
+ constructor(private config: I18nConfig) {
76
+ if (config.translations) {
77
+ this.translations = config.translations
78
+ }
79
+ this.globalInstance = new I18nInstance(this, config.defaultLocale)
80
+ }
81
+
82
+ // --- I18nService Implementation (Delegates to global instance) ---
83
+
84
+ get locale(): string {
85
+ return this.globalInstance.locale
86
+ }
87
+
88
+ set locale(value: string) {
89
+ this.globalInstance.locale = value
90
+ }
91
+
92
+ setLocale(locale: string): void {
93
+ this.globalInstance.setLocale(locale)
94
+ }
95
+
96
+ getLocale(): string {
97
+ return this.globalInstance.getLocale()
98
+ }
99
+
100
+ t(key: string, replacements?: Record<string, string | number>): string {
101
+ return this.globalInstance.t(key, replacements)
102
+ }
103
+
104
+ has(key: string): boolean {
105
+ return this.globalInstance.has(key)
106
+ }
107
+
108
+ clone(locale?: string): I18nService {
109
+ return new I18nInstance(this, locale || this.config.defaultLocale)
110
+ }
111
+
112
+ // --- Manager Internal API ---
113
+
114
+ getConfig(): I18nConfig {
115
+ return this.config
116
+ }
117
+
51
118
  addResource(locale: string, translations: Record<string, string>) {
52
119
  this.translations[locale] = {
53
120
  ...(this.translations[locale] || {}),
@@ -56,16 +123,15 @@ export class I18nManager implements I18nService {
56
123
  }
57
124
 
58
125
  /**
59
- * Translation helper
60
- * t('messages.welcome', { name: 'Carl' })
61
- * Supports nested keys via dot notation: t('auth.errors.invalid')
126
+ * Internal translation logic used by instances
62
127
  */
63
- t(key: string, replacements?: Record<string, string | number>): string {
128
+ translate(
129
+ locale: string,
130
+ key: string,
131
+ replacements?: Record<string, string | number>
132
+ ): string {
64
133
  const keys = key.split('.')
65
- let value: any = this.translations[this._locale]
66
-
67
- // Fallback to default locale if not found in current locale?
68
- // Implementation: Try current locale, then fallback.
134
+ let value: any = this.translations[locale]
69
135
 
70
136
  // 1. Try current locale
71
137
  for (const k of keys) {
@@ -78,7 +144,7 @@ export class I18nManager implements I18nService {
78
144
  }
79
145
 
80
146
  // 2. If not found, try fallback (defaultLocale)
81
- if (value === undefined && this._locale !== this.config.defaultLocale) {
147
+ if (value === undefined && locale !== this.config.defaultLocale) {
82
148
  let fallbackValue: any = this.translations[this.config.defaultLocale]
83
149
  for (const k of keys) {
84
150
  if (fallbackValue && typeof fallbackValue === 'object' && k in fallbackValue) {
@@ -104,11 +170,6 @@ export class I18nManager implements I18nService {
104
170
 
105
171
  return value
106
172
  }
107
-
108
- has(key: string): boolean {
109
- // Simplistic check
110
- return this.t(key) !== key
111
- }
112
173
  }
113
174
 
114
175
  /**
@@ -118,21 +179,25 @@ export class I18nManager implements I18nService {
118
179
  * 1. Route Parameter (e.g. /:locale/foo) - Recommended for SEO
119
180
  * 2. Header (Accept-Language) - Recommended for APIs
120
181
  */
121
- export const localeMiddleware = (i18n: I18nService): MiddlewareHandler => {
182
+ export const localeMiddleware = (i18nManager: I18nService): MiddlewareHandler => {
122
183
  return async (c, next) => {
123
- // 1. Check for route param 'locale'
124
- const paramLocale = c.req.param('locale')
125
- if (paramLocale) {
126
- i18n.setLocale(paramLocale)
184
+ // Determine initial locale
185
+ // Priority: 1. Route Param 2. Query ?lang= 3. Header 4. Default
186
+ let locale = c.req.param('locale') || c.req.query('lang')
187
+
188
+ if (!locale) {
189
+ const acceptLang = c.req.header('Accept-Language')
190
+ if (acceptLang) {
191
+ // Simple extraction: 'en-US,en;q=0.9' -> 'en-US'
192
+ locale = acceptLang.split(',')[0]?.trim()
193
+ }
127
194
  }
128
195
 
129
- // 2. Inject into context (using 'any' for now, or augment PlanetCore variables later)
130
- c.set('i18n', i18n)
196
+ // Clone a request-scoped instance
197
+ const i18n = i18nManager.clone(locale)
131
198
 
132
- // 3. Share with View layer (if Orbit View is present)
133
- // Assuming 'view' service might look at context variables or we explicitly pass it
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.
199
+ // Inject into context
200
+ c.set('i18n', i18n)
136
201
 
137
202
  await next()
138
203
  }
package/src/index.ts CHANGED
@@ -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 i18n = new I18nManager(this.config)
14
+ const i18nManager = new I18nManager(this.config)
15
15
 
16
- // Register globally if needed, or just prepare it to be used.
17
- // There isn't a global "services" container in PlanetCore yet other than 'Variables' injected via Config/Context.
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
- core.adapter.use('*', async (c, next) => {
22
- c.set('i18n', i18n)
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
  }