@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.
- package/MIGRATION.md +331 -0
- package/README.md +105 -45
- package/README.zh-TW.md +102 -22
- 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 +22 -6
- package/scripts/check-coverage.ts +64 -0
- package/src/HMRWatcher.ts +305 -0
- package/src/I18nService.ts +715 -91
- package/src/index.edge.ts +35 -0
- package/src/index.node.ts +20 -0
- package/src/index.ts +39 -6
- package/src/loader.ts +64 -14
- package/src/loaders/ChainedLoader.ts +117 -0
- package/src/loaders/CloudflareKVLoader.ts +194 -0
- package/src/loaders/EdgeKVLoader.ts +248 -0
- package/src/loaders/FileSystemLoader.ts +125 -0
- package/src/loaders/MemoryLoader.ts +161 -0
- package/src/loaders/RemoteLoader.ts +235 -0
- package/src/loaders/TranslationLoader.ts +98 -0
- package/src/loaders/VercelKVLoader.ts +192 -0
- package/src/runtime/detector.ts +97 -0
- package/src/runtime/path-utils.ts +169 -0
- 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 +65 -0
- package/tests/unit/edge-kv-loader.test.ts +202 -0
- package/tests/unit/edge.test.ts +100 -0
- package/tests/unit/fallback.test.ts +66 -0
- package/tests/unit/hmr.test.ts +255 -0
- package/tests/unit/lazy.test.ts +35 -0
- package/tests/unit/loader.test.ts +72 -0
- package/tests/unit/loaders.test.ts +332 -0
- package/tests/{manager.test.ts → unit/manager.test.ts} +1 -1
- package/tests/unit/memory-loader.test.ts +130 -0
- package/tests/unit/path-utils.test.ts +135 -0
- package/tests/unit/plural.test.ts +58 -0
- package/tests/unit/runtime-detector.test.ts +86 -0
- package/tests/{service.test.ts → unit/service.test.ts} +2 -2
- package/tsconfig.json +12 -24
- package/.turbo/turbo-build.log +0 -20
- package/.turbo/turbo-test$colon$ci.log +0 -35
- package/.turbo/turbo-test$colon$coverage.log +0 -35
- package/.turbo/turbo-test.log +0 -27
- package/.turbo/turbo-typecheck.log +0 -2
- package/dist/index.cjs +0 -309
- package/dist/index.d.cts +0 -274
- package/dist/index.d.ts +0 -274
- package/dist/index.js +0 -277
- package/tests/loader.test.ts +0 -44
|
@@ -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] 新增效能測試套件
|
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
# Phase 2: 架構改進計劃
|
|
2
|
+
|
|
3
|
+
> 優先級: P1 | 預估影響: 中高
|
|
4
|
+
|
|
5
|
+
## 現況分析
|
|
6
|
+
|
|
7
|
+
### 當前架構
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
OrbitCosmos
|
|
11
|
+
│
|
|
12
|
+
├── I18nManager (單例)
|
|
13
|
+
│ │
|
|
14
|
+
│ └── translations: Record<locale, TranslationMap>
|
|
15
|
+
│
|
|
16
|
+
├── I18nInstance (請求範圍)
|
|
17
|
+
│ └── 包裝 manager + locale
|
|
18
|
+
│
|
|
19
|
+
└── localeMiddleware
|
|
20
|
+
└── 語言檢測 → 注入 I18nInstance
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
### 識別問題
|
|
24
|
+
|
|
25
|
+
1. **型別定義不夠精確**
|
|
26
|
+
- `TranslationMap` 過於寬鬆
|
|
27
|
+
- 缺少嚴格的型別推斷
|
|
28
|
+
|
|
29
|
+
2. **缺少複數形式支援**
|
|
30
|
+
- 無法處理 `1 item` vs `2 items`
|
|
31
|
+
|
|
32
|
+
3. **無 ICU MessageFormat 支援**
|
|
33
|
+
- 複雜格式化需求無法滿足
|
|
34
|
+
|
|
35
|
+
4. **中間件耦合度高**
|
|
36
|
+
- 語言檢測邏輯硬編碼
|
|
37
|
+
|
|
38
|
+
## 優化方案
|
|
39
|
+
|
|
40
|
+
### 2.1 強化型別系統
|
|
41
|
+
|
|
42
|
+
**目標**: 提供編譯時期的翻譯鍵檢查
|
|
43
|
+
|
|
44
|
+
```typescript
|
|
45
|
+
// 定義翻譯結構型別
|
|
46
|
+
type NestedKeyOf<T> = T extends object
|
|
47
|
+
? { [K in keyof T]: K extends string
|
|
48
|
+
? T[K] extends object
|
|
49
|
+
? `${K}.${NestedKeyOf<T[K]>}`
|
|
50
|
+
: K
|
|
51
|
+
: never
|
|
52
|
+
}[keyof T]
|
|
53
|
+
: never
|
|
54
|
+
|
|
55
|
+
// 使用範例
|
|
56
|
+
interface Translations {
|
|
57
|
+
common: {
|
|
58
|
+
welcome: string
|
|
59
|
+
goodbye: string
|
|
60
|
+
}
|
|
61
|
+
auth: {
|
|
62
|
+
login: string
|
|
63
|
+
logout: string
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
type TranslationKey = NestedKeyOf<Translations>
|
|
68
|
+
// 結果: "common.welcome" | "common.goodbye" | "auth.login" | "auth.logout"
|
|
69
|
+
|
|
70
|
+
// 型別安全的翻譯函數
|
|
71
|
+
function t<T extends Translations>(key: NestedKeyOf<T>): string
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### 2.2 複數形式支援
|
|
75
|
+
|
|
76
|
+
**目標**: 支援根據數量選擇正確的翻譯形式
|
|
77
|
+
|
|
78
|
+
```typescript
|
|
79
|
+
interface PluralConfig {
|
|
80
|
+
zero?: string
|
|
81
|
+
one: string
|
|
82
|
+
two?: string
|
|
83
|
+
few?: string
|
|
84
|
+
many?: string
|
|
85
|
+
other: string
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// 翻譯檔案格式
|
|
89
|
+
{
|
|
90
|
+
"items": {
|
|
91
|
+
"zero": "沒有項目",
|
|
92
|
+
"one": "1 個項目",
|
|
93
|
+
"other": ":count 個項目"
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// API 使用
|
|
98
|
+
i18n.t('items', { count: 0 }) // "沒有項目"
|
|
99
|
+
i18n.t('items', { count: 1 }) // "1 個項目"
|
|
100
|
+
i18n.t('items', { count: 5 }) // "5 個項目"
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
**實作方式**:
|
|
104
|
+
|
|
105
|
+
```typescript
|
|
106
|
+
class I18nManager {
|
|
107
|
+
private pluralRules: Map<string, Intl.PluralRules> = new Map()
|
|
108
|
+
|
|
109
|
+
private getPluralForm(locale: string, count: number): string {
|
|
110
|
+
if (!this.pluralRules.has(locale)) {
|
|
111
|
+
this.pluralRules.set(locale, new Intl.PluralRules(locale))
|
|
112
|
+
}
|
|
113
|
+
return this.pluralRules.get(locale)!.select(count)
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
translate(locale: string, key: string, replacements?: Record<string, unknown>) {
|
|
117
|
+
const value = this.resolveKey(locale, key)
|
|
118
|
+
|
|
119
|
+
// 檢查是否為複數形式
|
|
120
|
+
if (typeof value === 'object' && replacements?.count !== undefined) {
|
|
121
|
+
const form = this.getPluralForm(locale, Number(replacements.count))
|
|
122
|
+
const pluralValue = value[form] ?? value.other
|
|
123
|
+
return this.replaceParams(pluralValue, replacements)
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return this.replaceParams(value, replacements)
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### 2.3 ICU MessageFormat 支援 (可選)
|
|
132
|
+
|
|
133
|
+
**目標**: 支援複雜的訊息格式化
|
|
134
|
+
|
|
135
|
+
```typescript
|
|
136
|
+
// ICU MessageFormat 範例
|
|
137
|
+
{
|
|
138
|
+
"greeting": "Hello {name}, you have {count, plural, =0 {no messages} one {# message} other {# messages}}."
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// 使用
|
|
142
|
+
i18n.t('greeting', { name: 'Carl', count: 5 })
|
|
143
|
+
// "Hello Carl, you have 5 messages."
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
**實作方式**: 整合 `@formatjs/intl-messageformat`
|
|
147
|
+
|
|
148
|
+
```typescript
|
|
149
|
+
import { IntlMessageFormat } from 'intl-messageformat'
|
|
150
|
+
|
|
151
|
+
class I18nManager {
|
|
152
|
+
private messageCache = new Map<string, IntlMessageFormat>()
|
|
153
|
+
|
|
154
|
+
translateICU(locale: string, key: string, values?: Record<string, unknown>) {
|
|
155
|
+
const cacheKey = `${locale}:${key}`
|
|
156
|
+
|
|
157
|
+
if (!this.messageCache.has(cacheKey)) {
|
|
158
|
+
const message = this.resolveKey(locale, key)
|
|
159
|
+
this.messageCache.set(cacheKey, new IntlMessageFormat(message, locale))
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
return this.messageCache.get(cacheKey)!.format(values)
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
### 2.4 可插拔的語言檢測策略
|
|
168
|
+
|
|
169
|
+
**目標**: 解耦語言檢測邏輯
|
|
170
|
+
|
|
171
|
+
```typescript
|
|
172
|
+
interface LocaleDetector {
|
|
173
|
+
name: string
|
|
174
|
+
priority: number
|
|
175
|
+
detect(c: Context): string | undefined
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// 內建檢測器
|
|
179
|
+
const routeParamDetector: LocaleDetector = {
|
|
180
|
+
name: 'routeParam',
|
|
181
|
+
priority: 100,
|
|
182
|
+
detect: (c) => c.req.param('locale')
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const queryDetector: LocaleDetector = {
|
|
186
|
+
name: 'query',
|
|
187
|
+
priority: 90,
|
|
188
|
+
detect: (c) => c.req.query('lang')
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const headerDetector: LocaleDetector = {
|
|
192
|
+
name: 'acceptLanguage',
|
|
193
|
+
priority: 80,
|
|
194
|
+
detect: (c) => parseAcceptLanguage(c.req.header('Accept-Language'))
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const cookieDetector: LocaleDetector = {
|
|
198
|
+
name: 'cookie',
|
|
199
|
+
priority: 85,
|
|
200
|
+
detect: (c) => c.req.cookie('locale')
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// 配置
|
|
204
|
+
const cosmos = new OrbitCosmos({
|
|
205
|
+
detectors: [
|
|
206
|
+
routeParamDetector,
|
|
207
|
+
cookieDetector,
|
|
208
|
+
headerDetector
|
|
209
|
+
]
|
|
210
|
+
})
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
### 2.5 命名空間支援
|
|
214
|
+
|
|
215
|
+
**目標**: 支援大型專案的翻譯組織
|
|
216
|
+
|
|
217
|
+
```typescript
|
|
218
|
+
interface NamespaceConfig {
|
|
219
|
+
defaultNamespace: string
|
|
220
|
+
namespaces: string[]
|
|
221
|
+
loadNamespace?: (locale: string, ns: string) => Promise<TranslationMap>
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// 使用
|
|
225
|
+
i18n.t('common:welcome') // 從 common 命名空間
|
|
226
|
+
i18n.t('auth:login.title') // 從 auth 命名空間
|
|
227
|
+
i18n.t('welcome') // 從預設命名空間
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
## 架構演進圖
|
|
231
|
+
|
|
232
|
+
```
|
|
233
|
+
現況:
|
|
234
|
+
┌─────────────────┐
|
|
235
|
+
│ OrbitCosmos │
|
|
236
|
+
│ (硬編碼邏輯) │
|
|
237
|
+
└────────┬────────┘
|
|
238
|
+
│
|
|
239
|
+
▼
|
|
240
|
+
┌─────────────────┐
|
|
241
|
+
│ I18nManager │
|
|
242
|
+
│ (單一職責) │
|
|
243
|
+
└─────────────────┘
|
|
244
|
+
|
|
245
|
+
優化後:
|
|
246
|
+
┌─────────────────┐
|
|
247
|
+
│ OrbitCosmos │
|
|
248
|
+
│ (協調者) │
|
|
249
|
+
└────────┬────────┘
|
|
250
|
+
│
|
|
251
|
+
┌────┴────┬────────────┐
|
|
252
|
+
▼ ▼ ▼
|
|
253
|
+
┌────────┐ ┌────────┐ ┌──────────┐
|
|
254
|
+
│Detector│ │Formatter│ │ Manager │
|
|
255
|
+
│Pipeline│ │ ICU │ │ Core │
|
|
256
|
+
└────────┘ └────────┘ └──────────┘
|
|
257
|
+
│
|
|
258
|
+
┌──────┴──────┐
|
|
259
|
+
▼ ▼
|
|
260
|
+
┌────────┐ ┌─────────┐
|
|
261
|
+
│ Cache │ │Namespace│
|
|
262
|
+
│Manager │ │ Loader │
|
|
263
|
+
└────────┘ └─────────┘
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
## 實施步驟
|
|
267
|
+
|
|
268
|
+
### Step 1: 型別強化
|
|
269
|
+
- [x] 定義 `NestedKeyOf` 工具型別
|
|
270
|
+
- [x] 建立型別安全的 API
|
|
271
|
+
- [x] 更新 JSDoc 文件
|
|
272
|
+
|
|
273
|
+
### Step 2: 複數形式
|
|
274
|
+
- [x] 實作 `PluralConfig` 介面 (Implicit in logic)
|
|
275
|
+
- [x] 整合 `Intl.PluralRules`
|
|
276
|
+
- [x] 建立測試案例
|
|
277
|
+
|
|
278
|
+
### Step 3: 可插拔檢測器
|
|
279
|
+
- [x] 定義 `LocaleDetector` 介面
|
|
280
|
+
- [x] 實作內建檢測器
|
|
281
|
+
- [x] 重構 `localeMiddleware`
|
|
282
|
+
|
|
283
|
+
### Step 4: ICU 支援 (可選)
|
|
284
|
+
- [ ] 評估 `intl-messageformat` 套件 (Skipped for now)
|
|
285
|
+
- [ ] 實作 `translateICU()` 方法
|
|
286
|
+
- [ ] 效能測試
|
|
287
|
+
|
|
288
|
+
### Step 5: 命名空間
|
|
289
|
+
- [ ] 設計命名空間載入機制 (Skipped/Future)
|
|
290
|
+
- [ ] 實作命名空間解析
|
|
291
|
+
- [ ] 文件更新
|
|
292
|
+
|
|
293
|
+
## 向後相容性
|
|
294
|
+
|
|
295
|
+
| 變更 | 相容性 | 遷移方式 |
|
|
296
|
+
|------|--------|----------|
|
|
297
|
+
| 型別強化 | ✅ 完全相容 | 無需遷移 |
|
|
298
|
+
| 複數形式 | ✅ 完全相容 | 漸進採用 |
|
|
299
|
+
| 可插拔檢測 | ✅ 完全相容 | 預設行為不變 |
|
|
300
|
+
| ICU 支援 | ✅ 完全相容 | 可選功能 |
|
|
301
|
+
| 命名空間 | ⚠️ 需配置 | 提供遷移指南 |
|
|
302
|
+
|
|
303
|
+
## 成功標準
|
|
304
|
+
|
|
305
|
+
- [x] 型別推斷覆蓋主要 API
|
|
306
|
+
- [x] 複數形式支援主要語言
|
|
307
|
+
- [x] 檢測器可自由組合
|
|
308
|
+
- [x] 向後相容性 100%
|
|
309
|
+
- [x] 文件完整更新
|