made-bridge 0.0.0-stage → 0.2.1

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 (57) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +45 -2
  3. package/builtin/plugins/README.md +22 -0
  4. package/builtin/themes/README.md +64 -0
  5. package/builtin/themes/sunset/theme.json +19 -0
  6. package/config.example.json +20 -0
  7. package/demo-project/README.md +5 -0
  8. package/demo-project/index.mjs +14 -0
  9. package/package.json +48 -4
  10. package/public/app.js +1028 -0
  11. package/public/assistants.html +67 -0
  12. package/public/bean-icon.png +0 -0
  13. package/public/i18n-messages.js +967 -0
  14. package/public/i18n-static.js +615 -0
  15. package/public/i18n.js +50 -0
  16. package/public/index.html +149 -0
  17. package/public/made-icon.png +0 -0
  18. package/public/plugins.html +64 -0
  19. package/public/plugins.js +335 -0
  20. package/public/provider-icon.js +77 -0
  21. package/public/styles.css +445 -0
  22. package/public/tasks.html +85 -0
  23. package/src/agent-icons.mjs +138 -0
  24. package/src/agents/codex.mjs +94 -0
  25. package/src/agents/cursor.mjs +31 -0
  26. package/src/agents/events.mjs +92 -0
  27. package/src/agents/index.mjs +7 -0
  28. package/src/agents/qoder.mjs +34 -0
  29. package/src/agents/shared.mjs +191 -0
  30. package/src/agents/workbuddy.mjs +121 -0
  31. package/src/board-link.mjs +266 -0
  32. package/src/config.mjs +196 -0
  33. package/src/doctor.mjs +146 -0
  34. package/src/i18n.mjs +257 -0
  35. package/src/installed.mjs +291 -0
  36. package/src/jobs.mjs +369 -0
  37. package/src/language-settings.mjs +38 -0
  38. package/src/model-settings.mjs +63 -0
  39. package/src/openai-key.mjs +47 -0
  40. package/src/oray.mjs +243 -0
  41. package/src/pairing.mjs +203 -0
  42. package/src/plugin-package.mjs +132 -0
  43. package/src/plugin-runtime.mjs +169 -0
  44. package/src/plugins/contracts.d.ts +184 -0
  45. package/src/plugins/enhance.mjs +156 -0
  46. package/src/plugins/registry.mjs +146 -0
  47. package/src/providers.mjs +75 -0
  48. package/src/server.mjs +950 -0
  49. package/src/sessions.mjs +134 -0
  50. package/src/speech/index.mjs +10 -0
  51. package/src/speech/openai.mjs +48 -0
  52. package/src/speech/whisper.mjs +59 -0
  53. package/src/speech-settings.mjs +209 -0
  54. package/src/transcribe.mjs +85 -0
  55. package/src/tunnel.mjs +531 -0
  56. package/src/usb-receiver.mjs +664 -0
  57. package/src/voice-settings.mjs +69 -0
