@gravito/cosmos 3.0.1 → 3.2.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.
Files changed (55) hide show
  1. package/MIGRATION.md +331 -0
  2. package/README.md +105 -45
  3. package/README.zh-TW.md +102 -22
  4. package/docs/plans/01-performance.md +187 -0
  5. package/docs/plans/02-architecture.md +309 -0
  6. package/docs/plans/03-api-enhancement.md +345 -0
  7. package/docs/plans/04-testing.md +431 -0
  8. package/docs/plans/README.md +47 -0
  9. package/ion/src/index.js +1179 -1138
  10. package/package.json +22 -6
  11. package/scripts/check-coverage.ts +64 -0
  12. package/src/HMRWatcher.ts +305 -0
  13. package/src/I18nService.ts +715 -91
  14. package/src/index.edge.ts +35 -0
  15. package/src/index.node.ts +20 -0
  16. package/src/index.ts +39 -6
  17. package/src/loader.ts +64 -14
  18. package/src/loaders/ChainedLoader.ts +117 -0
  19. package/src/loaders/CloudflareKVLoader.ts +194 -0
  20. package/src/loaders/EdgeKVLoader.ts +248 -0
  21. package/src/loaders/FileSystemLoader.ts +125 -0
  22. package/src/loaders/MemoryLoader.ts +161 -0
  23. package/src/loaders/RemoteLoader.ts +235 -0
  24. package/src/loaders/TranslationLoader.ts +98 -0
  25. package/src/loaders/VercelKVLoader.ts +192 -0
  26. package/src/runtime/detector.ts +97 -0
  27. package/src/runtime/path-utils.ts +169 -0
  28. package/tests/helpers/factory.ts +41 -0
  29. package/tests/performance/translate.bench.ts +27 -0
  30. package/tests/unit/api.test.ts +37 -0
  31. package/tests/unit/detector.test.ts +65 -0
  32. package/tests/unit/edge-kv-loader.test.ts +202 -0
  33. package/tests/unit/edge.test.ts +100 -0
  34. package/tests/unit/fallback.test.ts +66 -0
  35. package/tests/unit/hmr.test.ts +255 -0
  36. package/tests/unit/lazy.test.ts +35 -0
  37. package/tests/unit/loader.test.ts +72 -0
  38. package/tests/unit/loaders.test.ts +332 -0
  39. package/tests/{manager.test.ts → unit/manager.test.ts} +1 -1
  40. package/tests/unit/memory-loader.test.ts +130 -0
  41. package/tests/unit/path-utils.test.ts +135 -0
  42. package/tests/unit/plural.test.ts +58 -0
  43. package/tests/unit/runtime-detector.test.ts +86 -0
  44. package/tests/{service.test.ts → unit/service.test.ts} +2 -2
  45. package/tsconfig.json +12 -24
  46. package/.turbo/turbo-build.log +0 -20
  47. package/.turbo/turbo-test$colon$ci.log +0 -35
  48. package/.turbo/turbo-test$colon$coverage.log +0 -35
  49. package/.turbo/turbo-test.log +0 -27
  50. package/.turbo/turbo-typecheck.log +0 -2
  51. package/dist/index.cjs +0 -309
  52. package/dist/index.d.cts +0 -274
  53. package/dist/index.d.ts +0 -274
  54. package/dist/index.js +0 -277
  55. package/tests/loader.test.ts +0 -44
