dsh-plugin-mobile-link 0.8.0 → 0.8.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.
- package/lib/cli.js +8 -9
- package/lib/client.js +972 -95
- package/lib/handoff.js +1 -1
- package/lib/history/fold-summary.js +85 -32
- package/lib/history/fold.js +11 -1
- package/lib/history/question-record.js +186 -0
- package/lib/i18n.js +177 -0
- package/lib/image-originals.js +121 -0
- package/lib/image-variants.js +315 -0
- package/lib/index.js +50 -0
- package/lib/journal.js +2 -0
- package/lib/link.js +83 -10
- package/lib/router.js +63 -2
- package/lib/session-scope.js +89 -0
- package/lib/status.js +13 -10
- package/package.json +1 -1
package/lib/client.js
CHANGED
|
@@ -20,8 +20,863 @@ const SLOT_OVERLAY = 'shell.overlay'
|
|
|
20
20
|
const ENTRY_ID = 'mobile-link-entry'
|
|
21
21
|
const PANEL_ID = 'mobile-link-panel'
|
|
22
22
|
const STYLE_ID = 'dsh-mobile-link-style'
|
|
23
|
-
|
|
24
|
-
|
|
23
|
+
/**
|
|
24
|
+
* UI language: Chinese is the source language; English and Spanish come from the tables
|
|
25
|
+
* below, picked from `navigator.language`, and an untranslated sentence falls back to the
|
|
26
|
+
* source text. Inlined here because the client half is a hand-written bundle that only
|
|
27
|
+
* requires platform seeds.
|
|
28
|
+
*/
|
|
29
|
+
const MESSAGES = {
|
|
30
|
+
en: {
|
|
31
|
+
'(已配对 ${paired}/${max})': '(paired ${paired}/${max})',
|
|
32
|
+
'移动端连接': 'Mobile link',
|
|
33
|
+
'移动端连接:二维码、已配对的手机与连接状态': 'Mobile link: QR code, paired phones and link status',
|
|
34
|
+
'此电脑尚未登记': 'This computer is not registered',
|
|
35
|
+
'此电脑尚未登记。请使用命令行完成登记:': 'This computer is not registered. Register it from the command line:',
|
|
36
|
+
'登记此电脑以连接中转服务器。': 'Register this computer to connect to the relay.',
|
|
37
|
+
'此电脑已登记。': 'This computer is registered.',
|
|
38
|
+
'中转地址': 'Relay address',
|
|
39
|
+
'中转服务器': 'Relay',
|
|
40
|
+
'已预填,可修改。': 'Pre-filled; editable.',
|
|
41
|
+
'由配置指定,不可修改。': 'Set by the configuration; not editable.',
|
|
42
|
+
'请输入中转地址。': 'Enter the relay address.',
|
|
43
|
+
'中转地址无效。': 'The relay address is not valid.',
|
|
44
|
+
'请输入有效的中转地址。': 'Enter a valid relay address.',
|
|
45
|
+
'尝试次数过多,请稍后重试。': 'Too many attempts; try again later.',
|
|
46
|
+
'中转服务器未响应,请稍后重试。': 'The relay did not respond; try again later.',
|
|
47
|
+
'中转服务器登记失败,请稍后重试。': 'The relay refused the registration; try again later.',
|
|
48
|
+
'中转服务器返回了无法识别的响应。': 'The relay returned an unknown response.',
|
|
49
|
+
'连接器正在停止,请稍后重试。': 'The connector is stopping; try again later.',
|
|
50
|
+
'连接器内部错误,请稍后重试。': 'Internal connector error; try again later.',
|
|
51
|
+
'登记信息未能保存到此电脑,请检查磁盘权限。': 'The registration could not be saved; check the disk permissions.',
|
|
52
|
+
'登记后将自动连接。': 'It connects automatically once registered.',
|
|
53
|
+
'登记失败,请重试。': 'Registration failed; try again.',
|
|
54
|
+
'正在登记…': 'Registering…',
|
|
55
|
+
'正在获取…': 'Fetching…',
|
|
56
|
+
'正在读取状态…': 'Reading status…',
|
|
57
|
+
'正在连接…': 'Connecting…',
|
|
58
|
+
'登记': 'Register',
|
|
59
|
+
'重试': 'Retry',
|
|
60
|
+
'无法读取连接状态。': 'Could not read the link status.',
|
|
61
|
+
'无法连接中转服务器,请检查地址与网络。': 'Could not reach the relay; check the address and the network.',
|
|
62
|
+
'状态未知': 'Unknown state',
|
|
63
|
+
'已连接': 'Connected',
|
|
64
|
+
'在线': 'online',
|
|
65
|
+
'离线': 'offline',
|
|
66
|
+
'未连接,正在重新连接': 'Not connected, reconnecting',
|
|
67
|
+
'连接器已停用': 'Connector disabled',
|
|
68
|
+
'连接器已停用。': 'Connector disabled.',
|
|
69
|
+
'连接器已停用。请在插件配置中启用后重试。': 'Connector disabled. Enable it in the plugin configuration and retry.',
|
|
70
|
+
'配对二维码': 'Pairing QR code',
|
|
71
|
+
'获取配对码': 'Get pairing code',
|
|
72
|
+
'暂无配对码。': 'No pairing code yet.',
|
|
73
|
+
'复制配对码': 'Copy pairing code',
|
|
74
|
+
'已复制': 'Copied',
|
|
75
|
+
'重新生成': 'Regenerate',
|
|
76
|
+
'长期有效直到重新生成': 'Valid until regenerated',
|
|
77
|
+
'已配对设备': 'Paired devices',
|
|
78
|
+
'暂无已配对设备': 'No paired devices',
|
|
79
|
+
'暂无已配对设备。': 'No paired devices.',
|
|
80
|
+
'无法读取全部已配对设备。': 'Could not read the full device list.',
|
|
81
|
+
'撤销中…': 'Revoking…',
|
|
82
|
+
'撤销失败,请稍后重试。': 'Revocation failed; try again later.',
|
|
83
|
+
'撤销设备': 'Revoke device',
|
|
84
|
+
'撤销这台设备': 'Revoke this device',
|
|
85
|
+
'撤销这台设备?': 'Revoke this device?',
|
|
86
|
+
'这台设备不属于此电脑。': 'This device does not belong to this computer.',
|
|
87
|
+
'设备不存在或已被撤销。': 'The device does not exist or was already revoked.',
|
|
88
|
+
'撤销': 'Revoke',
|
|
89
|
+
'这台设备': 'this device',
|
|
90
|
+
'设备': 'Device',
|
|
91
|
+
'未命名设备': 'Unnamed device',
|
|
92
|
+
'关闭': 'Close',
|
|
93
|
+
'取消': 'Cancel',
|
|
94
|
+
'完成': 'Done',
|
|
95
|
+
'尚未安装 iOS App?': 'No iOS app yet?',
|
|
96
|
+
'安装 DSH Mobile': 'Install DSH Mobile',
|
|
97
|
+
'查看安装方式': 'See how to install',
|
|
98
|
+
'打开 DSH Mobile,扫描电脑上的配对二维码。': 'Open DSH Mobile and scan the QR code on this computer.',
|
|
99
|
+
'在 iPhone 或 iPad 上通过 TestFlight 安装。': 'Install it on iPhone or iPad with TestFlight.',
|
|
100
|
+
'从 App Store 安装 TestFlight。': 'Install TestFlight from the App Store.',
|
|
101
|
+
'打开 TestFlight 邀请链接,安装 DSH Mobile。': 'Open the TestFlight invite link and install DSH Mobile.',
|
|
102
|
+
'需要 iOS 17、iPadOS 17 或更高版本。': 'Requires iOS 17, iPadOS 17 or later.',
|
|
103
|
+
'命令行登记(适用于无界面环境)': 'Command-line registration (headless)',
|
|
104
|
+
'上次错误': 'Last error',
|
|
105
|
+
'dsh-mobile-link enroll --relay <中转地址>': 'dsh-mobile-link enroll --relay <relay-address>',
|
|
106
|
+
'wss://<中转地址>/dsh-link': 'wss://<relay>/dsh-link',
|
|
107
|
+
'GitHub 仓库': 'GitHub repository',
|
|
108
|
+
'欢迎在 GitHub 上为项目加星标。': 'If it helps you, star the project on GitHub.',
|
|
109
|
+
'官方原子组件不可用,移动端连接入口未注册': 'Official primitives unavailable; the entry was not registered',
|
|
110
|
+
'连不上本机连接器:${error}': 'Cannot reach the local connector: ${error}',
|
|
111
|
+
'面板状态层没起来:${error}': 'The status layer did not come up: ${error}',
|
|
112
|
+
'小窗没注册上:${error}': 'The panel was not registered: ${error}',
|
|
113
|
+
'侧栏入口没注册上:${error}': 'The sidebar entry was not registered: ${error}',
|
|
114
|
+
'连接器应答 HTTP ${status}': 'The connector answered HTTP ${status}',
|
|
115
|
+
'连接器应答无法解析(HTTP ${status})': 'Unreadable connector response (HTTP ${status})',
|
|
116
|
+
'“${name}”将无法继续连接。需要重新扫码才能再次配对。': '“${name}” will no longer connect. Scan the code again to pair it.',
|
|
117
|
+
'${online} 台在线': '${online} online',
|
|
118
|
+
'${total} 台在线': '${total} online',
|
|
119
|
+
'${online} 台在线,共 ${total} 台': '${online} of ${total} online',
|
|
120
|
+
'${online} 台在线,共 ${total} 台已配对': '${online} of ${total} paired online',
|
|
121
|
+
'有效期至 ${hasta}(剩余 ${resto})': 'Valid until ${hasta} (${resto} left)',
|
|
122
|
+
},
|
|
123
|
+
es: {
|
|
124
|
+
'(已配对 ${paired}/${max})': '(emparejados ${paired}/${max})',
|
|
125
|
+
'移动端连接': 'Conexión móvil',
|
|
126
|
+
'移动端连接:二维码、已配对的手机与连接状态': 'Conexión móvil: código QR, teléfonos emparejados y estado',
|
|
127
|
+
'此电脑尚未登记': 'Este equipo no está registrado',
|
|
128
|
+
'此电脑尚未登记。请使用命令行完成登记:': 'Este equipo no está registrado. Regístralo desde la línea de comandos:',
|
|
129
|
+
'登记此电脑以连接中转服务器。': 'Registra este equipo para conectarlo al relay.',
|
|
130
|
+
'此电脑已登记。': 'Este equipo ya está registrado.',
|
|
131
|
+
'中转地址': 'Dirección del relay',
|
|
132
|
+
'中转服务器': 'Relay',
|
|
133
|
+
'已预填,可修改。': 'Viene rellenada; se puede editar.',
|
|
134
|
+
'由配置指定,不可修改。': 'Fijada por la configuración; no se puede editar.',
|
|
135
|
+
'请输入中转地址。': 'Introduce la dirección del relay.',
|
|
136
|
+
'中转地址无效。': 'La dirección del relay no es válida.',
|
|
137
|
+
'请输入有效的中转地址。': 'Introduce una dirección de relay válida.',
|
|
138
|
+
'尝试次数过多,请稍后重试。': 'Demasiados intentos; vuelve a intentarlo más tarde.',
|
|
139
|
+
'中转服务器未响应,请稍后重试。': 'El relay no respondió; vuelve a intentarlo más tarde.',
|
|
140
|
+
'中转服务器登记失败,请稍后重试。': 'El relay rechazó el registro; vuelve a intentarlo más tarde.',
|
|
141
|
+
'中转服务器返回了无法识别的响应。': 'El relay devolvió una respuesta desconocida.',
|
|
142
|
+
'连接器正在停止,请稍后重试。': 'El conector se está deteniendo; vuelve a intentarlo más tarde.',
|
|
143
|
+
'连接器内部错误,请稍后重试。': 'Error interno del conector; vuelve a intentarlo más tarde.',
|
|
144
|
+
'登记信息未能保存到此电脑,请检查磁盘权限。': 'No se pudo guardar el registro; revisa los permisos del disco.',
|
|
145
|
+
'登记后将自动连接。': 'Se conecta solo al registrarlo.',
|
|
146
|
+
'登记失败,请重试。': 'Falló el registro; vuelve a intentarlo.',
|
|
147
|
+
'正在登记…': 'Registrando…',
|
|
148
|
+
'正在获取…': 'Obteniendo…',
|
|
149
|
+
'正在读取状态…': 'Leyendo el estado…',
|
|
150
|
+
'正在连接…': 'Conectando…',
|
|
151
|
+
'登记': 'Registrar',
|
|
152
|
+
'重试': 'Reintentar',
|
|
153
|
+
'无法读取连接状态。': 'No se pudo leer el estado de la conexión.',
|
|
154
|
+
'无法连接中转服务器,请检查地址与网络。': 'No se pudo conectar al relay; revisa la dirección y la red.',
|
|
155
|
+
'状态未知': 'Estado desconocido',
|
|
156
|
+
'已连接': 'Conectado',
|
|
157
|
+
'在线': 'en línea',
|
|
158
|
+
'离线': 'sin conexión',
|
|
159
|
+
'未连接,正在重新连接': 'Sin conexión, reconectando',
|
|
160
|
+
'连接器已停用': 'Conector desactivado',
|
|
161
|
+
'连接器已停用。': 'Conector desactivado.',
|
|
162
|
+
'连接器已停用。请在插件配置中启用后重试。': 'Conector desactivado. Actívalo en la configuración del plugin y reintenta.',
|
|
163
|
+
'配对二维码': 'Código QR de emparejamiento',
|
|
164
|
+
'获取配对码': 'Obtener código de emparejamiento',
|
|
165
|
+
'暂无配对码。': 'Todavía no hay código de emparejamiento.',
|
|
166
|
+
'复制配对码': 'Copiar código de emparejamiento',
|
|
167
|
+
'已复制': 'Copiado',
|
|
168
|
+
'重新生成': 'Regenerar',
|
|
169
|
+
'长期有效直到重新生成': 'Válido hasta que se regenere',
|
|
170
|
+
'已配对设备': 'Dispositivos emparejados',
|
|
171
|
+
'暂无已配对设备': 'Sin dispositivos emparejados',
|
|
172
|
+
'暂无已配对设备。': 'Sin dispositivos emparejados.',
|
|
173
|
+
'无法读取全部已配对设备。': 'No se pudo leer la lista completa de dispositivos.',
|
|
174
|
+
'撤销中…': 'Revocando…',
|
|
175
|
+
'撤销失败,请稍后重试。': 'Falló la revocación; vuelve a intentarlo más tarde.',
|
|
176
|
+
'撤销设备': 'Revocar dispositivo',
|
|
177
|
+
'撤销这台设备': 'Revocar este dispositivo',
|
|
178
|
+
'撤销这台设备?': '¿Revocar este dispositivo?',
|
|
179
|
+
'这台设备不属于此电脑。': 'Este dispositivo no pertenece a este equipo.',
|
|
180
|
+
'设备不存在或已被撤销。': 'El dispositivo no existe o ya fue revocado.',
|
|
181
|
+
'撤销': 'Revocar',
|
|
182
|
+
'这台设备': 'este dispositivo',
|
|
183
|
+
'设备': 'Dispositivo',
|
|
184
|
+
'未命名设备': 'Dispositivo sin nombre',
|
|
185
|
+
'关闭': 'Cerrar',
|
|
186
|
+
'取消': 'Cancelar',
|
|
187
|
+
'完成': 'Listo',
|
|
188
|
+
'尚未安装 iOS App?': '¿Todavía no tienes la app de iOS?',
|
|
189
|
+
'安装 DSH Mobile': 'Instalar DSH Mobile',
|
|
190
|
+
'查看安装方式': 'Ver cómo instalar',
|
|
191
|
+
'打开 DSH Mobile,扫描电脑上的配对二维码。': 'Abre DSH Mobile y escanea el código QR de este equipo.',
|
|
192
|
+
'在 iPhone 或 iPad 上通过 TestFlight 安装。': 'Instálala en iPhone o iPad con TestFlight.',
|
|
193
|
+
'从 App Store 安装 TestFlight。': 'Instala TestFlight desde el App Store.',
|
|
194
|
+
'打开 TestFlight 邀请链接,安装 DSH Mobile。': 'Abre el enlace de invitación de TestFlight e instala DSH Mobile.',
|
|
195
|
+
'需要 iOS 17、iPadOS 17 或更高版本。': 'Requiere iOS 17, iPadOS 17 o superior.',
|
|
196
|
+
'命令行登记(适用于无界面环境)': 'Registro por línea de comandos (sin interfaz)',
|
|
197
|
+
'上次错误': 'Último error',
|
|
198
|
+
'dsh-mobile-link enroll --relay <中转地址>': 'dsh-mobile-link enroll --relay <direccion-del-relay>',
|
|
199
|
+
'wss://<中转地址>/dsh-link': 'wss://<relay>/dsh-link',
|
|
200
|
+
'GitHub 仓库': 'Repositorio en GitHub',
|
|
201
|
+
'欢迎在 GitHub 上为项目加星标。': 'Si te sirve, deja una estrella en GitHub.',
|
|
202
|
+
'官方原子组件不可用,移动端连接入口未注册': 'Los componentes oficiales no están disponibles; la entrada no se registró',
|
|
203
|
+
'连不上本机连接器:${error}': 'No se pudo conectar al conector local: ${error}',
|
|
204
|
+
'面板状态层没起来:${error}': 'No se pudo iniciar la capa de estado: ${error}',
|
|
205
|
+
'小窗没注册上:${error}': 'No se pudo registrar la ventana: ${error}',
|
|
206
|
+
'侧栏入口没注册上:${error}': 'No se pudo registrar la entrada de la barra lateral: ${error}',
|
|
207
|
+
'连接器应答 HTTP ${status}': 'El conector respondió HTTP ${status}',
|
|
208
|
+
'连接器应答无法解析(HTTP ${status})': 'Respuesta del conector ilegible (HTTP ${status})',
|
|
209
|
+
'“${name}”将无法继续连接。需要重新扫码才能再次配对。': '“${name}” dejará de conectarse. Habrá que escanear el código otra vez.',
|
|
210
|
+
'${online} 台在线': '${online} en línea',
|
|
211
|
+
'${total} 台在线': '${total} en línea',
|
|
212
|
+
'${online} 台在线,共 ${total} 台': '${online} de ${total} en línea',
|
|
213
|
+
'${online} 台在线,共 ${total} 台已配对': '${online} de ${total} emparejados en línea',
|
|
214
|
+
'有效期至 ${hasta}(剩余 ${resto})': 'Válido hasta ${hasta} (quedan ${resto})',
|
|
215
|
+
},
|
|
216
|
+
ja: {
|
|
217
|
+
'(已配对 ${paired}/${max})': '(ペアリング済み ${paired}/${max})',
|
|
218
|
+
'移动端连接': 'モバイルリンク',
|
|
219
|
+
'移动端连接:二维码、已配对的手机与连接状态': 'モバイルリンク:QRコード、ペアリング済みスマートフォン、リンク状態',
|
|
220
|
+
'此电脑尚未登记': 'このコンピューターは登録されていません',
|
|
221
|
+
'此电脑尚未登记。请使用命令行完成登记:': 'このコンピューターは登録されていません。コマンドラインから登録してください:',
|
|
222
|
+
'登记此电脑以连接中转服务器。': 'リレーに接続するには、このコンピューターを登録してください。',
|
|
223
|
+
'此电脑已登记。': 'このコンピューターは登録されています。',
|
|
224
|
+
'中转地址': 'リレーアドレス',
|
|
225
|
+
'中转服务器': 'リレー',
|
|
226
|
+
'已预填,可修改。': '事前入力済み。編集可能です。',
|
|
227
|
+
'由配置指定,不可修改。': '設定により指定されています。編集できません。',
|
|
228
|
+
'请输入中转地址。': 'リレーアドレスを入力してください。',
|
|
229
|
+
'中转地址无效。': 'リレーアドレスが無効です。',
|
|
230
|
+
'请输入有效的中转地址。': '有効なリレーアドレスを入力してください。',
|
|
231
|
+
'尝试次数过多,请稍后重试。': '試行回数が多すぎます。後でもう一度お試しください。',
|
|
232
|
+
'中转服务器未响应,请稍后重试。': 'リレーが応答しませんでした。後でもう一度お試しください。',
|
|
233
|
+
'中转服务器登记失败,请稍后重试。': 'リレーが登録を拒否しました。後でもう一度お試しください。',
|
|
234
|
+
'中转服务器返回了无法识别的响应。': 'リレーから不明な応答が返されました。',
|
|
235
|
+
'连接器正在停止,请稍后重试。': 'コネクターは停止中です。後でもう一度お試しください。',
|
|
236
|
+
'连接器内部错误,请稍后重试。': 'コネクターの内部エラーです。後でもう一度お試しください。',
|
|
237
|
+
'登记信息未能保存到此电脑,请检查磁盘权限。': '登録を保存できませんでした。ディスクの権限を確認してください。',
|
|
238
|
+
'登记后将自动连接。': '登録すると自動的に接続されます。',
|
|
239
|
+
'登记失败,请重试。': '登録に失敗しました。もう一度お試しください。',
|
|
240
|
+
'正在登记…': '登録中…',
|
|
241
|
+
'正在获取…': '取得中…',
|
|
242
|
+
'正在读取状态…': '状態を読み取り中…',
|
|
243
|
+
'正在连接…': '接続中…',
|
|
244
|
+
'登记': '登録',
|
|
245
|
+
'重试': '再試行',
|
|
246
|
+
'无法读取连接状态。': 'リンク状態を読み取れませんでした。',
|
|
247
|
+
'无法连接中转服务器,请检查地址与网络。': 'リレーに到達できませんでした。アドレスとネットワークを確認してください。',
|
|
248
|
+
'状态未知': '不明な状態',
|
|
249
|
+
'已连接': '接続済み',
|
|
250
|
+
'在线': 'オンライン',
|
|
251
|
+
'离线': 'オフライン',
|
|
252
|
+
'未连接,正在重新连接': '未接続、再接続中',
|
|
253
|
+
'连接器已停用': 'コネクターが無効です',
|
|
254
|
+
'连接器已停用。': 'コネクターが無効です。',
|
|
255
|
+
'连接器已停用。请在插件配置中启用后重试。': 'コネクターが無効です。プラグイン設定で有効にして再試行してください。',
|
|
256
|
+
'配对二维码': 'ペアリング用QRコード',
|
|
257
|
+
'获取配对码': 'ペアリングコードを取得',
|
|
258
|
+
'暂无配对码。': 'ペアリングコードはまだありません。',
|
|
259
|
+
'复制配对码': 'ペアリングコードをコピー',
|
|
260
|
+
'已复制': 'コピーしました',
|
|
261
|
+
'重新生成': '再生成',
|
|
262
|
+
'长期有效直到重新生成': '再生成するまで有効',
|
|
263
|
+
'已配对设备': 'ペアリング済みデバイス',
|
|
264
|
+
'暂无已配对设备': 'ペアリング済みデバイスなし',
|
|
265
|
+
'暂无已配对设备。': 'ペアリング済みデバイスはありません。',
|
|
266
|
+
'无法读取全部已配对设备。': 'デバイスリスト全体を読み取れませんでした。',
|
|
267
|
+
'撤销中…': '取り消し中…',
|
|
268
|
+
'撤销失败,请稍后重试。': '取り消しに失敗しました。後でもう一度お試しください。',
|
|
269
|
+
'撤销设备': 'デバイスを取り消す',
|
|
270
|
+
'撤销这台设备': 'このデバイスを取り消す',
|
|
271
|
+
'撤销这台设备?': 'このデバイスを取り消しますか?',
|
|
272
|
+
'这台设备不属于此电脑。': 'このデバイスはこのコンピューターに属していません。',
|
|
273
|
+
'设备不存在或已被撤销。': 'デバイスが存在しないか、すでに取り消されています。',
|
|
274
|
+
'撤销': '取り消す',
|
|
275
|
+
'这台设备': 'このデバイス',
|
|
276
|
+
'设备': 'デバイス',
|
|
277
|
+
'未命名设备': '名前のないデバイス',
|
|
278
|
+
'关闭': '閉じる',
|
|
279
|
+
'取消': 'キャンセル',
|
|
280
|
+
'完成': '完了',
|
|
281
|
+
'尚未安装 iOS App?': 'iOSアプリをまだお持ちでないですか?',
|
|
282
|
+
'安装 DSH Mobile': 'DSH Mobileをインストール',
|
|
283
|
+
'查看安装方式': 'インストール方法を見る',
|
|
284
|
+
'打开 DSH Mobile,扫描电脑上的配对二维码。': 'DSH Mobileを開き、このコンピューターのQRコードをスキャンしてください。',
|
|
285
|
+
'在 iPhone 或 iPad 上通过 TestFlight 安装。': 'TestFlightを使用してiPhoneまたはiPadにインストールします。',
|
|
286
|
+
'从 App Store 安装 TestFlight。': 'App StoreからTestFlightをインストールしてください。',
|
|
287
|
+
'打开 TestFlight 邀请链接,安装 DSH Mobile。': 'TestFlightの招待リンクを開き、DSH Mobileをインストールしてください。',
|
|
288
|
+
'需要 iOS 17、iPadOS 17 或更高版本。': 'iOS 17、iPadOS 17以降が必要です。',
|
|
289
|
+
'命令行登记(适用于无界面环境)': 'コマンドライン登録(ヘッドレス)',
|
|
290
|
+
'上次错误': '最後のエラー',
|
|
291
|
+
'dsh-mobile-link enroll --relay <中转地址>': 'dsh-mobile-link enroll --relay <relay-address>',
|
|
292
|
+
'wss://<中转地址>/dsh-link': 'wss://<relay>/dsh-link',
|
|
293
|
+
'GitHub 仓库': 'GitHubリポジトリ',
|
|
294
|
+
'欢迎在 GitHub 上为项目加星标。': 'お役に立てたら、GitHubでプロジェクトにスターを付けてください。',
|
|
295
|
+
'官方原子组件不可用,移动端连接入口未注册': '公式プリミティブが利用できないため、エントリは登録されませんでした',
|
|
296
|
+
'连不上本机连接器:${error}': 'ローカルコネクターに到達できません:${error}',
|
|
297
|
+
'面板状态层没起来:${error}': 'ステータスレイヤーが起動しませんでした:${error}',
|
|
298
|
+
'小窗没注册上:${error}': 'パネルは登録されませんでした:${error}',
|
|
299
|
+
'侧栏入口没注册上:${error}': 'サイドバーエントリは登録されませんでした:${error}',
|
|
300
|
+
'连接器应答 HTTP ${status}': 'コネクターがHTTP ${status}を返しました',
|
|
301
|
+
'连接器应答无法解析(HTTP ${status})': 'コネクターの応答を読み取れません(HTTP ${status})',
|
|
302
|
+
'“${name}”将无法继续连接。需要重新扫码才能再次配对。': '「${name}」は今後接続できなくなります。ペアリングし直すには、もう一度コードをスキャンしてください。',
|
|
303
|
+
'${online} 台在线': '${online} オンライン',
|
|
304
|
+
'${total} 台在线': '${total} オンライン',
|
|
305
|
+
'${online} 台在线,共 ${total} 台': '${total} 台中 ${online} がオンライン',
|
|
306
|
+
'${online} 台在线,共 ${total} 台已配对': 'ペアリング済み ${total} 台中 ${online} がオンライン',
|
|
307
|
+
'有效期至 ${hasta}(剩余 ${resto})': '${hasta} まで有効(残り ${resto})',
|
|
308
|
+
},
|
|
309
|
+
'zh-Hant': {
|
|
310
|
+
'(已配对 ${paired}/${max})': '(已配對 ${paired}/${max})',
|
|
311
|
+
'移动端连接': '手機連結',
|
|
312
|
+
'移动端连接:二维码、已配对的手机与连接状态': '手機連結:QR 碼、已配對手機和連結狀態',
|
|
313
|
+
'此电脑尚未登记': '這台電腦尚未註冊',
|
|
314
|
+
'此电脑尚未登记。请使用命令行完成登记:': '這台電腦尚未註冊。請從指令列註冊:',
|
|
315
|
+
'登记此电脑以连接中转服务器。': '註冊這台電腦以連線至中繼站。',
|
|
316
|
+
'此电脑已登记。': '這台電腦已註冊。',
|
|
317
|
+
'中转地址': '中繼站位址',
|
|
318
|
+
'中转服务器': '中繼站',
|
|
319
|
+
'已预填,可修改。': '已預填;可編輯。',
|
|
320
|
+
'由配置指定,不可修改。': '由設定檔設定;不可編輯。',
|
|
321
|
+
'请输入中转地址。': '輸入中繼站位址。',
|
|
322
|
+
'中转地址无效。': '中繼站位址無效。',
|
|
323
|
+
'请输入有效的中转地址。': '輸入有效的中繼站位址。',
|
|
324
|
+
'尝试次数过多,请稍后重试。': '嘗試次數過多;請稍後再試。',
|
|
325
|
+
'中转服务器未响应,请稍后重试。': '中繼站無回應;請稍後再試。',
|
|
326
|
+
'中转服务器登记失败,请稍后重试。': '中繼站拒絕註冊;請稍後再試。',
|
|
327
|
+
'中转服务器返回了无法识别的响应。': '中繼站回傳未知回應。',
|
|
328
|
+
'连接器正在停止,请稍后重试。': '連接器正在停止;請稍後再試。',
|
|
329
|
+
'连接器内部错误,请稍后重试。': '連接器內部錯誤;請稍後再試。',
|
|
330
|
+
'登记信息未能保存到此电脑,请检查磁盘权限。': '無法儲存註冊;請檢查磁碟權限。',
|
|
331
|
+
'登记后将自动连接。': '註冊後會自動連線。',
|
|
332
|
+
'登记失败,请重试。': '註冊失敗;請重試。',
|
|
333
|
+
'正在登记…': '註冊中…',
|
|
334
|
+
'正在获取…': '擷取中…',
|
|
335
|
+
'正在读取状态…': '讀取狀態中…',
|
|
336
|
+
'正在连接…': '連線中…',
|
|
337
|
+
'登记': '註冊',
|
|
338
|
+
'重试': '重試',
|
|
339
|
+
'无法读取连接状态。': '無法讀取連結狀態。',
|
|
340
|
+
'无法连接中转服务器,请检查地址与网络。': '無法連上中繼站;請檢查位址和網路。',
|
|
341
|
+
'状态未知': '未知狀態',
|
|
342
|
+
'已连接': '已連線',
|
|
343
|
+
'在线': '線上',
|
|
344
|
+
'离线': '離線',
|
|
345
|
+
'未连接,正在重新连接': '未連線,重新連線中',
|
|
346
|
+
'连接器已停用': '連接器已停用',
|
|
347
|
+
'连接器已停用。': '連接器已停用。',
|
|
348
|
+
'连接器已停用。请在插件配置中启用后重试。': '連接器已停用。請在外掛程式設定中啟用並重試。',
|
|
349
|
+
'配对二维码': '配對 QR 碼',
|
|
350
|
+
'获取配对码': '取得配對碼',
|
|
351
|
+
'暂无配对码。': '尚無配對碼。',
|
|
352
|
+
'复制配对码': '複製配對碼',
|
|
353
|
+
'已复制': '已複製',
|
|
354
|
+
'重新生成': '重新產生',
|
|
355
|
+
'长期有效直到重新生成': '在重新產生前有效',
|
|
356
|
+
'已配对设备': '已配對裝置',
|
|
357
|
+
'暂无已配对设备': '沒有已配對裝置',
|
|
358
|
+
'暂无已配对设备。': '沒有已配對裝置。',
|
|
359
|
+
'无法读取全部已配对设备。': '無法讀取完整裝置清單。',
|
|
360
|
+
'撤销中…': '撤銷中…',
|
|
361
|
+
'撤销失败,请稍后重试。': '撤銷失敗;請稍後再試。',
|
|
362
|
+
'撤销设备': '撤銷裝置',
|
|
363
|
+
'撤销这台设备': '撤銷此裝置',
|
|
364
|
+
'撤销这台设备?': '要撤銷此裝置嗎?',
|
|
365
|
+
'这台设备不属于此电脑。': '此裝置不屬於這台電腦。',
|
|
366
|
+
'设备不存在或已被撤销。': '此裝置不存在或已撤銷。',
|
|
367
|
+
'撤销': '撤銷',
|
|
368
|
+
'这台设备': '此裝置',
|
|
369
|
+
'设备': '裝置',
|
|
370
|
+
'未命名设备': '未命名裝置',
|
|
371
|
+
'关闭': '關閉',
|
|
372
|
+
'取消': '取消',
|
|
373
|
+
'完成': '完成',
|
|
374
|
+
'尚未安装 iOS App?': '還沒有 iOS App 嗎?',
|
|
375
|
+
'安装 DSH Mobile': '安裝 DSH Mobile',
|
|
376
|
+
'查看安装方式': '查看安裝方式',
|
|
377
|
+
'打开 DSH Mobile,扫描电脑上的配对二维码。': '開啟 DSH Mobile 並掃描這台電腦上的 QR 碼。',
|
|
378
|
+
'在 iPhone 或 iPad 上通过 TestFlight 安装。': '使用 TestFlight 在 iPhone 或 iPad 上安裝。',
|
|
379
|
+
'从 App Store 安装 TestFlight。': '從 App Store 安裝 TestFlight。',
|
|
380
|
+
'打开 TestFlight 邀请链接,安装 DSH Mobile。': '開啟 TestFlight 邀請連結並安裝 DSH Mobile。',
|
|
381
|
+
'需要 iOS 17、iPadOS 17 或更高版本。': '需要 iOS 17、iPadOS 17 或以上版本。',
|
|
382
|
+
'命令行登记(适用于无界面环境)': '指令列註冊(無頭模式)',
|
|
383
|
+
'上次错误': '最後錯誤',
|
|
384
|
+
'dsh-mobile-link enroll --relay <中转地址>': 'dsh-mobile-link enroll --relay <relay-address>',
|
|
385
|
+
'wss://<中转地址>/dsh-link': 'wss://<relay>/dsh-link',
|
|
386
|
+
'GitHub 仓库': 'GitHub 儲存庫',
|
|
387
|
+
'欢迎在 GitHub 上为项目加星标。': '如果有幫助,請在 GitHub 上為專案按星號。',
|
|
388
|
+
'官方原子组件不可用,移动端连接入口未注册': '官方基本功能不可用;條目未註冊',
|
|
389
|
+
'连不上本机连接器:${error}': '無法連線至本機連接器:${error}',
|
|
390
|
+
'面板状态层没起来:${error}': '狀態層未能啟動:${error}',
|
|
391
|
+
'小窗没注册上:${error}': '面板未註冊:${error}',
|
|
392
|
+
'侧栏入口没注册上:${error}': '側邊欄條目未註冊:${error}',
|
|
393
|
+
'连接器应答 HTTP ${status}': '連接器回應 HTTP ${status}',
|
|
394
|
+
'连接器应答无法解析(HTTP ${status})': '無法讀取的連接器回應(HTTP ${status})',
|
|
395
|
+
'“${name}”将无法继续连接。需要重新扫码才能再次配对。': '「${name}」將不再連線。請再次掃描代碼以進行配對。',
|
|
396
|
+
'${online} 台在线': '${online} 線上',
|
|
397
|
+
'${total} 台在线': '${total} 線上',
|
|
398
|
+
'${online} 台在线,共 ${total} 台': '${online} / ${total} 線上',
|
|
399
|
+
'${online} 台在线,共 ${total} 台已配对': '${online} / ${total} 已配對線上',
|
|
400
|
+
'有效期至 ${hasta}(剩余 ${resto})': '有效至 ${hasta}(剩餘 ${resto})',
|
|
401
|
+
},
|
|
402
|
+
ko: {
|
|
403
|
+
'(已配对 ${paired}/${max})': '(페어링됨 ${paired}/${max})',
|
|
404
|
+
'移动端连接': '모바일 링크',
|
|
405
|
+
'移动端连接:二维码、已配对的手机与连接状态': '모바일 링크: QR 코드, 페어링된 휴대폰 및 링크 상태',
|
|
406
|
+
'此电脑尚未登记': '이 컴퓨터는 등록되지 않았습니다',
|
|
407
|
+
'此电脑尚未登记。请使用命令行完成登记:': '이 컴퓨터는 등록되지 않았습니다. 명령줄에서 등록하세요:',
|
|
408
|
+
'登记此电脑以连接中转服务器。': '이 컴퓨터를 등록하여 릴레이에 연결하세요.',
|
|
409
|
+
'此电脑已登记。': '이 컴퓨터는 등록되었습니다.',
|
|
410
|
+
'中转地址': '릴레이 주소',
|
|
411
|
+
'中转服务器': '릴레이',
|
|
412
|
+
'已预填,可修改。': '미리 채워짐; 편집 가능.',
|
|
413
|
+
'由配置指定,不可修改。': '구성에서 설정됨; 편집할 수 없습니다.',
|
|
414
|
+
'请输入中转地址。': '릴레이 주소를 입력하세요.',
|
|
415
|
+
'中转地址无效。': '릴레이 주소가 유효하지 않습니다.',
|
|
416
|
+
'请输入有效的中转地址。': '유효한 릴레이 주소를 입력하세요.',
|
|
417
|
+
'尝试次数过多,请稍后重试。': '시도 횟수가 너무 많습니다. 나중에 다시 시도하세요.',
|
|
418
|
+
'中转服务器未响应,请稍后重试。': '릴레이가 응답하지 않았습니다. 나중에 다시 시도하세요.',
|
|
419
|
+
'中转服务器登记失败,请稍后重试。': '릴레이가 등록을 거부했습니다. 나중에 다시 시도하세요.',
|
|
420
|
+
'中转服务器返回了无法识别的响应。': '릴레이가 알 수 없는 응답을 반환했습니다.',
|
|
421
|
+
'连接器正在停止,请稍后重试。': '커넥터가 중지 중입니다. 나중에 다시 시도하세요.',
|
|
422
|
+
'连接器内部错误,请稍后重试。': '내부 커넥터 오류입니다. 나중에 다시 시도하세요.',
|
|
423
|
+
'登记信息未能保存到此电脑,请检查磁盘权限。': '등록을 저장할 수 없습니다. 디스크 권한을 확인하세요.',
|
|
424
|
+
'登记后将自动连接。': '등록되면 자동으로 연결됩니다.',
|
|
425
|
+
'登记失败,请重试。': '등록에 실패했습니다. 다시 시도하세요.',
|
|
426
|
+
'正在登记…': '등록 중…',
|
|
427
|
+
'正在获取…': '가져오는 중…',
|
|
428
|
+
'正在读取状态…': '상태 읽는 중…',
|
|
429
|
+
'正在连接…': '연결 중…',
|
|
430
|
+
'登记': '등록',
|
|
431
|
+
'重试': '다시 시도',
|
|
432
|
+
'无法读取连接状态。': '링크 상태를 읽을 수 없습니다.',
|
|
433
|
+
'无法连接中转服务器,请检查地址与网络。': '릴레이에 연결할 수 없습니다. 주소와 네트워크를 확인하세요.',
|
|
434
|
+
'状态未知': '알 수 없는 상태',
|
|
435
|
+
'已连接': '연결됨',
|
|
436
|
+
'在线': '온라인',
|
|
437
|
+
'离线': '오프라인',
|
|
438
|
+
'未连接,正在重新连接': '연결되지 않음, 다시 연결 중',
|
|
439
|
+
'连接器已停用': '커넥터 비활성화됨',
|
|
440
|
+
'连接器已停用。': '커넥터가 비활성화되었습니다.',
|
|
441
|
+
'连接器已停用。请在插件配置中启用后重试。': '커넥터가 비활성화되었습니다. 플러그인 구성에서 활성화한 후 다시 시도하세요.',
|
|
442
|
+
'配对二维码': '페어링 QR 코드',
|
|
443
|
+
'获取配对码': '페어링 코드 받기',
|
|
444
|
+
'暂无配对码。': '아직 페어링 코드가 없습니다.',
|
|
445
|
+
'复制配对码': '페어링 코드 복사',
|
|
446
|
+
'已复制': '복사됨',
|
|
447
|
+
'重新生成': '재생성',
|
|
448
|
+
'长期有效直到重新生成': '재생성할 때까지 유효',
|
|
449
|
+
'已配对设备': '페어링된 기기',
|
|
450
|
+
'暂无已配对设备': '페어링된 기기 없음',
|
|
451
|
+
'暂无已配对设备。': '페어링된 기기가 없습니다.',
|
|
452
|
+
'无法读取全部已配对设备。': '전체 기기 목록을 읽을 수 없습니다.',
|
|
453
|
+
'撤销中…': '해지 중…',
|
|
454
|
+
'撤销失败,请稍后重试。': '해지에 실패했습니다. 나중에 다시 시도하세요.',
|
|
455
|
+
'撤销设备': '기기 해지',
|
|
456
|
+
'撤销这台设备': '이 기기 해지',
|
|
457
|
+
'撤销这台设备?': '이 기기를 해지하시겠습니까?',
|
|
458
|
+
'这台设备不属于此电脑。': '이 기기는 이 컴퓨터에 속하지 않습니다.',
|
|
459
|
+
'设备不存在或已被撤销。': '기기가 존재하지 않거나 이미 해지되었습니다.',
|
|
460
|
+
'撤销': '해지',
|
|
461
|
+
'这台设备': '이 기기',
|
|
462
|
+
'设备': '기기',
|
|
463
|
+
'未命名设备': '이름 없는 기기',
|
|
464
|
+
'关闭': '닫기',
|
|
465
|
+
'取消': '취소',
|
|
466
|
+
'完成': '완료',
|
|
467
|
+
'尚未安装 iOS App?': '아직 iOS 앱이 없나요?',
|
|
468
|
+
'安装 DSH Mobile': 'DSH Mobile 설치',
|
|
469
|
+
'查看安装方式': '설치 방법 보기',
|
|
470
|
+
'打开 DSH Mobile,扫描电脑上的配对二维码。': 'DSH Mobile을 열고 이 컴퓨터의 QR 코드를 스캔하세요.',
|
|
471
|
+
'在 iPhone 或 iPad 上通过 TestFlight 安装。': 'TestFlight로 iPhone 또는 iPad에 설치하세요.',
|
|
472
|
+
'从 App Store 安装 TestFlight。': 'App Store에서 TestFlight를 설치하세요.',
|
|
473
|
+
'打开 TestFlight 邀请链接,安装 DSH Mobile。': 'TestFlight 초대 링크를 열고 DSH Mobile을 설치하세요.',
|
|
474
|
+
'需要 iOS 17、iPadOS 17 或更高版本。': 'iOS 17, iPadOS 17 이상이 필요합니다.',
|
|
475
|
+
'命令行登记(适用于无界面环境)': '명령줄 등록(헤드리스)',
|
|
476
|
+
'上次错误': '마지막 오류',
|
|
477
|
+
'dsh-mobile-link enroll --relay <中转地址>': 'dsh-mobile-link enroll --relay <relay-address>',
|
|
478
|
+
'wss://<中转地址>/dsh-link': 'wss://<relay>/dsh-link',
|
|
479
|
+
'GitHub 仓库': 'GitHub 저장소',
|
|
480
|
+
'欢迎在 GitHub 上为项目加星标。': '도움이 되셨다면 GitHub에서 프로젝트에 별을 눌러 주세요.',
|
|
481
|
+
'官方原子组件不可用,移动端连接入口未注册': '공식 프리미티브를 사용할 수 없어 항목이 등록되지 않았습니다',
|
|
482
|
+
'连不上本机连接器:${error}': '로컬 커넥터에 연결할 수 없습니다: ${error}',
|
|
483
|
+
'面板状态层没起来:${error}': '상태 계층이 시작되지 않았습니다: ${error}',
|
|
484
|
+
'小窗没注册上:${error}': '패널이 등록되지 않았습니다: ${error}',
|
|
485
|
+
'侧栏入口没注册上:${error}': '사이드바 항목이 등록되지 않았습니다: ${error}',
|
|
486
|
+
'连接器应答 HTTP ${status}': '커넥터가 HTTP ${status}로 응답했습니다',
|
|
487
|
+
'连接器应答无法解析(HTTP ${status})': '커넥터 응답을 읽을 수 없습니다 (HTTP ${status})',
|
|
488
|
+
'“${name}”将无法继续连接。需要重新扫码才能再次配对。': '“${name}”은(는) 더 이상 연결되지 않습니다. 코드를 다시 스캔하여 페어링하세요.',
|
|
489
|
+
'${online} 台在线': '${online}개 온라인',
|
|
490
|
+
'${total} 台在线': '${total}개 온라인',
|
|
491
|
+
'${online} 台在线,共 ${total} 台': '${total}개 중 ${online}개 온라인',
|
|
492
|
+
'${online} 台在线,共 ${total} 台已配对': '페어링된 ${total}개 중 ${online}개 온라인',
|
|
493
|
+
'有效期至 ${hasta}(剩余 ${resto})': '${hasta}까지 유효 (${resto} 남음)',
|
|
494
|
+
},
|
|
495
|
+
'pt-BR': {
|
|
496
|
+
'(已配对 ${paired}/${max})': '(pareados ${paired}/${max})',
|
|
497
|
+
'移动端连接': 'Link móvel',
|
|
498
|
+
'移动端连接:二维码、已配对的手机与连接状态': 'Link móvel: código QR, celulares pareados e status do link',
|
|
499
|
+
'此电脑尚未登记': 'Este computador não está registrado',
|
|
500
|
+
'此电脑尚未登记。请使用命令行完成登记:': 'Este computador não está registrado. Registre-o pela linha de comando:',
|
|
501
|
+
'登记此电脑以连接中转服务器。': 'Registre este computador para conectar-se ao relay.',
|
|
502
|
+
'此电脑已登记。': 'Este computador está registrado.',
|
|
503
|
+
'中转地址': 'Endereço do relay',
|
|
504
|
+
'中转服务器': 'Relay',
|
|
505
|
+
'已预填,可修改。': 'Pré-preenchido; editável.',
|
|
506
|
+
'由配置指定,不可修改。': 'Definido pela configuração; não editável.',
|
|
507
|
+
'请输入中转地址。': 'Digite o endereço do relay.',
|
|
508
|
+
'中转地址无效。': 'O endereço do relay não é válido.',
|
|
509
|
+
'请输入有效的中转地址。': 'Digite um endereço de relay válido.',
|
|
510
|
+
'尝试次数过多,请稍后重试。': 'Muitas tentativas; tente novamente mais tarde.',
|
|
511
|
+
'中转服务器未响应,请稍后重试。': 'O relay não respondeu; tente novamente mais tarde.',
|
|
512
|
+
'中转服务器登记失败,请稍后重试。': 'O relay recusou o registro; tente novamente mais tarde.',
|
|
513
|
+
'中转服务器返回了无法识别的响应。': 'O relay retornou uma resposta desconhecida.',
|
|
514
|
+
'连接器正在停止,请稍后重试。': 'O conector está parando; tente novamente mais tarde.',
|
|
515
|
+
'连接器内部错误,请稍后重试。': 'Erro interno do conector; tente novamente mais tarde.',
|
|
516
|
+
'登记信息未能保存到此电脑,请检查磁盘权限。': 'Não foi possível salvar o registro; verifique as permissões do disco.',
|
|
517
|
+
'登记后将自动连接。': 'Ele se conecta automaticamente após o registro.',
|
|
518
|
+
'登记失败,请重试。': 'Falha no registro; tente novamente.',
|
|
519
|
+
'正在登记…': 'Registrando…',
|
|
520
|
+
'正在获取…': 'Buscando…',
|
|
521
|
+
'正在读取状态…': 'Lendo status…',
|
|
522
|
+
'正在连接…': 'Conectando…',
|
|
523
|
+
'登记': 'Registrar',
|
|
524
|
+
'重试': 'Tentar novamente',
|
|
525
|
+
'无法读取连接状态。': 'Não foi possível ler o status do link.',
|
|
526
|
+
'无法连接中转服务器,请检查地址与网络。': 'Não foi possível alcançar o relay; verifique o endereço e a rede.',
|
|
527
|
+
'状态未知': 'Estado desconhecido',
|
|
528
|
+
'已连接': 'Conectado',
|
|
529
|
+
'在线': 'online',
|
|
530
|
+
'离线': 'offline',
|
|
531
|
+
'未连接,正在重新连接': 'Desconectado, reconectando',
|
|
532
|
+
'连接器已停用': 'Conector desabilitado',
|
|
533
|
+
'连接器已停用。': 'Conector desabilitado.',
|
|
534
|
+
'连接器已停用。请在插件配置中启用后重试。': 'Conector desabilitado. Habilite-o na configuração do plugin e tente novamente.',
|
|
535
|
+
'配对二维码': 'Código QR de pareamento',
|
|
536
|
+
'获取配对码': 'Obter código de pareamento',
|
|
537
|
+
'暂无配对码。': 'Nenhum código de pareamento ainda.',
|
|
538
|
+
'复制配对码': 'Copiar código de pareamento',
|
|
539
|
+
'已复制': 'Copiado',
|
|
540
|
+
'重新生成': 'Regenerar',
|
|
541
|
+
'长期有效直到重新生成': 'Válido até ser regenerado',
|
|
542
|
+
'已配对设备': 'Dispositivos pareados',
|
|
543
|
+
'暂无已配对设备': 'Nenhum dispositivo pareado',
|
|
544
|
+
'暂无已配对设备。': 'Nenhum dispositivo pareado.',
|
|
545
|
+
'无法读取全部已配对设备。': 'Não foi possível ler a lista completa de dispositivos.',
|
|
546
|
+
'撤销中…': 'Revogando…',
|
|
547
|
+
'撤销失败,请稍后重试。': 'Falha na revogação; tente novamente mais tarde.',
|
|
548
|
+
'撤销设备': 'Revogar dispositivo',
|
|
549
|
+
'撤销这台设备': 'Revogar este dispositivo',
|
|
550
|
+
'撤销这台设备?': 'Revogar este dispositivo?',
|
|
551
|
+
'这台设备不属于此电脑。': 'Este dispositivo não pertence a este computador.',
|
|
552
|
+
'设备不存在或已被撤销。': 'O dispositivo não existe ou já foi revogado.',
|
|
553
|
+
'撤销': 'Revogar',
|
|
554
|
+
'这台设备': 'este dispositivo',
|
|
555
|
+
'设备': 'Dispositivo',
|
|
556
|
+
'未命名设备': 'Dispositivo sem nome',
|
|
557
|
+
'关闭': 'Fechar',
|
|
558
|
+
'取消': 'Cancelar',
|
|
559
|
+
'完成': 'Concluir',
|
|
560
|
+
'尚未安装 iOS App?': 'Ainda não tem o app para iOS?',
|
|
561
|
+
'安装 DSH Mobile': 'Instalar o DSH Mobile',
|
|
562
|
+
'查看安装方式': 'Veja como instalar',
|
|
563
|
+
'打开 DSH Mobile,扫描电脑上的配对二维码。': 'Abra o DSH Mobile e escaneie o código QR neste computador.',
|
|
564
|
+
'在 iPhone 或 iPad 上通过 TestFlight 安装。': 'Instale-o no iPhone ou iPad com o TestFlight.',
|
|
565
|
+
'从 App Store 安装 TestFlight。': 'Instale o TestFlight pela App Store.',
|
|
566
|
+
'打开 TestFlight 邀请链接,安装 DSH Mobile。': 'Abra o link de convite do TestFlight e instale o DSH Mobile.',
|
|
567
|
+
'需要 iOS 17、iPadOS 17 或更高版本。': 'Requer iOS 17, iPadOS 17 ou posterior.',
|
|
568
|
+
'命令行登记(适用于无界面环境)': 'Registro por linha de comando (headless)',
|
|
569
|
+
'上次错误': 'Último erro',
|
|
570
|
+
'dsh-mobile-link enroll --relay <中转地址>': 'dsh-mobile-link enroll --relay <relay-address>',
|
|
571
|
+
'wss://<中转地址>/dsh-link': 'wss://<relay>/dsh-link',
|
|
572
|
+
'GitHub 仓库': 'Repositório no GitHub',
|
|
573
|
+
'欢迎在 GitHub 上为项目加星标。': 'Se ajudar, dê uma estrela no projeto no GitHub.',
|
|
574
|
+
'官方原子组件不可用,移动端连接入口未注册': 'Primitivas oficiais indisponíveis; a entrada não foi registrada',
|
|
575
|
+
'连不上本机连接器:${error}': 'Não é possível alcançar o conector local: ${error}',
|
|
576
|
+
'面板状态层没起来:${error}': 'A camada de status não iniciou: ${error}',
|
|
577
|
+
'小窗没注册上:${error}': 'O painel não foi registrado: ${error}',
|
|
578
|
+
'侧栏入口没注册上:${error}': 'A entrada da barra lateral não foi registrada: ${error}',
|
|
579
|
+
'连接器应答 HTTP ${status}': 'O conector respondeu HTTP ${status}',
|
|
580
|
+
'连接器应答无法解析(HTTP ${status})': 'Resposta ilegível do conector (HTTP ${status})',
|
|
581
|
+
'“${name}”将无法继续连接。需要重新扫码才能再次配对。': '“${name}” não se conectará mais. Escaneie o código novamente para pareá-lo.',
|
|
582
|
+
'${online} 台在线': '${online} online',
|
|
583
|
+
'${total} 台在线': '${total} online',
|
|
584
|
+
'${online} 台在线,共 ${total} 台': '${online} de ${total} online',
|
|
585
|
+
'${online} 台在线,共 ${total} 台已配对': '${online} de ${total} pareados online',
|
|
586
|
+
'有效期至 ${hasta}(剩余 ${resto})': 'Válido até ${hasta} (faltam ${resto})',
|
|
587
|
+
},
|
|
588
|
+
fr: {
|
|
589
|
+
'(已配对 ${paired}/${max})': '(appairés ${paired}/${max})',
|
|
590
|
+
'移动端连接': 'Lien mobile',
|
|
591
|
+
'移动端连接:二维码、已配对的手机与连接状态': 'Lien mobile : code QR, téléphones appairés et état du lien',
|
|
592
|
+
'此电脑尚未登记': 'Cet ordinateur n\'est pas enregistré',
|
|
593
|
+
'此电脑尚未登记。请使用命令行完成登记:': 'Cet ordinateur n\'est pas enregistré. Enregistrez-le depuis la ligne de commande :',
|
|
594
|
+
'登记此电脑以连接中转服务器。': 'Enregistrez cet ordinateur pour vous connecter au relais.',
|
|
595
|
+
'此电脑已登记。': 'Cet ordinateur est enregistré.',
|
|
596
|
+
'中转地址': 'Adresse du relais',
|
|
597
|
+
'中转服务器': 'Relais',
|
|
598
|
+
'已预填,可修改。': 'Préremplie ; modifiable.',
|
|
599
|
+
'由配置指定,不可修改。': 'Définie par la configuration ; non modifiable.',
|
|
600
|
+
'请输入中转地址。': 'Saisissez l\'adresse du relais.',
|
|
601
|
+
'中转地址无效。': 'L\'adresse du relais n\'est pas valide.',
|
|
602
|
+
'请输入有效的中转地址。': 'Saisissez une adresse de relais valide.',
|
|
603
|
+
'尝试次数过多,请稍后重试。': 'Trop de tentatives ; réessayez plus tard.',
|
|
604
|
+
'中转服务器未响应,请稍后重试。': 'Le relais n\'a pas répondu ; réessayez plus tard.',
|
|
605
|
+
'中转服务器登记失败,请稍后重试。': 'Le relais a refusé l\'enregistrement ; réessayez plus tard.',
|
|
606
|
+
'中转服务器返回了无法识别的响应。': 'Le relais a renvoyé une réponse inconnue.',
|
|
607
|
+
'连接器正在停止,请稍后重试。': 'Le connecteur s\'arrête ; réessayez plus tard.',
|
|
608
|
+
'连接器内部错误,请稍后重试。': 'Erreur interne du connecteur ; réessayez plus tard.',
|
|
609
|
+
'登记信息未能保存到此电脑,请检查磁盘权限。': 'L\'enregistrement n\'a pas pu être sauvegardé ; vérifiez les permissions du disque.',
|
|
610
|
+
'登记后将自动连接。': 'Il se connecte automatiquement une fois enregistré.',
|
|
611
|
+
'登记失败,请重试。': 'Échec de l\'enregistrement ; réessayez.',
|
|
612
|
+
'正在登记…': 'Enregistrement…',
|
|
613
|
+
'正在获取…': 'Récupération…',
|
|
614
|
+
'正在读取状态…': 'Lecture de l\'état…',
|
|
615
|
+
'正在连接…': 'Connexion…',
|
|
616
|
+
'登记': 'Enregistrer',
|
|
617
|
+
'重试': 'Réessayer',
|
|
618
|
+
'无法读取连接状态。': 'Impossible de lire l\'état du lien.',
|
|
619
|
+
'无法连接中转服务器,请检查地址与网络。': 'Impossible de joindre le relais ; vérifiez l\'adresse et le réseau.',
|
|
620
|
+
'状态未知': 'État inconnu',
|
|
621
|
+
'已连接': 'Connecté',
|
|
622
|
+
'在线': 'en ligne',
|
|
623
|
+
'离线': 'hors ligne',
|
|
624
|
+
'未连接,正在重新连接': 'Non connecté, reconnexion en cours',
|
|
625
|
+
'连接器已停用': 'Connecteur désactivé',
|
|
626
|
+
'连接器已停用。': 'Connecteur désactivé.',
|
|
627
|
+
'连接器已停用。请在插件配置中启用后重试。': 'Connecteur désactivé. Activez-le dans la configuration du plugin et réessayez.',
|
|
628
|
+
'配对二维码': 'Code QR d\'appairage',
|
|
629
|
+
'获取配对码': 'Obtenir le code d\'appairage',
|
|
630
|
+
'暂无配对码。': 'Aucun code d\'appairage pour l\'instant.',
|
|
631
|
+
'复制配对码': 'Copier le code d\'appairage',
|
|
632
|
+
'已复制': 'Copié',
|
|
633
|
+
'重新生成': 'Régénérer',
|
|
634
|
+
'长期有效直到重新生成': 'Valable jusqu\'à régénération',
|
|
635
|
+
'已配对设备': 'Appareils appairés',
|
|
636
|
+
'暂无已配对设备': 'Aucun appareil appairé',
|
|
637
|
+
'暂无已配对设备。': 'Aucun appareil appairé.',
|
|
638
|
+
'无法读取全部已配对设备。': 'Impossible de lire la liste complète des appareils.',
|
|
639
|
+
'撤销中…': 'Révocation…',
|
|
640
|
+
'撤销失败,请稍后重试。': 'Échec de la révocation ; réessayez plus tard.',
|
|
641
|
+
'撤销设备': 'Révoquer l\'appareil',
|
|
642
|
+
'撤销这台设备': 'Révoquer cet appareil',
|
|
643
|
+
'撤销这台设备?': 'Révoquer cet appareil ?',
|
|
644
|
+
'这台设备不属于此电脑。': 'Cet appareil n\'appartient pas à cet ordinateur.',
|
|
645
|
+
'设备不存在或已被撤销。': 'L\'appareil n\'existe pas ou a déjà été révoqué.',
|
|
646
|
+
'撤销': 'Révoquer',
|
|
647
|
+
'这台设备': 'cet appareil',
|
|
648
|
+
'设备': 'Appareil',
|
|
649
|
+
'未命名设备': 'Appareil sans nom',
|
|
650
|
+
'关闭': 'Fermer',
|
|
651
|
+
'取消': 'Annuler',
|
|
652
|
+
'完成': 'Terminé',
|
|
653
|
+
'尚未安装 iOS App?': 'Pas encore d\'application iOS ?',
|
|
654
|
+
'安装 DSH Mobile': 'Installer DSH Mobile',
|
|
655
|
+
'查看安装方式': 'Voir comment l\'installer',
|
|
656
|
+
'打开 DSH Mobile,扫描电脑上的配对二维码。': 'Ouvrez DSH Mobile et scannez le code QR sur cet ordinateur.',
|
|
657
|
+
'在 iPhone 或 iPad 上通过 TestFlight 安装。': 'Installez-la sur iPhone ou iPad avec TestFlight.',
|
|
658
|
+
'从 App Store 安装 TestFlight。': 'Installez TestFlight depuis l\'App Store.',
|
|
659
|
+
'打开 TestFlight 邀请链接,安装 DSH Mobile。': 'Ouvrez le lien d\'invitation TestFlight et installez DSH Mobile.',
|
|
660
|
+
'需要 iOS 17、iPadOS 17 或更高版本。': 'Nécessite iOS 17, iPadOS 17 ou une version ultérieure.',
|
|
661
|
+
'命令行登记(适用于无界面环境)': 'Enregistrement en ligne de commande (sans interface)',
|
|
662
|
+
'上次错误': 'Dernière erreur',
|
|
663
|
+
'dsh-mobile-link enroll --relay <中转地址>': 'dsh-mobile-link enroll --relay <relay-address>',
|
|
664
|
+
'wss://<中转地址>/dsh-link': 'wss://<relay>/dsh-link',
|
|
665
|
+
'GitHub 仓库': 'Dépôt GitHub',
|
|
666
|
+
'欢迎在 GitHub 上为项目加星标。': 'Si cela vous aide, ajoutez une étoile au projet sur GitHub.',
|
|
667
|
+
'官方原子组件不可用,移动端连接入口未注册': 'Primitives officielles indisponibles ; l\'entrée n\'a pas été enregistrée',
|
|
668
|
+
'连不上本机连接器:${error}': 'Impossible de joindre le connecteur local : ${error}',
|
|
669
|
+
'面板状态层没起来:${error}': 'La couche d\'état ne s\'est pas initialisée : ${error}',
|
|
670
|
+
'小窗没注册上:${error}': 'Le panneau n\'a pas été enregistré : ${error}',
|
|
671
|
+
'侧栏入口没注册上:${error}': 'L\'entrée de la barre latérale n\'a pas été enregistrée : ${error}',
|
|
672
|
+
'连接器应答 HTTP ${status}': 'Le connecteur a répondu HTTP ${status}',
|
|
673
|
+
'连接器应答无法解析(HTTP ${status})': 'Réponse illisible du connecteur (HTTP ${status})',
|
|
674
|
+
'“${name}”将无法继续连接。需要重新扫码才能再次配对。': '« ${name} » ne se connectera plus. Scannez à nouveau le code pour l\'appairer.',
|
|
675
|
+
'${online} 台在线': '${online} en ligne',
|
|
676
|
+
'${total} 台在线': '${total} en ligne',
|
|
677
|
+
'${online} 台在线,共 ${total} 台': '${online} sur ${total} en ligne',
|
|
678
|
+
'${online} 台在线,共 ${total} 台已配对': '${online} sur ${total} appairés en ligne',
|
|
679
|
+
'有效期至 ${hasta}(剩余 ${resto})': 'Valable jusqu\'à ${hasta} (${resto} restant)',
|
|
680
|
+
},
|
|
681
|
+
de: {
|
|
682
|
+
'(已配对 ${paired}/${max})': '(gekoppelt: ${paired}/${max})',
|
|
683
|
+
'移动端连接': 'Mobile Verbindung',
|
|
684
|
+
'移动端连接:二维码、已配对的手机与连接状态': 'Mobile Verbindung: QR-Code, gekoppelte Telefone und Verbindungsstatus',
|
|
685
|
+
'此电脑尚未登记': 'Dieser Computer ist nicht registriert',
|
|
686
|
+
'此电脑尚未登记。请使用命令行完成登记:': 'Dieser Computer ist nicht registriert. Registrieren Sie ihn über die Befehlszeile:',
|
|
687
|
+
'登记此电脑以连接中转服务器。': 'Registrieren Sie diesen Computer, um eine Verbindung zum Relay herzustellen.',
|
|
688
|
+
'此电脑已登记。': 'Dieser Computer ist registriert.',
|
|
689
|
+
'中转地址': 'Relay-Adresse',
|
|
690
|
+
'中转服务器': 'Relay',
|
|
691
|
+
'已预填,可修改。': 'Vorausgefüllt; bearbeitbar.',
|
|
692
|
+
'由配置指定,不可修改。': 'Durch die Konfiguration festgelegt; nicht bearbeitbar.',
|
|
693
|
+
'请输入中转地址。': 'Geben Sie die Relay-Adresse ein.',
|
|
694
|
+
'中转地址无效。': 'Die Relay-Adresse ist nicht gültig.',
|
|
695
|
+
'请输入有效的中转地址。': 'Geben Sie eine gültige Relay-Adresse ein.',
|
|
696
|
+
'尝试次数过多,请稍后重试。': 'Zu viele Versuche; versuchen Sie es später erneut.',
|
|
697
|
+
'中转服务器未响应,请稍后重试。': 'Das Relay hat nicht geantwortet; versuchen Sie es später erneut.',
|
|
698
|
+
'中转服务器登记失败,请稍后重试。': 'Das Relay hat die Registrierung abgelehnt; versuchen Sie es später erneut.',
|
|
699
|
+
'中转服务器返回了无法识别的响应。': 'Das Relay hat eine unbekannte Antwort zurückgegeben.',
|
|
700
|
+
'连接器正在停止,请稍后重试。': 'Der Connector wird beendet; versuchen Sie es später erneut.',
|
|
701
|
+
'连接器内部错误,请稍后重试。': 'Interner Connector-Fehler; versuchen Sie es später erneut.',
|
|
702
|
+
'登记信息未能保存到此电脑,请检查磁盘权限。': 'Die Registrierung konnte nicht gespeichert werden; prüfen Sie die Datenträgerberechtigungen.',
|
|
703
|
+
'登记后将自动连接。': 'Die Verbindung wird nach der Registrierung automatisch hergestellt.',
|
|
704
|
+
'登记失败,请重试。': 'Registrierung fehlgeschlagen; versuchen Sie es erneut.',
|
|
705
|
+
'正在登记…': 'Wird registriert …',
|
|
706
|
+
'正在获取…': 'Wird abgerufen …',
|
|
707
|
+
'正在读取状态…': 'Status wird gelesen …',
|
|
708
|
+
'正在连接…': 'Verbindung wird hergestellt …',
|
|
709
|
+
'登记': 'Registrieren',
|
|
710
|
+
'重试': 'Erneut versuchen',
|
|
711
|
+
'无法读取连接状态。': 'Der Verbindungsstatus konnte nicht gelesen werden.',
|
|
712
|
+
'无法连接中转服务器,请检查地址与网络。': 'Das Relay war nicht erreichbar; überprüfen Sie die Adresse und das Netzwerk.',
|
|
713
|
+
'状态未知': 'Unbekannter Status',
|
|
714
|
+
'已连接': 'Verbunden',
|
|
715
|
+
'在线': 'online',
|
|
716
|
+
'离线': 'offline',
|
|
717
|
+
'未连接,正在重新连接': 'Nicht verbunden, Verbindung wird wiederhergestellt',
|
|
718
|
+
'连接器已停用': 'Connector deaktiviert',
|
|
719
|
+
'连接器已停用。': 'Connector deaktiviert.',
|
|
720
|
+
'连接器已停用。请在插件配置中启用后重试。': 'Connector deaktiviert. Aktivieren Sie ihn in der Plugin-Konfiguration und versuchen Sie es erneut.',
|
|
721
|
+
'配对二维码': 'Kopplungs-QR-Code',
|
|
722
|
+
'获取配对码': 'Kopplungscode abrufen',
|
|
723
|
+
'暂无配对码。': 'Noch kein Kopplungscode.',
|
|
724
|
+
'复制配对码': 'Kopplungscode kopieren',
|
|
725
|
+
'已复制': 'Kopiert',
|
|
726
|
+
'重新生成': 'Neu generieren',
|
|
727
|
+
'长期有效直到重新生成': 'Gültig bis zur Neugenerierung',
|
|
728
|
+
'已配对设备': 'Gekoppelte Geräte',
|
|
729
|
+
'暂无已配对设备': 'Keine gekoppelten Geräte',
|
|
730
|
+
'暂无已配对设备。': 'Keine gekoppelten Geräte.',
|
|
731
|
+
'无法读取全部已配对设备。': 'Die vollständige Geräteliste konnte nicht gelesen werden.',
|
|
732
|
+
'撤销中…': 'Wird widerrufen …',
|
|
733
|
+
'撤销失败,请稍后重试。': 'Widerruf fehlgeschlagen; versuchen Sie es später erneut.',
|
|
734
|
+
'撤销设备': 'Gerät widerrufen',
|
|
735
|
+
'撤销这台设备': 'Dieses Gerät widerrufen',
|
|
736
|
+
'撤销这台设备?': 'Dieses Gerät widerrufen?',
|
|
737
|
+
'这台设备不属于此电脑。': 'Dieses Gerät gehört nicht zu diesem Computer.',
|
|
738
|
+
'设备不存在或已被撤销。': 'Das Gerät existiert nicht oder wurde bereits widerrufen.',
|
|
739
|
+
'撤销': 'Widerrufen',
|
|
740
|
+
'这台设备': 'dieses Gerät',
|
|
741
|
+
'设备': 'Gerät',
|
|
742
|
+
'未命名设备': 'Unbenanntes Gerät',
|
|
743
|
+
'关闭': 'Schließen',
|
|
744
|
+
'取消': 'Abbrechen',
|
|
745
|
+
'完成': 'Fertig',
|
|
746
|
+
'尚未安装 iOS App?': 'Noch keine iOS-App?',
|
|
747
|
+
'安装 DSH Mobile': 'DSH Mobile installieren',
|
|
748
|
+
'查看安装方式': 'Installationsanleitung ansehen',
|
|
749
|
+
'打开 DSH Mobile,扫描电脑上的配对二维码。': 'Öffnen Sie DSH Mobile und scannen Sie den QR-Code auf diesem Computer.',
|
|
750
|
+
'在 iPhone 或 iPad 上通过 TestFlight 安装。': 'Installieren Sie es mit TestFlight auf iPhone oder iPad.',
|
|
751
|
+
'从 App Store 安装 TestFlight。': 'Installieren Sie TestFlight aus dem App Store.',
|
|
752
|
+
'打开 TestFlight 邀请链接,安装 DSH Mobile。': 'Öffnen Sie den TestFlight-Einladungslink und installieren Sie DSH Mobile.',
|
|
753
|
+
'需要 iOS 17、iPadOS 17 或更高版本。': 'Erfordert iOS 17, iPadOS 17 oder neuer.',
|
|
754
|
+
'命令行登记(适用于无界面环境)': 'Befehlszeilen-Registrierung (headless)',
|
|
755
|
+
'上次错误': 'Letzter Fehler',
|
|
756
|
+
'dsh-mobile-link enroll --relay <中转地址>': 'dsh-mobile-link enroll --relay <relay-address>',
|
|
757
|
+
'wss://<中转地址>/dsh-link': 'wss://<relay>/dsh-link',
|
|
758
|
+
'GitHub 仓库': 'GitHub-Repository',
|
|
759
|
+
'欢迎在 GitHub 上为项目加星标。': 'Wenn es Ihnen hilft, geben Sie dem Projekt auf GitHub einen Stern.',
|
|
760
|
+
'官方原子组件不可用,移动端连接入口未注册': 'Offizielle Basisfunktionen nicht verfügbar; der Eintrag wurde nicht registriert.',
|
|
761
|
+
'连不上本机连接器:${error}': 'Der lokale Connector ist nicht erreichbar: ${error}',
|
|
762
|
+
'面板状态层没起来:${error}': 'Die Statusebene konnte nicht gestartet werden: ${error}',
|
|
763
|
+
'小窗没注册上:${error}': 'Das Panel wurde nicht registriert: ${error}',
|
|
764
|
+
'侧栏入口没注册上:${error}': 'Der Seitenleisten-Eintrag wurde nicht registriert: ${error}',
|
|
765
|
+
'连接器应答 HTTP ${status}': 'Der Connector antwortete mit HTTP ${status}',
|
|
766
|
+
'连接器应答无法解析(HTTP ${status})': 'Nicht lesbare Connector-Antwort (HTTP ${status})',
|
|
767
|
+
'“${name}”将无法继续连接。需要重新扫码才能再次配对。': '„${name}“ wird sich nicht mehr verbinden. Scannen Sie den Code erneut, um es zu koppeln.',
|
|
768
|
+
'${online} 台在线': '${online} online',
|
|
769
|
+
'${total} 台在线': '${total} online',
|
|
770
|
+
'${online} 台在线,共 ${total} 台': '${online} von ${total} online',
|
|
771
|
+
'${online} 台在线,共 ${total} 台已配对': '${online} von ${total} gekoppelten Geräten online',
|
|
772
|
+
'有效期至 ${hasta}(剩余 ${resto})': 'Gültig bis ${hasta} (noch ${resto})',
|
|
773
|
+
},
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
/**
|
|
777
|
+
* 界面语言:宿主能表达就用宿主;宿主表达不了用户要的语言(例如宿主只有 zh / en 而浏览器报
|
|
778
|
+
* es)时,由面板补上自己有词表的那一档。宿主将来支持西语,面板会自动回到宿主,不需要再改。
|
|
779
|
+
* 宿主没有 locale 服务(旧宿主、纯浏览器调试)时完全按浏览器语言,行为与改动前一致。
|
|
780
|
+
*/
|
|
781
|
+
const PANEL_LOCALES = ['zh', 'en', 'es', 'zh-Hant', 'ja', 'ko', 'pt-BR', 'fr', 'de']
|
|
782
|
+
|
|
783
|
+
function baseLanguage(tag) {
|
|
784
|
+
const value = String(tag ?? '').trim().toLowerCase()
|
|
785
|
+
return value ? value.split('-')[0] : ''
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
/** 面板有词表的语言档;没有词表返回 null。 */
|
|
789
|
+
function panelLocaleOf(tag) {
|
|
790
|
+
const raw = String(tag ?? '').trim().toLowerCase()
|
|
791
|
+
if (raw.startsWith('zh-hant') || raw.startsWith('zh-tw') || raw.startsWith('zh-hk')) return 'zh-Hant'
|
|
792
|
+
if (raw.startsWith('pt')) return 'pt-BR'
|
|
793
|
+
const base = baseLanguage(raw)
|
|
794
|
+
return PANEL_LOCALES.includes(base) ? base : null
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
/** 浏览器要的语言,按优先级排开;桌面版的这一串由宿主启动参数决定,这里只读。 */
|
|
798
|
+
function browserLanguages() {
|
|
799
|
+
if (typeof navigator === 'undefined') return []
|
|
800
|
+
const list = Array.isArray(navigator.languages) && navigator.languages.length > 0
|
|
801
|
+
? navigator.languages
|
|
802
|
+
: [navigator.language]
|
|
803
|
+
return list.filter((tag) => typeof tag === 'string' && tag)
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
/** 宿主 locale 快照;没有 locale 服务的宿主返回 null。 */
|
|
807
|
+
/** 宿主自己维护的 `<html lang>`:locale 服务切换语言时会同步写它,服务取不到时用它兜底。 */
|
|
808
|
+
function documentLanguage() {
|
|
809
|
+
if (typeof document === 'undefined') return null
|
|
810
|
+
const lang = document?.documentElement?.lang
|
|
811
|
+
return typeof lang === 'string' && lang ? lang : null
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
/** 宿主切换语言时会同步改写 `<html lang>`;订阅它,比"等服务挂到 ctx 上"可靠。 */
|
|
815
|
+
function watchDocumentLanguage(onChange) {
|
|
816
|
+
if (typeof MutationObserver === 'undefined' || typeof document === 'undefined'
|
|
817
|
+
|| !document.documentElement) return null
|
|
818
|
+
const observer = new MutationObserver(() => onChange())
|
|
819
|
+
observer.observe(document.documentElement, { attributes: true, attributeFilter: ['lang'] })
|
|
820
|
+
return () => observer.disconnect()
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
function hostSnapshot(ctx) {
|
|
824
|
+
try {
|
|
825
|
+
// 直属性与 `get` 两种取法都试:不同宿主版本里 locale 服务挂上的时机不同。
|
|
826
|
+
const service = ctx?.locale ?? ctx?.get?.('locale')
|
|
827
|
+
const snapshot = service?.getSnapshot?.()
|
|
828
|
+
return snapshot && typeof snapshot === 'object' ? snapshot : null
|
|
829
|
+
} catch {
|
|
830
|
+
return null
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
function resolveLocale(ctx) {
|
|
835
|
+
const snapshot = hostSnapshot(ctx)
|
|
836
|
+
const hostActive = (typeof snapshot?.active === 'string' && snapshot.active)
|
|
837
|
+
|| documentLanguage()
|
|
838
|
+
// 语言表在不同版本里既可能是 `[{ id, label }]` 也可能是纯 id 字符串,两种都认。
|
|
839
|
+
const hostIds = new Set()
|
|
840
|
+
for (const item of Array.isArray(snapshot?.locales) ? snapshot.locales : []) {
|
|
841
|
+
const id = typeof item === 'string' ? item : item?.id
|
|
842
|
+
if (typeof id === 'string' && id) hostIds.add(id)
|
|
843
|
+
}
|
|
844
|
+
// 快照没带语言表(老宿主)时按 DSH 内置的 zh / en 兜底:把"表达不了"误判成"能表达",
|
|
845
|
+
// 西语就会一直落到 en,面板再也拿不到西语。
|
|
846
|
+
if (hostIds.size === 0) {
|
|
847
|
+
hostIds.add('zh')
|
|
848
|
+
hostIds.add('en')
|
|
849
|
+
}
|
|
850
|
+
const wanted = browserLanguages().find((tag) => baseLanguage(tag)) ?? ''
|
|
851
|
+
const wantedBase = baseLanguage(wanted)
|
|
852
|
+
const hostPanel = hostActive ? panelLocaleOf(hostActive) : null
|
|
853
|
+
const browserPanel = wanted ? panelLocaleOf(wanted) : null
|
|
854
|
+
const hostLeftDefault = baseLanguage(hostActive) === 'en'
|
|
855
|
+
// 宿主还停在默认 en 时,面板可以补浏览器明确要的语言(含 zh-Hant / pt-BR 这类带地区的档)。
|
|
856
|
+
if (hostPanel && (!wantedBase || (hostIds.has(wantedBase) && !hostLeftDefault))) return hostPanel
|
|
857
|
+
if (hostPanel && !(hostLeftDefault && browserPanel)) return hostPanel
|
|
858
|
+
return browserPanel ?? hostPanel ?? 'zh'
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
/** 当前界面语言:`apply` 时按宿主取值,之后由 `locale/change` 改写。 */
|
|
862
|
+
let LOCALE = resolveLocale(null)
|
|
863
|
+
|
|
864
|
+
/** Current-language sentence; `vars` fills the `${name}` placeholders. */
|
|
865
|
+
function T(text, vars) {
|
|
866
|
+
const raw = (MESSAGES[LOCALE] && MESSAGES[LOCALE][text]) || text
|
|
867
|
+
if (!vars) return raw
|
|
868
|
+
return raw.replace(/\$\{(\w+)\}/g, (m, key) => (key in vars ? String(vars[key]) : m))
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
/** 入口与标题在渲染时取值,跟随语言变化;不要在模块加载时定死。 */
|
|
872
|
+
function panelLabel() {
|
|
873
|
+
return T('移动端连接')
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
function panelTitle() {
|
|
877
|
+
return T('移动端连接:二维码、已配对的手机与连接状态')
|
|
878
|
+
}
|
|
879
|
+
|
|
25
880
|
const PRIMITIVES = '@deepseek-ai/dsh-client-ui-primitives'
|
|
26
881
|
/** GitHub 仓库与 iOS 公开测试链接;都只做展示,不参与准入。 */
|
|
27
882
|
const GITHUB_URL = 'https://github.com/jayantTang/DSH_Mobile'
|
|
@@ -179,16 +1034,16 @@ async function requestJson(path, init) {
|
|
|
179
1034
|
try {
|
|
180
1035
|
response = await fetch(path, init)
|
|
181
1036
|
} catch (error) {
|
|
182
|
-
throw new Error(
|
|
1037
|
+
throw new Error(T('连不上本机连接器:${error}', { error: describe(error) }))
|
|
183
1038
|
}
|
|
184
1039
|
let body
|
|
185
1040
|
try {
|
|
186
1041
|
body = await response.json()
|
|
187
1042
|
} catch {
|
|
188
|
-
throw new Error(
|
|
1043
|
+
throw new Error(T('连接器应答无法解析(HTTP ${status})', { status: response.status }))
|
|
189
1044
|
}
|
|
190
1045
|
if (!response.ok || body?.ok === false) {
|
|
191
|
-
const error = new Error(body?.message ??
|
|
1046
|
+
const error = new Error(body?.message ?? T('连接器应答 HTTP ${status}', { status: response.status }))
|
|
192
1047
|
// Keep the machine code: the panel maps it to Chinese copy and must not
|
|
193
1048
|
// parse English messages to tell "used" from "expired".
|
|
194
1049
|
error.code = body?.code
|
|
@@ -204,12 +1059,12 @@ function needsEnroll(status) {
|
|
|
204
1059
|
}
|
|
205
1060
|
/** 状态点与状态词的唯一来源:侧栏入口与标题行共用,不会出现"点上是绿的、文字说断开"。 */
|
|
206
1061
|
function connectionState(state) {
|
|
207
|
-
if (state.statusError) return { dot: 'error', text: '状态未知' }
|
|
208
|
-
if (!state.status) return { dot: 'ongoing', text: '正在读取状态…' }
|
|
209
|
-
if (needsEnroll(state.status)) return { dot: 'error', text: '此电脑尚未登记' }
|
|
210
|
-
if (state.status.enabled === false) return { dot: 'idle', text: '连接器已停用' }
|
|
211
|
-
if (state.status.connected) return { dot: 'done', text: '已连接' }
|
|
212
|
-
return { dot: 'warning', text: '未连接,正在重新连接' }
|
|
1062
|
+
if (state.statusError) return { dot: 'error', text: T('状态未知') }
|
|
1063
|
+
if (!state.status) return { dot: 'ongoing', text: T('正在读取状态…') }
|
|
1064
|
+
if (needsEnroll(state.status)) return { dot: 'error', text: T('此电脑尚未登记') }
|
|
1065
|
+
if (state.status.enabled === false) return { dot: 'idle', text: T('连接器已停用') }
|
|
1066
|
+
if (state.status.connected) return { dot: 'done', text: T('已连接') }
|
|
1067
|
+
return { dot: 'warning', text: T('未连接,正在重新连接') }
|
|
213
1068
|
}
|
|
214
1069
|
/** 手机报上来的版本:`hello` 存的是 `{name,version,build,label}`,直接插值会渲染成 [object Object]。 */
|
|
215
1070
|
function clientLabel(client) {
|
|
@@ -220,9 +1075,11 @@ function clientLabel(client) {
|
|
|
220
1075
|
/** 在线/总数分开说:`deviceCount` 把"连着但已离线"的记录也算进去了。 */
|
|
221
1076
|
function deviceSummary(devices) {
|
|
222
1077
|
const list = Array.isArray(devices) ? devices : []
|
|
223
|
-
if (list.length === 0) return '暂无已配对设备'
|
|
1078
|
+
if (list.length === 0) return T('暂无已配对设备')
|
|
224
1079
|
const online = list.filter((device) => device.offlineSince === null || device.offlineSince === undefined).length
|
|
225
|
-
return online === list.length
|
|
1080
|
+
return online === list.length
|
|
1081
|
+
? T('${online} 台在线', { online })
|
|
1082
|
+
: T('${online} 台在线,共 ${total} 台', { online, total: list.length })
|
|
226
1083
|
}
|
|
227
1084
|
/** 全量已配对设备 + /status 的在线标记;离线设备也要有行。 */
|
|
228
1085
|
function pairedRows(state) {
|
|
@@ -253,9 +1110,11 @@ function pairedTotal(state) {
|
|
|
253
1110
|
}
|
|
254
1111
|
function deviceSummaryOf(state) {
|
|
255
1112
|
const total = pairedTotal(state)
|
|
256
|
-
if (total === 0) return '暂无已配对设备'
|
|
1113
|
+
if (total === 0) return T('暂无已配对设备')
|
|
257
1114
|
const online = pairedRows(state).filter((device) => device.online).length
|
|
258
|
-
return online === total
|
|
1115
|
+
return online === total
|
|
1116
|
+
? T('${total} 台在线', { total })
|
|
1117
|
+
: T('${online} 台在线,共 ${total} 台已配对', { online, total })
|
|
259
1118
|
}
|
|
260
1119
|
function clockText(expiresAt) {
|
|
261
1120
|
const date = new Date(Number(expiresAt))
|
|
@@ -292,6 +1151,8 @@ function createPanel() {
|
|
|
292
1151
|
revoking: false,
|
|
293
1152
|
revokeError: null,
|
|
294
1153
|
guideOpen: false,
|
|
1154
|
+
// 界面语言:由宿主 locale 决定,宿主切换时写入这份快照以推动重渲染。
|
|
1155
|
+
locale: LOCALE,
|
|
295
1156
|
}),
|
|
296
1157
|
disposed: false,
|
|
297
1158
|
style: null,
|
|
@@ -339,7 +1200,7 @@ async function loadPairedDevices(panel) {
|
|
|
339
1200
|
} catch (error) {
|
|
340
1201
|
if (panel.disposed) return
|
|
341
1202
|
panel.store.set({
|
|
342
|
-
pairedError: '无法读取全部已配对设备。',
|
|
1203
|
+
pairedError: T('无法读取全部已配对设备。'),
|
|
343
1204
|
pairedBusy: false,
|
|
344
1205
|
})
|
|
345
1206
|
}
|
|
@@ -493,30 +1354,33 @@ function copyCode(panel) {
|
|
|
493
1354
|
done()
|
|
494
1355
|
}
|
|
495
1356
|
}
|
|
496
|
-
/** 服务端的机器码 →
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
1357
|
+
/** 服务端的机器码 → 面板文案;原始 message 留在 details 里给排障。 */
|
|
1358
|
+
function enrollTexts() {
|
|
1359
|
+
return {
|
|
1360
|
+
'enroll/bad-request': T('请输入中转地址。'),
|
|
1361
|
+
'enroll/invalid-relay': T('中转地址无效。'),
|
|
1362
|
+
'enroll/placeholder-relay': T('请输入有效的中转地址。'),
|
|
1363
|
+
'enroll/rate-limited': T('尝试次数过多,请稍后重试。'),
|
|
1364
|
+
'enroll/unreachable': T('无法连接中转服务器,请检查地址与网络。'),
|
|
1365
|
+
'enroll/timeout': T('中转服务器未响应,请稍后重试。'),
|
|
1366
|
+
'enroll/bad-response': T('中转服务器返回了无法识别的响应。'),
|
|
1367
|
+
'enroll/already-registered': T('此电脑已登记。'),
|
|
1368
|
+
'enroll/disabled': T('连接器已停用。'),
|
|
1369
|
+
'enroll/unavailable': T('连接器正在停止,请稍后重试。'),
|
|
1370
|
+
'enroll/state-write': T('登记信息未能保存到此电脑,请检查磁盘权限。'),
|
|
1371
|
+
'enroll/relay-error': T('中转服务器登记失败,请稍后重试。'),
|
|
1372
|
+
'enroll/internal': T('连接器内部错误,请稍后重试。'),
|
|
1373
|
+
}
|
|
511
1374
|
}
|
|
512
1375
|
function enrollMessage(error) {
|
|
513
|
-
|
|
1376
|
+
// 按调用时的语言现算:模块加载时定死会让宿主切换语言后错误提示停在旧语言。
|
|
1377
|
+
return enrollTexts()[error?.code] ?? describe(error) ?? T('登记失败,请重试。')
|
|
514
1378
|
}
|
|
515
1379
|
function revokeMessage(error) {
|
|
516
1380
|
const code = error?.code
|
|
517
|
-
if (code === 'request/unknown-device') return '设备不存在或已被撤销。'
|
|
518
|
-
if (code === 'auth/agent-mismatch') return '这台设备不属于此电脑。'
|
|
519
|
-
return '撤销失败,请稍后重试。'
|
|
1381
|
+
if (code === 'request/unknown-device') return T('设备不存在或已被撤销。')
|
|
1382
|
+
if (code === 'auth/agent-mismatch') return T('这台设备不属于此电脑。')
|
|
1383
|
+
return T('撤销失败,请稍后重试。')
|
|
520
1384
|
}
|
|
521
1385
|
/** 表单字段只改草稿;不在这里写“是否已登记”之类的业务状态。 */
|
|
522
1386
|
function setEnrollField(panel, field, value) {
|
|
@@ -543,7 +1407,7 @@ async function submitEnroll(panel) {
|
|
|
543
1407
|
: (draft.relayTouched ? draft.relay : status?.enroll?.relayPrefill) || '').trim()
|
|
544
1408
|
if (!relayUrl) {
|
|
545
1409
|
panel.store.set({
|
|
546
|
-
enrollError: { code: 'enroll/bad-request', message: '请输入中转地址。' },
|
|
1410
|
+
enrollError: { code: 'enroll/bad-request', message: T('请输入中转地址。') },
|
|
547
1411
|
})
|
|
548
1412
|
return
|
|
549
1413
|
}
|
|
@@ -675,10 +1539,10 @@ function makeEntry(React, controller, ui) {
|
|
|
675
1539
|
const conn = connectionState(state)
|
|
676
1540
|
return React.createElement(ui.Button, {
|
|
677
1541
|
variant: 'ghost', type: 'button', id: ENTRY_ID, className: 'dsh-mobile-link-entry',
|
|
678
|
-
title: `${
|
|
1542
|
+
title: `${panelTitle()}(${conn.text})`, 'aria-label': `${panelLabel()}:${conn.text}`,
|
|
679
1543
|
onClick: () => controller.open(),
|
|
680
1544
|
}, React.createElement(ui.StateDot, { state: conn.dot }),
|
|
681
|
-
React.createElement('span', { className: 'dsh-mobile-link-entry-label' },
|
|
1545
|
+
React.createElement('span', { className: 'dsh-mobile-link-entry-label' }, panelLabel()))
|
|
682
1546
|
}
|
|
683
1547
|
}
|
|
684
1548
|
function row(React, key, value, mono = false) {
|
|
@@ -696,12 +1560,12 @@ function statusSection(React, ui, state) {
|
|
|
696
1560
|
React.createElement(ui.StateDot, { state: conn.dot }),
|
|
697
1561
|
React.createElement('span', null, conn.text)),
|
|
698
1562
|
state.statusError ? React.createElement('div', { className: 'dsh-mobile-link-note dsh-mobile-link-error' },
|
|
699
|
-
'无法读取连接状态。') : null,
|
|
1563
|
+
T('无法读取连接状态。')) : null,
|
|
700
1564
|
state.status && !needsEnroll(state.status)
|
|
701
|
-
? row(React, '中转服务器', state.status.relayUrl ?? '—', true)
|
|
1565
|
+
? row(React, T('中转服务器'), state.status.relayUrl ?? '—', true)
|
|
702
1566
|
: null,
|
|
703
1567
|
state.status && !needsEnroll(state.status)
|
|
704
|
-
? row(React, '设备', deviceSummaryOf(state))
|
|
1568
|
+
? row(React, T('设备'), deviceSummaryOf(state))
|
|
705
1569
|
: null)
|
|
706
1570
|
}
|
|
707
1571
|
/** 官方 Input 在旧宿主可能不存在;没有就用原生 input,表单不会因此消失。 */
|
|
@@ -722,9 +1586,9 @@ function enrollField(React, id, labelText, children) {
|
|
|
722
1586
|
function enrollCommandSection(React, status) {
|
|
723
1587
|
return React.createElement('div', { className: 'dsh-mobile-link-section' },
|
|
724
1588
|
React.createElement('div', { className: 'dsh-mobile-link-note' },
|
|
725
|
-
'此电脑尚未登记。请使用命令行完成登记:'),
|
|
1589
|
+
T('此电脑尚未登记。请使用命令行完成登记:')),
|
|
726
1590
|
React.createElement('div', { className: 'dsh-mobile-link-mono dsh-mobile-link-v', style: { textAlign: 'left' } },
|
|
727
|
-
status?.enroll?.command ?? 'dsh-mobile-link enroll --relay <中转地址>'))
|
|
1591
|
+
status?.enroll?.command ?? T('dsh-mobile-link enroll --relay <中转地址>')))
|
|
728
1592
|
}
|
|
729
1593
|
/**
|
|
730
1594
|
* 未登记:开放登记。只提交中转地址;电脑名称由连接器自动获取。
|
|
@@ -736,7 +1600,7 @@ function enrollSection(React, controller, ui, state) {
|
|
|
736
1600
|
if (status.enabled === false) {
|
|
737
1601
|
return React.createElement('div', { className: 'dsh-mobile-link-section' },
|
|
738
1602
|
React.createElement('div', { className: 'dsh-mobile-link-note' },
|
|
739
|
-
'连接器已停用。请在插件配置中启用后重试。'))
|
|
1603
|
+
T('连接器已停用。请在插件配置中启用后重试。')))
|
|
740
1604
|
}
|
|
741
1605
|
const enroll = status.enroll
|
|
742
1606
|
const locked = Boolean(enroll.relayLocked)
|
|
@@ -751,20 +1615,20 @@ function enrollSection(React, controller, ui, state) {
|
|
|
751
1615
|
event.preventDefault?.()
|
|
752
1616
|
if (!submitting) controller.enroll()
|
|
753
1617
|
}
|
|
754
|
-
const command = enroll.command ?? 'dsh-mobile-link enroll --relay <中转地址>'
|
|
1618
|
+
const command = enroll.command ?? T('dsh-mobile-link enroll --relay <中转地址>')
|
|
755
1619
|
|
|
756
1620
|
return React.createElement('div', { className: 'dsh-mobile-link-form' },
|
|
757
1621
|
React.createElement('div', { className: 'dsh-mobile-link-note', key: 'intro' },
|
|
758
|
-
'登记此电脑以连接中转服务器。'),
|
|
759
|
-
enrollField(React, 'mobile-link-relay', '中转地址',
|
|
1622
|
+
T('登记此电脑以连接中转服务器。')),
|
|
1623
|
+
enrollField(React, 'mobile-link-relay', T('中转地址'),
|
|
760
1624
|
enrollInput(React, ui, {
|
|
761
1625
|
id: 'mobile-link-relay', type: 'text', inputMode: 'url', value: relayValue,
|
|
762
|
-
placeholder: 'wss://<中转地址>/dsh-link', readOnly: locked, disabled: submitting,
|
|
1626
|
+
placeholder: T('wss://<中转地址>/dsh-link'), readOnly: locked, disabled: submitting,
|
|
763
1627
|
autoComplete: 'off', spellCheck: false,
|
|
764
1628
|
onChange: setField('relay'), onKeyDown: submitOnEnter,
|
|
765
1629
|
})),
|
|
766
1630
|
React.createElement('div', { className: 'dsh-mobile-link-note', key: 'relay-hint' },
|
|
767
|
-
locked ? '由配置指定,不可修改。' : '已预填,可修改。'),
|
|
1631
|
+
locked ? T('由配置指定,不可修改。') : T('已预填,可修改。')),
|
|
768
1632
|
error ? React.createElement('div', {
|
|
769
1633
|
className: 'dsh-mobile-link-error-inline', role: 'alert', key: 'error',
|
|
770
1634
|
title: error.detail ?? undefined,
|
|
@@ -772,28 +1636,28 @@ function enrollSection(React, controller, ui, state) {
|
|
|
772
1636
|
React.createElement(ui.Button, {
|
|
773
1637
|
key: 'submit', variant: 'primary', type: 'button', disabled: submitting,
|
|
774
1638
|
onClick: () => controller.enroll(),
|
|
775
|
-
}, submitting ? '正在登记…' : (error ? '重试' : '登记')),
|
|
1639
|
+
}, submitting ? T('正在登记…') : (error ? T('重试') : T('登记'))),
|
|
776
1640
|
React.createElement('div', { className: 'dsh-mobile-link-note', key: 'footnote' },
|
|
777
|
-
'登记后将自动连接。'),
|
|
1641
|
+
T('登记后将自动连接。')),
|
|
778
1642
|
React.createElement('details', { className: 'dsh-mobile-link-cmd', key: 'cli' },
|
|
779
|
-
React.createElement('summary', null, '命令行登记(适用于无界面环境)'),
|
|
1643
|
+
React.createElement('summary', null, T('命令行登记(适用于无界面环境)')),
|
|
780
1644
|
React.createElement('div', { className: 'dsh-mobile-link-mono' }, command)))
|
|
781
1645
|
}
|
|
782
1646
|
function githubSection(React) {
|
|
783
1647
|
return React.createElement('div', { className: 'dsh-mobile-link-github', key: 'github' },
|
|
784
1648
|
React.createElement('div', { className: 'dsh-mobile-link-github-line' },
|
|
785
|
-
React.createElement('span', { className: 'dsh-mobile-link-k' }, 'GitHub 仓库'),
|
|
1649
|
+
React.createElement('span', { className: 'dsh-mobile-link-k' }, T('GitHub 仓库')),
|
|
786
1650
|
React.createElement('a', { href: GITHUB_URL, target: '_blank', rel: 'noreferrer' },
|
|
787
1651
|
'github.com/jayantTang/DSH_Mobile')),
|
|
788
1652
|
React.createElement('div', { className: 'dsh-mobile-link-welcome' },
|
|
789
|
-
'欢迎在 GitHub 上为项目加星标。'))
|
|
1653
|
+
T('欢迎在 GitHub 上为项目加星标。')))
|
|
790
1654
|
}
|
|
791
1655
|
function iosLinkSection(React, controller) {
|
|
792
1656
|
return React.createElement('div', { className: 'dsh-mobile-link-ioslink', key: 'ios-link' },
|
|
793
|
-
React.createElement('span', null, '尚未安装 iOS App?'),
|
|
1657
|
+
React.createElement('span', null, T('尚未安装 iOS App?')),
|
|
794
1658
|
React.createElement('a', {
|
|
795
1659
|
href: '#', onClick: (event) => { event?.preventDefault?.(); controller.openGuide() },
|
|
796
|
-
}, '查看安装方式'))
|
|
1660
|
+
}, T('查看安装方式')))
|
|
797
1661
|
}
|
|
798
1662
|
/**
|
|
799
1663
|
* 二维码卡。默认是持久码,所以写“长期有效直到重新生成”,不再有倒计时;
|
|
@@ -807,37 +1671,40 @@ function codeSection(React, controller, ui, state) {
|
|
|
807
1671
|
main.push(React.createElement('div', { className: 'dsh-mobile-link-code', key: 'code' }, code.code))
|
|
808
1672
|
if (code.persistent || code.expiresAt == null) {
|
|
809
1673
|
main.push(React.createElement('div', { className: 'dsh-mobile-link-count', key: 'count' },
|
|
810
|
-
React.createElement('span', null, '长期有效直到重新生成'),
|
|
1674
|
+
React.createElement('span', null, T('长期有效直到重新生成')),
|
|
811
1675
|
code.maxPairedDevices
|
|
812
1676
|
? React.createElement('span', { className: 'dsh-mobile-link-count-tag' },
|
|
813
|
-
|
|
814
|
-
|
|
1677
|
+
T('(已配对 ${paired}/${max})', {
|
|
1678
|
+
paired: state.pairedCount ?? (Array.isArray(state.pairedDevices)
|
|
1679
|
+
? state.pairedDevices.length : (code.pairedDevices ?? 0)),
|
|
1680
|
+
max: code.maxPairedDevices,
|
|
1681
|
+
}))
|
|
815
1682
|
: null))
|
|
816
1683
|
} else {
|
|
817
1684
|
const remaining = Math.max(0, code.expiresAt - state.now)
|
|
818
1685
|
const percent = Math.max(0, Math.min(100, (remaining / (code.ttlMs || CODE_TTL_MS)) * 100))
|
|
819
1686
|
main.push(React.createElement('div', { className: 'dsh-mobile-link-count', key: 'count' },
|
|
820
1687
|
React.createElement('div', { className: 'dsh-mobile-link-bar' }, React.createElement('i', { style: { width: `${percent.toFixed(1)}%` } })),
|
|
821
|
-
React.createElement('span', null,
|
|
1688
|
+
React.createElement('span', null, T('有效期至 ${hasta}(剩余 ${resto})', { hasta: clockText(code.expiresAt), resto: remainingText(remaining) }))))
|
|
822
1689
|
}
|
|
823
1690
|
} else if (state.codeError) {
|
|
824
1691
|
main.push(React.createElement('div', { className: 'dsh-mobile-link-note dsh-mobile-link-error', key: 'err' }, state.codeError))
|
|
825
1692
|
} else if (connecting) {
|
|
826
1693
|
main.push(React.createElement('div', { className: 'dsh-mobile-link-note', key: 'connecting' },
|
|
827
|
-
state.enrolling ? '正在登记…' : '正在连接…'))
|
|
1694
|
+
state.enrolling ? T('正在登记…') : T('正在连接…')))
|
|
828
1695
|
} else {
|
|
829
1696
|
main.push(React.createElement('div', { className: 'dsh-mobile-link-note', key: 'loading' },
|
|
830
|
-
state.busy ? '正在获取…' : '暂无配对码。'))
|
|
1697
|
+
state.busy ? T('正在获取…') : T('暂无配对码。')))
|
|
831
1698
|
}
|
|
832
1699
|
const buttons = connecting ? [] : [React.createElement(ui.Button, {
|
|
833
1700
|
key: 'swap', variant: 'primary', type: 'button', disabled: Boolean(state.busy),
|
|
834
1701
|
onClick: () => void controller.refreshCode({ regenerate: Boolean(state.code) }),
|
|
835
|
-
}, state.busy ? '正在获取…'
|
|
836
|
-
: (state.codeError ? '重试' : (state.code ? '重新生成' : '获取配对码')))]
|
|
1702
|
+
}, state.busy ? T('正在获取…')
|
|
1703
|
+
: (state.codeError ? T('重试') : (state.code ? T('重新生成') : T('获取配对码'))))]
|
|
837
1704
|
if (code) {
|
|
838
1705
|
buttons.push(React.createElement(ui.Button, {
|
|
839
1706
|
key: 'copy', variant: 'outline', type: 'button', onClick: () => controller.copy(),
|
|
840
|
-
}, state.copied ? '已复制' : '复制配对码'))
|
|
1707
|
+
}, state.copied ? T('已复制') : T('复制配对码')))
|
|
841
1708
|
}
|
|
842
1709
|
if (buttons.length > 0) {
|
|
843
1710
|
main.push(React.createElement('div', { className: 'dsh-mobile-link-actions', key: 'actions' }, buttons))
|
|
@@ -845,7 +1712,7 @@ function codeSection(React, controller, ui, state) {
|
|
|
845
1712
|
const children = []
|
|
846
1713
|
if (code) {
|
|
847
1714
|
children.push(React.createElement('img', {
|
|
848
|
-
key: 'qr', className: 'dsh-mobile-link-qr', alt: '配对二维码',
|
|
1715
|
+
key: 'qr', className: 'dsh-mobile-link-qr', alt: T('配对二维码'),
|
|
849
1716
|
src: `data:image/svg+xml;charset=utf-8,${encodeURIComponent(String(code.svg))}`,
|
|
850
1717
|
}))
|
|
851
1718
|
}
|
|
@@ -858,22 +1725,22 @@ function deviceRow(React, ui, device, online, controller) {
|
|
|
858
1725
|
React.createElement(ui.StateDot, { state: online ? 'done' : 'idle' }),
|
|
859
1726
|
React.createElement('div', { style: { minWidth: 0 } },
|
|
860
1727
|
React.createElement('div', { className: 'dsh-mobile-link-device-line' },
|
|
861
|
-
React.createElement('div', { className: 'dsh-mobile-link-device-name' }, device.name ?? '未命名设备'),
|
|
1728
|
+
React.createElement('div', { className: 'dsh-mobile-link-device-name' }, device.name ?? T('未命名设备')),
|
|
862
1729
|
React.createElement(ui.Tag, { tone: online ? 'success' : 'quiet', className: 'dsh-mobile-link-tag' },
|
|
863
|
-
online ? '在线' : '离线')),
|
|
1730
|
+
online ? T('在线') : T('离线'))),
|
|
864
1731
|
meta ? React.createElement('div', { className: 'dsh-mobile-link-device-meta' }, meta) : null),
|
|
865
1732
|
React.createElement('button', {
|
|
866
1733
|
type: 'button', className: 'dsh-mobile-link-revoke',
|
|
867
1734
|
onClick: () => controller.requestRevoke(device),
|
|
868
|
-
}, '撤销'))
|
|
1735
|
+
}, T('撤销')))
|
|
869
1736
|
}
|
|
870
1737
|
function devicesSection(React, ui, state, controller) {
|
|
871
1738
|
const devices = pairedRows(state)
|
|
872
1739
|
const rows = devices.length === 0
|
|
873
|
-
? [React.createElement('div', { className: 'dsh-mobile-link-note', key: 'none' }, '暂无已配对设备。')]
|
|
1740
|
+
? [React.createElement('div', { className: 'dsh-mobile-link-note', key: 'none' }, T('暂无已配对设备。'))]
|
|
874
1741
|
: devices.map((device) => deviceRow(React, ui, device, device.online, controller))
|
|
875
1742
|
return React.createElement('div', { className: 'dsh-mobile-link-section' },
|
|
876
|
-
React.createElement('div', { className: 'dsh-mobile-link-section-title' }, '已配对设备'),
|
|
1743
|
+
React.createElement('div', { className: 'dsh-mobile-link-section-title' }, T('已配对设备')),
|
|
877
1744
|
React.createElement('div', { className: 'dsh-mobile-link-devices' }, rows),
|
|
878
1745
|
state.pairedError
|
|
879
1746
|
? React.createElement('div', { className: 'dsh-mobile-link-note dsh-mobile-link-error' },
|
|
@@ -884,7 +1751,7 @@ function devicesSection(React, ui, state, controller) {
|
|
|
884
1751
|
function errorSection(React, controller, ui, status, open) {
|
|
885
1752
|
if (!status?.lastError) return null
|
|
886
1753
|
return React.createElement(ui.DisclosureRow, {
|
|
887
|
-
title: '上次错误', open: Boolean(open), expandable: true,
|
|
1754
|
+
title: T('上次错误'), open: Boolean(open), expandable: true,
|
|
888
1755
|
onToggle: () => controller.toggleError(),
|
|
889
1756
|
}, React.createElement('div', { className: 'dsh-mobile-link-mono dsh-mobile-link-error' }, status.lastError))
|
|
890
1757
|
}
|
|
@@ -892,62 +1759,62 @@ function errorSection(React, controller, ui, status, open) {
|
|
|
892
1759
|
function revokeOverlay(React, ui, state, controller) {
|
|
893
1760
|
const target = state.revokeTarget
|
|
894
1761
|
if (!target) return null
|
|
895
|
-
const name = target.name ?? '这台设备'
|
|
1762
|
+
const name = target.name ?? T('这台设备')
|
|
896
1763
|
return React.createElement('div', { className: 'dsh-mobile-link-overlay', key: 'revoke-overlay' },
|
|
897
1764
|
React.createElement('div', {
|
|
898
1765
|
className: 'dsh-mobile-link-alert', role: 'dialog', 'aria-modal': 'true',
|
|
899
|
-
'aria-label': '撤销这台设备',
|
|
1766
|
+
'aria-label': T('撤销这台设备'),
|
|
900
1767
|
},
|
|
901
|
-
React.createElement('div', { className: 'dsh-mobile-link-alert-title' }, '撤销这台设备?'),
|
|
1768
|
+
React.createElement('div', { className: 'dsh-mobile-link-alert-title' }, T('撤销这台设备?')),
|
|
902
1769
|
React.createElement('div', { className: 'dsh-mobile-link-note' },
|
|
903
|
-
|
|
1770
|
+
T('“${name}”将无法继续连接。需要重新扫码才能再次配对。', { name })),
|
|
904
1771
|
state.revokeError ? React.createElement('div', { className: 'dsh-mobile-link-error-inline', role: 'alert' },
|
|
905
1772
|
state.revokeError.message) : null,
|
|
906
1773
|
React.createElement('div', { className: 'dsh-mobile-link-alert-actions' },
|
|
907
1774
|
React.createElement(ui.Button, {
|
|
908
1775
|
variant: 'outline', type: 'button', disabled: Boolean(state.revoking),
|
|
909
1776
|
onClick: () => controller.cancelRevoke(),
|
|
910
|
-
}, '取消'),
|
|
1777
|
+
}, T('取消')),
|
|
911
1778
|
React.createElement('button', {
|
|
912
1779
|
type: 'button', className: 'dsh-mobile-link-danger', disabled: Boolean(state.revoking),
|
|
913
1780
|
onClick: () => controller.confirmRevoke(),
|
|
914
|
-
}, state.revoking ? '撤销中…' : '撤销设备'))))
|
|
1781
|
+
}, state.revoking ? T('撤销中…') : T('撤销设备')))))
|
|
915
1782
|
}
|
|
916
1783
|
/** iOS 安装指引:只给 TestFlight 公开链接。 */
|
|
917
1784
|
function guideOverlay(React, ui, controller) {
|
|
918
1785
|
return React.createElement('div', { className: 'dsh-mobile-link-overlay', key: 'guide-overlay' },
|
|
919
1786
|
React.createElement('div', {
|
|
920
1787
|
className: 'dsh-mobile-link-guide', role: 'dialog', 'aria-modal': 'true',
|
|
921
|
-
'aria-label': '安装 DSH Mobile',
|
|
1788
|
+
'aria-label': T('安装 DSH Mobile'),
|
|
922
1789
|
},
|
|
923
|
-
React.createElement('div', { className: 'dsh-mobile-link-guide-title' }, '安装 DSH Mobile'),
|
|
924
|
-
React.createElement('div', { className: 'dsh-mobile-link-note' }, '在 iPhone 或 iPad 上通过 TestFlight 安装。'),
|
|
1790
|
+
React.createElement('div', { className: 'dsh-mobile-link-guide-title' }, T('安装 DSH Mobile')),
|
|
1791
|
+
React.createElement('div', { className: 'dsh-mobile-link-note' }, T('在 iPhone 或 iPad 上通过 TestFlight 安装。')),
|
|
925
1792
|
React.createElement('div', { className: 'dsh-mobile-link-step' },
|
|
926
1793
|
React.createElement('span', { className: 'dsh-mobile-link-step-n' }, '1'),
|
|
927
|
-
React.createElement('span', { className: 'dsh-mobile-link-step-t' }, '从 App Store 安装 TestFlight。')),
|
|
1794
|
+
React.createElement('span', { className: 'dsh-mobile-link-step-t' }, T('从 App Store 安装 TestFlight。'))),
|
|
928
1795
|
React.createElement('div', { className: 'dsh-mobile-link-step' },
|
|
929
1796
|
React.createElement('span', { className: 'dsh-mobile-link-step-n' }, '2'),
|
|
930
|
-
React.createElement('span', { className: 'dsh-mobile-link-step-t' }, '打开 TestFlight 邀请链接,安装 DSH Mobile。')),
|
|
1797
|
+
React.createElement('span', { className: 'dsh-mobile-link-step-t' }, T('打开 TestFlight 邀请链接,安装 DSH Mobile。'))),
|
|
931
1798
|
React.createElement('div', { className: 'dsh-mobile-link-step' },
|
|
932
1799
|
React.createElement('span', { className: 'dsh-mobile-link-step-n' }, '3'),
|
|
933
|
-
React.createElement('span', { className: 'dsh-mobile-link-step-t' }, '打开 DSH Mobile,扫描电脑上的配对二维码。')),
|
|
1800
|
+
React.createElement('span', { className: 'dsh-mobile-link-step-t' }, T('打开 DSH Mobile,扫描电脑上的配对二维码。'))),
|
|
934
1801
|
React.createElement('a', {
|
|
935
1802
|
className: 'dsh-mobile-link-testflight', href: TESTFLIGHT_URL, target: '_blank', rel: 'noreferrer',
|
|
936
1803
|
}, TESTFLIGHT_URL),
|
|
937
|
-
React.createElement('div', { className: 'dsh-mobile-link-note' }, '需要 iOS 17、iPadOS 17 或更高版本。'),
|
|
1804
|
+
React.createElement('div', { className: 'dsh-mobile-link-note' }, T('需要 iOS 17、iPadOS 17 或更高版本。')),
|
|
938
1805
|
React.createElement(ui.Button, {
|
|
939
1806
|
variant: 'primary', type: 'button', onClick: () => controller.closeGuide(),
|
|
940
|
-
}, '完成')))
|
|
1807
|
+
}, T('完成'))))
|
|
941
1808
|
}
|
|
942
1809
|
function panelBody(React, controller, ui, state) {
|
|
943
1810
|
const children = [statusSection(React, ui, state)]
|
|
944
1811
|
if (!state.status) {
|
|
945
1812
|
children.push(React.createElement('div', { className: 'dsh-mobile-link-note', key: 'loading' },
|
|
946
|
-
state.statusError ? '无法读取连接状态。' : '正在读取状态…'))
|
|
1813
|
+
state.statusError ? T('无法读取连接状态。') : T('正在读取状态…')))
|
|
947
1814
|
if (state.statusError) {
|
|
948
1815
|
children.push(React.createElement(ui.Button, {
|
|
949
1816
|
key: 'retry', variant: 'outline', type: 'button', onClick: () => controller.reloadStatus(),
|
|
950
|
-
}, '重试'))
|
|
1817
|
+
}, T('重试')))
|
|
951
1818
|
}
|
|
952
1819
|
return React.createElement('div', { className: 'dsh-mobile-link-body' }, children)
|
|
953
1820
|
}
|
|
@@ -973,8 +1840,8 @@ function makePanel(React, controller, ui) {
|
|
|
973
1840
|
return React.createElement(ui.Modal, {
|
|
974
1841
|
open: state.open,
|
|
975
1842
|
onClose: () => controller.close(),
|
|
976
|
-
title:
|
|
977
|
-
closeLabel: '关闭',
|
|
1843
|
+
title: panelLabel(),
|
|
1844
|
+
closeLabel: T('关闭'),
|
|
978
1845
|
className: 'dsh-mobile-link-dialog',
|
|
979
1846
|
contentClassName: 'dsh-mobile-link-content',
|
|
980
1847
|
}, React.createElement('div', { className: 'dsh-mobile-link-shell' },
|
|
@@ -1002,22 +1869,32 @@ window.__ModuleLoader__.load({
|
|
|
1002
1869
|
if (!ui?.Modal || !ui?.Button || !ui?.StateDot) {
|
|
1003
1870
|
// 官方原子组件是平台 seed 字(宿主启动时就冻结在模块表里),正常不会缺;缺了说明
|
|
1004
1871
|
// 宿主形态不对,此时宁可不注册,也不摆一个与官方视觉无关的入口。
|
|
1005
|
-
report(ctx, '官方原子组件不可用,移动端连接入口未注册')
|
|
1872
|
+
report(ctx, T('官方原子组件不可用,移动端连接入口未注册'))
|
|
1006
1873
|
return
|
|
1007
1874
|
}
|
|
1875
|
+
LOCALE = resolveLocale(ctx)
|
|
1008
1876
|
const controller = createController()
|
|
1877
|
+
// 宿主切换语言时不重载面板:改写全局语言并推动一次重渲染,入口与小窗一起换。
|
|
1878
|
+
// 宿主切换语言时按同一套优先级重算,再推一次重渲染。
|
|
1879
|
+
const applyLocale = () => {
|
|
1880
|
+
LOCALE = resolveLocale(ctx)
|
|
1881
|
+
controller.store.set({ locale: LOCALE })
|
|
1882
|
+
}
|
|
1883
|
+
ctx.on?.('locale/change', applyLocale)
|
|
1884
|
+
const stopWatching = watchDocumentLanguage(applyLocale)
|
|
1885
|
+
if (stopWatching) ctx.effect(() => stopWatching, 'mobile-link: locale watcher')
|
|
1009
1886
|
try {
|
|
1010
1887
|
ctx.effect(() => controller.start(), 'mobile-link: panel state')
|
|
1011
1888
|
} catch (error) {
|
|
1012
|
-
report(ctx,
|
|
1889
|
+
report(ctx, T('面板状态层没起来:${error}', { error: describe(error) }))
|
|
1013
1890
|
}
|
|
1014
1891
|
try {
|
|
1015
1892
|
ctx.slots.inject(SLOT_ENTRY, () => ctx.slots.register(
|
|
1016
|
-
{ name: SLOT_ENTRY, id: ENTRY_ID, order: 20, label: () =>
|
|
1893
|
+
{ name: SLOT_ENTRY, id: ENTRY_ID, order: 20, label: () => panelLabel() },
|
|
1017
1894
|
makeEntry(React, controller, ui),
|
|
1018
1895
|
))
|
|
1019
1896
|
} catch (error) {
|
|
1020
|
-
report(ctx,
|
|
1897
|
+
report(ctx, T('侧栏入口没注册上:${error}', { error: describe(error) }))
|
|
1021
1898
|
}
|
|
1022
1899
|
try {
|
|
1023
1900
|
ctx.slots.inject(SLOT_OVERLAY, () => ctx.slots.register(
|
|
@@ -1025,7 +1902,7 @@ window.__ModuleLoader__.load({
|
|
|
1025
1902
|
makePanel(React, controller, ui),
|
|
1026
1903
|
))
|
|
1027
1904
|
} catch (error) {
|
|
1028
|
-
report(ctx,
|
|
1905
|
+
report(ctx, T('小窗没注册上:${error}', { error: describe(error) }))
|
|
1029
1906
|
}
|
|
1030
1907
|
}
|
|
1031
1908
|
|