@gravito/cosmos 3.0.0 → 3.1.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/CHANGELOG.md +7 -0
- package/README.md +105 -45
- package/README.zh-TW.md +102 -22
- package/build.ts +35 -0
- package/docs/plans/01-performance.md +187 -0
- package/docs/plans/02-architecture.md +309 -0
- package/docs/plans/03-api-enhancement.md +345 -0
- package/docs/plans/04-testing.md +431 -0
- package/docs/plans/README.md +47 -0
- package/ion/src/index.js +1179 -1138
- package/package.json +18 -5
- package/src/I18nService.ts +657 -94
- package/src/index.ts +51 -6
- package/src/loader.ts +45 -12
- package/tests/helpers/factory.ts +41 -0
- package/tests/performance/translate.bench.ts +27 -0
- package/tests/unit/api.test.ts +37 -0
- package/tests/unit/detector.test.ts +70 -0
- package/tests/unit/edge.test.ts +100 -0
- package/tests/unit/fallback.test.ts +66 -0
- package/tests/unit/lazy.test.ts +35 -0
- package/tests/{loader.test.ts → unit/loader.test.ts} +1 -1
- package/tests/{manager.test.ts → unit/manager.test.ts} +1 -2
- package/tests/unit/plural.test.ts +58 -0
- package/tests/{service.test.ts → unit/service.test.ts} +7 -2
- package/tsconfig.json +12 -24
- package/dist/core/src/Application.d.ts +0 -185
- package/dist/core/src/ConfigManager.d.ts +0 -21
- package/dist/core/src/Container.d.ts +0 -38
- package/dist/core/src/Event.d.ts +0 -5
- package/dist/core/src/EventManager.d.ts +0 -123
- package/dist/core/src/GlobalErrorHandlers.d.ts +0 -31
- package/dist/core/src/GravitoServer.d.ts +0 -20
- package/dist/core/src/HookManager.d.ts +0 -70
- package/dist/core/src/Listener.d.ts +0 -4
- package/dist/core/src/Logger.d.ts +0 -20
- package/dist/core/src/PlanetCore.d.ts +0 -207
- package/dist/core/src/Route.d.ts +0 -25
- package/dist/core/src/Router.d.ts +0 -232
- package/dist/core/src/ServiceProvider.d.ts +0 -150
- package/dist/core/src/adapters/PhotonAdapter.d.ts +0 -142
- package/dist/core/src/adapters/bun/BunContext.d.ts +0 -36
- package/dist/core/src/adapters/bun/BunNativeAdapter.d.ts +0 -21
- package/dist/core/src/adapters/bun/BunRequest.d.ts +0 -27
- package/dist/core/src/adapters/bun/RadixNode.d.ts +0 -15
- package/dist/core/src/adapters/bun/RadixRouter.d.ts +0 -31
- package/dist/core/src/adapters/bun/types.d.ts +0 -20
- package/dist/core/src/adapters/types.d.ts +0 -186
- package/dist/core/src/engine/AOTRouter.d.ts +0 -117
- package/dist/core/src/engine/FastContext.d.ts +0 -34
- package/dist/core/src/engine/Gravito.d.ts +0 -191
- package/dist/core/src/engine/MinimalContext.d.ts +0 -36
- package/dist/core/src/engine/analyzer.d.ts +0 -21
- package/dist/core/src/engine/index.d.ts +0 -26
- package/dist/core/src/engine/path.d.ts +0 -26
- package/dist/core/src/engine/pool.d.ts +0 -83
- package/dist/core/src/engine/types.d.ts +0 -107
- package/dist/core/src/exceptions/AuthenticationException.d.ts +0 -4
- package/dist/core/src/exceptions/AuthorizationException.d.ts +0 -4
- package/dist/core/src/exceptions/GravitoException.d.ts +0 -15
- package/dist/core/src/exceptions/HttpException.d.ts +0 -5
- package/dist/core/src/exceptions/ModelNotFoundException.d.ts +0 -6
- package/dist/core/src/exceptions/ValidationException.d.ts +0 -14
- package/dist/core/src/exceptions/index.d.ts +0 -6
- package/dist/core/src/helpers/Arr.d.ts +0 -14
- package/dist/core/src/helpers/Str.d.ts +0 -18
- package/dist/core/src/helpers/data.d.ts +0 -5
- package/dist/core/src/helpers/errors.d.ts +0 -12
- package/dist/core/src/helpers/response.d.ts +0 -17
- package/dist/core/src/helpers.d.ts +0 -38
- package/dist/core/src/http/CookieJar.d.ts +0 -37
- package/dist/core/src/http/middleware/BodySizeLimit.d.ts +0 -6
- package/dist/core/src/http/middleware/Cors.d.ts +0 -12
- package/dist/core/src/http/middleware/Csrf.d.ts +0 -11
- package/dist/core/src/http/middleware/HeaderTokenGate.d.ts +0 -11
- package/dist/core/src/http/middleware/SecurityHeaders.d.ts +0 -17
- package/dist/core/src/http/middleware/ThrottleRequests.d.ts +0 -12
- package/dist/core/src/http/types.d.ts +0 -312
- package/dist/core/src/index.d.ts +0 -60
- package/dist/core/src/runtime.d.ts +0 -63
- package/dist/core/src/security/Encrypter.d.ts +0 -24
- package/dist/core/src/security/Hasher.d.ts +0 -29
- package/dist/core/src/testing/HttpTester.d.ts +0 -38
- package/dist/core/src/testing/TestResponse.d.ts +0 -78
- package/dist/core/src/testing/index.d.ts +0 -2
- package/dist/core/src/types/events.d.ts +0 -94
- package/dist/cosmos/src/I18nService.d.ts +0 -144
- package/dist/cosmos/src/index.d.ts +0 -21
- package/dist/cosmos/src/loader.d.ts +0 -11
- package/dist/index.js +0 -168
- package/dist/photon/src/index.d.ts +0 -2
- package/dist/src/index.js +0 -173
package/CHANGELOG.md
CHANGED
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
|
-
|
|
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.
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
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
|
-
|
|
74
|
+
### 3. Use in Routes
|
|
52
75
|
|
|
53
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
14
|
-
import { PlanetCore } from '@gravito/core'
|
|
15
|
-
import { OrbitI18n } from '@gravito/cosmos'
|
|
25
|
+
### 1. 註冊 Orbit
|
|
16
26
|
|
|
17
|
-
|
|
27
|
+
將 `OrbitCosmos` 加入您的 PlanetCore 配置中:
|
|
18
28
|
|
|
19
|
-
|
|
20
|
-
|
|
29
|
+
```typescript
|
|
30
|
+
import { PlanetCore } from '@gravito/core';
|
|
31
|
+
import { OrbitCosmos } from '@gravito/cosmos';
|
|
32
|
+
|
|
33
|
+
const core = new PlanetCore({
|
|
21
34
|
config: {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
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
|
-
|
|
55
|
+
### 2. 建立語言檔案 (若使用懶加載)
|
|
32
56
|
|
|
57
|
+
建立 `./lang/en.json`:
|
|
33
58
|
```json
|
|
34
59
|
{
|
|
35
|
-
"
|
|
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('
|
|
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
|
package/build.ts
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { spawn } from 'bun'
|
|
2
|
+
|
|
3
|
+
console.log('Building @gravito/cosmos...')
|
|
4
|
+
|
|
5
|
+
// Clean dist
|
|
6
|
+
await Bun.$`rm -rf dist`
|
|
7
|
+
|
|
8
|
+
// Use tsup for multi-format build
|
|
9
|
+
const tsup = spawn(
|
|
10
|
+
[
|
|
11
|
+
'npx',
|
|
12
|
+
'tsup',
|
|
13
|
+
'src/index.ts',
|
|
14
|
+
'--format',
|
|
15
|
+
'esm,cjs',
|
|
16
|
+
'--dts',
|
|
17
|
+
'--external',
|
|
18
|
+
'@gravito/core,@gravito/photon',
|
|
19
|
+
'--outDir',
|
|
20
|
+
'dist',
|
|
21
|
+
],
|
|
22
|
+
{
|
|
23
|
+
stdout: 'inherit',
|
|
24
|
+
stderr: 'inherit',
|
|
25
|
+
}
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
const tsupCode = await tsup.exited
|
|
29
|
+
if (tsupCode !== 0) {
|
|
30
|
+
console.error('❌ tsup build failed')
|
|
31
|
+
process.exit(1)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
console.log('✅ Build complete!')
|
|
35
|
+
process.exit(0)
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
# Phase 1: 效能優化計劃
|
|
2
|
+
|
|
3
|
+
> 優先級: P0 | 預估影響: 高
|
|
4
|
+
|
|
5
|
+
## 現況分析
|
|
6
|
+
|
|
7
|
+
### 當前瓶頸
|
|
8
|
+
|
|
9
|
+
1. **翻譯查找無快取**
|
|
10
|
+
- 每次 `translate()` 都重新遍歷嵌套鍵
|
|
11
|
+
- 點號分割 (`key.split('.')`) 在熱路徑重複執行
|
|
12
|
+
|
|
13
|
+
2. **參數替換效率**
|
|
14
|
+
- 正則表達式每次重新編譯
|
|
15
|
+
- 多次字串替換操作
|
|
16
|
+
|
|
17
|
+
3. **檔案加載同步阻塞**
|
|
18
|
+
- `loadTranslations()` 同步讀取所有檔案
|
|
19
|
+
- 大型翻譯檔案影響啟動時間
|
|
20
|
+
|
|
21
|
+
## 優化方案
|
|
22
|
+
|
|
23
|
+
### 1.1 翻譯查找快取
|
|
24
|
+
|
|
25
|
+
**目標**: 將重複查找的翻譯結果快取
|
|
26
|
+
|
|
27
|
+
```typescript
|
|
28
|
+
// 現況
|
|
29
|
+
translate(locale: string, key: string) {
|
|
30
|
+
const keys = key.split('.')
|
|
31
|
+
let value = this.translations[locale]
|
|
32
|
+
for (const k of keys) {
|
|
33
|
+
value = value?.[k]
|
|
34
|
+
}
|
|
35
|
+
return value
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// 優化後
|
|
39
|
+
private cache = new Map<string, string>()
|
|
40
|
+
|
|
41
|
+
translate(locale: string, key: string) {
|
|
42
|
+
const cacheKey = `${locale}:${key}`
|
|
43
|
+
|
|
44
|
+
if (this.cache.has(cacheKey)) {
|
|
45
|
+
return this.cache.get(cacheKey)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const result = this.resolveKey(locale, key)
|
|
49
|
+
this.cache.set(cacheKey, result)
|
|
50
|
+
return result
|
|
51
|
+
}
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
**評估指標**:
|
|
55
|
+
- [ ] 快取命中率 > 80%
|
|
56
|
+
- [ ] 熱路徑查找時間減少 50%+
|
|
57
|
+
|
|
58
|
+
### 1.2 預編譯正則表達式
|
|
59
|
+
|
|
60
|
+
**目標**: 避免重複編譯正則
|
|
61
|
+
|
|
62
|
+
```typescript
|
|
63
|
+
// 現況 - 每次呼叫都編譯
|
|
64
|
+
for (const [param, val] of Object.entries(replacements)) {
|
|
65
|
+
result = result.replace(new RegExp(`:${param}`, 'g'), String(val))
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// 優化後 - 預編譯快取
|
|
69
|
+
private regexCache = new Map<string, RegExp>()
|
|
70
|
+
|
|
71
|
+
private getParamRegex(param: string): RegExp {
|
|
72
|
+
if (!this.regexCache.has(param)) {
|
|
73
|
+
this.regexCache.set(param, new RegExp(`:${param}`, 'g'))
|
|
74
|
+
}
|
|
75
|
+
return this.regexCache.get(param)!
|
|
76
|
+
}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### 1.3 懶加載翻譯資源
|
|
80
|
+
|
|
81
|
+
**目標**: 按需加載語言包,減少初始載入時間
|
|
82
|
+
|
|
83
|
+
```typescript
|
|
84
|
+
interface LazyLoadConfig {
|
|
85
|
+
baseDir: string
|
|
86
|
+
preload?: string[] // 預載入的語言
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
class I18nManager {
|
|
90
|
+
private loadedLocales = new Set<string>()
|
|
91
|
+
|
|
92
|
+
async ensureLocale(locale: string): Promise<void> {
|
|
93
|
+
if (this.loadedLocales.has(locale)) return
|
|
94
|
+
|
|
95
|
+
const translations = await this.loadLocale(locale)
|
|
96
|
+
this.addResource(locale, translations)
|
|
97
|
+
this.loadedLocales.add(locale)
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
private async loadLocale(locale: string): Promise<TranslationMap> {
|
|
101
|
+
const path = `${this.config.baseDir}/${locale}.json`
|
|
102
|
+
return Bun.file(path).json()
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
**評估指標**:
|
|
108
|
+
- [ ] 初始載入時間減少 40%+
|
|
109
|
+
- [ ] 記憶體佔用按需增長
|
|
110
|
+
|
|
111
|
+
### 1.4 快取失效策略
|
|
112
|
+
|
|
113
|
+
**目標**: 確保快取正確性
|
|
114
|
+
|
|
115
|
+
```typescript
|
|
116
|
+
class I18nManager {
|
|
117
|
+
addResource(locale: string, translations: TranslationMap) {
|
|
118
|
+
this.translations[locale] = {
|
|
119
|
+
...this.translations[locale],
|
|
120
|
+
...translations
|
|
121
|
+
}
|
|
122
|
+
this.invalidateCache(locale)
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
private invalidateCache(locale?: string) {
|
|
126
|
+
if (locale) {
|
|
127
|
+
// 精確失效
|
|
128
|
+
for (const key of this.cache.keys()) {
|
|
129
|
+
if (key.startsWith(`${locale}:`)) {
|
|
130
|
+
this.cache.delete(key)
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
} else {
|
|
134
|
+
// 全部失效
|
|
135
|
+
this.cache.clear()
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
## 實施步驟
|
|
142
|
+
|
|
143
|
+
### Step 1: 基準測試建立
|
|
144
|
+
```bash
|
|
145
|
+
# 建立效能測試套件
|
|
146
|
+
bun test:perf
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
- [x] 建立翻譯查找基準測試
|
|
150
|
+
- [x] 建立參數替換基準測試
|
|
151
|
+
- [x] 建立檔案加載基準測試
|
|
152
|
+
|
|
153
|
+
### Step 2: 實施快取機制
|
|
154
|
+
- [x] 實作 `TranslationCache` 類別 (Implemented as Map in I18nManager)
|
|
155
|
+
- [x] 整合至 `I18nManager`
|
|
156
|
+
- [x] 單元測試覆蓋
|
|
157
|
+
|
|
158
|
+
### Step 3: 正則預編譯
|
|
159
|
+
- [x] 實作 `RegexCache` (Implemented as constant REPLACEMENT_REGEX)
|
|
160
|
+
- [x] 替換現有實作
|
|
161
|
+
- [x] 效能驗證
|
|
162
|
+
|
|
163
|
+
### Step 4: 懶加載機制
|
|
164
|
+
- [x] 設計 `LazyLoadConfig` 介面
|
|
165
|
+
- [x] 實作 `ensureLocale()` 方法
|
|
166
|
+
- [x] 整合至中間件
|
|
167
|
+
|
|
168
|
+
### Step 5: 效能驗證
|
|
169
|
+
- [x] 執行基準測試比對
|
|
170
|
+
- [x] 記憶體使用分析
|
|
171
|
+
- [x] 真實場景壓測
|
|
172
|
+
|
|
173
|
+
## 風險評估
|
|
174
|
+
|
|
175
|
+
| 風險 | 機率 | 影響 | 緩解措施 |
|
|
176
|
+
|------|------|------|----------|
|
|
177
|
+
| 快取記憶體爆增 | 中 | 中 | 設定快取上限,使用 LRU |
|
|
178
|
+
| 快取失效遺漏 | 低 | 高 | 完善單元測試 |
|
|
179
|
+
| 懶加載競態條件 | 中 | 中 | 使用 Promise 鎖 |
|
|
180
|
+
|
|
181
|
+
## 成功標準
|
|
182
|
+
|
|
183
|
+
- [x] 翻譯查找效能提升 50%+
|
|
184
|
+
- [x] 初始載入時間減少 40%+
|
|
185
|
+
- [x] 無記憶體洩漏
|
|
186
|
+
- [x] 所有現有測試通過
|
|
187
|
+
- [x] 新增效能測試套件
|