package/MIGRATION.md ADDED
@@ -0,0 +1,331 @@
1
+ # Migration Guide: Cosmos v1.x → v2.0
2
+
3
+ ## Overview
4
+
5
+ Cosmos v2.0 introduces Edge Runtime support while maintaining full backward compatibility with v1.x. This guide helps you migrate to v2.0 and leverage new features.
6
+
7
+ ## Breaking Changes
8
+
9
+ **None!** v2.0 is fully backward compatible with v1.x.
10
+
11
+ All existing code will continue to work without modifications.
12
+
13
+ ## What's New in v2.0
14
+
15
+ ### Edge Runtime Support
16
+
17
+ Cosmos now runs in Edge Runtime environments:
18
+ - ✅ Cloudflare Workers
19
+ - ✅ Vercel Edge Functions
20
+ - ✅ Deno Deploy
21
+ - ✅ Node.js (existing support)
22
+
23
+ ### New Loaders
24
+
25
+ - **MemoryLoader**: Static translations for Edge environments
26
+ - **EdgeKVLoader**: Generic KV storage abstraction
27
+ - **CloudflareKVLoader**: Cloudflare Workers KV
28
+ - **VercelKVLoader**: Vercel KV
29
+
30
+ ### Runtime Detection
31
+
32
+ - `detectRuntime()`: Auto-detect execution environment
33
+ - `isNode()`, `isEdge()`: Helper functions
34
+
35
+ ## Migration Paths
36
+
37
+ ### For Node.js Projects
38
+
39
+ **No changes required!** Your existing code works as-is.
40
+
41
+ ```typescript
42
+ // ✅ This still works
43
+ import { OrbitCosmos } from '@gravito/cosmos'
44
+
45
+ const cosmos = new OrbitCosmos({
46
+ defaultLocale: 'zh-TW',
47
+ supportedLocales: ['zh-TW', 'en'],
48
+ lazyLoad: {
49
+ baseDir: './lang'
50
+ }
51
+ })
52
+ ```
53
+
54
+ ### For Edge Runtime Projects
55
+
56
+ #### Option 1: Static Translations (Recommended for <100KB)
57
+
58
+ ```typescript
59
+ // Before: Not possible
60
+ // After: Use MemoryLoader
61
+ import { OrbitCosmos, MemoryLoader } from '@gravito/cosmos/edge'
62
+ import en from './lang/en.json'
63
+ import zhTW from './lang/zh-TW.json'
64
+
65
+ const cosmos = new OrbitCosmos({
66
+ defaultLocale: 'zh-TW',
67
+ supportedLocales: ['zh-TW', 'en'],
68
+ loaders: [
69
+ new MemoryLoader({
70
+ translations: { en, 'zh-TW': zhTW }
71
+ })
72
+ ]
73
+ })
74
+ ```
75
+
76
+ #### Option 2: Remote API (Recommended for dynamic content)
77
+
78
+ ```typescript
79
+ import { OrbitCosmos, RemoteLoader } from '@gravito/cosmos/edge'
80
+
81
+ const cosmos = new OrbitCosmos({
82
+ defaultLocale: 'zh-TW',
83
+ supportedLocales: ['zh-TW', 'en'],
84
+ loaders: [
85
+ new RemoteLoader({
86
+ url: 'https://api.example.com/i18n/:locale',
87
+ etagCache: true,
88
+ retries: 3
89
+ })
90
+ ]
91
+ })
92
+ ```
93
+
94
+ #### Option 3: KV Storage (Recommended for Cloudflare/Vercel)
95
+
96
+ **Cloudflare Workers:**
97
+
98
+ ```typescript
99
+ import { OrbitCosmos, CloudflareKVLoader } from '@gravito/cosmos/edge'
100
+
101
+ export interface Env {
102
+ I18N_KV: KVNamespace
103
+ }
104
+
105
+ export default {
106
+ async fetch(request: Request, env: Env) {
107
+ const cosmos = new OrbitCosmos({
108
+ defaultLocale: 'zh-TW',
109
+ supportedLocales: ['zh-TW', 'en'],
110
+ loaders: [
111
+ new CloudflareKVLoader({
112
+ namespace: env.I18N_KV
113
+ })
114
+ ]
115
+ })
116
+
117
+ // ... use cosmos
118
+ }
119
+ }
120
+ ```
121
+
122
+ **Vercel Edge Functions:**
123
+
124
+ ```typescript
125
+ import { OrbitCosmos, VercelKVLoader } from '@gravito/cosmos/edge'
126
+ import { kv } from '@vercel/kv'
127
+
128
+ const cosmos = new OrbitCosmos({
129
+ defaultLocale: 'zh-TW',
130
+ supportedLocales: ['zh-TW', 'en'],
131
+ loaders: [
132
+ new VercelKVLoader({ kv })
133
+ ]
134
+ })
135
+ ```
136
+
137
+ ## Best Practices
138
+
139
+ ### 1. Use Fallback Chains
140
+
141
+ Combine loaders for reliability:
142
+
143
+ ```typescript
144
+ import { MemoryLoader, RemoteLoader, CloudflareKVLoader } from '@gravito/cosmos/edge'
145
+
146
+ const cosmos = new OrbitCosmos({
147
+ loaders: [
148
+ new CloudflareKVLoader({ namespace: env.I18N_KV })
149
+ .fallback(new RemoteLoader({ url: env.I18N_API }))
150
+ .fallback(new MemoryLoader({ translations: fallbackData }))
151
+ ]
152
+ })
153
+ ```
154
+
155
+ ### 2. Choose the Right Loader
156
+
157
+ | Scenario | Recommended Loader | Reason |
158
+ |----------|-------------------|--------|
159
+ | Small static translations (<100KB) | MemoryLoader | Fastest, no network |
160
+ | Dynamic CMS translations | RemoteLoader | Real-time updates |
161
+ | Cloudflare Workers | CloudflareKVLoader | Edge-optimized |
162
+ | Vercel Edge | VercelKVLoader | Edge-optimized |
163
+ | Node.js server | FileSystemLoader | Native fs access |
164
+
165
+ ### 3. Optimize Bundle Size
166
+
167
+ Edge environments have size limits:
168
+
169
+ ```typescript
170
+ // ✅ Good: Import only what you need
171
+ import { OrbitCosmos, MemoryLoader } from '@gravito/cosmos/edge'
172
+
173
+ // ❌ Avoid: Importing Node.js-only features in Edge
174
+ import { FileSystemLoader } from '@gravito/cosmos/node'
175
+ ```
176
+
177
+ ## Deprecated Features
178
+
179
+ ### lazyLoad.loader (Still works, but deprecated)
180
+
181
+ ```typescript
182
+ // ⚠️ Deprecated
183
+ {
184
+ lazyLoad: {
185
+ baseDir: './lang',
186
+ loader: customLoaderFn
187
+ }
188
+ }
189
+
190
+ // ✅ Recommended
191
+ {
192
+ loaders: [
193
+ new FileSystemLoader({ baseDir: './lang' })
194
+ ]
195
+ }
196
+ ```
197
+
198
+ ## Troubleshooting
199
+
200
+ ### Error: "FileSystemLoader requires Node.js"
201
+
202
+ **Cause**: Using FileSystemLoader in Edge Runtime
203
+
204
+ **Solution**: Use MemoryLoader, RemoteLoader, or EdgeKVLoader
205
+
206
+ ```typescript
207
+ // ❌ Wrong
208
+ import { FileSystemLoader } from '@gravito/cosmos/edge'
209
+
210
+ // ✅ Correct
211
+ import { MemoryLoader } from '@gravito/cosmos/edge'
212
+ ```
213
+
214
+ ### Warning: "HMR is not supported in Edge Runtime"
215
+
216
+ **Cause**: HMRWatcher is Node.js-only
217
+
218
+ **Solution**: Use RemoteLoader with ETag caching for dynamic updates
219
+
220
+ ```typescript
221
+ // Edge Runtime alternative to HMR
222
+ new RemoteLoader({
223
+ url: 'https://api.example.com/i18n/:locale',
224
+ etagCache: true // Automatically refetches when content changes
225
+ })
226
+ ```
227
+
228
+ ### Translations not loading
229
+
230
+ **Debug steps:**
231
+
232
+ 1. Check loader configuration
233
+ 2. Verify translations are uploaded (for KV loaders)
234
+ 3. Check network requests (for RemoteLoader)
235
+ 4. Enable verbose logging
236
+
237
+ ```typescript
238
+ const loader = new RemoteLoader({
239
+ url: 'https://api.example.com/i18n/:locale',
240
+ // Add console.log in load method for debugging
241
+ })
242
+
243
+ console.log('Testing load:', await loader.load('en'))
244
+ ```
245
+
246
+ ## Examples
247
+
248
+ ### Cloudflare Workers Complete Example
249
+
250
+ ```typescript
251
+ import { OrbitCosmos, CloudflareKVLoader, MemoryLoader } from '@gravito/cosmos/edge'
252
+
253
+ export interface Env {
254
+ I18N_KV: KVNamespace
255
+ }
256
+
257
+ // Fallback translations
258
+ const fallback = {
259
+ en: { error: 'An error occurred' },
260
+ 'zh-TW': { error: '發生錯誤' }
261
+ }
262
+
263
+ export default {
264
+ async fetch(request: Request, env: Env) {
265
+ const cosmos = new OrbitCosmos({
266
+ defaultLocale: 'zh-TW',
267
+ supportedLocales: ['zh-TW', 'en'],
268
+ loaders: [
269
+ new CloudflareKVLoader({ namespace: env.I18N_KV })
270
+ .fallback(new MemoryLoader({ translations: fallback }))
271
+ ]
272
+ })
273
+
274
+ const i18n = cosmos.clone('zh-TW')
275
+ await i18n.ensureLocale('zh-TW')
276
+
277
+ return new Response(i18n.t('welcome'))
278
+ }
279
+ }
280
+ ```
281
+
282
+ ### Vercel Edge Functions Complete Example
283
+
284
+ ```typescript
285
+ import { OrbitCosmos, VercelKVLoader } from '@gravito/cosmos/edge'
286
+ import { kv } from '@vercel/kv'
287
+
288
+ const cosmos = new OrbitCosmos({
289
+ defaultLocale: 'zh-TW',
290
+ supportedLocales: ['zh-TW', 'en'],
291
+ loaders: [
292
+ new VercelKVLoader({ kv, prefix: 'i18n' })
293
+ ]
294
+ })
295
+
296
+ export async function GET(request: Request) {
297
+ const url = new URL(request.url)
298
+ const locale = url.searchParams.get('lang') || 'zh-TW'
299
+
300
+ const i18n = cosmos.clone(locale)
301
+ await i18n.ensureLocale(locale)
302
+
303
+ return new Response(JSON.stringify({
304
+ message: i18n.t('welcome'),
305
+ locale: i18n.getLocale()
306
+ }))
307
+ }
308
+ ```
309
+
310
+ ## FAQ
311
+
312
+ **Q: Do I need to update my code for v2.0?**
313
+ A: No, v2.0 is fully backward compatible.
314
+
315
+ **Q: Can I use FileSystemLoader in Edge Runtime?**
316
+ A: No, use MemoryLoader, RemoteLoader, or EdgeKVLoader instead.
317
+
318
+ **Q: How do I upload translations to KV storage?**
319
+ A: See the CloudflareKVLoader and VercelKVLoader documentation for upload scripts.
320
+
321
+ **Q: What's the performance difference between loaders?**
322
+ A: MemoryLoader (fastest) > KV Loaders (fast) > RemoteLoader (depends on network)
323
+
324
+ **Q: Can I mix Node.js and Edge loaders?**
325
+ A: In Node.js, yes. In Edge, only Edge-compatible loaders work.
326
+
327
+ ## Support
328
+
329
+ - Documentation: [README.md](./README.md)
330
+ - Architecture: [docs/architecture/cosmos.md](../../docs/architecture/cosmos.md)
331
+ - Issues: [GitHub Issues](https://github.com/gravito-framework/gravito/issues)
package/README.md CHANGED
@@ -1,8 +1,18 @@
1
- # @gravito/cosmos
1
+ # @gravito/cosmos 🌌
2
2
 
3
- > Lightweight Internationalization (i18n) Orbit for Gravito.
3
+ > Lightweight, high-performance Internationalization (i18n) Orbit for the Gravito framework.
4
4
 
5
- Provides simple JSON-based localization support for your Gravito application, with seamless integration into Photon context.
5
+ `@gravito/cosmos` brings seamless localization support to your Gravito applications. It features request-scoped i18n instances, lazy loading of translation files, parameter replacement, and flexible locale detection.
6
+
7
+ ## ✨ Features
8
+
9
+ - **🚀 Performance-First**: Highly optimized translation resolution with internal caching.
10
+ - **🛡️ Type-Safe**: Support for type-safe translation keys using TypeScript generics.
11
+ - **🔄 Request-Scoped**: Clones i18n instances per request to maintain locale state without resource duplication.
12
+ - **📂 Lazy Loading**: Load translation files from the filesystem only when needed.
13
+ - **🔗 Flexible Fallbacks**: Define custom fallback chains and missing key strategies.
14
+ - **🌍 Pluralization**: Integrated support for `Intl.PluralRules` based pluralization.
15
+ - **📡 Auto-Detection**: Detects locale from Route Params, Query Strings, or `Accept-Language` headers.
6
16
 
7
17
  ## 📦 Installation
8
18
 
@@ -12,55 +22,105 @@ bun add @gravito/cosmos
12
22
 
13
23
  ## 🚀 Quick Start
14
24
 
15
- 1. **Register the Orbit**:
16
- ```typescript
17
- import { PlanetCore } from '@gravito/core';
18
- import { OrbitI18n } from '@gravito/cosmos';
19
-
20
- const core = new PlanetCore();
21
-
22
- core.boot({
23
- orbits: [OrbitI18n],
24
- config: {
25
- i18n: {
26
- defaultLocale: 'en',
27
- fallbackLocale: 'en',
28
- path: './lang' // Path to your locale JSON files
29
- }
30
- }
31
- });
32
- ```
33
-
34
- 2. **Create Locale Files**:
35
- Create `./lang/en.json` and `./lang/zh-TW.json`:
36
- ```json
37
- // en.json
38
- {
39
- "welcome": "Welcome, {name}!"
25
+ ### 1. Register the Orbit
26
+
27
+ Add `OrbitCosmos` to your PlanetCore configuration:
28
+
29
+ ```typescript
30
+ import { PlanetCore } from '@gravito/core';
31
+ import { OrbitCosmos } from '@gravito/cosmos';
32
+
33
+ const core = new PlanetCore({
34
+ config: {
35
+ // Optional static translations
36
+ translations: {
37
+ en: { welcome: 'Welcome, :name!' },
38
+ 'zh-TW': { welcome: '歡迎,:name!' }
40
39
  }
41
- ```
40
+ }
41
+ });
42
+
43
+ core.addOrbit(new OrbitCosmos({
44
+ defaultLocale: 'en',
45
+ supportedLocales: ['en', 'zh-TW'],
46
+ // Optional: configure lazy loading from a directory
47
+ lazyLoad: {
48
+ baseDir: './lang'
49
+ }
50
+ }));
51
+
52
+ await core.bootstrap();
53
+ ```
42
54
 
43
- 3. **Use in Routes**:
44
- ```typescript
45
- app.get('/', (c) => {
46
- const t = c.get('t');
47
- return c.text(t('welcome', { name: 'Carl' }));
48
- });
49
- ```
55
+ ### 2. Create Locale Files (If using lazy loading)
56
+
57
+ Create `./lang/en.json`:
58
+ ```json
59
+ {
60
+ "auth": {
61
+ "login_success": "Welcome back!",
62
+ "failed": "Invalid credentials."
63
+ },
64
+ "items": {
65
+ "count": {
66
+ "zero": "No items found",
67
+ "one": "Found :count item",
68
+ "other": "Found :count items"
69
+ }
70
+ }
71
+ }
72
+ ```
50
73
 
51
- ## Features
74
+ ### 3. Use in Routes
52
75
 
53
- - **Context Injection**: Automatically injects `t()` helper into Photon context.
54
- - **Parameter Replacement**: Supports `{key}` style parameter replacement.
55
- - **Locale Detection**: Automatically detects locale from `Accept-Language` header or query parameter `?lang=`.
56
- - **Fallback**: gracefully falls back to default locale if key is missing.
76
+ The `i18n` service is automatically injected into the context:
57
77
 
58
- ## 📚 API
78
+ ```typescript
79
+ app.get('/hello', (c) => {
80
+ const t = c.get('i18n').t;
81
+ return c.text(t('welcome', { name: 'Carl' }));
82
+ });
59
83
 
60
- ### `t(key: string, params?: Record<string, any>): string`
84
+ // Pluralization
85
+ app.get('/items', (c) => {
86
+ const i18n = c.get('i18n');
87
+ return c.text(i18n.t('items.count', { count: 5 })); // "Found 5 items"
88
+ });
89
+ ```
61
90
 
62
- Main translation helper function.
91
+ ## 📚 Core Concepts
92
+
93
+ ### I18nManager
94
+ The central hub that holds shared configuration and translation resources. It handles the heavy lifting of loading files and resolving keys.
95
+
96
+ ### I18nInstance
97
+ A lightweight, request-scoped object that holds the current locale. It delegates to the `I18nManager` for translations.
98
+
99
+ ### Locale Detectors
100
+ Built-in detectors allow automatic locale selection:
101
+ - `RouteParamDetector`: Looks for `:locale` in route parameters.
102
+ - `QueryDetector`: Looks for `?lang=` in the URL.
103
+ - `HeaderDetector`: Uses the `Accept-Language` header.
104
+
105
+ ## 🛠️ Configuration
106
+
107
+ ```typescript
108
+ export interface I18nConfig {
109
+ defaultLocale: string;
110
+ supportedLocales: string[];
111
+ translations?: Record<string, TranslationMap>;
112
+ lazyLoad?: {
113
+ baseDir: string;
114
+ preload?: string[];
115
+ };
116
+ fallback?: {
117
+ fallbackChain?: Record<string, string[]>;
118
+ onMissingKey?: 'key' | 'empty' | 'throw' | ((key: string, locale: string) => string);
119
+ warnOnMissing?: boolean;
120
+ };
121
+ }
122
+ ```
63
123
 
64
124
  ## License
65
125
 
66
- MIT
126
+ MIT © Carl Lee
package/README.zh-TW.md CHANGED
@@ -1,46 +1,126 @@
1
- # @gravito/cosmos
1
+ # @gravito/cosmos 🌌
2
2
 
3
- > Gravito 的輕量 i18n 模組,提供 JSON 檔案式在地化。
3
+ > Gravito 框架設計的輕量級、高效能國際化 (i18n) 擴展。
4
4
 
5
- ## 安裝
5
+ `@gravito/cosmos` 為您的 Gravito 應用程式提供無縫的在地化支援。具備請求級別的 i18n 實例、翻譯檔案懶加載、參數替換以及靈活的語言偵測功能。
6
+
7
+ ## ✨ 特性
8
+
9
+ - **🚀 效能優先**:高度優化的翻譯查找與內部快取機制。
10
+ - **🛡️ 類型安全**:支援 TypeScript 泛型,提供類型安全的翻譯鍵值。
11
+ - **🔄 請求級別實例**:為每個請求克隆輕量實例,保持語言狀態而不重複佔用資源。
12
+ - **📂 懶加載**:僅在需要時從檔案系統載入翻譯檔案。
13
+ - **🔗 靈活的回退機制**:可自定義回退鏈與缺失鍵值的處理策略。
14
+ - **🌍 複數支援**:整合 `Intl.PluralRules` 的複數處理。
15
+ - **📡 自動偵測**:支援從路由參數、查詢字串或 `Accept-Language` 標頭偵測語言。
16
+
17
+ ## 📦 安裝
6
18
 
7
19
  ```bash
8
20
  bun add @gravito/cosmos
9
21
  ```
10
22
 
11
- ## 快速開始
23
+ ## 🚀 快速開始
12
24
 
13
- ```typescript
14
- import { PlanetCore } from '@gravito/core'
15
- import { OrbitI18n } from '@gravito/cosmos'
25
+ ### 1. 註冊 Orbit
16
26
 
17
- const core = new PlanetCore()
27
+ `OrbitCosmos` 加入您的 PlanetCore 配置中:
18
28
 
19
- core.boot({
20
- orbits: [OrbitI18n],
29
+ ```typescript
30
+ import { PlanetCore } from '@gravito/core';
31
+ import { OrbitCosmos } from '@gravito/cosmos';
32
+
33
+ const core = new PlanetCore({
21
34
  config: {
22
- i18n: {
23
- defaultLocale: 'en',
24
- fallbackLocale: 'en',
25
- path: './lang'
35
+ // 可選的靜態翻譯
36
+ translations: {
37
+ en: { welcome: 'Welcome, :name!' },
38
+ 'zh-TW': { welcome: '歡迎,:name!' }
26
39
  }
27
40
  }
28
- })
41
+ });
42
+
43
+ core.addOrbit(new OrbitCosmos({
44
+ defaultLocale: 'en',
45
+ supportedLocales: ['en', 'zh-TW'],
46
+ // 可選:配置語言檔案目錄
47
+ lazyLoad: {
48
+ baseDir: './lang'
49
+ }
50
+ }));
51
+
52
+ await core.bootstrap();
29
53
  ```
30
54
 
31
- 建立 `./lang/en.json`:
55
+ ### 2. 建立語言檔案 (若使用懶加載)
32
56
 
57
+ 建立 `./lang/en.json`:
33
58
  ```json
34
59
  {
35
- "welcome": "Welcome, {name}!"
60
+ "auth": {
61
+ "login_success": "Welcome back!",
62
+ "failed": "Invalid credentials."
63
+ },
64
+ "items": {
65
+ "count": {
66
+ "zero": "No items found",
67
+ "one": "Found :count item",
68
+ "other": "Found :count items"
69
+ }
70
+ }
36
71
  }
37
72
  ```
38
73
 
39
- 在路由中使用:
74
+ ### 3. 在路由中使用
75
+
76
+ `i18n` 服務會自動注入到上下文 (Context) 中:
40
77
 
41
78
  ```typescript
42
- app.get('/', (c) => {
43
- const t = c.get('t')
44
- return c.text(t('welcome', { name: 'Carl' }))
45
- })
79
+ app.get('/hello', (c) => {
80
+ const t = c.get('i18n').t;
81
+ return c.text(t('welcome', { name: 'Carl' }));
82
+ });
83
+
84
+ // 複數處理
85
+ app.get('/items', (c) => {
86
+ const i18n = c.get('i18n');
87
+ return c.text(i18n.t('items.count', { count: 5 })); // "Found 5 items"
88
+ });
46
89
  ```
90
+
91
+ ## 📚 核心概念
92
+
93
+ ### I18nManager
94
+ 核心管理中心,持有共享配置與翻譯資源。負責載入檔案、處理翻譯邏輯、回退策略與快取。
95
+
96
+ ### I18nInstance
97
+ 輕量級的請求範圍物件,持有當前的語言狀態。實際翻譯邏輯委託給 `I18nManager` 執行。
98
+
99
+ ### 語言偵測器 (Locale Detectors)
100
+ 內建多種偵測器:
101
+ - `RouteParamDetector`:從路由參數 `:locale` 獲取。
102
+ - `QueryDetector`:從 URL 查詢字串 `?lang=` 獲取。
103
+ - `HeaderDetector`:從 `Accept-Language` HTTP 標頭獲取。
104
+
105
+ ## 🛠️ 配置選項
106
+
107
+ ```typescript
108
+ export interface I18nConfig {
109
+ defaultLocale: string;
110
+ supportedLocales: string[];
111
+ translations?: Record<string, TranslationMap>;
112
+ lazyLoad?: {
113
+ baseDir: string;
114
+ preload?: string[];
115
+ };
116
+ fallback?: {
117
+ fallbackChain?: Record<string, string[]>;
118
+ onMissingKey?: 'key' | 'empty' | 'throw' | ((key: string, locale: string) => string);
119
+ warnOnMissing?: boolean;
120
+ };
121
+ }
122
+ ```
123
+
124
+ ## License
125
+
126
+ MIT © Carl Lee