@@ -0,0 +1,615 @@
1
+ // Explicitly marked, product-authored interface text.
2
+ export const staticMessages = {
3
+ "static.001": {
4
+ "zh-CN": "大盘 · 码得",
5
+ "en": "Dashboard · made"
6
+ },
7
+ "static.002": {
8
+ "zh-CN": "码得大盘",
9
+ "en": "made dashboard"
10
+ },
11
+ "static.003": {
12
+ "zh-CN": "码得",
13
+ "en": "made"
14
+ },
15
+ "static.004": {
16
+ "zh-CN": "码得 · 编程伙伴",
17
+ "en": "made · CODING COMPANION"
18
+ },
19
+ "static.005": {
20
+ "zh-CN": "页面导航",
21
+ "en": "Page navigation"
22
+ },
23
+ "static.006": {
24
+ "zh-CN": "大盘",
25
+ "en": "Dashboard"
26
+ },
27
+ "static.007": {
28
+ "zh-CN": "助手",
29
+ "en": "Assistants"
30
+ },
31
+ "static.008": {
32
+ "zh-CN": "任务",
33
+ "en": "Tasks"
34
+ },
35
+ "static.009": {
36
+ "zh-CN": "正在连接",
37
+ "en": "Connecting"
38
+ },
39
+ "static.010": {
40
+ "zh-CN": "刷新状态",
41
+ "en": "Refresh status"
42
+ },
43
+ "static.011": {
44
+ "zh-CN": "ESP32-S3 · 码得",
45
+ "en": "ESP32-S3 · made"
46
+ },
47
+ "static.012": {
48
+ "zh-CN": "码得,",
49
+ "en": "Made,"
50
+ },
51
+ "static.013": {
52
+ "zh-CN": "代码触手可得",
53
+ "en": "when you want to make"
54
+ },
55
+ "static.014": {
56
+ "zh-CN": "在这里查看码得交互、配对设备并配置语音输入。编程模型在“助手”设置,任务创建和管理在“任务”;小智继续作为独立应用运行。",
57
+ "en": "Preview made, pair devices, and configure speech input here. Choose coding models in Assistants and manage tasks in Tasks. Xiaozhi continues as a separate app."
58
+ },
59
+ "static.015": {
60
+ "zh-CN": "码得交互预览",
61
+ "en": "made interaction preview"
62
+ },
63
+ "static.016": {
64
+ "zh-CN": "任务在设备上的显示和操作方式。",
65
+ "en": "How tasks appear and work on the device."
66
+ },
67
+ "static.017": {
68
+ "zh-CN": "360 × 360 · 网页模拟",
69
+ "en": "360 × 360 · Web preview"
70
+ },
71
+ "static.018": {
72
+ "zh-CN": "码得交互预览:左右滑动换助手,顶部上下滑动换任务",
73
+ "en": "made preview: swipe left or right to switch assistants; swipe up or down on the header to switch tasks"
74
+ },
75
+ "static.019": {
76
+ "zh-CN": "新建任务",
77
+ "en": "New task"
78
+ },
79
+ "static.020": {
80
+ "zh-CN": "等待连接",
81
+ "en": "Waiting for connection"
82
+ },
83
+ "static.021": {
84
+ "zh-CN": "连接后显示助手",
85
+ "en": "Assistants appear after connecting"
86
+ },
87
+ "static.022": {
88
+ "zh-CN": "任务答复,可上下滚动",
89
+ "en": "Task reply; scroll up or down"
90
+ },
91
+ "static.023": {
92
+ "zh-CN": "左右滑动换助手,顶部上下滑动换任务;答复区上下滑动阅读。单击 BOOT 录音,静音后自动上传;双击确认,三击取消,长按返回桌面。",
93
+ "en": "Swipe left or right to switch assistants, or up and down on the header to switch tasks. Scroll replies to read more. Press BOOT to record; upload starts after silence. Double-press to confirm, triple-press to cancel, and hold to return to the desktop."
94
+ },
95
+ "static.024": {
96
+ "zh-CN": "码得配对",
97
+ "en": "Pair made"
98
+ },
99
+ "static.025": {
100
+ "zh-CN": "USB 直连只需一条数据线;也支持同 Wi-Fi、独立接收端热点和公网地址。两端核对六位码后确认。",
101
+ "en": "Direct USB needs only a cable. Same Wi-Fi, a separate receiver hotspot, and public addresses are also supported. Compare the six-digit code on both sides before confirming."
102
+ },
103
+ "static.026": {
104
+ "zh-CN": "查看请求",
105
+ "en": "View request"
106
+ },
107
+ "static.027": {
108
+ "zh-CN": "添加码得",
109
+ "en": "Add made"
110
+ },
111
+ "static.028": {
112
+ "zh-CN": "结束添加",
113
+ "en": "Finish adding"
114
+ },
115
+ "static.029": {
116
+ "zh-CN": "正在读取配对状态…",
117
+ "en": "Loading pairing status…"
118
+ },
119
+ "static.030": {
120
+ "zh-CN": "已配对设备",
121
+ "en": "Paired devices"
122
+ },
123
+ "static.031": {
124
+ "zh-CN": "USB 连接",
125
+ "en": "USB connection"
126
+ },
127
+ "static.032": {
128
+ "zh-CN": "码得可通过 USB 数据线直接连接这台电脑,不用 Wi-Fi。也支持独立 ESP32-S3 / C3 接收端提供专用热点。",
129
+ "en": "Connect made directly to this computer with USB, without Wi-Fi. A separate ESP32-S3 / C3 receiver can also provide a dedicated hotspot."
130
+ },
131
+ "static.033": {
132
+ "zh-CN": "正在检测 USB 连接…",
133
+ "en": "Checking USB connection…"
134
+ },
135
+ "static.034": {
136
+ "zh-CN": "直连码得",
137
+ "en": "Directly connected made"
138
+ },
139
+ "static.035": {
140
+ "zh-CN": "设备编号",
141
+ "en": "Device ID"
142
+ },
143
+ "static.036": {
144
+ "zh-CN": "热点名称",
145
+ "en": "Hotspot name"
146
+ },
147
+ "static.037": {
148
+ "zh-CN": "复制",
149
+ "en": "Copy"
150
+ },
151
+ "static.038": {
152
+ "zh-CN": "热点密码",
153
+ "en": "Hotspot password"
154
+ },
155
+ "static.039": {
156
+ "zh-CN": "显示",
157
+ "en": "Show"
158
+ },
159
+ "static.040": {
160
+ "zh-CN": "桥接地址",
161
+ "en": "Bridge address"
162
+ },
163
+ "static.041": {
164
+ "zh-CN": "独立接收端无需屏幕:通过 USB 插入电脑后,这里自动显示热点名称和密码。在码得内打开“齿轮 → 连接 → 接收端”,扫描并点选热点,再输入这里显示的密码并保存;无需退出到桌面设置。新固件的热点密码为 8 位数字,六位配对码随后单独核对。码得直接用 USB 插电脑时,无需填写热点信息。",
165
+ "en": "The receiver needs no screen: plug it into the computer to see its hotspot name and password here. In made, open Settings → Connection → Receiver, scan and choose the hotspot, then enter the eight-digit password and save. Compare the separate six-digit pairing code afterward. Direct USB needs no hotspot details."
166
+ },
167
+ "static.042": {
168
+ "zh-CN": "语音输入设置",
169
+ "en": "Speech input settings"
170
+ },
171
+ "static.043": {
172
+ "zh-CN": "填写转写接口 URL、API Key 和模型名,即可使用兼容 OpenAI 音频转写接口的服务。密钥只保存在这台电脑,不写入码得。",
173
+ "en": "Enter an endpoint URL, API Key, and model name for an OpenAI-compatible audio transcription service. Keys stay on this computer and are never written to made."
174
+ },
175
+ "static.044": {
176
+ "zh-CN": "正在读取语音设置…",
177
+ "en": "Loading speech settings…"
178
+ },
179
+ "static.045": {
180
+ "zh-CN": "快速填写:",
181
+ "en": "Quick fill:"
182
+ },
183
+ "static.046": {
184
+ "zh-CN": "OpenAI",
185
+ "en": "OpenAI"
186
+ },
187
+ "static.047": {
188
+ "zh-CN": "硅基流动",
189
+ "en": "SiliconFlow"
190
+ },
191
+ "static.048": {
192
+ "zh-CN": "转写接口 URL",
193
+ "en": "Transcription endpoint URL"
194
+ },
195
+ "static.049": {
196
+ "zh-CN": "API Key",
197
+ "en": "API Key"
198
+ },
199
+ "static.050": {
200
+ "zh-CN": "首次填写;留空表示保持已保存的密钥",
201
+ "en": "Required on first setup; leave blank to keep the saved key"
202
+ },
203
+ "static.051": {
204
+ "zh-CN": "模型名",
205
+ "en": "Model name"
206
+ },
207
+ "static.052": {
208
+ "zh-CN": "保存语音设置",
209
+ "en": "Save speech settings"
210
+ },
211
+ "static.053": {
212
+ "zh-CN": "清除 API Key",
213
+ "en": "Clear API Key"
214
+ },
215
+ "static.054": {
216
+ "zh-CN": "URL 请填写完整的音频转写地址,通常以 /v1/audio/transcriptions 结尾。仅兼容聊天接口的服务不一定支持语音转写。",
217
+ "en": "Enter the complete audio transcription URL, usually ending in /v1/audio/transcriptions. A service that only supports chat APIs may not support speech transcription."
218
+ },
219
+ "static.055": {
220
+ "zh-CN": "公网接入",
221
+ "en": "Public access"
222
+ },
223
+ "static.056": {
224
+ "zh-CN": "码得可通过公网 HTTPS 地址连接桥接器。只有码得通信接口会通过该地址开放;电脑管理页仍只能在本机打开。",
225
+ "en": "made can connect through a public HTTPS address. Only the device communication endpoint is exposed; the management page remains local to this computer."
226
+ },
227
+ "static.057": {
228
+ "zh-CN": "开启穿透",
229
+ "en": "Start tunnel"
230
+ },
231
+ "static.058": {
232
+ "zh-CN": "关闭穿透",
233
+ "en": "Stop tunnel"
234
+ },
235
+ "static.059": {
236
+ "zh-CN": "接入方式",
237
+ "en": "Connection method"
238
+ },
239
+ "static.060": {
240
+ "zh-CN": "Cloudflare 临时地址",
241
+ "en": "Cloudflare temporary address"
242
+ },
243
+ "static.061": {
244
+ "zh-CN": "Cloudflare 固定域名",
245
+ "en": "Cloudflare fixed domain"
246
+ },
247
+ "static.062": {
248
+ "zh-CN": "Tailscale Funnel 固定地址",
249
+ "en": "Tailscale Funnel fixed address"
250
+ },
251
+ "static.063": {
252
+ "zh-CN": "ngrok",
253
+ "en": "ngrok"
254
+ },
255
+ "static.064": {
256
+ "zh-CN": "花生壳 HTTPS 映射",
257
+ "en": "Oray HTTPS mapping"
258
+ },
259
+ "static.065": {
260
+ "zh-CN": "公网 HTTPS 地址",
261
+ "en": "Public HTTPS address"
262
+ },
263
+ "static.066": {
264
+ "zh-CN": "Cloudflare Tunnel 令牌",
265
+ "en": "Cloudflare Tunnel token"
266
+ },
267
+ "static.067": {
268
+ "zh-CN": "首次填写;留空表示保持已保存的令牌",
269
+ "en": "Required on first setup; leave blank to keep the saved token"
270
+ },
271
+ "static.068": {
272
+ "zh-CN": "ngrok HTTPS 地址(可选)",
273
+ "en": "ngrok HTTPS address (optional)"
274
+ },
275
+ "static.069": {
276
+ "zh-CN": "留空由 ngrok 分配;或填写已预留域名",
277
+ "en": "Leave blank for an assigned address, or enter a reserved domain"
278
+ },
279
+ "static.070": {
280
+ "zh-CN": "ngrok Authtoken",
281
+ "en": "ngrok Authtoken"
282
+ },
283
+ "static.071": {
284
+ "zh-CN": "首次填写;留空保持已保存的令牌",
285
+ "en": "Required on first setup; leave blank to keep the saved token"
286
+ },
287
+ "static.072": {
288
+ "zh-CN": "花生壳 HTTPS 地址",
289
+ "en": "Oray HTTPS address"
290
+ },
291
+ "static.073": {
292
+ "zh-CN": "花生壳 API Key",
293
+ "en": "Oray API Key"
294
+ },
295
+ "static.074": {
296
+ "zh-CN": "首次填写;留空保持已保存的密钥",
297
+ "en": "Required on first setup; leave blank to keep the saved key"
298
+ },
299
+ "static.075": {
300
+ "zh-CN": "保存接入设置",
301
+ "en": "Save connection settings"
302
+ },
303
+ "static.076": {
304
+ "zh-CN": "正在读取公网接入状态…",
305
+ "en": "Loading public access status…"
306
+ },
307
+ "static.077": {
308
+ "zh-CN": "复制地址",
309
+ "en": "Copy address"
310
+ },
311
+ "static.078": {
312
+ "zh-CN": "公网方式均只转发码得通信接口",
313
+ "en": "Public connections only forward the made endpoint"
314
+ },
315
+ "static.079": {
316
+ "zh-CN": "。配对时仍须在电脑和码得核对接入地址与六位码。",
317
+ "en": ". When pairing, compare the endpoint and six-digit code on the computer and made."
318
+ },
319
+ "static.080": {
320
+ "zh-CN": "连接状态",
321
+ "en": "Connection status"
322
+ },
323
+ "static.081": {
324
+ "zh-CN": "电脑桥接、码得控制与独立语音能力。",
325
+ "en": "Computer bridge, made control, and speech recognition."
326
+ },
327
+ "static.082": {
328
+ "zh-CN": "码得 · 多助手桥接",
329
+ "en": "made · Multi-assistant bridge"
330
+ },
331
+ "static.083": {
332
+ "zh-CN": "等待首次同步",
333
+ "en": "Waiting for first sync"
334
+ },
335
+ "static.084": {
336
+ "zh-CN": "设备请求连接",
337
+ "en": "Device connection request"
338
+ },
339
+ "static.085": {
340
+ "zh-CN": "确认码得配对",
341
+ "en": "Confirm made pairing"
342
+ },
343
+ "static.086": {
344
+ "zh-CN": "稍后处理",
345
+ "en": "Later"
346
+ },
347
+ "static.087": {
348
+ "zh-CN": "请核对下方数字是否与码得显示的一致。只有你确认后,这台设备才能控制电脑上的编程助手。",
349
+ "en": "Check that the code matches the one on made. The device can control the computer's coding assistants only after you confirm."
350
+ },
351
+ "static.088": {
352
+ "zh-CN": "配对验证码",
353
+ "en": "Pairing code"
354
+ },
355
+ "static.089": {
356
+ "zh-CN": "000000",
357
+ "en": "000000"
358
+ },
359
+ "static.090": {
360
+ "zh-CN": "拒绝连接",
361
+ "en": "Reject"
362
+ },
363
+ "static.091": {
364
+ "zh-CN": "数字一致,确认连接",
365
+ "en": "Codes match — connect"
366
+ },
367
+ "static.092": {
368
+ "zh-CN": "助手 · 码得",
369
+ "en": "Assistants · made"
370
+ },
371
+ "static.093": {
372
+ "zh-CN": "电脑助手 · 模型设置",
373
+ "en": "DESKTOP AGENTS · MODEL SETTINGS"
374
+ },
375
+ "static.094": {
376
+ "zh-CN": "你的助手,",
377
+ "en": "Your assistants. "
378
+ },
379
+ "static.095": {
380
+ "zh-CN": "各司其职。",
381
+ "en": "Ready to work."
382
+ },
383
+ "static.096": {
384
+ "zh-CN": "查看已安装的编程助手,并为支持模型配置的助手分别选择模型。安装插件并重启桥接器后,它会自动出现在这里。",
385
+ "en": "View installed coding assistants and choose models for those that support it. Install a plugin and restart the bridge to add it here automatically."
386
+ },
387
+ "static.097": {
388
+ "zh-CN": "编程助手",
389
+ "en": "Coding assistants"
390
+ },
391
+ "static.098": {
392
+ "zh-CN": "当前电脑上可用的执行入口。",
393
+ "en": "Available execution options on this computer."
394
+ },
395
+ "static.099": {
396
+ "zh-CN": "编程助手模型",
397
+ "en": "Assistant models"
398
+ },
399
+ "static.100": {
400
+ "zh-CN": "为支持此功能的助手分别选择模型;保存后从下一条指令开始生效。语音识别模型在“大盘”中设置。",
401
+ "en": "Choose a model for each supported assistant. Saved settings apply to the next instruction. Configure the speech recognition model on Dashboard."
402
+ },
403
+ "static.101": {
404
+ "zh-CN": "Codex",
405
+ "en": "Codex"
406
+ },
407
+ "static.102": {
408
+ "zh-CN": "当前电脑账号已验证 gpt-6-astra 可用。",
409
+ "en": "This computer's account has been verified to support gpt-6-astra."
410
+ },
411
+ "static.103": {
412
+ "zh-CN": "Cursor",
413
+ "en": "Cursor"
414
+ },
415
+ "static.104": {
416
+ "zh-CN": "留空则使用 Cursor 当前默认模型",
417
+ "en": "Leave blank to use Cursor's current default model"
418
+ },
419
+ "static.105": {
420
+ "zh-CN": "Qoder",
421
+ "en": "Qoder"
422
+ },
423
+ "static.106": {
424
+ "zh-CN": "留空则使用 Qoder 当前默认模型",
425
+ "en": "Leave blank to use Qoder's current default model"
426
+ },
427
+ "static.107": {
428
+ "zh-CN": "保存模型选择",
429
+ "en": "Save model selection"
430
+ },
431
+ "static.108": {
432
+ "zh-CN": "模型名称需由对应助手账号支持。",
433
+ "en": "Your assistant account must support the selected model."
434
+ },
435
+ "static.109": {
436
+ "zh-CN": "任务 · 码得",
437
+ "en": "Tasks · made"
438
+ },
439
+ "static.110": {
440
+ "zh-CN": "任务 · 编程助手",
441
+ "en": "TASKS · CODING AGENT"
442
+ },
443
+ "static.111": {
444
+ "zh-CN": "说出任务,",
445
+ "en": "One task. "
446
+ },
447
+ "static.112": {
448
+ "zh-CN": "看见进度",
449
+ "en": "An ongoing conversation."
450
+ },
451
+ "static.113": {
452
+ "zh-CN": "点“新建任务”选择助手和代码文件夹。第一句语音或文字会成为任务标题,后续指令都在这个任务里继续;每次执行前仍需确认。",
453
+ "en": "Choose “New task” to select an assistant and code folder. The first voice or text instruction becomes the title. Later instructions continue in the same task; each execution still needs confirmation."
454
+ },
455
+ "static.114": {
456
+ "zh-CN": "选择一个任务,查看最新进展并继续对话。",
457
+ "en": "Select a task to see its latest progress and continue the conversation."
458
+ },
459
+ "static.115": {
460
+ "zh-CN": "+ 新建任务",
461
+ "en": "+ New task"
462
+ },
463
+ "static.116": {
464
+ "zh-CN": "正在加载…",
465
+ "en": "Loading…"
466
+ },
467
+ "static.117": {
468
+ "zh-CN": "代码文件夹",
469
+ "en": "Code folder"
470
+ },
471
+ "static.118": {
472
+ "zh-CN": "第一句指令会自动成为任务标题,之后可以改标题。",
473
+ "en": "Your first instruction becomes the task title. You can rename it later."
474
+ },
475
+ "static.119": {
476
+ "zh-CN": "Codex、Cursor、Qoder 将尽可能沿用各自 CLI 的任务上下文。",
477
+ "en": "Codex, Cursor, and Qoder reuse their CLI task context where supported."
478
+ },
479
+ "static.120": {
480
+ "zh-CN": "创建任务",
481
+ "en": "Create task"
482
+ },
483
+ "static.121": {
484
+ "zh-CN": "任务列表",
485
+ "en": "Task list"
486
+ },
487
+ "static.122": {
488
+ "zh-CN": "正在读取任务",
489
+ "en": "Loading tasks"
490
+ },
491
+ "static.123": {
492
+ "zh-CN": "历史记录",
493
+ "en": "History"
494
+ },
495
+ "static.124": {
496
+ "zh-CN": "历史任务列表",
497
+ "en": "Historical task list"
498
+ },
499
+ "static.125": {
500
+ "zh-CN": "当前任务",
501
+ "en": "Current task"
502
+ },
503
+ "static.126": {
504
+ "zh-CN": "请选择任务",
505
+ "en": "Select a task"
506
+ },
507
+ "static.127": {
508
+ "zh-CN": "选择左侧任务,或新建一个任务。",
509
+ "en": "Select a task on the left, or create one."
510
+ },
511
+ "static.128": {
512
+ "zh-CN": "改标题",
513
+ "en": "Rename"
514
+ },
515
+ "static.129": {
516
+ "zh-CN": "新标题",
517
+ "en": "New title"
518
+ },
519
+ "static.130": {
520
+ "zh-CN": "保存",
521
+ "en": "Save"
522
+ },
523
+ "static.131": {
524
+ "zh-CN": "取消",
525
+ "en": "Cancel"
526
+ },
527
+ "static.132": {
528
+ "zh-CN": "最新进展",
529
+ "en": "Latest progress"
530
+ },
531
+ "static.133": {
532
+ "zh-CN": "显示这个任务最新一次指令、执行状态和助手回复。",
533
+ "en": "The most recent instruction, execution status, and assistant reply for this task."
534
+ },
535
+ "static.134": {
536
+ "zh-CN": "正在连接本机桥接服务…",
537
+ "en": "Connecting to the local bridge…"
538
+ },
539
+ "static.135": {
540
+ "zh-CN": "继续这个任务",
541
+ "en": "Continue this task"
542
+ },
543
+ "static.136": {
544
+ "zh-CN": "下一句会接着当前任务的上下文继续。",
545
+ "en": "The next instruction continues with this task's context."
546
+ },
547
+ "static.137": {
548
+ "zh-CN": "下一条指令",
549
+ "en": "Next instruction"
550
+ },
551
+ "static.138": {
552
+ "zh-CN": "例如:接着修复刚才发现的第一个问题。",
553
+ "en": "For example: Fix the first issue you just found."
554
+ },
555
+ "static.139": {
556
+ "zh-CN": "发送并回显",
557
+ "en": "Send for review"
558
+ },
559
+ "static.140": {
560
+ "zh-CN": "插件",
561
+ "en": "Plugins"
562
+ },
563
+ "static.141": {
564
+ "zh-CN": "插件 · 码得",
565
+ "en": "Plugins · made"
566
+ },
567
+ "static.142": {
568
+ "zh-CN": "PLUGINS · THEMES",
569
+ "en": "PLUGINS · THEMES"
570
+ },
571
+ "static.143": {
572
+ "zh-CN": "装在这台电脑上,",
573
+ "en": "Installed on this computer, "
574
+ },
575
+ "static.144": {
576
+ "zh-CN": "由你开关。",
577
+ "en": "and switched by you."
578
+ },
579
+ "static.145": {
580
+ "zh-CN": "插件放在 ~/made/plugins,主题放在 ~/made/themes,桥接器每次启动自动扫描。bridge/builtin 里的内置包跟代码走,同名时 ~/made 覆盖内置。启用和停用会立即生效;替换插件文件内容后,请重启桥接器。",
581
+ "en": "Plugins live in ~/made/plugins and themes in ~/made/themes; the bridge scans them on every start. Built-in packs under bridge/builtin travel with the code, and ~/made overrides a built-in of the same name. Enabling or disabling takes effect immediately. Restart the bridge after you replace a plugin file."
582
+ },
583
+ "static.146": {
584
+ "zh-CN": "接收码得事件,可以下发命令。停用后不再处理这些事件。",
585
+ "en": "These receive made events and can send commands. A disabled plugin stops handling those events."
586
+ },
587
+ "static.147": {
588
+ "zh-CN": "扩展一种助手的执行方式。停用后不再出现在助手列表。",
589
+ "en": "These add a way for an assistant to run. A disabled plugin leaves the assistant list."
590
+ },
591
+ "static.148": {
592
+ "zh-CN": "已安装主题",
593
+ "en": "Installed themes"
594
+ },
595
+ "static.149": {
596
+ "zh-CN": "主题包都在这里管理。应用到码得后,设备解锁并连上电脑时才会换上这套外观。",
597
+ "en": "Theme packs are managed here. made applies one after it is unlocked and connected to this computer."
598
+ },
599
+ "language.label": {
600
+ "zh-CN": "语言",
601
+ "en": "Language"
602
+ },
603
+ "language.saving": {
604
+ "zh-CN": "正在保存语言…",
605
+ "en": "Saving language…"
606
+ },
607
+ "language.failed": {
608
+ "zh-CN": "语言保存失败,请重试。",
609
+ "en": "Could not save the language. Try again."
610
+ },
611
+ "language.saved": {
612
+ "zh-CN": "已切换为简体中文。",
613
+ "en": "Language changed to English."
614
+ }
615
+ };
package/public/i18n.js ADDED
@@ -0,0 +1,50 @@
1
+ import { messages } from './i18n-messages.js';
2
+ import { staticMessages } from './i18n-static.js';
3
+
4
+ export const supportedLocales = ['zh-CN', 'en'];
5
+ const catalog = { ...messages, ...staticMessages };
6
+ let locale = globalThis.navigator?.language?.toLowerCase().startsWith('zh') ? 'zh-CN' : 'en';
7
+ const uiMessages = new Map();
8
+
9
+ export const getLocale = () => locale;
10
+ export function setLocale(value) {
11
+ if (!supportedLocales.includes(value)) return false;
12
+ const changed = locale !== value;
13
+ locale = value;
14
+ return changed;
15
+ }
16
+ export function t(key, params = {}) {
17
+ const message = catalog[key]?.[locale];
18
+ if (typeof message !== 'string') throw new Error(`Missing interface translation: ${key}/${locale}`);
19
+ // Replacement is one pass: user-provided parameter text is never treated as a template.
20
+ return message.replace(/\{([a-zA-Z0-9_]+)\}/g, (match, name) => Object.hasOwn(params, name) ? String(params[name] ?? '') : match);
21
+ }
22
+ export const m = (key, params = {}) => ({ key, params });
23
+ export function setUiMessage(node, value) {
24
+ if (!node) return;
25
+ uiMessages.set(node, value);
26
+ node.textContent = value && typeof value === 'object' && Object.hasOwn(value, 'key') ? t(value.key, value.params) : String(value ?? '');
27
+ }
28
+ export function refreshUiMessages() {
29
+ for (const [node, value] of uiMessages) {
30
+ if (!node.isConnected) { uiMessages.delete(node); continue; }
31
+ // Raw service errors belong to the response's locale; discard them on a language change.
32
+ setUiMessage(node, value && typeof value === 'object' ? value : '');
33
+ }
34
+ }
35
+ export function applyStaticTranslations(root = document) {
36
+ document.documentElement.lang = locale;
37
+ for (const node of root.querySelectorAll('[data-i18n]')) node.textContent = t(node.dataset.i18n);
38
+ for (const attribute of ['title', 'placeholder', 'aria-label', 'alt']) {
39
+ for (const node of root.querySelectorAll(`[data-i18n-${attribute}]`)) {
40
+ node.setAttribute(attribute, t(node.getAttribute(`data-i18n-${attribute}`)));
41
+ }
42
+ }
43
+ }
44
+ export function formatDateTime(value, options = {
45
+ year: 'numeric', month: 'numeric', day: 'numeric',
46
+ hour: '2-digit', minute: '2-digit', second: '2-digit',
47
+ }) {
48
+ const date = value instanceof Date ? value : new Date(value);
49
+ return Number.isNaN(date.getTime()) ? '' : new Intl.DateTimeFormat(locale, options).format(date);
50
+ }