opencode-visual-cache 1.6.1 → 1.7.0-beta.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/LICENSE +21 -21
- package/README.md +247 -247
- package/README_EN.md +247 -247
- package/dist/_version.d.ts +1 -1
- package/dist/_version.js +1 -1
- package/dist/core/color.d.ts +37 -0
- package/dist/core/color.js +108 -0
- package/dist/core/currency.d.ts +17 -0
- package/dist/core/currency.js +43 -0
- package/dist/core/estimate.d.ts +1 -0
- package/dist/core/estimate.js +40 -0
- package/dist/core/format.d.ts +15 -0
- package/dist/core/format.js +90 -0
- package/dist/core/index.d.ts +5 -0
- package/dist/core/index.js +5 -0
- package/dist/core/types.d.ts +17 -0
- package/dist/core/types.js +1 -0
- package/dist/index.js +159 -815
- package/dist/panel/TokenCachePanel.d.ts +10 -0
- package/dist/panel/TokenCachePanel.js +549 -0
- package/dist/panel/panel-api.d.ts +119 -0
- package/dist/panel/panel-api.js +1 -0
- package/dist/tui.js +349 -264
- package/dist/v2/commands.d.ts +10 -0
- package/dist/v2/commands.js +490 -0
- package/dist/v2/data.d.ts +27 -0
- package/dist/v2/data.js +103 -0
- package/dist/v2/index.d.ts +4 -0
- package/dist/v2/index.js +163 -0
- package/dist/v2/sidebar.d.ts +6 -0
- package/dist/v2/sidebar.js +36 -0
- package/dist/v2/status.d.ts +14 -0
- package/dist/v2/status.js +83 -0
- package/dist/v2/theme.d.ts +8 -0
- package/dist/v2/theme.js +16 -0
- package/dist/v2/types.d.ts +219 -0
- package/dist/v2/types.js +6 -0
- package/dist/v2/v2-panel-api.d.ts +8 -0
- package/dist/v2/v2-panel-api.js +176 -0
- package/dist/v2.js +2941 -0
- package/package.json +70 -67
- package/src/_version.ts +1 -1
- package/src/balance-providers.ts +153 -153
- package/src/core/color.ts +108 -0
- package/src/core/currency.ts +46 -0
- package/src/core/estimate.ts +36 -0
- package/src/core/format.ts +81 -0
- package/src/core/index.ts +5 -0
- package/src/core/types.ts +17 -0
- package/src/i18n.ts +380 -380
- package/src/index.tsx +1035 -1980
- package/src/panel/TokenCachePanel.tsx +776 -0
- package/src/panel/panel-api.ts +104 -0
- package/src/server.ts +10 -10
- package/src/v2/commands.ts +471 -0
- package/src/v2/data.ts +112 -0
- package/src/v2/index.tsx +184 -0
- package/src/v2/sidebar.tsx +69 -0
- package/src/v2/status.tsx +96 -0
- package/src/v2/theme.ts +19 -0
- package/src/v2/types.ts +159 -0
- package/src/v2/v2-panel-api.ts +177 -0
package/src/i18n.ts
CHANGED
|
@@ -1,380 +1,380 @@
|
|
|
1
|
-
// ---------------------------------------------------------------------------
|
|
2
|
-
// i18n — centralized translations. Add a language by appending a table that
|
|
3
|
-
// satisfies `Translation`; the compiler enforces key completeness.
|
|
4
|
-
// ---------------------------------------------------------------------------
|
|
5
|
-
|
|
6
|
-
export type LangCode = "zh" | "en" | "ja" | "ko"
|
|
7
|
-
|
|
8
|
-
const ZH_T = {
|
|
9
|
-
title: "缓存统计",
|
|
10
|
-
hit: "命中率",
|
|
11
|
-
totalHit: "总命中:",
|
|
12
|
-
read: "缓存读:",
|
|
13
|
-
write: "缓存写:",
|
|
14
|
-
miss: "未命中:",
|
|
15
|
-
out: "输出:",
|
|
16
|
-
cost: "费用:",
|
|
17
|
-
saved: "累计节省:",
|
|
18
|
-
model: "模型:",
|
|
19
|
-
provider: "提供商:",
|
|
20
|
-
rate: "单价:",
|
|
21
|
-
hitFolded: "命中",
|
|
22
|
-
inputRate: "输入",
|
|
23
|
-
cacheRate: "缓存",
|
|
24
|
-
writeRate: "写入",
|
|
25
|
-
noData: "等待缓存数据...",
|
|
26
|
-
tok: "tok",
|
|
27
|
-
distTitle: "估算 Token 分布",
|
|
28
|
-
distSys: "系统提示:",
|
|
29
|
-
distUser: "用户:",
|
|
30
|
-
distAgent: "子代理指令:",
|
|
31
|
-
distTool: "Tool 调用:",
|
|
32
|
-
distRes: "Tool 结果:",
|
|
33
|
-
distReason: "推理:",
|
|
34
|
-
stepsCount: "本回合 {n} 次调用:",
|
|
35
|
-
secDetail: "明细",
|
|
36
|
-
secModel: "模型",
|
|
37
|
-
secSkills: "已加载技能",
|
|
38
|
-
balTotal: "总余额:",
|
|
39
|
-
balNoKey: "未配置 {p} API Key",
|
|
40
|
-
balLoading: "查询中...",
|
|
41
|
-
balError: "查询失败",
|
|
42
|
-
balErr401: "API Key 无效",
|
|
43
|
-
balErr403: "余额查询被拒绝",
|
|
44
|
-
balErrEmpty:"未获取到余额数据",
|
|
45
|
-
balErrTimeout: "查询超时",
|
|
46
|
-
balUnsupported: "当前提供商不支持余额查询",
|
|
47
|
-
barHit: "命中率",
|
|
48
|
-
barBal: "余额",
|
|
49
|
-
barTok: "Tokens",
|
|
50
|
-
// ── /cache-section 面板 ──
|
|
51
|
-
secToggle: "切换区块",
|
|
52
|
-
secBalance: "余额",
|
|
53
|
-
secBottom: "底部状态栏",
|
|
54
|
-
secBorder: "面板边框",
|
|
55
|
-
// ── toast ──
|
|
56
|
-
keySaved: "API Key 已保存,正在查询余额...",
|
|
57
|
-
keyCleared: "API Key 已清除",
|
|
58
|
-
currencySet: "币种: {v} ({s}), 汇率: {r}",
|
|
59
|
-
rateSet: "汇率已设为 {r}",
|
|
60
|
-
panelConfigTitle: "缓存面板配置",
|
|
61
|
-
panelConfigMsg: "币种: {c} | 汇率: {r} | 明细: {d} | 模型: {m} | 分布: {t} | 技能: {k} | 余额: {b} | 底部: {f}",
|
|
62
|
-
borderShown: "面板边框 已显示",
|
|
63
|
-
borderHidden: "面板边框 已隐藏",
|
|
64
|
-
sectionShown: "{s} 已显示",
|
|
65
|
-
sectionHidden: "{s} 已隐藏",
|
|
66
|
-
langSwitched: "语言已切换为中文",
|
|
67
|
-
autoSwitchOn: "自动切换余额提供商: 开",
|
|
68
|
-
autoSwitchOff: "自动切换余额提供商: 关",
|
|
69
|
-
providerManual: "余额提供商: {p}(自动切换已关闭)",
|
|
70
|
-
runInSession: "请在会话内运行此命令",
|
|
71
|
-
backToMain: "已切回主会话",
|
|
72
|
-
subAgentSwitched: "已切换至子代理: {s}",
|
|
73
|
-
// ── 对话框 / 菜单 ──
|
|
74
|
-
langTitle: "显示语言",
|
|
75
|
-
subPrefix: "子代理: ",
|
|
76
|
-
keyUser: "(用户 key)",
|
|
77
|
-
keyOpenCode: "(OpenCode)",
|
|
78
|
-
keyNotSet: "(未配置)",
|
|
79
|
-
balKeyPrompt: "输入 {p} API Key 以显示账户余额(留空清除)",
|
|
80
|
-
balProvTitle: "余额提供商 / 自动切换",
|
|
81
|
-
autoSwitchOpt: "自动切换提供商",
|
|
82
|
-
balSelectTitle: "选择余额提供商",
|
|
83
|
-
backToMainTitle: "回到主会话",
|
|
84
|
-
subSelectTitle: "选择子代理",
|
|
85
|
-
subSwitchTitle: "切换子代理",
|
|
86
|
-
subViewTitle: "查看子代理缓存",
|
|
87
|
-
subNoFound: "未找到子代理,请手动粘贴 Session ID",
|
|
88
|
-
} as const
|
|
89
|
-
|
|
90
|
-
/** 结构约束:值放宽为 string,键集合来自中文表(新增语言缺 key 会编译报错)。 */
|
|
91
|
-
export type Translation = { [K in keyof typeof ZH_T]: string }
|
|
92
|
-
|
|
93
|
-
const EN_T: Translation = {
|
|
94
|
-
title: "Token Cache",
|
|
95
|
-
hit: "Hit",
|
|
96
|
-
totalHit: "Total Hit:",
|
|
97
|
-
read: "Read:",
|
|
98
|
-
write: "Write:",
|
|
99
|
-
miss: "Miss:",
|
|
100
|
-
out: "Out:",
|
|
101
|
-
cost: "Cost:",
|
|
102
|
-
saved: "Total Saved:",
|
|
103
|
-
model: "Model:",
|
|
104
|
-
provider: "Provider:",
|
|
105
|
-
rate: "Rate:",
|
|
106
|
-
hitFolded: "hit",
|
|
107
|
-
inputRate: "in",
|
|
108
|
-
cacheRate: "cache",
|
|
109
|
-
writeRate: "write",
|
|
110
|
-
noData: "Waiting for cache data...",
|
|
111
|
-
tok: "tok",
|
|
112
|
-
distTitle: "Estimated Token Dist.",
|
|
113
|
-
distSys: "System:",
|
|
114
|
-
distUser: "User:",
|
|
115
|
-
distAgent: "Sub-Agent Instr:",
|
|
116
|
-
distTool: "Tool Call:",
|
|
117
|
-
distRes: "Tool Result:",
|
|
118
|
-
distReason: "Reasoning:",
|
|
119
|
-
stepsCount: "Turn #{n} calls:",
|
|
120
|
-
secDetail: "Detail",
|
|
121
|
-
secModel: "Model",
|
|
122
|
-
secSkills: "Loaded Skills",
|
|
123
|
-
balTotal: "Total:",
|
|
124
|
-
balNoKey: "{p} API Key not set",
|
|
125
|
-
balLoading: "Fetching...",
|
|
126
|
-
balError: "Fetch failed",
|
|
127
|
-
balErr401: "Invalid API Key",
|
|
128
|
-
balErr403: "Balance request rejected",
|
|
129
|
-
balErrEmpty:"No balance data",
|
|
130
|
-
balErrTimeout: "Request timed out",
|
|
131
|
-
balUnsupported: "Balance query unsupported",
|
|
132
|
-
barHit: "Hit",
|
|
133
|
-
barBal: "Balance",
|
|
134
|
-
barTok: "Tokens",
|
|
135
|
-
// ── /cache-section panel ──
|
|
136
|
-
secToggle: "Toggle Section",
|
|
137
|
-
secBalance: "Balance",
|
|
138
|
-
secBottom: "Bottom Bar",
|
|
139
|
-
secBorder: "Panel Border",
|
|
140
|
-
// ── toasts ──
|
|
141
|
-
keySaved: "API Key saved, fetching balance...",
|
|
142
|
-
keyCleared: "API Key cleared",
|
|
143
|
-
currencySet: "Currency: {v} ({s}), rate: {r}",
|
|
144
|
-
rateSet: "Exchange rate set to {r}",
|
|
145
|
-
panelConfigTitle: "Cache Panel Config",
|
|
146
|
-
panelConfigMsg: "Currency: {c} | Rate: {r} | Detail: {d} | Model: {m} | Dist: {t} | Skills: {k} | Balance: {b} | Bottom: {f}",
|
|
147
|
-
borderShown: "Panel border shown",
|
|
148
|
-
borderHidden: "Panel border hidden",
|
|
149
|
-
sectionShown: "{s} section shown",
|
|
150
|
-
sectionHidden: "{s} section hidden",
|
|
151
|
-
langSwitched: "Switched to English",
|
|
152
|
-
autoSwitchOn: "Auto-switch balance provider: ON",
|
|
153
|
-
autoSwitchOff: "Auto-switch balance provider: OFF",
|
|
154
|
-
providerManual: "Balance provider: {p} (auto-switch off)",
|
|
155
|
-
runInSession: "Please run this command inside a session",
|
|
156
|
-
backToMain: "Switched to main session",
|
|
157
|
-
subAgentSwitched: "Showing sub-agent: {s}",
|
|
158
|
-
// ── dialogs / menus ──
|
|
159
|
-
langTitle: "Display Language",
|
|
160
|
-
subPrefix: "Sub: ",
|
|
161
|
-
keyUser: " (user key)",
|
|
162
|
-
keyOpenCode: " (OpenCode)",
|
|
163
|
-
keyNotSet: " (not set)",
|
|
164
|
-
balKeyPrompt: "Enter your {p} API key to show account balance (leave empty to clear)",
|
|
165
|
-
balProvTitle: "Balance Provider / Auto-switch",
|
|
166
|
-
autoSwitchOpt: "Auto-switch provider",
|
|
167
|
-
balSelectTitle: "Select Balance Provider",
|
|
168
|
-
backToMainTitle: "Back to Main",
|
|
169
|
-
subSelectTitle: "Select Sub-Agent",
|
|
170
|
-
subSwitchTitle: "Switch Sub",
|
|
171
|
-
subViewTitle: "View Sub Cache",
|
|
172
|
-
subNoFound: "No sub-agents found. Paste a Session ID manually",
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
const JA_T: Translation = {
|
|
176
|
-
title: "キャッシュ統計",
|
|
177
|
-
hit: "ヒット率",
|
|
178
|
-
totalHit: "総ヒット:",
|
|
179
|
-
read: "キャッシュ読込:",
|
|
180
|
-
write: "キャッシュ書込:",
|
|
181
|
-
miss: "未ヒット:",
|
|
182
|
-
out: "出力:",
|
|
183
|
-
cost: "費用:",
|
|
184
|
-
saved: "累計節約:",
|
|
185
|
-
model: "モデル:",
|
|
186
|
-
provider: "プロバイダ:",
|
|
187
|
-
rate: "単価:",
|
|
188
|
-
hitFolded: "ヒット",
|
|
189
|
-
inputRate: "入力",
|
|
190
|
-
cacheRate: "キャッシュ",
|
|
191
|
-
writeRate: "書込",
|
|
192
|
-
noData: "キャッシュデータ待機中...",
|
|
193
|
-
tok: "tok",
|
|
194
|
-
distTitle: "Token 分布(推定)",
|
|
195
|
-
distSys: "システム:",
|
|
196
|
-
distUser: "ユーザー:",
|
|
197
|
-
distAgent: "サブエージェント指示:",
|
|
198
|
-
distTool: "Tool 呼び出し:",
|
|
199
|
-
distRes: "Tool 結果:",
|
|
200
|
-
distReason: "推論:",
|
|
201
|
-
stepsCount: "今回 {n} 回呼出:",
|
|
202
|
-
secDetail: "明細",
|
|
203
|
-
secModel: "モデル",
|
|
204
|
-
secSkills: "読み込み済みスキル",
|
|
205
|
-
balTotal: "総残高:",
|
|
206
|
-
balNoKey: "{p} の API Key 未設定",
|
|
207
|
-
balLoading: "取得中...",
|
|
208
|
-
balError: "取得失敗",
|
|
209
|
-
balErr401: "API Key 無効",
|
|
210
|
-
balErr403: "残高照会が拒否されました",
|
|
211
|
-
balErrEmpty:"残高データなし",
|
|
212
|
-
balErrTimeout: "タイムアウト",
|
|
213
|
-
balUnsupported: "このプロバイダは残高照会非対応",
|
|
214
|
-
barHit: "ヒット率",
|
|
215
|
-
barBal: "残高",
|
|
216
|
-
barTok: "Tokens",
|
|
217
|
-
// ── /cache-section パネル ──
|
|
218
|
-
secToggle: "セクション切替",
|
|
219
|
-
secBalance: "残高",
|
|
220
|
-
secBottom: "下部ステータスバー",
|
|
221
|
-
secBorder: "パネル枠線",
|
|
222
|
-
// ── toast ──
|
|
223
|
-
keySaved: "API Key 保存済み、残高を取得中...",
|
|
224
|
-
keyCleared: "API Key をクリアしました",
|
|
225
|
-
currencySet: "通貨: {v} ({s}), レート: {r}",
|
|
226
|
-
rateSet: "レートを {r} に設定しました",
|
|
227
|
-
panelConfigTitle: "パネル設定",
|
|
228
|
-
panelConfigMsg: "通貨: {c} | レート: {r} | 明細: {d} | モデル: {m} | 分布: {t} | スキル: {k} | 残高: {b} | 下部: {f}",
|
|
229
|
-
borderShown: "パネル枠線 表示",
|
|
230
|
-
borderHidden: "パネル枠線 非表示",
|
|
231
|
-
sectionShown: "{s} 表示",
|
|
232
|
-
sectionHidden: "{s} 非表示",
|
|
233
|
-
langSwitched: "日本語に切り替えました",
|
|
234
|
-
autoSwitchOn: "残高プロバイダ自動切替: オン",
|
|
235
|
-
autoSwitchOff: "残高プロバイダ自動切替: オフ",
|
|
236
|
-
providerManual: "残高プロバイダ: {p}(自動切替オフ)",
|
|
237
|
-
runInSession: "セッション内で実行してください",
|
|
238
|
-
backToMain: "メインセッションに戻りました",
|
|
239
|
-
subAgentSwitched: "サブエージェントへ切替: {s}",
|
|
240
|
-
// ── ダイアログ / メニュー ──
|
|
241
|
-
langTitle: "表示言語",
|
|
242
|
-
subPrefix: "サブ: ",
|
|
243
|
-
keyUser: "(ユーザー key)",
|
|
244
|
-
keyOpenCode: "(OpenCode)",
|
|
245
|
-
keyNotSet: "(未設定)",
|
|
246
|
-
balKeyPrompt: "{p} の API Key を入力して残高を表示(空欄でクリア)",
|
|
247
|
-
balProvTitle: "残高プロバイダ / 自動切替",
|
|
248
|
-
autoSwitchOpt: "自動切替プロバイダ",
|
|
249
|
-
balSelectTitle: "残高プロバイダ選択",
|
|
250
|
-
backToMainTitle: "メインに戻る",
|
|
251
|
-
subSelectTitle: "サブエージェント選択",
|
|
252
|
-
subSwitchTitle: "サブ切替",
|
|
253
|
-
subViewTitle: "サブキャッシュ表示",
|
|
254
|
-
subNoFound: "サブエージェントが見つかりません。Session ID を手動で貼り付けてください",
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
const KO_T: Translation = {
|
|
258
|
-
title: "캐시 통계",
|
|
259
|
-
hit: "히트율",
|
|
260
|
-
totalHit: "총 히트:",
|
|
261
|
-
read: "캐시 읽기:",
|
|
262
|
-
write: "캐시 쓰기:",
|
|
263
|
-
miss: "미히트:",
|
|
264
|
-
out: "출력:",
|
|
265
|
-
cost: "비용:",
|
|
266
|
-
saved: "누적 절약:",
|
|
267
|
-
model: "모델:",
|
|
268
|
-
provider: "프로바이더:",
|
|
269
|
-
rate: "단가:",
|
|
270
|
-
hitFolded: "히트",
|
|
271
|
-
inputRate: "입력",
|
|
272
|
-
cacheRate: "캐시",
|
|
273
|
-
writeRate: "쓰기",
|
|
274
|
-
noData: "캐시 데이터 대기 중...",
|
|
275
|
-
tok: "tok",
|
|
276
|
-
distTitle: "Token 분포(추정)",
|
|
277
|
-
distSys: "시스템:",
|
|
278
|
-
distUser: "사용자:",
|
|
279
|
-
distAgent: "서브 에이전트 지시:",
|
|
280
|
-
distTool: "Tool 호출:",
|
|
281
|
-
distRes: "Tool 결과:",
|
|
282
|
-
distReason: "추론:",
|
|
283
|
-
stepsCount: "이번 턴 {n}회 호출:",
|
|
284
|
-
secDetail: "상세",
|
|
285
|
-
secModel: "모델",
|
|
286
|
-
secSkills: "로드된 스킬",
|
|
287
|
-
balTotal: "총 잔액:",
|
|
288
|
-
balNoKey: "{p} API Key 미설정",
|
|
289
|
-
balLoading: "조회 중...",
|
|
290
|
-
balError: "조회 실패",
|
|
291
|
-
balErr401: "API Key 무효",
|
|
292
|
-
balErr403: "잔액 조회가 거부되었습니다",
|
|
293
|
-
balErrEmpty:"잔액 데이터 없음",
|
|
294
|
-
balErrTimeout: "시간 초과",
|
|
295
|
-
balUnsupported: "이 프로바이더는 잔액 조회 미지원",
|
|
296
|
-
barHit: "히트율",
|
|
297
|
-
barBal: "잔액",
|
|
298
|
-
barTok: "Tokens",
|
|
299
|
-
// ── /cache-section 패널 ──
|
|
300
|
-
secToggle: "섹션 전환",
|
|
301
|
-
secBalance: "잔액",
|
|
302
|
-
secBottom: "하단 상태바",
|
|
303
|
-
secBorder: "패널 테두리",
|
|
304
|
-
// ── toast ──
|
|
305
|
-
keySaved: "API Key 저장됨, 잔액 조회 중...",
|
|
306
|
-
keyCleared: "API Key 삭제됨",
|
|
307
|
-
currencySet: "통화: {v} ({s}), 환율: {r}",
|
|
308
|
-
rateSet: "환율을 {r}(으)로 설정했습니다",
|
|
309
|
-
panelConfigTitle: "패널 설정",
|
|
310
|
-
panelConfigMsg: "통화: {c} | 환율: {r} | 상세: {d} | 모델: {m} | 분포: {t} | 스킬: {k} | 잔액: {b} | 하단: {f}",
|
|
311
|
-
borderShown: "패널 테두리 표시",
|
|
312
|
-
borderHidden: "패널 테두리 숨김",
|
|
313
|
-
sectionShown: "{s} 표시",
|
|
314
|
-
sectionHidden: "{s} 숨김",
|
|
315
|
-
langSwitched: "한국어로 전환했습니다",
|
|
316
|
-
autoSwitchOn: "잔액 프로바이더 자동 전환: 켜짐",
|
|
317
|
-
autoSwitchOff: "잔액 프로바이더 자동 전환: 꺼짐",
|
|
318
|
-
providerManual: "잔액 프로바이더: {p}(자동 전환 꺼짐)",
|
|
319
|
-
runInSession: "세션 내에서 실행해 주세요",
|
|
320
|
-
backToMain: "메인 세션으로 돌아갔습니다",
|
|
321
|
-
subAgentSwitched: "서브 에이전트로 전환: {s}",
|
|
322
|
-
// ── 다이얼로그 / 메뉴 ──
|
|
323
|
-
langTitle: "표시 언어",
|
|
324
|
-
subPrefix: "서브: ",
|
|
325
|
-
keyUser: "(사용자 key)",
|
|
326
|
-
keyOpenCode: "(OpenCode)",
|
|
327
|
-
keyNotSet: "(미설정)",
|
|
328
|
-
balKeyPrompt: "{p} API Key를 입력하여 잔액 표시(비우면 삭제)",
|
|
329
|
-
balProvTitle: "잔액 프로바이더 / 자동 전환",
|
|
330
|
-
autoSwitchOpt: "자동 전환 프로바이더",
|
|
331
|
-
balSelectTitle: "잔액 프로바이더 선택",
|
|
332
|
-
backToMainTitle: "메인으로 돌아가기",
|
|
333
|
-
subSelectTitle: "서브 에이전트 선택",
|
|
334
|
-
subSwitchTitle: "서브 전환",
|
|
335
|
-
subViewTitle: "서브 캐시 보기",
|
|
336
|
-
subNoFound: "서브 에이전트를 찾을 수 없습니다. Session ID를 직접 붙여넣으세요",
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
export const LANGS: Record<LangCode, Translation> = { zh: ZH_T, en: EN_T, ja: JA_T, ko: KO_T }
|
|
340
|
-
|
|
341
|
-
/** 语言元数据:/cache-lang 选项与自动检测共用。 */
|
|
342
|
-
export const LANG_META: { code: LangCode; label: string }[] = [
|
|
343
|
-
{ code: "zh", label: "中文" },
|
|
344
|
-
{ code: "en", label: "English" },
|
|
345
|
-
{ code: "ja", label: "日本語" },
|
|
346
|
-
{ code: "ko", label: "한국어" },
|
|
347
|
-
]
|
|
348
|
-
|
|
349
|
-
/**
|
|
350
|
-
* 模板参数替换:`{key}` 占位符统一在此处理。
|
|
351
|
-
* 未提供的参数保留原占位符,避免静默丢失。
|
|
352
|
-
*/
|
|
353
|
-
export function applyParams(tpl: string, params?: Record<string, string | number>): string {
|
|
354
|
-
if (!params) return tpl
|
|
355
|
-
return tpl.replace(/\{(\w+)\}/g, (m, k: string) =>
|
|
356
|
-
k in params ? String(params[k]) : m,
|
|
357
|
-
)
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
/**
|
|
361
|
-
* 翻译查找函数工厂:`t("key")` 返回当前语言的文本,`t("key", { p })` 附带模板参数。
|
|
362
|
-
* `getCode` 读取语言信号——在 SolidJS 渲染/memo 上下文中调用时自动建立响应式依赖。
|
|
363
|
-
*/
|
|
364
|
-
export function createT(getCode: () => LangCode) {
|
|
365
|
-
return (key: keyof Translation, params?: Record<string, string | number>): string =>
|
|
366
|
-
applyParams(LANGS[getCode()][key], params)
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
/** 按系统 locale 自动检测语言(zh → 中文,ja → 日语,ko → 韩语,其余 → 英文)。 */
|
|
370
|
-
export function detectLang(): LangCode {
|
|
371
|
-
try {
|
|
372
|
-
const loc = Intl.DateTimeFormat().resolvedOptions().locale.toLowerCase()
|
|
373
|
-
if (loc.startsWith("zh")) return "zh"
|
|
374
|
-
if (loc.startsWith("ja")) return "ja"
|
|
375
|
-
if (loc.startsWith("ko")) return "ko"
|
|
376
|
-
return "en"
|
|
377
|
-
} catch {
|
|
378
|
-
return "en"
|
|
379
|
-
}
|
|
380
|
-
}
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// i18n — centralized translations. Add a language by appending a table that
|
|
3
|
+
// satisfies `Translation`; the compiler enforces key completeness.
|
|
4
|
+
// ---------------------------------------------------------------------------
|
|
5
|
+
|
|
6
|
+
export type LangCode = "zh" | "en" | "ja" | "ko"
|
|
7
|
+
|
|
8
|
+
const ZH_T = {
|
|
9
|
+
title: "缓存统计",
|
|
10
|
+
hit: "命中率",
|
|
11
|
+
totalHit: "总命中:",
|
|
12
|
+
read: "缓存读:",
|
|
13
|
+
write: "缓存写:",
|
|
14
|
+
miss: "未命中:",
|
|
15
|
+
out: "输出:",
|
|
16
|
+
cost: "费用:",
|
|
17
|
+
saved: "累计节省:",
|
|
18
|
+
model: "模型:",
|
|
19
|
+
provider: "提供商:",
|
|
20
|
+
rate: "单价:",
|
|
21
|
+
hitFolded: "命中",
|
|
22
|
+
inputRate: "输入",
|
|
23
|
+
cacheRate: "缓存",
|
|
24
|
+
writeRate: "写入",
|
|
25
|
+
noData: "等待缓存数据...",
|
|
26
|
+
tok: "tok",
|
|
27
|
+
distTitle: "估算 Token 分布",
|
|
28
|
+
distSys: "系统提示:",
|
|
29
|
+
distUser: "用户:",
|
|
30
|
+
distAgent: "子代理指令:",
|
|
31
|
+
distTool: "Tool 调用:",
|
|
32
|
+
distRes: "Tool 结果:",
|
|
33
|
+
distReason: "推理:",
|
|
34
|
+
stepsCount: "本回合 {n} 次调用:",
|
|
35
|
+
secDetail: "明细",
|
|
36
|
+
secModel: "模型",
|
|
37
|
+
secSkills: "已加载技能",
|
|
38
|
+
balTotal: "总余额:",
|
|
39
|
+
balNoKey: "未配置 {p} API Key",
|
|
40
|
+
balLoading: "查询中...",
|
|
41
|
+
balError: "查询失败",
|
|
42
|
+
balErr401: "API Key 无效",
|
|
43
|
+
balErr403: "余额查询被拒绝",
|
|
44
|
+
balErrEmpty:"未获取到余额数据",
|
|
45
|
+
balErrTimeout: "查询超时",
|
|
46
|
+
balUnsupported: "当前提供商不支持余额查询",
|
|
47
|
+
barHit: "命中率",
|
|
48
|
+
barBal: "余额",
|
|
49
|
+
barTok: "Tokens",
|
|
50
|
+
// ── /cache-section 面板 ──
|
|
51
|
+
secToggle: "切换区块",
|
|
52
|
+
secBalance: "余额",
|
|
53
|
+
secBottom: "底部状态栏",
|
|
54
|
+
secBorder: "面板边框",
|
|
55
|
+
// ── toast ──
|
|
56
|
+
keySaved: "API Key 已保存,正在查询余额...",
|
|
57
|
+
keyCleared: "API Key 已清除",
|
|
58
|
+
currencySet: "币种: {v} ({s}), 汇率: {r}",
|
|
59
|
+
rateSet: "汇率已设为 {r}",
|
|
60
|
+
panelConfigTitle: "缓存面板配置",
|
|
61
|
+
panelConfigMsg: "币种: {c} | 汇率: {r} | 明细: {d} | 模型: {m} | 分布: {t} | 技能: {k} | 余额: {b} | 底部: {f}",
|
|
62
|
+
borderShown: "面板边框 已显示",
|
|
63
|
+
borderHidden: "面板边框 已隐藏",
|
|
64
|
+
sectionShown: "{s} 已显示",
|
|
65
|
+
sectionHidden: "{s} 已隐藏",
|
|
66
|
+
langSwitched: "语言已切换为中文",
|
|
67
|
+
autoSwitchOn: "自动切换余额提供商: 开",
|
|
68
|
+
autoSwitchOff: "自动切换余额提供商: 关",
|
|
69
|
+
providerManual: "余额提供商: {p}(自动切换已关闭)",
|
|
70
|
+
runInSession: "请在会话内运行此命令",
|
|
71
|
+
backToMain: "已切回主会话",
|
|
72
|
+
subAgentSwitched: "已切换至子代理: {s}",
|
|
73
|
+
// ── 对话框 / 菜单 ──
|
|
74
|
+
langTitle: "显示语言",
|
|
75
|
+
subPrefix: "子代理: ",
|
|
76
|
+
keyUser: "(用户 key)",
|
|
77
|
+
keyOpenCode: "(OpenCode)",
|
|
78
|
+
keyNotSet: "(未配置)",
|
|
79
|
+
balKeyPrompt: "输入 {p} API Key 以显示账户余额(留空清除)",
|
|
80
|
+
balProvTitle: "余额提供商 / 自动切换",
|
|
81
|
+
autoSwitchOpt: "自动切换提供商",
|
|
82
|
+
balSelectTitle: "选择余额提供商",
|
|
83
|
+
backToMainTitle: "回到主会话",
|
|
84
|
+
subSelectTitle: "选择子代理",
|
|
85
|
+
subSwitchTitle: "切换子代理",
|
|
86
|
+
subViewTitle: "查看子代理缓存",
|
|
87
|
+
subNoFound: "未找到子代理,请手动粘贴 Session ID",
|
|
88
|
+
} as const
|
|
89
|
+
|
|
90
|
+
/** 结构约束:值放宽为 string,键集合来自中文表(新增语言缺 key 会编译报错)。 */
|
|
91
|
+
export type Translation = { [K in keyof typeof ZH_T]: string }
|
|
92
|
+
|
|
93
|
+
const EN_T: Translation = {
|
|
94
|
+
title: "Token Cache",
|
|
95
|
+
hit: "Hit",
|
|
96
|
+
totalHit: "Total Hit:",
|
|
97
|
+
read: "Read:",
|
|
98
|
+
write: "Write:",
|
|
99
|
+
miss: "Miss:",
|
|
100
|
+
out: "Out:",
|
|
101
|
+
cost: "Cost:",
|
|
102
|
+
saved: "Total Saved:",
|
|
103
|
+
model: "Model:",
|
|
104
|
+
provider: "Provider:",
|
|
105
|
+
rate: "Rate:",
|
|
106
|
+
hitFolded: "hit",
|
|
107
|
+
inputRate: "in",
|
|
108
|
+
cacheRate: "cache",
|
|
109
|
+
writeRate: "write",
|
|
110
|
+
noData: "Waiting for cache data...",
|
|
111
|
+
tok: "tok",
|
|
112
|
+
distTitle: "Estimated Token Dist.",
|
|
113
|
+
distSys: "System:",
|
|
114
|
+
distUser: "User:",
|
|
115
|
+
distAgent: "Sub-Agent Instr:",
|
|
116
|
+
distTool: "Tool Call:",
|
|
117
|
+
distRes: "Tool Result:",
|
|
118
|
+
distReason: "Reasoning:",
|
|
119
|
+
stepsCount: "Turn #{n} calls:",
|
|
120
|
+
secDetail: "Detail",
|
|
121
|
+
secModel: "Model",
|
|
122
|
+
secSkills: "Loaded Skills",
|
|
123
|
+
balTotal: "Total:",
|
|
124
|
+
balNoKey: "{p} API Key not set",
|
|
125
|
+
balLoading: "Fetching...",
|
|
126
|
+
balError: "Fetch failed",
|
|
127
|
+
balErr401: "Invalid API Key",
|
|
128
|
+
balErr403: "Balance request rejected",
|
|
129
|
+
balErrEmpty:"No balance data",
|
|
130
|
+
balErrTimeout: "Request timed out",
|
|
131
|
+
balUnsupported: "Balance query unsupported",
|
|
132
|
+
barHit: "Hit",
|
|
133
|
+
barBal: "Balance",
|
|
134
|
+
barTok: "Tokens",
|
|
135
|
+
// ── /cache-section panel ──
|
|
136
|
+
secToggle: "Toggle Section",
|
|
137
|
+
secBalance: "Balance",
|
|
138
|
+
secBottom: "Bottom Bar",
|
|
139
|
+
secBorder: "Panel Border",
|
|
140
|
+
// ── toasts ──
|
|
141
|
+
keySaved: "API Key saved, fetching balance...",
|
|
142
|
+
keyCleared: "API Key cleared",
|
|
143
|
+
currencySet: "Currency: {v} ({s}), rate: {r}",
|
|
144
|
+
rateSet: "Exchange rate set to {r}",
|
|
145
|
+
panelConfigTitle: "Cache Panel Config",
|
|
146
|
+
panelConfigMsg: "Currency: {c} | Rate: {r} | Detail: {d} | Model: {m} | Dist: {t} | Skills: {k} | Balance: {b} | Bottom: {f}",
|
|
147
|
+
borderShown: "Panel border shown",
|
|
148
|
+
borderHidden: "Panel border hidden",
|
|
149
|
+
sectionShown: "{s} section shown",
|
|
150
|
+
sectionHidden: "{s} section hidden",
|
|
151
|
+
langSwitched: "Switched to English",
|
|
152
|
+
autoSwitchOn: "Auto-switch balance provider: ON",
|
|
153
|
+
autoSwitchOff: "Auto-switch balance provider: OFF",
|
|
154
|
+
providerManual: "Balance provider: {p} (auto-switch off)",
|
|
155
|
+
runInSession: "Please run this command inside a session",
|
|
156
|
+
backToMain: "Switched to main session",
|
|
157
|
+
subAgentSwitched: "Showing sub-agent: {s}",
|
|
158
|
+
// ── dialogs / menus ──
|
|
159
|
+
langTitle: "Display Language",
|
|
160
|
+
subPrefix: "Sub: ",
|
|
161
|
+
keyUser: " (user key)",
|
|
162
|
+
keyOpenCode: " (OpenCode)",
|
|
163
|
+
keyNotSet: " (not set)",
|
|
164
|
+
balKeyPrompt: "Enter your {p} API key to show account balance (leave empty to clear)",
|
|
165
|
+
balProvTitle: "Balance Provider / Auto-switch",
|
|
166
|
+
autoSwitchOpt: "Auto-switch provider",
|
|
167
|
+
balSelectTitle: "Select Balance Provider",
|
|
168
|
+
backToMainTitle: "Back to Main",
|
|
169
|
+
subSelectTitle: "Select Sub-Agent",
|
|
170
|
+
subSwitchTitle: "Switch Sub",
|
|
171
|
+
subViewTitle: "View Sub Cache",
|
|
172
|
+
subNoFound: "No sub-agents found. Paste a Session ID manually",
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const JA_T: Translation = {
|
|
176
|
+
title: "キャッシュ統計",
|
|
177
|
+
hit: "ヒット率",
|
|
178
|
+
totalHit: "総ヒット:",
|
|
179
|
+
read: "キャッシュ読込:",
|
|
180
|
+
write: "キャッシュ書込:",
|
|
181
|
+
miss: "未ヒット:",
|
|
182
|
+
out: "出力:",
|
|
183
|
+
cost: "費用:",
|
|
184
|
+
saved: "累計節約:",
|
|
185
|
+
model: "モデル:",
|
|
186
|
+
provider: "プロバイダ:",
|
|
187
|
+
rate: "単価:",
|
|
188
|
+
hitFolded: "ヒット",
|
|
189
|
+
inputRate: "入力",
|
|
190
|
+
cacheRate: "キャッシュ",
|
|
191
|
+
writeRate: "書込",
|
|
192
|
+
noData: "キャッシュデータ待機中...",
|
|
193
|
+
tok: "tok",
|
|
194
|
+
distTitle: "Token 分布(推定)",
|
|
195
|
+
distSys: "システム:",
|
|
196
|
+
distUser: "ユーザー:",
|
|
197
|
+
distAgent: "サブエージェント指示:",
|
|
198
|
+
distTool: "Tool 呼び出し:",
|
|
199
|
+
distRes: "Tool 結果:",
|
|
200
|
+
distReason: "推論:",
|
|
201
|
+
stepsCount: "今回 {n} 回呼出:",
|
|
202
|
+
secDetail: "明細",
|
|
203
|
+
secModel: "モデル",
|
|
204
|
+
secSkills: "読み込み済みスキル",
|
|
205
|
+
balTotal: "総残高:",
|
|
206
|
+
balNoKey: "{p} の API Key 未設定",
|
|
207
|
+
balLoading: "取得中...",
|
|
208
|
+
balError: "取得失敗",
|
|
209
|
+
balErr401: "API Key 無効",
|
|
210
|
+
balErr403: "残高照会が拒否されました",
|
|
211
|
+
balErrEmpty:"残高データなし",
|
|
212
|
+
balErrTimeout: "タイムアウト",
|
|
213
|
+
balUnsupported: "このプロバイダは残高照会非対応",
|
|
214
|
+
barHit: "ヒット率",
|
|
215
|
+
barBal: "残高",
|
|
216
|
+
barTok: "Tokens",
|
|
217
|
+
// ── /cache-section パネル ──
|
|
218
|
+
secToggle: "セクション切替",
|
|
219
|
+
secBalance: "残高",
|
|
220
|
+
secBottom: "下部ステータスバー",
|
|
221
|
+
secBorder: "パネル枠線",
|
|
222
|
+
// ── toast ──
|
|
223
|
+
keySaved: "API Key 保存済み、残高を取得中...",
|
|
224
|
+
keyCleared: "API Key をクリアしました",
|
|
225
|
+
currencySet: "通貨: {v} ({s}), レート: {r}",
|
|
226
|
+
rateSet: "レートを {r} に設定しました",
|
|
227
|
+
panelConfigTitle: "パネル設定",
|
|
228
|
+
panelConfigMsg: "通貨: {c} | レート: {r} | 明細: {d} | モデル: {m} | 分布: {t} | スキル: {k} | 残高: {b} | 下部: {f}",
|
|
229
|
+
borderShown: "パネル枠線 表示",
|
|
230
|
+
borderHidden: "パネル枠線 非表示",
|
|
231
|
+
sectionShown: "{s} 表示",
|
|
232
|
+
sectionHidden: "{s} 非表示",
|
|
233
|
+
langSwitched: "日本語に切り替えました",
|
|
234
|
+
autoSwitchOn: "残高プロバイダ自動切替: オン",
|
|
235
|
+
autoSwitchOff: "残高プロバイダ自動切替: オフ",
|
|
236
|
+
providerManual: "残高プロバイダ: {p}(自動切替オフ)",
|
|
237
|
+
runInSession: "セッション内で実行してください",
|
|
238
|
+
backToMain: "メインセッションに戻りました",
|
|
239
|
+
subAgentSwitched: "サブエージェントへ切替: {s}",
|
|
240
|
+
// ── ダイアログ / メニュー ──
|
|
241
|
+
langTitle: "表示言語",
|
|
242
|
+
subPrefix: "サブ: ",
|
|
243
|
+
keyUser: "(ユーザー key)",
|
|
244
|
+
keyOpenCode: "(OpenCode)",
|
|
245
|
+
keyNotSet: "(未設定)",
|
|
246
|
+
balKeyPrompt: "{p} の API Key を入力して残高を表示(空欄でクリア)",
|
|
247
|
+
balProvTitle: "残高プロバイダ / 自動切替",
|
|
248
|
+
autoSwitchOpt: "自動切替プロバイダ",
|
|
249
|
+
balSelectTitle: "残高プロバイダ選択",
|
|
250
|
+
backToMainTitle: "メインに戻る",
|
|
251
|
+
subSelectTitle: "サブエージェント選択",
|
|
252
|
+
subSwitchTitle: "サブ切替",
|
|
253
|
+
subViewTitle: "サブキャッシュ表示",
|
|
254
|
+
subNoFound: "サブエージェントが見つかりません。Session ID を手動で貼り付けてください",
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
const KO_T: Translation = {
|
|
258
|
+
title: "캐시 통계",
|
|
259
|
+
hit: "히트율",
|
|
260
|
+
totalHit: "총 히트:",
|
|
261
|
+
read: "캐시 읽기:",
|
|
262
|
+
write: "캐시 쓰기:",
|
|
263
|
+
miss: "미히트:",
|
|
264
|
+
out: "출력:",
|
|
265
|
+
cost: "비용:",
|
|
266
|
+
saved: "누적 절약:",
|
|
267
|
+
model: "모델:",
|
|
268
|
+
provider: "프로바이더:",
|
|
269
|
+
rate: "단가:",
|
|
270
|
+
hitFolded: "히트",
|
|
271
|
+
inputRate: "입력",
|
|
272
|
+
cacheRate: "캐시",
|
|
273
|
+
writeRate: "쓰기",
|
|
274
|
+
noData: "캐시 데이터 대기 중...",
|
|
275
|
+
tok: "tok",
|
|
276
|
+
distTitle: "Token 분포(추정)",
|
|
277
|
+
distSys: "시스템:",
|
|
278
|
+
distUser: "사용자:",
|
|
279
|
+
distAgent: "서브 에이전트 지시:",
|
|
280
|
+
distTool: "Tool 호출:",
|
|
281
|
+
distRes: "Tool 결과:",
|
|
282
|
+
distReason: "추론:",
|
|
283
|
+
stepsCount: "이번 턴 {n}회 호출:",
|
|
284
|
+
secDetail: "상세",
|
|
285
|
+
secModel: "모델",
|
|
286
|
+
secSkills: "로드된 스킬",
|
|
287
|
+
balTotal: "총 잔액:",
|
|
288
|
+
balNoKey: "{p} API Key 미설정",
|
|
289
|
+
balLoading: "조회 중...",
|
|
290
|
+
balError: "조회 실패",
|
|
291
|
+
balErr401: "API Key 무효",
|
|
292
|
+
balErr403: "잔액 조회가 거부되었습니다",
|
|
293
|
+
balErrEmpty:"잔액 데이터 없음",
|
|
294
|
+
balErrTimeout: "시간 초과",
|
|
295
|
+
balUnsupported: "이 프로바이더는 잔액 조회 미지원",
|
|
296
|
+
barHit: "히트율",
|
|
297
|
+
barBal: "잔액",
|
|
298
|
+
barTok: "Tokens",
|
|
299
|
+
// ── /cache-section 패널 ──
|
|
300
|
+
secToggle: "섹션 전환",
|
|
301
|
+
secBalance: "잔액",
|
|
302
|
+
secBottom: "하단 상태바",
|
|
303
|
+
secBorder: "패널 테두리",
|
|
304
|
+
// ── toast ──
|
|
305
|
+
keySaved: "API Key 저장됨, 잔액 조회 중...",
|
|
306
|
+
keyCleared: "API Key 삭제됨",
|
|
307
|
+
currencySet: "통화: {v} ({s}), 환율: {r}",
|
|
308
|
+
rateSet: "환율을 {r}(으)로 설정했습니다",
|
|
309
|
+
panelConfigTitle: "패널 설정",
|
|
310
|
+
panelConfigMsg: "통화: {c} | 환율: {r} | 상세: {d} | 모델: {m} | 분포: {t} | 스킬: {k} | 잔액: {b} | 하단: {f}",
|
|
311
|
+
borderShown: "패널 테두리 표시",
|
|
312
|
+
borderHidden: "패널 테두리 숨김",
|
|
313
|
+
sectionShown: "{s} 표시",
|
|
314
|
+
sectionHidden: "{s} 숨김",
|
|
315
|
+
langSwitched: "한국어로 전환했습니다",
|
|
316
|
+
autoSwitchOn: "잔액 프로바이더 자동 전환: 켜짐",
|
|
317
|
+
autoSwitchOff: "잔액 프로바이더 자동 전환: 꺼짐",
|
|
318
|
+
providerManual: "잔액 프로바이더: {p}(자동 전환 꺼짐)",
|
|
319
|
+
runInSession: "세션 내에서 실행해 주세요",
|
|
320
|
+
backToMain: "메인 세션으로 돌아갔습니다",
|
|
321
|
+
subAgentSwitched: "서브 에이전트로 전환: {s}",
|
|
322
|
+
// ── 다이얼로그 / 메뉴 ──
|
|
323
|
+
langTitle: "표시 언어",
|
|
324
|
+
subPrefix: "서브: ",
|
|
325
|
+
keyUser: "(사용자 key)",
|
|
326
|
+
keyOpenCode: "(OpenCode)",
|
|
327
|
+
keyNotSet: "(미설정)",
|
|
328
|
+
balKeyPrompt: "{p} API Key를 입력하여 잔액 표시(비우면 삭제)",
|
|
329
|
+
balProvTitle: "잔액 프로바이더 / 자동 전환",
|
|
330
|
+
autoSwitchOpt: "자동 전환 프로바이더",
|
|
331
|
+
balSelectTitle: "잔액 프로바이더 선택",
|
|
332
|
+
backToMainTitle: "메인으로 돌아가기",
|
|
333
|
+
subSelectTitle: "서브 에이전트 선택",
|
|
334
|
+
subSwitchTitle: "서브 전환",
|
|
335
|
+
subViewTitle: "서브 캐시 보기",
|
|
336
|
+
subNoFound: "서브 에이전트를 찾을 수 없습니다. Session ID를 직접 붙여넣으세요",
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
export const LANGS: Record<LangCode, Translation> = { zh: ZH_T, en: EN_T, ja: JA_T, ko: KO_T }
|
|
340
|
+
|
|
341
|
+
/** 语言元数据:/cache-lang 选项与自动检测共用。 */
|
|
342
|
+
export const LANG_META: { code: LangCode; label: string }[] = [
|
|
343
|
+
{ code: "zh", label: "中文" },
|
|
344
|
+
{ code: "en", label: "English" },
|
|
345
|
+
{ code: "ja", label: "日本語" },
|
|
346
|
+
{ code: "ko", label: "한국어" },
|
|
347
|
+
]
|
|
348
|
+
|
|
349
|
+
/**
|
|
350
|
+
* 模板参数替换:`{key}` 占位符统一在此处理。
|
|
351
|
+
* 未提供的参数保留原占位符,避免静默丢失。
|
|
352
|
+
*/
|
|
353
|
+
export function applyParams(tpl: string, params?: Record<string, string | number>): string {
|
|
354
|
+
if (!params) return tpl
|
|
355
|
+
return tpl.replace(/\{(\w+)\}/g, (m, k: string) =>
|
|
356
|
+
k in params ? String(params[k]) : m,
|
|
357
|
+
)
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
/**
|
|
361
|
+
* 翻译查找函数工厂:`t("key")` 返回当前语言的文本,`t("key", { p })` 附带模板参数。
|
|
362
|
+
* `getCode` 读取语言信号——在 SolidJS 渲染/memo 上下文中调用时自动建立响应式依赖。
|
|
363
|
+
*/
|
|
364
|
+
export function createT(getCode: () => LangCode) {
|
|
365
|
+
return (key: keyof Translation, params?: Record<string, string | number>): string =>
|
|
366
|
+
applyParams(LANGS[getCode()][key], params)
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
/** 按系统 locale 自动检测语言(zh → 中文,ja → 日语,ko → 韩语,其余 → 英文)。 */
|
|
370
|
+
export function detectLang(): LangCode {
|
|
371
|
+
try {
|
|
372
|
+
const loc = Intl.DateTimeFormat().resolvedOptions().locale.toLowerCase()
|
|
373
|
+
if (loc.startsWith("zh")) return "zh"
|
|
374
|
+
if (loc.startsWith("ja")) return "ja"
|
|
375
|
+
if (loc.startsWith("ko")) return "ko"
|
|
376
|
+
return "en"
|
|
377
|
+
} catch {
|
|
378
|
+
return "en"
|
|
379
|
+
}
|
|
380
|
+
}
|