feat: add private relay for end-to-end-encrypted remote access (#2087)
Adds OpenChamber Relay — an opt-in way to reach an instance from a phone, browser, or another desktop from anywhere, with no open inbound ports, no tunnel, and no shared LAN. The instance dials outbound to a relay; all app traffic (HTTP, the event stream, terminal, dictation) is multiplexed and encrypted through a single connection per client, so the relay only ever forwards opaque ciphertext. Transport - End-to-end-encrypted channel over WebCrypto (ECDH P-256 -> HKDF -> AES-256-GCM) with a capability-negotiated handshake and a small HTTP/SSE/WebSocket multiplexing protocol. A byte-compatible JS host mirror is cross-checked by tests. - Host: outbound connection manager, per-client tunnel dispatcher to the local server over loopback, reuse of the existing instance identity key, and management routes. Disabled by default; explicit opt-in. - Client: plugs into the existing runtime layer (runtime-fetch/-url/-switch/ -auth, event pipeline, terminal, dictation) so features work over the relay unchanged; direct-URL and Electron realtime-proxy paths are untouched. Pairing & UX - Relay section in Settings -> Remote Instances (live status, QR/link pairing, revocation via the existing client-token list) and the mobile connect flow. - Frame batching and idle-gated keepalive keep tunnel message volume low without affecting streaming smoothness. Security - The tunnel is transport only; the server authenticates every tunneled request exactly as for a direct remote client. fragments only. The relay stores no keys, tokens, or payloads. Operability - The endpoint can be pinned to a self-hosted rel paired clients inherit it from the offer automatically. - Relay module DOCUMENTATION.md and a relay-trans invariants that future WebSocket/streaming changes must follow. The relay transport is complete and tested; the UI for enabling and pairing is gated behind openchamber_relay_gate and stays
This commit is contained in:
committed by
GitHub
parent
42e470cefa
commit
859b4529da
@@ -287,6 +287,37 @@ export const settingsDict = {
|
||||
'settings.remoteInstances.clientAuth.state.thisDevice': 'This device',
|
||||
'settings.remoteInstances.clientAuth.lastUsed': 'Last used {date}',
|
||||
'settings.remoteInstances.clientAuth.neverUsed': 'Never used',
|
||||
'settings.remoteInstances.relay.title': 'OpenChamber Relay',
|
||||
'settings.remoteInstances.relay.description': 'Let your other devices connect from anywhere without opening ports. Traffic is end-to-end encrypted — the relay cannot read it.',
|
||||
'settings.remoteInstances.relay.enableHint': 'Nothing is shared until you enable the relay on this server.',
|
||||
'settings.remoteInstances.relay.actions.enable': 'Enable Relay',
|
||||
'settings.remoteInstances.relay.actions.disable': 'Disable',
|
||||
'settings.remoteInstances.relay.confirm.disable': 'Disable the relay? Devices connected through it will be disconnected immediately.',
|
||||
'settings.remoteInstances.relay.state.loading': 'Checking relay status...',
|
||||
'settings.remoteInstances.relay.state.disabled': 'Disabled',
|
||||
'settings.remoteInstances.relay.state.connecting': 'Connecting',
|
||||
'settings.remoteInstances.relay.state.connected': 'Connected',
|
||||
'settings.remoteInstances.relay.state.reconnecting': 'Reconnecting',
|
||||
'settings.remoteInstances.relay.state.error': 'Error',
|
||||
'settings.remoteInstances.relay.status.clientsOne': '{count} device connected',
|
||||
'settings.remoteInstances.relay.status.clientsMany': '{count} devices connected',
|
||||
'settings.remoteInstances.relay.pair.title': 'Pair a device',
|
||||
'settings.remoteInstances.relay.pair.labelPlaceholder': 'Device name (optional)',
|
||||
'settings.remoteInstances.relay.pair.includeToken': 'Include access token (one-scan pairing)',
|
||||
'settings.remoteInstances.relay.pair.noTokenHint': 'Without a token, the device signs in with this server’s UI password after connecting.',
|
||||
'settings.remoteInstances.relay.pair.generate': 'Create pairing link',
|
||||
'settings.remoteInstances.relay.pair.requiresConnected': 'Pairing becomes available once the relay is connected.',
|
||||
'settings.remoteInstances.relay.pair.linkLabel': 'Pairing link',
|
||||
'settings.remoteInstances.relay.pair.warning': 'This link grants access to this server. Do not share it.',
|
||||
'settings.remoteInstances.relay.pair.qrAlt': 'Relay pairing QR code',
|
||||
'settings.remoteInstances.relay.pair.showQr': 'Show QR code',
|
||||
'settings.remoteInstances.relay.pair.qrDialogTitle': 'Scan to pair',
|
||||
'settings.remoteInstances.relay.pair.qrDialogDescription': 'Scan this QR code with the OpenChamber app on your other device.',
|
||||
'settings.remoteInstances.relay.pair.manageHint': 'Manage or revoke paired devices in the “Connect to this server” list above.',
|
||||
'settings.remoteInstances.relay.toast.enableFailed': 'Failed to enable relay',
|
||||
'settings.remoteInstances.relay.toast.disableFailed': 'Failed to disable relay',
|
||||
'settings.remoteInstances.relay.toast.offerFailed': 'Failed to create pairing link',
|
||||
'settings.remoteInstances.relay.toast.linkCopied': 'Pairing link copied',
|
||||
'settings.remoteInstances.sidebar.phase.ready': 'Ready',
|
||||
'settings.remoteInstances.sidebar.phase.error': 'Error',
|
||||
'settings.remoteInstances.sidebar.phase.reconnect': 'Reconnect',
|
||||
|
||||
@@ -58,6 +58,7 @@ export const dict = {
|
||||
'mobile.connect.scan.unsupported': 'QR scanning is only available in the installed mobile app.',
|
||||
'mobile.connect.saved.title': 'Saved connections',
|
||||
'mobile.connect.saved.empty': 'No saved connections yet.',
|
||||
'mobile.connect.relay.badge': 'via OpenChamber Relay',
|
||||
'mobile.connect.error.urlRequired': 'Enter a server URL.',
|
||||
'mobile.connect.error.invalidUrl': 'That server URL is not valid.',
|
||||
'mobile.connect.error.unreachable': 'Could not reach that OpenChamber server.',
|
||||
|
||||
@@ -254,6 +254,37 @@ export const settingsDict = {
|
||||
"settings.remoteInstances.clientAuth.state.thisDevice": "Este dispositivo",
|
||||
"settings.remoteInstances.clientAuth.lastUsed": "Último uso {date}",
|
||||
"settings.remoteInstances.clientAuth.neverUsed": "Nunca usado",
|
||||
"settings.remoteInstances.relay.title": "OpenChamber Relay",
|
||||
"settings.remoteInstances.relay.description": "Permite que tus otros dispositivos se conecten desde cualquier lugar sin abrir puertos. El tráfico está cifrado de extremo a extremo: el relay no puede leerlo.",
|
||||
"settings.remoteInstances.relay.enableHint": "No se comparte nada hasta que actives el relay en este servidor.",
|
||||
"settings.remoteInstances.relay.actions.enable": "Activar Relay",
|
||||
"settings.remoteInstances.relay.actions.disable": "Desactivar",
|
||||
"settings.remoteInstances.relay.confirm.disable": "¿Desactivar el relay? Los dispositivos conectados a través de él se desconectarán de inmediato.",
|
||||
"settings.remoteInstances.relay.state.loading": "Comprobando el estado del relay...",
|
||||
"settings.remoteInstances.relay.state.disabled": "Desactivado",
|
||||
"settings.remoteInstances.relay.state.connecting": "Conectando",
|
||||
"settings.remoteInstances.relay.state.connected": "Conectado",
|
||||
"settings.remoteInstances.relay.state.reconnecting": "Reconectando",
|
||||
"settings.remoteInstances.relay.state.error": "Error",
|
||||
"settings.remoteInstances.relay.status.clientsOne": "{count} dispositivo conectado",
|
||||
"settings.remoteInstances.relay.status.clientsMany": "{count} dispositivos conectados",
|
||||
"settings.remoteInstances.relay.pair.title": "Emparejar un dispositivo",
|
||||
"settings.remoteInstances.relay.pair.labelPlaceholder": "Nombre del dispositivo (opcional)",
|
||||
"settings.remoteInstances.relay.pair.includeToken": "Incluir token de acceso (emparejamiento con un solo escaneo)",
|
||||
"settings.remoteInstances.relay.pair.noTokenHint": "Sin token, el dispositivo inicia sesión con la contraseña de la interfaz de este servidor tras conectarse.",
|
||||
"settings.remoteInstances.relay.pair.generate": "Crear enlace de emparejamiento",
|
||||
"settings.remoteInstances.relay.pair.requiresConnected": "El emparejamiento estará disponible cuando el relay esté conectado.",
|
||||
"settings.remoteInstances.relay.pair.linkLabel": "Enlace de emparejamiento",
|
||||
"settings.remoteInstances.relay.pair.warning": "Este enlace concede acceso a este servidor. No lo compartas.",
|
||||
"settings.remoteInstances.relay.pair.qrAlt": "Código QR de emparejamiento del relay",
|
||||
"settings.remoteInstances.relay.pair.showQr": "Mostrar código QR",
|
||||
"settings.remoteInstances.relay.pair.qrDialogTitle": "Escanear para emparejar",
|
||||
"settings.remoteInstances.relay.pair.qrDialogDescription": "Escanea este código QR con la app de OpenChamber en tu otro dispositivo.",
|
||||
"settings.remoteInstances.relay.pair.manageHint": "Gestiona o revoca los dispositivos emparejados en la lista «Conexión a este servidor» de arriba.",
|
||||
"settings.remoteInstances.relay.toast.enableFailed": "No se pudo activar el relay",
|
||||
"settings.remoteInstances.relay.toast.disableFailed": "No se pudo desactivar el relay",
|
||||
"settings.remoteInstances.relay.toast.offerFailed": "No se pudo crear el enlace de emparejamiento",
|
||||
"settings.remoteInstances.relay.toast.linkCopied": "Enlace de emparejamiento copiado",
|
||||
"settings.remoteInstances.sidebar.phase.ready": "Listo",
|
||||
"settings.remoteInstances.sidebar.phase.error": "Error",
|
||||
"settings.remoteInstances.sidebar.phase.reconnect": "Reconectar",
|
||||
|
||||
@@ -59,6 +59,7 @@ export const dict: Record<I18nKey, string> = {
|
||||
"mobile.connect.scan.unsupported": "El escaneo de QR solo está disponible en la app móvil instalada.",
|
||||
"mobile.connect.saved.title": "Conexiones guardadas",
|
||||
"mobile.connect.saved.empty": "Aún no hay conexiones guardadas.",
|
||||
"mobile.connect.relay.badge": "a través de OpenChamber Relay",
|
||||
"mobile.connect.error.urlRequired": "Introduce una URL de servidor.",
|
||||
"mobile.connect.error.invalidUrl": "Esa URL de servidor no es válida.",
|
||||
"mobile.connect.error.unreachable": "No se pudo conectar con ese servidor de OpenChamber.",
|
||||
|
||||
@@ -1789,6 +1789,37 @@ export const settingsDict = {
|
||||
'settings.remoteInstances.clientAuth.state.thisDevice': 'Cet appareil',
|
||||
'settings.remoteInstances.clientAuth.lastUsed': 'Dernière utilisation le {date}',
|
||||
'settings.remoteInstances.clientAuth.neverUsed': 'Jamais utilisé',
|
||||
'settings.remoteInstances.relay.title': 'OpenChamber Relay',
|
||||
'settings.remoteInstances.relay.description': 'Permettez à vos autres appareils de se connecter depuis n’importe où sans ouvrir de ports. Le trafic est chiffré de bout en bout — le relais ne peut pas le lire.',
|
||||
'settings.remoteInstances.relay.enableHint': 'Rien n’est partagé tant que vous n’activez pas le relais sur ce serveur.',
|
||||
'settings.remoteInstances.relay.actions.enable': 'Activer le relais',
|
||||
'settings.remoteInstances.relay.actions.disable': 'Désactiver',
|
||||
'settings.remoteInstances.relay.confirm.disable': 'Désactiver le relais ? Les appareils connectés via celui-ci seront déconnectés immédiatement.',
|
||||
'settings.remoteInstances.relay.state.loading': 'Vérification de l’état du relais...',
|
||||
'settings.remoteInstances.relay.state.disabled': 'Désactivé',
|
||||
'settings.remoteInstances.relay.state.connecting': 'Connexion',
|
||||
'settings.remoteInstances.relay.state.connected': 'Connecté',
|
||||
'settings.remoteInstances.relay.state.reconnecting': 'Reconnexion',
|
||||
'settings.remoteInstances.relay.state.error': 'Erreur',
|
||||
'settings.remoteInstances.relay.status.clientsOne': '{count} appareil connecté',
|
||||
'settings.remoteInstances.relay.status.clientsMany': '{count} appareils connectés',
|
||||
'settings.remoteInstances.relay.pair.title': 'Associer un appareil',
|
||||
'settings.remoteInstances.relay.pair.labelPlaceholder': 'Nom de l’appareil (facultatif)',
|
||||
'settings.remoteInstances.relay.pair.includeToken': 'Inclure le jeton d’accès (association en un seul scan)',
|
||||
'settings.remoteInstances.relay.pair.noTokenHint': 'Sans jeton, l’appareil se connecte avec le mot de passe de l’interface de ce serveur.',
|
||||
'settings.remoteInstances.relay.pair.generate': 'Créer un lien d’association',
|
||||
'settings.remoteInstances.relay.pair.requiresConnected': 'L’association devient disponible une fois le relais connecté.',
|
||||
'settings.remoteInstances.relay.pair.linkLabel': 'Lien d’association',
|
||||
'settings.remoteInstances.relay.pair.warning': 'Ce lien donne accès à ce serveur. Ne le partagez pas.',
|
||||
'settings.remoteInstances.relay.pair.qrAlt': 'Code QR d’association du relais',
|
||||
'settings.remoteInstances.relay.pair.showQr': 'Afficher le code QR',
|
||||
'settings.remoteInstances.relay.pair.qrDialogTitle': 'Scanner pour associer',
|
||||
'settings.remoteInstances.relay.pair.qrDialogDescription': 'Scannez ce code QR avec l’application OpenChamber sur votre autre appareil.',
|
||||
'settings.remoteInstances.relay.pair.manageHint': 'Gérez ou révoquez les appareils associés dans la liste « Connexion à ce serveur » ci-dessus.',
|
||||
'settings.remoteInstances.relay.toast.enableFailed': 'Échec de l’activation du relais',
|
||||
'settings.remoteInstances.relay.toast.disableFailed': 'Échec de la désactivation du relais',
|
||||
'settings.remoteInstances.relay.toast.offerFailed': 'Échec de la création du lien d’association',
|
||||
'settings.remoteInstances.relay.toast.linkCopied': 'Lien d’association copié',
|
||||
'settings.openchamber.about.field.openCodeVersion': 'Version d’OpenCode',
|
||||
'settings.openchamber.about.state.unknown': 'inconnue',
|
||||
'settings.voice.page.field.ttsInputMode': 'Mode d’entrée TTS',
|
||||
|
||||
@@ -2503,6 +2503,7 @@ export const dict = {
|
||||
'mobile.connect.scan.unsupported': 'Le scan QR est disponible uniquement dans l\'app mobile installée.',
|
||||
'mobile.connect.saved.title': 'Connexions enregistrées',
|
||||
'mobile.connect.saved.empty': 'Aucune connexion enregistrée pour le moment.',
|
||||
'mobile.connect.relay.badge': 'via OpenChamber Relay',
|
||||
'mobile.connect.error.urlRequired': 'Saisissez une URL de serveur.',
|
||||
'mobile.connect.error.invalidUrl': 'Cette URL de serveur n\'est pas valide.',
|
||||
'mobile.connect.error.unreachable': 'Impossible de joindre ce serveur OpenChamber.',
|
||||
|
||||
@@ -287,6 +287,37 @@ export const settingsDict = {
|
||||
'settings.remoteInstances.clientAuth.state.thisDevice': 'このデバイス',
|
||||
'settings.remoteInstances.clientAuth.lastUsed': '最終使用 {date}',
|
||||
'settings.remoteInstances.clientAuth.neverUsed': '未使用',
|
||||
'settings.remoteInstances.relay.title': 'OpenChamber Relay',
|
||||
'settings.remoteInstances.relay.description': 'ポートを開放せずに、他のデバイスからどこからでも接続できます。通信はエンドツーエンドで暗号化され、リレーは内容を読めません。',
|
||||
'settings.remoteInstances.relay.enableHint': 'このサーバーでリレーを有効にするまで、何も共有されません。',
|
||||
'settings.remoteInstances.relay.actions.enable': 'リレーを有効にする',
|
||||
'settings.remoteInstances.relay.actions.disable': '無効にする',
|
||||
'settings.remoteInstances.relay.confirm.disable': 'リレーを無効にしますか?リレー経由で接続中のデバイスは即座に切断されます。',
|
||||
'settings.remoteInstances.relay.state.loading': 'リレーの状態を確認中...',
|
||||
'settings.remoteInstances.relay.state.disabled': '無効',
|
||||
'settings.remoteInstances.relay.state.connecting': '接続中',
|
||||
'settings.remoteInstances.relay.state.connected': '接続済み',
|
||||
'settings.remoteInstances.relay.state.reconnecting': '再接続中',
|
||||
'settings.remoteInstances.relay.state.error': 'エラー',
|
||||
'settings.remoteInstances.relay.status.clientsOne': '{count} 台のデバイスが接続中',
|
||||
'settings.remoteInstances.relay.status.clientsMany': '{count} 台のデバイスが接続中',
|
||||
'settings.remoteInstances.relay.pair.title': 'デバイスをペアリング',
|
||||
'settings.remoteInstances.relay.pair.labelPlaceholder': 'デバイス名(任意)',
|
||||
'settings.remoteInstances.relay.pair.includeToken': 'アクセストークンを含める(1回のスキャンでペアリング)',
|
||||
'settings.remoteInstances.relay.pair.noTokenHint': 'トークンなしの場合、デバイスは接続後にこのサーバーのUIパスワードでサインインします。',
|
||||
'settings.remoteInstances.relay.pair.generate': 'ペアリングリンクを作成',
|
||||
'settings.remoteInstances.relay.pair.requiresConnected': 'ペアリングはリレーの接続後に利用できます。',
|
||||
'settings.remoteInstances.relay.pair.linkLabel': 'ペアリングリンク',
|
||||
'settings.remoteInstances.relay.pair.warning': 'このリンクはこのサーバーへのアクセスを許可します。共有しないでください。',
|
||||
'settings.remoteInstances.relay.pair.qrAlt': 'リレーペアリング用QRコード',
|
||||
'settings.remoteInstances.relay.pair.showQr': 'QRコードを表示',
|
||||
'settings.remoteInstances.relay.pair.qrDialogTitle': 'スキャンしてペアリング',
|
||||
'settings.remoteInstances.relay.pair.qrDialogDescription': '他のデバイスのOpenChamberアプリでこのQRコードをスキャンします。',
|
||||
'settings.remoteInstances.relay.pair.manageHint': 'ペアリング済みデバイスの管理や取り消しは、上の「このサーバーへの接続」一覧で行えます。',
|
||||
'settings.remoteInstances.relay.toast.enableFailed': 'リレーを有効にできませんでした',
|
||||
'settings.remoteInstances.relay.toast.disableFailed': 'リレーを無効にできませんでした',
|
||||
'settings.remoteInstances.relay.toast.offerFailed': 'ペアリングリンクを作成できませんでした',
|
||||
'settings.remoteInstances.relay.toast.linkCopied': 'ペアリングリンクをコピーしました',
|
||||
'settings.remoteInstances.sidebar.phase.ready': '準備完了',
|
||||
'settings.remoteInstances.sidebar.phase.error': 'エラー',
|
||||
'settings.remoteInstances.sidebar.phase.reconnect': '再接続',
|
||||
|
||||
@@ -58,6 +58,7 @@ export const dict: Record<I18nKey, string> = {
|
||||
'mobile.connect.cancelPassword': '別のサーバーを使用',
|
||||
'mobile.connect.saved.title': '保存された接続',
|
||||
'mobile.connect.saved.empty': '保存された接続はまだありません。',
|
||||
'mobile.connect.relay.badge': 'OpenChamber Relay 経由',
|
||||
'mobile.connect.error.urlRequired': 'サーバー URL を入力してください。',
|
||||
'mobile.connect.error.invalidUrl': 'そのサーバー URL は無効です。',
|
||||
'mobile.connect.error.unreachable': 'その OpenChamber サーバーに接続できませんでした。',
|
||||
|
||||
@@ -254,6 +254,37 @@ export const settingsDict = {
|
||||
'settings.remoteInstances.clientAuth.state.thisDevice': '이 기기',
|
||||
'settings.remoteInstances.clientAuth.lastUsed': '마지막 사용 {date}',
|
||||
'settings.remoteInstances.clientAuth.neverUsed': '사용한 적 없음',
|
||||
'settings.remoteInstances.relay.title': 'OpenChamber Relay',
|
||||
'settings.remoteInstances.relay.description': '포트를 열지 않고도 다른 기기가 어디서든 연결할 수 있습니다. 트래픽은 종단 간 암호화되어 릴레이는 내용을 읽을 수 없습니다.',
|
||||
'settings.remoteInstances.relay.enableHint': '이 서버에서 릴레이를 켜기 전까지는 아무것도 공유되지 않습니다.',
|
||||
'settings.remoteInstances.relay.actions.enable': '릴레이 켜기',
|
||||
'settings.remoteInstances.relay.actions.disable': '끄기',
|
||||
'settings.remoteInstances.relay.confirm.disable': '릴레이를 끄시겠습니까? 릴레이를 통해 연결된 기기는 즉시 연결이 끊어집니다.',
|
||||
'settings.remoteInstances.relay.state.loading': '릴레이 상태 확인 중...',
|
||||
'settings.remoteInstances.relay.state.disabled': '꺼짐',
|
||||
'settings.remoteInstances.relay.state.connecting': '연결 중',
|
||||
'settings.remoteInstances.relay.state.connected': '연결됨',
|
||||
'settings.remoteInstances.relay.state.reconnecting': '재연결 중',
|
||||
'settings.remoteInstances.relay.state.error': '오류',
|
||||
'settings.remoteInstances.relay.status.clientsOne': '기기 {count}대 연결됨',
|
||||
'settings.remoteInstances.relay.status.clientsMany': '기기 {count}대 연결됨',
|
||||
'settings.remoteInstances.relay.pair.title': '기기 페어링',
|
||||
'settings.remoteInstances.relay.pair.labelPlaceholder': '기기 이름 (선택 사항)',
|
||||
'settings.remoteInstances.relay.pair.includeToken': '액세스 토큰 포함 (한 번 스캔으로 페어링)',
|
||||
'settings.remoteInstances.relay.pair.noTokenHint': '토큰이 없으면 기기는 연결 후 이 서버의 UI 비밀번호로 로그인합니다.',
|
||||
'settings.remoteInstances.relay.pair.generate': '페어링 링크 만들기',
|
||||
'settings.remoteInstances.relay.pair.requiresConnected': '페어링은 릴레이가 연결된 후 사용할 수 있습니다.',
|
||||
'settings.remoteInstances.relay.pair.linkLabel': '페어링 링크',
|
||||
'settings.remoteInstances.relay.pair.warning': '이 링크는 이 서버에 대한 접근 권한을 부여합니다. 공유하지 마세요.',
|
||||
'settings.remoteInstances.relay.pair.qrAlt': '릴레이 페어링 QR 코드',
|
||||
'settings.remoteInstances.relay.pair.showQr': 'QR 코드 표시',
|
||||
'settings.remoteInstances.relay.pair.qrDialogTitle': '스캔하여 페어링',
|
||||
'settings.remoteInstances.relay.pair.qrDialogDescription': '다른 기기의 OpenChamber 앱으로 이 QR 코드를 스캔하세요.',
|
||||
'settings.remoteInstances.relay.pair.manageHint': '페어링된 기기는 위의 “이 서버에 연결” 목록에서 관리하거나 철회할 수 있습니다.',
|
||||
'settings.remoteInstances.relay.toast.enableFailed': '릴레이를 켤 수 없습니다',
|
||||
'settings.remoteInstances.relay.toast.disableFailed': '릴레이를 끔 수 없습니다',
|
||||
'settings.remoteInstances.relay.toast.offerFailed': '페어링 링크를 만들지 못했습니다',
|
||||
'settings.remoteInstances.relay.toast.linkCopied': '페어링 링크를 복사했습니다',
|
||||
'settings.remoteInstances.sidebar.phase.ready': '준비됨',
|
||||
'settings.remoteInstances.sidebar.phase.error': '오류',
|
||||
'settings.remoteInstances.sidebar.phase.reconnect': '재연결',
|
||||
|
||||
@@ -59,6 +59,7 @@ export const dict: Record<I18nKey, string> = {
|
||||
'mobile.connect.scan.unsupported': 'QR 스캔은 설치된 모바일 앱에서만 사용할 수 있습니다.',
|
||||
'mobile.connect.saved.title': '저장된 연결',
|
||||
'mobile.connect.saved.empty': '아직 저장된 연결이 없습니다.',
|
||||
'mobile.connect.relay.badge': 'OpenChamber Relay 경유',
|
||||
'mobile.connect.error.urlRequired': '서버 URL을 입력하세요.',
|
||||
'mobile.connect.error.invalidUrl': '유효하지 않은 서버 URL입니다.',
|
||||
'mobile.connect.error.unreachable': '해당 OpenChamber 서버에 연결할 수 없습니다.',
|
||||
|
||||
@@ -1477,6 +1477,37 @@ export const settingsDict = {
|
||||
'settings.remoteInstances.clientAuth.state.thisDevice': 'To urządzenie',
|
||||
'settings.remoteInstances.clientAuth.lastUsed': 'Ostatnio użyto {date}',
|
||||
'settings.remoteInstances.clientAuth.neverUsed': 'Nigdy nie użyto',
|
||||
'settings.remoteInstances.relay.title': 'OpenChamber Relay',
|
||||
'settings.remoteInstances.relay.description': 'Pozwól swoim innym urządzeniom łączyć się z dowolnego miejsca bez otwierania portów. Ruch jest szyfrowany od końca do końca — relay nie może go odczytać.',
|
||||
'settings.remoteInstances.relay.enableHint': 'Nic nie jest udostępniane, dopóki nie włączysz relay na tym serwerze.',
|
||||
'settings.remoteInstances.relay.actions.enable': 'Włącz Relay',
|
||||
'settings.remoteInstances.relay.actions.disable': 'Wyłącz',
|
||||
'settings.remoteInstances.relay.confirm.disable': 'Wyłączyć relay? Urządzenia połączone przez niego zostaną natychmiast rozłączone.',
|
||||
'settings.remoteInstances.relay.state.loading': 'Sprawdzanie stanu relay...',
|
||||
'settings.remoteInstances.relay.state.disabled': 'Wyłączony',
|
||||
'settings.remoteInstances.relay.state.connecting': 'Łączenie',
|
||||
'settings.remoteInstances.relay.state.connected': 'Połączono',
|
||||
'settings.remoteInstances.relay.state.reconnecting': 'Ponowne łączenie',
|
||||
'settings.remoteInstances.relay.state.error': 'Błąd',
|
||||
'settings.remoteInstances.relay.status.clientsOne': '{count} urządzenie połączone',
|
||||
'settings.remoteInstances.relay.status.clientsMany': 'Połączone urządzenia: {count}',
|
||||
'settings.remoteInstances.relay.pair.title': 'Sparuj urządzenie',
|
||||
'settings.remoteInstances.relay.pair.labelPlaceholder': 'Nazwa urządzenia (opcjonalnie)',
|
||||
'settings.remoteInstances.relay.pair.includeToken': 'Dołącz token dostępu (parowanie jednym skanem)',
|
||||
'settings.remoteInstances.relay.pair.noTokenHint': 'Bez tokenu urządzenie zaloguje się po połączeniu hasłem interfejsu tego serwera.',
|
||||
'settings.remoteInstances.relay.pair.generate': 'Utwórz link parowania',
|
||||
'settings.remoteInstances.relay.pair.requiresConnected': 'Parowanie będzie dostępne, gdy relay zostanie połączony.',
|
||||
'settings.remoteInstances.relay.pair.linkLabel': 'Link parowania',
|
||||
'settings.remoteInstances.relay.pair.warning': 'Ten link daje dostęp do tego serwera. Nie udostępniaj go.',
|
||||
'settings.remoteInstances.relay.pair.qrAlt': 'Kod QR parowania relay',
|
||||
'settings.remoteInstances.relay.pair.showQr': 'Pokaż kod QR',
|
||||
'settings.remoteInstances.relay.pair.qrDialogTitle': 'Zeskanuj, aby sparować',
|
||||
'settings.remoteInstances.relay.pair.qrDialogDescription': 'Zeskanuj ten kod QR aplikacją OpenChamber na drugim urządzeniu.',
|
||||
'settings.remoteInstances.relay.pair.manageHint': 'Zarządzaj sparowanymi urządzeniami lub odbieraj im dostęp na liście „Połączenie z tym serwerem” powyżej.',
|
||||
'settings.remoteInstances.relay.toast.enableFailed': 'Nie udało się włączyć relay',
|
||||
'settings.remoteInstances.relay.toast.disableFailed': 'Nie udało się wyłączyć relay',
|
||||
'settings.remoteInstances.relay.toast.offerFailed': 'Nie udało się utworzyć linku parowania',
|
||||
'settings.remoteInstances.relay.toast.linkCopied': 'Skopiowano link parowania',
|
||||
'settings.remoteInstances.sidebar.toast.retriedWithRandomPort': 'Ponowiono próbę z losowym lokalnym portem',
|
||||
'settings.remoteInstances.sidebar.toast.retryFailed': 'Nie udało się ponowić połączenia',
|
||||
'settings.remoteInstances.sidebar.total': 'Suma: {count}',
|
||||
|
||||
@@ -60,6 +60,7 @@ export const dict: Record<I18nKey, string> = {
|
||||
'mobile.connect.scan.unsupported': 'Skanowanie QR jest dostępne tylko w zainstalowanej aplikacji mobilnej.',
|
||||
'mobile.connect.saved.title': 'Zapisane połączenia',
|
||||
'mobile.connect.saved.empty': 'Brak zapisanych połączeń.',
|
||||
'mobile.connect.relay.badge': 'przez OpenChamber Relay',
|
||||
'mobile.connect.error.urlRequired': 'Podaj adres URL serwera.',
|
||||
'mobile.connect.error.invalidUrl': 'Ten adres URL serwera jest nieprawidłowy.',
|
||||
'mobile.connect.error.unreachable': 'Nie udało się połączyć z tym serwerem OpenChamber.',
|
||||
|
||||
@@ -254,6 +254,37 @@ export const settingsDict = {
|
||||
"settings.remoteInstances.clientAuth.state.thisDevice": "Este dispositivo",
|
||||
"settings.remoteInstances.clientAuth.lastUsed": "Último uso em {date}",
|
||||
"settings.remoteInstances.clientAuth.neverUsed": "Nunca usado",
|
||||
"settings.remoteInstances.relay.title": "OpenChamber Relay",
|
||||
"settings.remoteInstances.relay.description": "Permita que seus outros dispositivos se conectem de qualquer lugar sem abrir portas. O tráfego é criptografado de ponta a ponta — o relay não consegue lê-lo.",
|
||||
"settings.remoteInstances.relay.enableHint": "Nada é compartilhado até você ativar o relay neste servidor.",
|
||||
"settings.remoteInstances.relay.actions.enable": "Ativar Relay",
|
||||
"settings.remoteInstances.relay.actions.disable": "Desativar",
|
||||
"settings.remoteInstances.relay.confirm.disable": "Desativar o relay? Os dispositivos conectados por ele serão desconectados imediatamente.",
|
||||
"settings.remoteInstances.relay.state.loading": "Verificando o status do relay...",
|
||||
"settings.remoteInstances.relay.state.disabled": "Desativado",
|
||||
"settings.remoteInstances.relay.state.connecting": "Conectando",
|
||||
"settings.remoteInstances.relay.state.connected": "Conectado",
|
||||
"settings.remoteInstances.relay.state.reconnecting": "Reconectando",
|
||||
"settings.remoteInstances.relay.state.error": "Erro",
|
||||
"settings.remoteInstances.relay.status.clientsOne": "{count} dispositivo conectado",
|
||||
"settings.remoteInstances.relay.status.clientsMany": "{count} dispositivos conectados",
|
||||
"settings.remoteInstances.relay.pair.title": "Parear um dispositivo",
|
||||
"settings.remoteInstances.relay.pair.labelPlaceholder": "Nome do dispositivo (opcional)",
|
||||
"settings.remoteInstances.relay.pair.includeToken": "Incluir token de acesso (pareamento com um único escaneamento)",
|
||||
"settings.remoteInstances.relay.pair.noTokenHint": "Sem token, o dispositivo faz login com a senha da interface deste servidor após conectar.",
|
||||
"settings.remoteInstances.relay.pair.generate": "Criar link de pareamento",
|
||||
"settings.remoteInstances.relay.pair.requiresConnected": "O pareamento fica disponível quando o relay estiver conectado.",
|
||||
"settings.remoteInstances.relay.pair.linkLabel": "Link de pareamento",
|
||||
"settings.remoteInstances.relay.pair.warning": "Este link concede acesso a este servidor. Não o compartilhe.",
|
||||
"settings.remoteInstances.relay.pair.qrAlt": "Código QR de pareamento do relay",
|
||||
"settings.remoteInstances.relay.pair.showQr": "Mostrar código QR",
|
||||
"settings.remoteInstances.relay.pair.qrDialogTitle": "Escanear para parear",
|
||||
"settings.remoteInstances.relay.pair.qrDialogDescription": "Escaneie este código QR com o app OpenChamber no seu outro dispositivo.",
|
||||
"settings.remoteInstances.relay.pair.manageHint": "Gerencie ou revogue dispositivos pareados na lista “Conectar a este servidor” acima.",
|
||||
"settings.remoteInstances.relay.toast.enableFailed": "Falha ao ativar o relay",
|
||||
"settings.remoteInstances.relay.toast.disableFailed": "Falha ao desativar o relay",
|
||||
"settings.remoteInstances.relay.toast.offerFailed": "Falha ao criar o link de pareamento",
|
||||
"settings.remoteInstances.relay.toast.linkCopied": "Link de pareamento copiado",
|
||||
"settings.remoteInstances.sidebar.phase.ready": "Pronto",
|
||||
"settings.remoteInstances.sidebar.phase.error": "Erro",
|
||||
"settings.remoteInstances.sidebar.phase.reconnect": "Reconectar",
|
||||
|
||||
@@ -59,6 +59,7 @@ export const dict: Record<I18nKey, string> = {
|
||||
"mobile.connect.scan.unsupported": "A leitura de QR só está disponível no app móvel instalado.",
|
||||
"mobile.connect.saved.title": "Conexões salvas",
|
||||
"mobile.connect.saved.empty": "Nenhuma conexão salva ainda.",
|
||||
"mobile.connect.relay.badge": "via OpenChamber Relay",
|
||||
"mobile.connect.error.urlRequired": "Informe a URL de um servidor.",
|
||||
"mobile.connect.error.invalidUrl": "Essa URL de servidor não é válida.",
|
||||
"mobile.connect.error.unreachable": "Não foi possível acessar esse servidor OpenChamber.",
|
||||
|
||||
@@ -254,6 +254,37 @@ export const settingsDict = {
|
||||
"settings.remoteInstances.clientAuth.state.thisDevice": "Цей пристрій",
|
||||
"settings.remoteInstances.clientAuth.lastUsed": "Останнє використання {date}",
|
||||
"settings.remoteInstances.clientAuth.neverUsed": "Ще не використовувався",
|
||||
"settings.remoteInstances.relay.title": "OpenChamber Relay",
|
||||
"settings.remoteInstances.relay.description": "Дозволяє вашим іншим пристроям підключатися звідки завгодно без відкриття портів. Трафік шифрується наскрізно — релей не може його прочитати.",
|
||||
"settings.remoteInstances.relay.enableHint": "Нічого не передається, доки ви не увімкнете релей на цьому сервері.",
|
||||
"settings.remoteInstances.relay.actions.enable": "Увімкнути Relay",
|
||||
"settings.remoteInstances.relay.actions.disable": "Вимкнути",
|
||||
"settings.remoteInstances.relay.confirm.disable": "Вимкнути релей? Пристрої, підключені через нього, будуть негайно відключені.",
|
||||
"settings.remoteInstances.relay.state.loading": "Перевірка стану релею...",
|
||||
"settings.remoteInstances.relay.state.disabled": "Вимкнено",
|
||||
"settings.remoteInstances.relay.state.connecting": "Підключення",
|
||||
"settings.remoteInstances.relay.state.connected": "Підключено",
|
||||
"settings.remoteInstances.relay.state.reconnecting": "Повторне підключення",
|
||||
"settings.remoteInstances.relay.state.error": "Помилка",
|
||||
"settings.remoteInstances.relay.status.clientsOne": "Підключено пристроїв: {count}",
|
||||
"settings.remoteInstances.relay.status.clientsMany": "Підключено пристроїв: {count}",
|
||||
"settings.remoteInstances.relay.pair.title": "Спарувати пристрій",
|
||||
"settings.remoteInstances.relay.pair.labelPlaceholder": "Назва пристрою (необов'язково)",
|
||||
"settings.remoteInstances.relay.pair.includeToken": "Додати токен доступу (спарювання одним скануванням)",
|
||||
"settings.remoteInstances.relay.pair.noTokenHint": "Без токена пристрій після підключення входить за паролем інтерфейсу цього сервера.",
|
||||
"settings.remoteInstances.relay.pair.generate": "Створити посилання для спарювання",
|
||||
"settings.remoteInstances.relay.pair.requiresConnected": "Спарювання стане доступним після підключення релею.",
|
||||
"settings.remoteInstances.relay.pair.linkLabel": "Посилання для спарювання",
|
||||
"settings.remoteInstances.relay.pair.warning": "Це посилання надає доступ до цього сервера. Не діліться ним.",
|
||||
"settings.remoteInstances.relay.pair.qrAlt": "QR-код спарювання релею",
|
||||
"settings.remoteInstances.relay.pair.showQr": "Показати QR-код",
|
||||
"settings.remoteInstances.relay.pair.qrDialogTitle": "Скануйте для спарювання",
|
||||
"settings.remoteInstances.relay.pair.qrDialogDescription": "Відскануйте цей QR-код застосунком OpenChamber на іншому пристрої.",
|
||||
"settings.remoteInstances.relay.pair.manageHint": "Керуйте спареними пристроями або відкликайте їх у списку «Підключення до цього сервера» вище.",
|
||||
"settings.remoteInstances.relay.toast.enableFailed": "Не вдалося увімкнути релей",
|
||||
"settings.remoteInstances.relay.toast.disableFailed": "Не вдалося вимкнути релей",
|
||||
"settings.remoteInstances.relay.toast.offerFailed": "Не вдалося створити посилання для спарювання",
|
||||
"settings.remoteInstances.relay.toast.linkCopied": "Посилання для спарювання скопійовано",
|
||||
"settings.remoteInstances.sidebar.phase.ready": "Готово",
|
||||
"settings.remoteInstances.sidebar.phase.error": "Помилка",
|
||||
"settings.remoteInstances.sidebar.phase.reconnect": "Повторне підключення",
|
||||
|
||||
@@ -59,6 +59,7 @@ export const dict: Record<I18nKey, string> = {
|
||||
"mobile.connect.scan.unsupported": "Сканування QR доступне лише у встановленій мобільній апці.",
|
||||
"mobile.connect.saved.title": "Збережені підключення",
|
||||
"mobile.connect.saved.empty": "Збережених підключень ще немає.",
|
||||
"mobile.connect.relay.badge": "через OpenChamber Relay",
|
||||
"mobile.connect.error.urlRequired": "Введи адресу сервера.",
|
||||
"mobile.connect.error.invalidUrl": "Ця адреса сервера некоректна.",
|
||||
"mobile.connect.error.unreachable": "Не вдалося достукатись до цього OpenChamber сервера.",
|
||||
|
||||
@@ -254,6 +254,37 @@ export const settingsDict = {
|
||||
'settings.remoteInstances.clientAuth.state.thisDevice': '此设备',
|
||||
'settings.remoteInstances.clientAuth.lastUsed': '上次使用 {date}',
|
||||
'settings.remoteInstances.clientAuth.neverUsed': '从未使用',
|
||||
'settings.remoteInstances.relay.title': 'OpenChamber Relay',
|
||||
'settings.remoteInstances.relay.description': '无需开放端口,即可让你的其他设备从任何地方连接。流量端到端加密,中继无法读取内容。',
|
||||
'settings.remoteInstances.relay.enableHint': '在此服务器上启用中继之前,不会共享任何内容。',
|
||||
'settings.remoteInstances.relay.actions.enable': '启用中继',
|
||||
'settings.remoteInstances.relay.actions.disable': '停用',
|
||||
'settings.remoteInstances.relay.confirm.disable': '停用中继?通过它连接的设备将立即断开。',
|
||||
'settings.remoteInstances.relay.state.loading': '正在检查中继状态...',
|
||||
'settings.remoteInstances.relay.state.disabled': '已停用',
|
||||
'settings.remoteInstances.relay.state.connecting': '连接中',
|
||||
'settings.remoteInstances.relay.state.connected': '已连接',
|
||||
'settings.remoteInstances.relay.state.reconnecting': '重新连接中',
|
||||
'settings.remoteInstances.relay.state.error': '错误',
|
||||
'settings.remoteInstances.relay.status.clientsOne': '已连接 {count} 台设备',
|
||||
'settings.remoteInstances.relay.status.clientsMany': '已连接 {count} 台设备',
|
||||
'settings.remoteInstances.relay.pair.title': '配对设备',
|
||||
'settings.remoteInstances.relay.pair.labelPlaceholder': '设备名称(可选)',
|
||||
'settings.remoteInstances.relay.pair.includeToken': '包含访问令牌(扫一次即完成配对)',
|
||||
'settings.remoteInstances.relay.pair.noTokenHint': '不包含令牌时,设备连接后需使用此服务器的界面密码登录。',
|
||||
'settings.remoteInstances.relay.pair.generate': '创建配对链接',
|
||||
'settings.remoteInstances.relay.pair.requiresConnected': '中继连接后即可配对。',
|
||||
'settings.remoteInstances.relay.pair.linkLabel': '配对链接',
|
||||
'settings.remoteInstances.relay.pair.warning': '此链接可访问此服务器,请勿分享。',
|
||||
'settings.remoteInstances.relay.pair.qrAlt': '中继配对二维码',
|
||||
'settings.remoteInstances.relay.pair.showQr': '显示二维码',
|
||||
'settings.remoteInstances.relay.pair.qrDialogTitle': '扫描配对',
|
||||
'settings.remoteInstances.relay.pair.qrDialogDescription': '用其他设备上的 OpenChamber 应用扫描此二维码。',
|
||||
'settings.remoteInstances.relay.pair.manageHint': '可在上方“连接到此服务器”列表中管理或吊销已配对的设备。',
|
||||
'settings.remoteInstances.relay.toast.enableFailed': '无法启用中继',
|
||||
'settings.remoteInstances.relay.toast.disableFailed': '无法停用中继',
|
||||
'settings.remoteInstances.relay.toast.offerFailed': '无法创建配对链接',
|
||||
'settings.remoteInstances.relay.toast.linkCopied': '已复制配对链接',
|
||||
'settings.remoteInstances.sidebar.phase.ready': '就绪',
|
||||
'settings.remoteInstances.sidebar.phase.error': '错误',
|
||||
'settings.remoteInstances.sidebar.phase.reconnect': '重连',
|
||||
|
||||
@@ -59,6 +59,7 @@ export const dict: Record<I18nKey, string> = {
|
||||
'mobile.connect.scan.unsupported': '二维码扫描仅在已安装的移动应用中可用。',
|
||||
'mobile.connect.saved.title': '已保存的连接',
|
||||
'mobile.connect.saved.empty': '暂无已保存的连接。',
|
||||
'mobile.connect.relay.badge': '通过 OpenChamber Relay 连接',
|
||||
'mobile.connect.error.urlRequired': '请输入服务器 URL。',
|
||||
'mobile.connect.error.invalidUrl': '该服务器 URL 无效。',
|
||||
'mobile.connect.error.unreachable': '无法连接到该 OpenChamber 服务器。',
|
||||
|
||||
@@ -260,6 +260,37 @@
|
||||
'settings.remoteInstances.clientAuth.state.thisDevice': '此裝置',
|
||||
'settings.remoteInstances.clientAuth.lastUsed': '上次使用:{date}',
|
||||
'settings.remoteInstances.clientAuth.neverUsed': '從未使用',
|
||||
'settings.remoteInstances.relay.title': 'OpenChamber Relay',
|
||||
'settings.remoteInstances.relay.description': '無需開放連接埠,即可讓你的其他裝置從任何地方連線。流量端對端加密,中繼無法讀取內容。',
|
||||
'settings.remoteInstances.relay.enableHint': '在此伺服器上啟用中繼之前,不會共享任何內容。',
|
||||
'settings.remoteInstances.relay.actions.enable': '啟用中繼',
|
||||
'settings.remoteInstances.relay.actions.disable': '停用',
|
||||
'settings.remoteInstances.relay.confirm.disable': '停用中繼?透過它連線的裝置將立即中斷連線。',
|
||||
'settings.remoteInstances.relay.state.loading': '正在檢查中繼狀態...',
|
||||
'settings.remoteInstances.relay.state.disabled': '已停用',
|
||||
'settings.remoteInstances.relay.state.connecting': '連線中',
|
||||
'settings.remoteInstances.relay.state.connected': '已連線',
|
||||
'settings.remoteInstances.relay.state.reconnecting': '重新連線中',
|
||||
'settings.remoteInstances.relay.state.error': '錯誤',
|
||||
'settings.remoteInstances.relay.status.clientsOne': '已連線 {count} 台裝置',
|
||||
'settings.remoteInstances.relay.status.clientsMany': '已連線 {count} 台裝置',
|
||||
'settings.remoteInstances.relay.pair.title': '配對裝置',
|
||||
'settings.remoteInstances.relay.pair.labelPlaceholder': '裝置名稱(選填)',
|
||||
'settings.remoteInstances.relay.pair.includeToken': '包含存取權杖(掃一次即完成配對)',
|
||||
'settings.remoteInstances.relay.pair.noTokenHint': '不包含權杖時,裝置連線後需使用此伺服器的介面密碼登入。',
|
||||
'settings.remoteInstances.relay.pair.generate': '建立配對連結',
|
||||
'settings.remoteInstances.relay.pair.requiresConnected': '中繼連線後即可配對。',
|
||||
'settings.remoteInstances.relay.pair.linkLabel': '配對連結',
|
||||
'settings.remoteInstances.relay.pair.warning': '此連結可存取此伺服器,請勿分享。',
|
||||
'settings.remoteInstances.relay.pair.qrAlt': '中繼配對 QR 碼',
|
||||
'settings.remoteInstances.relay.pair.showQr': '顯示 QR 碼',
|
||||
'settings.remoteInstances.relay.pair.qrDialogTitle': '掃描配對',
|
||||
'settings.remoteInstances.relay.pair.qrDialogDescription': '用另一台裝置上的 OpenChamber 應用程式掃描此 QR 碼。',
|
||||
'settings.remoteInstances.relay.pair.manageHint': '可在上方「連線到此伺服器」清單中管理或撤銷已配對的裝置。',
|
||||
'settings.remoteInstances.relay.toast.enableFailed': '無法啟用中繼',
|
||||
'settings.remoteInstances.relay.toast.disableFailed': '無法停用中繼',
|
||||
'settings.remoteInstances.relay.toast.offerFailed': '無法建立配對連結',
|
||||
'settings.remoteInstances.relay.toast.linkCopied': '已複製配對連結',
|
||||
'settings.remoteInstances.page.section.instance': '執行個體',
|
||||
'settings.remoteInstances.page.section.instanceDescription': '核心 SSH 設定。',
|
||||
'settings.remoteInstances.page.field.mode': '模式',
|
||||
|
||||
@@ -59,6 +59,7 @@ export const dict: Record<I18nKey, string> = {
|
||||
'mobile.connect.scan.unsupported': 'QR code 掃描僅在已安裝的行動應用程式中可用。',
|
||||
'mobile.connect.saved.title': '已儲存的連線',
|
||||
'mobile.connect.saved.empty': '尚未儲存任何連線。',
|
||||
'mobile.connect.relay.badge': '透過 OpenChamber Relay 連線',
|
||||
'mobile.connect.error.urlRequired': '請輸入伺服器網址。',
|
||||
'mobile.connect.error.invalidUrl': '該伺服器網址無效。',
|
||||
'mobile.connect.error.unreachable': '無法連線至該 OpenChamber 伺服器。',
|
||||
|
||||
Reference in New Issue
Block a user