clauderipple 0.11.1 → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/CHANGELOG.md +34 -0
  2. package/README.es.md +48 -0
  3. package/README.ja.md +42 -0
  4. package/README.ko.md +39 -0
  5. package/README.md +44 -0
  6. package/README.pt-BR.md +46 -0
  7. package/README.zh-CN.md +40 -0
  8. package/dist/app/dist/claude-desktop.js +31 -0
  9. package/dist/app/dist/main.js +127 -8
  10. package/dist/app/dist/status-model.js +12 -0
  11. package/dist/app/dist/update-model.js +32 -0
  12. package/dist/app/package.json +1 -1
  13. package/dist/cli/src/index.js +5 -1
  14. package/dist/mod/hooks/i18n.ts +6 -0
  15. package/dist/mod/hooks/modes.ts +15 -8
  16. package/dist/mod/hooks/register.tsx +67 -19
  17. package/dist/mod/hooks/settings.js +37 -2
  18. package/dist/mod/hooks/settings.ts +31 -3
  19. package/dist/mod/hooks/ui/agents.tsx +1 -1
  20. package/dist/mod/hooks/ui/art.ts +8 -3
  21. package/dist/mod/hooks/ui/band.tsx +6 -2
  22. package/dist/mod/hooks/ui/flat.ts +11 -8
  23. package/dist/mod/hooks/ui/look.ts +3 -1
  24. package/dist/mod/hooks/ui/view.ts +2 -0
  25. package/dist/mod/hooks/workers.ts +8 -1
  26. package/dist/mod/tests/delegation.test.tsx +50 -0
  27. package/dist/mod/tests/register.test.tsx +138 -1
  28. package/dist/mod/tests/ui.test.tsx +6 -3
  29. package/dist/mod/types/index.d.ts +14 -0
  30. package/dist/router/src/admin.js +41 -76
  31. package/dist/router/src/autoupdate.js +299 -0
  32. package/dist/router/src/bootstrap.js +3 -2
  33. package/dist/router/src/cli-control.js +65 -0
  34. package/dist/router/src/config.js +70 -11
  35. package/dist/router/src/index.js +11 -1
  36. package/dist/router/src/picker.js +25 -1
  37. package/dist/router/src/presets.js +21 -13
  38. package/dist/router/src/providers/chatgpt/index.js +11 -0
  39. package/dist/router/src/providers/google/index.js +7 -2
  40. package/dist/router/src/providers/google/transport.js +16 -1
  41. package/dist/router/src/proxy.js +46 -6
  42. package/dist/router/src/requestlog.js +3 -2
  43. package/dist/router/src/version.js +1 -1
  44. package/dist/router/src/websearch.js +2 -2
  45. package/dist/ui/app.js +247 -42
  46. package/dist/ui/i18n.js +154 -18
  47. package/dist/ui/style.css +21 -0
  48. package/docs/ARCHITECTURE.md +61 -11
  49. package/package.json +1 -1
package/dist/ui/i18n.js CHANGED
@@ -2,6 +2,8 @@
2
2
 
3
3
  const I18N = {
4
4
  ko: {
5
+ "update.auto": "자동 업데이트",
6
+ "update.autoHelp": "ClaudeRipple이 유휴 상태일 때 새 버전을 자동 설치합니다. 진행 중인 요청이 끝나면 다시 시작합니다.",
5
7
  "brand": "ClaudeRipple",
6
8
  "nav.health": "상태", "nav.slots": "모델 매핑", "nav.providers": "프로바이더", "nav.clients": "클라이언트", "nav.logs": "로그", "nav.about": "정보",
7
9
  "lang.label": "언어",
@@ -9,7 +11,9 @@ const I18N = {
9
11
  "health.title": "상태", "health.subtitle": "Claude Desktop 연결과 프로바이더, 모델 피커 상태를 5초마다 확인합니다.", "health.desktop": "Claude Desktop 연결", "health.connection": "연결 상태", "health.connected": "연결됨", "health.notConnected": "연결 안 됨", "health.disconnected": "연결 끊김", "health.connectedHelp": "Claude Desktop의 Code 탭에서 보낸 요청을 리플이 처리합니다.", "health.notConnectedHelp": "Claude Desktop이 리플에 연결되어 있지 않습니다. 메뉴 막대 앱에서 리플을 켜거나 터미널에서 clauderipple install을 실행하세요.", "health.requests": "요청 (리플 시작 후)", "health.requestCount": "요청 수", "health.requestsFmt": "완료 {completed} · 실패 {failed} · 진행 중 {inFlight}", "health.providers": "프로바이더", "health.noProviders": "연결된 프로바이더가 없습니다. 프로바이더 메뉴에서 추가하세요.", "health.quota": "사용량 {percent}%{reset}","health.version": "버전", "health.runtime": "리플 실행 파일 · 시작 시각", "health.routes": "매핑 수", "health.cli": "Claude Code 버전",
10
12
  "update.check": "업데이트 확인", "update.checking": "확인 중…", "update.latest": "최신 버전입니다.", "update.install": "{latest} 업데이트", "update.download": "{latest} 다운로드", "update.checkout": "{latest} 버전이 나왔습니다. 소스 코드에서 실행 중이므로 git pull로 업데이트하세요.", "update.running": "업데이트 중…", "update.checkFailed": "업데이트를 확인하지 못했습니다.", "update.confirm": "리플을 {current}에서 {latest} 버전으로 업데이트할까요? 진행 중인 요청이 끝나면 리플이 새 버전으로 다시 시작되고, 이 페이지도 자동으로 새로고침됩니다.", "update.started": "업데이트를 시작했습니다. 리플이 새 버전으로 다시 시작되면 이 창이 새로고침됩니다. 진행 상황은 ~/.clauderipple/logs/update.log에서 확인할 수 있습니다.", "update.failed": "업데이트를 시작하지 못했습니다.",
11
13
  "picker.title": "모델 피커","picker.restartHelp": "켠 뒤에는 Claude Desktop을 완전히 종료하고 다시 여세요.", "picker.confirmOn": "인증서를 신뢰하도록 설정할 때 운영체제의 확인 창이 열립니다(macOS: 로그인 암호 입력, Windows: 인증서 확인). 리플은 암호를 받거나 확인하지 않습니다. 계속할까요?", "picker.working": "처리 중입니다.", "picker.doneOn": "모델 피커를 켰습니다. Claude Desktop을 완전히 종료하고 다시 여세요.", "picker.doneOff": "모델 피커를 껐습니다. Claude Desktop을 완전히 종료하고 다시 여세요.",
12
- "slots.title": "모델 매핑", "slots.subtitle": "Claude 앱에서 모델을 골랐을 때 실제로 답할 모델을 정합니다. 바꾸면 자동으로 저장됩니다.","slots.pickerModels": "피커에 표시할 모델", "slots.pickerModelsHelp": "고른 모델이 Claude Desktop 피커에 나오고, 피커에서 고르면 그 모델이 바로 답합니다. 새로 추가한 모델은 새 세션부터 고를 수 있습니다(진행 중인 세션은 시작할 때의 목록을 그대로 씁니다).", "slots.add": "+ 매핑 추가", "slots.th.claude": "Claude 앱에서 고른 모델", "slots.th.target": "실제로 답할 모델", "slots.th.effort": "추론 강도 (effort)", "slots.passthrough": "Claude가 직접 처리", "slots.noProviderModels": "먼저 프로바이더를 연결하고 쓸 모델을 고르세요.", "slots.noChanges": "설정한 매핑이 없습니다. 모든 요청을 Claude가 기본 설정대로 처리합니다.", "slots.duplicate": "같은 Claude 모델에 매핑을 두 개 이상 둘 수 없습니다.", "slots.effortHint": "모델이 앱에서 고른 추론 강도를 지원하지 않으면 가장 가까운 단계로 바꿔 보냅니다. (예: xhigh → high)", "slots.noEffort": "이 모델은 추론 강도를 설정할 수 없습니다.", "pool.ready": "사용 중", "pool.cooling": "{seconds}초 후 다시 사용", "pool.quarantined": "인증 실패 — API 키 확인 필요", "slots.modelSlots": "Claude Code 용도별 모델", "slots.modelSlotsHelp": "Claude Code가 요청을 만들기 전에 용도별로 고르는 모델입니다. 일반 모델 매핑은 여기에 적용되지 않습니다. 비워 두면 Claude가 처리하고 Claude 사용량을 씁니다. 웹 검색은 '작고 빠른 모델'이 맡습니다. 이 항목을 그대로 두면 다른 모델과 대화할 때도 검색에는 Claude 한도를 씁니다. 바꾸면 settings.json에 저장됩니다.", "slots.noWebSearch": "웹 검색을 지원하지 않는 모델", "slots.noWebSearchWarn": "저장했습니다. 다만 리플에서는 이 모델로 웹 검색을 할 수 없어 검색이 실패합니다. (모델 자체는 웹 검색을 지원할 수도 있습니다. 기본값으로 바꾸면 Claude가 검색을 맡습니다.)", "slots.slotDefault": "기본값 (Claude)", "slots.slot.smallFast": "작고 빠른 모델", "slots.slot.subagent": "서브에이전트", "slots.slot.main": "세션 기본 모델", "slots.slotHelp.smallFast": "웹 검색, 제목 생성, 분류", "slots.slotHelp.subagent": "모델을 따로 정하지 않은 서브에이전트", "slots.slotHelp.main": "세션을 시작할 때 쓸 모델", "slots.slotSaved": "용도별 모델을 저장했습니다. 새 세션부터 적용됩니다.", "slots.windowGlobal": "기본값", "slots.windowHelp": "모델이 한 번에 읽을 수 있는 컨텍스트 크기를 토큰 단위로 정합니다. 모델마다 지원하는 크기가 달라 따로 정합니다. 비워 두면 프로바이더가 알려 준 값을 쓰고, 그 값도 없으면 시스템 기본값을 씁니다. 대화를 압축할 시점이 아니라 모델이 실제로 읽을 수 있는 최대 크기를 입력하세요.",
14
+ "slots.title": "모델 매핑", "slots.subtitle": "Claude 앱에서 모델을 골랐을 때 실제로 답할 모델을 정합니다. 바꾸면 자동으로 저장됩니다.","slots.pickerModels": "피커에 표시할 모델", "slots.pickerModelsHelp": "고른 모델이 Claude Desktop 피커에 나오고, 피커에서 고르면 그 모델이 바로 답합니다. 새로 추가한 모델은 새 세션부터 고를 수 있습니다(진행 중인 세션은 시작할 때의 목록을 그대로 씁니다).", "slots.add": "+ 매핑 추가", "slots.th.claude": "Claude 앱에서 고른 모델", "slots.th.target": "실제로 답할 모델", "slots.th.effort": "추론 강도 (effort)", "slots.passthrough": "Claude가 직접 처리", "slots.noProviderModels": "먼저 프로바이더를 연결하고 쓸 모델을 고르세요.", "slots.noChanges": "설정한 매핑이 없습니다. 모든 요청을 Claude가 기본 설정대로 처리합니다.", "slots.duplicate": "같은 Claude 모델에 매핑을 두 개 이상 둘 수 없습니다.", "slots.effortHint": "모델이 앱에서 고른 추론 강도를 지원하지 않으면 가장 가까운 단계로 바꿔 보냅니다. (예: xhigh → high)", "slots.noEffort": "이 모델은 추론 강도를 설정할 수 없습니다.", "pool.ready": "사용 중", "pool.cooling": "{seconds}초 후 다시 사용", "pool.quarantined": "인증 실패 — API 키 확인 필요", "slots.modelSlots": "Claude Code 용도별 모델", "slots.modelSlotsHelp": "Claude Code가 요청을 만들기 전에 용도별로 고르는 모델입니다. 일반 모델 매핑은 여기에 적용되지 않습니다. 비워 두면 Claude가 처리하고 Claude 사용량을 씁니다. 웹 검색은 아래에서 따로 고르지 않으면 '작고 빠른 모델'이 맡습니다. 이 항목을 그대로 두면 다른 모델과 대화할 때도 검색에는 Claude 한도를 씁니다. 바꾸면 settings.json에 저장됩니다.", "slots.noWebSearch": "웹 검색을 지원하지 않는 모델", "slots.noWebSearchWarn": "저장했습니다. 다만 리플에서는 이 모델로 웹 검색을 할 수 없어 검색이 실패합니다. (모델 자체는 웹 검색을 지원할 수도 있습니다. 기본값으로 바꾸면 Claude가 검색을 맡습니다.)", "slots.slotDefault": "기본값 (Claude)", "slots.slot.smallFast": "작고 빠른 모델", "slots.slot.subagent": "서브에이전트", "slots.slot.main": "세션 기본 모델", "slots.slotHelp.smallFast": "웹 검색, 제목 생성, 분류", "slots.slotHelp.subagent": "모델을 따로 정하지 않은 서브에이전트", "slots.slotHelp.main": "세션을 시작할 때 쓸 모델", "slots.slotSaved": "용도별 모델을 저장했습니다. 새 세션부터 적용됩니다.",
15
+ "slots.webSearch": "웹 검색", "slots.webSearchFollow": "작고 빠른 모델을 따름", "slots.webSearchHelp": "웹 검색만 이 모델로 처리합니다. 저장하면 새 세션 없이 바로 적용됩니다. 선택한 모델에서 검색이 실패하면 '작고 빠른 모델'로 넘어갑니다.", "slots.webSearchSaved": "웹 검색 모델을 저장했습니다. 바로 적용됩니다.",
16
+ "slots.windowGlobal": "기본값", "slots.windowHelp": "모델이 한 번에 읽을 수 있는 컨텍스트 크기를 토큰 단위로 정합니다. 모델마다 지원하는 크기가 달라 따로 정합니다. 비워 두면 프로바이더가 알려 준 값을 쓰고, 그 값도 없으면 시스템 기본값을 씁니다. 대화를 압축할 시점이 아니라 모델이 실제로 읽을 수 있는 최대 크기를 입력하세요.",
13
17
  "providers.title": "프로바이더", "providers.subtitle": "연결한 서비스와 계정을 한곳에서 관리합니다. 프로바이더를 고르면 오른쪽에 세부 설정과 계정 목록이 나옵니다.", "providers.search": "프로바이더 검색…", "providers.overview": "개요", "providers.accounts": "계정","providers.modelsTab": "모델", "providers.selectedModels": "선택한 모델", "providers.modelsCountShort": "모델 {count}개", "providers.noSelection": "왼쪽에서 프로바이더를 고르세요.", "providers.rotationOn": "자동 전환 켜짐", "providers.rotationOff": "자동 전환 꺼짐", "providers.rotationSaving": "자동 전환 설정 저장 중…", "providers.rotationSaved": "자동 전환 설정을 저장했습니다.", "providers.accountCount": "연결된 계정 {count}개", "providers.accountCountHelp": "현재 Claude에 로그인한 계정과 리플에 추가한 계정을 모두 합한 수입니다.", "providers.accountPoolTitle": "Claude 계정", "providers.accountPoolSubtitle": "한 대화는 같은 계정으로 이어갑니다. 답변을 시작하기 전에 사용 한도에 걸리거나 인증 오류가 날 때만 다음 계정으로 넘어갑니다.", "providers.addClaudeAccount": "+ Claude 계정 추가", "providers.currentAccountHelp": "지금 Claude Code나 Claude Desktop에 로그인한 계정입니다. 리플에서는 삭제할 수 없습니다.", "providers.addedAccountHelp": "리플이 이 컴퓨터의 현재 사용자만 읽을 수 있는 파일에 OAuth 로그인 정보를 안전하게 저장하고 자동으로 갱신합니다.", "providers.reauthAction": "다시 로그인", "providers.preferBadge": "1순위", "providers.preferAction": "1순위로 지정", "providers.preferHelp": "새 대화는 목록 맨 위의 1순위 계정부터 씁니다. 한도에 닿으면 다음 계정으로 넘어가고, 한도가 초기화되면 새 대화부터 다시 1순위 계정을 씁니다. 진행 중인 대화는 프롬프트 캐시를 지키려고 같은 계정으로 이어갑니다.","providers.dangerZone": "추가 계정 관리","providers.connection": "연결 상태","providers.rotationRequired": "계정을 여러 개 추가해도 자동 전환을 켜야 Claude 앱과 Claude Code에서 쓸 수 있습니다.", "providers.anthropic": "Claude (Anthropic)", "providers.anthropicHelp": "Claude 구독 계정을 자동으로 돌려 쓰거나, Codex 같은 OpenAI 호환 도구에서 Claude 모델을 쓰게 연결합니다.", "providers.anthropicIngressOnly": "자동 전환을 끄면 고른 모델은 Codex 같은 OpenAI 호환 도구에서만 쓸 수 있고, Claude 앱의 모델 매핑에는 나오지 않습니다.", "providers.anthropicPoolRouting": "자동 전환을 켜면 Claude 앱과 Claude Code의 대화마다 같은 계정을 써서 프롬프트 캐시를 지킵니다. 답변을 만들기 전에 429 한도 초과나 인증 거부 오류가 오면 다음 계정으로 넘어갑니다. 구독 계정을 여러 개 쓰는 것은 Anthropic 이용약관에 따라 제한될 수 있습니다. 본인 계정만 허용된 용도로 쓰세요.","providers.anthropicPool": "Claude 계정 자동 전환","providers.anthropicCurrent": "현재 로그인 계정", "providers.anthropicReauth": "다시 로그인 필요", "providers.anthropicNoAccounts": "리플에 추가한 계정이 없습니다.", "providers.anthropicRenamePrompt": "이 계정을 표시할 이름", "providers.anthropicRemoveConfirm": "리플에서 Claude 계정 {name}을(를) 삭제할까요?", "providers.anthropicLogoutAll": "추가 계정 모두 삭제", "providers.anthropicLogoutAllConfirm": "리플에 추가한 Claude 계정을 모두 삭제할까요? Claude Code와 Claude Desktop의 현재 로그인은 그대로 둡니다.", "providers.anthropicAuthClaudeCode": "Claude Code 로그인 사용 (자동)", "providers.anthropicAuthApiKey": "API 키", "providers.anthropicCredentialsHelp": "Claude Code 로그인을 그대로 쓸지, Anthropic API 키로 연결할지 고르세요.", "providers.anthropicSourceObserved": "Claude Desktop 대화에서 확인됨", "providers.anthropicSourceClaudeCode": "터미널 claude 로그인", "providers.anthropicSourceTokenFile": "리플 토큰", "providers.anthropicSourceMissing": "없음 — 아래 버튼으로 연결","providers.anthropicOAuthTitle": "Claude 계정 추가", "providers.anthropicOAuthWarning": "Claude 구독의 OAuth 로그인을 리플 같은 다른 회사 프로그램에서 쓰는 것은 Anthropic이 공식 지원하지 않습니다. 약관을 어겨 계정 이용이 제한될 수 있습니다. 반드시 본인 계정만 허용된 용도로 쓰세요.", "providers.anthropicOAuthAccept": "위험을 확인했고 OAuth로 계속합니다.", "providers.anthropicOAuthContinue": "OAuth로 계속","providers.anthropicLoginDone": "Claude 구독을 연결했습니다.", "providers.anthropicSignInBrowser": "브라우저에서 Claude에 로그인하세요. 창이 열리지 않으면 아래 링크를 여세요.", "providers.anthropicSignInLink": "로그인 페이지 열기", "providers.anthropicSignInPaste": "브라우저에서 로그인한 뒤 나오는 코드를 여기에 붙여넣으세요.", "providers.anthropicSignInSubmit": "코드 입력 완료", "providers.anthropicSignInManual": "코드 붙여넣기로 다시 시도", "providers.anthropicSignInWaiting": "브라우저에서 승인 대기 중… (최대 5분)", "providers.anthropicSignInFailed": "로그인하지 못했습니다.","providers.add": "+ 프로바이더 추가", "providers.refresh": "전체 연결 확인", "providers.check": "연결 확인", "providers.checking": "확인 중", "providers.empty": "아직 연결된 프로바이더가 없습니다. 위쪽 버튼으로 추가하세요.","providers.noModels": "고른 모델이 없습니다. 수정 메뉴에서 모델을 고르세요.", "providers.removeConfirm": "프로바이더 {name}을(를) 삭제할까요? 이 프로바이더를 쓰는 모델 매핑도 함께 삭제됩니다.", "providers.choose": "프로바이더 추가", "providers.chooseHelp": "연결할 서비스를 고르세요.", "providers.chatgpt": "ChatGPT 구독", "providers.chatgptHelp": "Codex가 포함된 ChatGPT 요금제(Plus, Pro, Business 등)로 GPT 모델을 씁니다. API 키 없이 로그인만 하면 연결됩니다.", "providers.presetHelp": "API 키만 입력하면 바로 연결됩니다.", "providers.openaiGroup": "OpenAI 호환", "providers.openaiGroupHelp": "리플이 요청을 OpenAI API 규격에 맞게 바꿔 보냅니다.","providers.custom": "직접 입력", "providers.customName": "새 프로바이더", "providers.customHelp": "목록에 없는 Anthropic 호환 또는 OpenAI 호환 API를 연결합니다. API 주소(엔드포인트 URL)와 키를 직접 입력하세요.", "providers.formatHelp": "서비스 문서에 나온 규격을 고르세요. API 주소가 /chat/completions로 끝나면 OpenAI Chat Completions 규격입니다.", "providers.addTitle": "프로바이더 추가", "providers.edit": "프로바이더 수정", "providers.name": "이름", "providers.nameHelp": "목록과 모델 매핑에 표시할 프로바이더 이름입니다.", "providers.nameRequired": "이름을 입력하세요.", "providers.apiKey": "API 키", "providers.keyHelp": "프로바이더에서 발급받은 API 키입니다. 이 컴퓨터의 설정 파일에만 저장합니다.", "providers.keyPlaceholder": "API 키를 붙여넣으세요", "providers.keySaved": "저장한 키를 쓰고 있습니다. 바꾸려면 새 키를 입력하세요.", "providers.url": "API 기본 주소 (URL)", "providers.urlHelp": "Anthropic 호환 API의 기본 주소입니다. 서비스 문서의 주소와 다를 때만 바꾸세요.", "providers.openaiUrlHelp": "OpenAI 호환 API의 기본 주소입니다. 요청은 Chat Completions 또는 Responses API에 맞게 바꿔 보냅니다.", "providers.wire": "API 규격", "providers.wireHelp": "대부분 Chat Completions를 씁니다. 프로바이더가 이전 대화를 서버에 저장하지 않는 Responses API를 지원할 때만 Responses를 고르세요.", "providers.urlRequired": "올바른 URL을 입력하세요.", "providers.keyType": "인증 헤더", "providers.keyTypeHelp": "보통 x-api-key를 씁니다. 서비스 문서에 Authorization: Bearer 방식이 나와 있으면 그 항목을 고르세요.", "providers.extraHeaders": "추가 HTTP 헤더", "providers.extraHeadersHelp": "서비스에서 요구하는 추가 헤더를 '이름: 값' 형식으로 한 줄에 하나씩 입력하세요.", "providers.models": "사용할 모델", "providers.modelsHelp": "고른 모델이 모델 매핑에서 고를 수 있는 목록에 나옵니다.", "providers.modelsFound": "프로바이더에서 받아 온 모델 목록입니다.", "providers.modelsFallback": "모델 목록을 받아 오지 못해 기본으로 알려진 모델을 보여 줍니다.", "measure.running": "모델 기능 확인 중… ({done}/{total})", "measure.help": "모델마다 어떤 API 규격과 추론 강도를 지원하는지 확인합니다. 공개 모델 목록(models.dev)에 정보가 있으면 그 정보를 쓰고, 없으면 모델에 직접 요청을 보냅니다.", "measure.done": "모델 기능 확인 완료: {summary}", "measure.nothing": "모델이 응답하지 않아 기능을 확인하지 못했습니다.", "measure.notEntitled": "{models}: 이 프로바이더의 자체 앱에서만 쓸 수 있는 모델입니다. 여기서 골라도 쓸 수 없습니다.", "measure.failed": "모델 기능을 확인하지 못했습니다.", "measure.authFailed": "API 키 인증이 거부되어 모델 기능을 확인하지 못했습니다.", "providers.probeNotEntitled": "API 키는 정상이지만 요금제 제한으로 요청이 거부되었습니다.", "providerStatus.notEntitled": "요금제 제한", "providers.probeOk": "연결했습니다.", "providers.probeFailed": "연결하지 못했습니다.", "providers.apiSoon": "잠시 후 다시 시도하세요.", "providers.showInPicker": "Claude 앱 피커에 실제 모델 이름 표시", "providers.pickerOffHint": "피커 모드가 꺼져 있어 골라도 피커에 나오지 않습니다. 저장할 때 피커를 켤지 묻습니다.", "providers.pickerOffPrompt": "피커 모드가 꺼져 있어 이 모델이 Claude Desktop 피커에 나오지 않습니다. 지금 켤까요? 인증서를 신뢰하도록 설정할 때 운영체제의 확인 창이 열립니다.", "providers.credentials": "로그인 방식", "providers.credentialsHelp": "어디에 저장된 ChatGPT 로그인 정보(토큰)를 쓸지 정합니다.", "providers.authAuto": "자동 (리플 로그인 우선, 없으면 Codex CLI 로그인 사용)", "providers.authOwn": "리플에서 직접 로그인", "providers.authBorrow": "Codex CLI 로그인 사용 (~/.codex/auth.json)", "providers.login": "ChatGPT 로그인 안내", "providers.loginHelp": "메뉴 막대 앱에서 'ChatGPT 로그인…'을 누르거나 터미널에서 clauderipple login을 실행하면 브라우저 로그인 창이 열립니다. 토큰은 이 컴퓨터에만 저장합니다.","providers.chatgptLogin": "ChatGPT 로그인", "providers.chatgptLoginWaiting": "브라우저에서 로그인을 마치면 이 화면이 자동으로 새로고침됩니다.", "providers.chatgptLoginDone": "ChatGPT에 로그인했습니다.", "providers.chatgptAccountsTitle": "ChatGPT 계정", "providers.chatgptAccountsSubtitle": "계정을 여러 개 추가하면 한 계정이 한도에 닿았을 때 다음 계정이 같은 요청을 이어받습니다. 답한 뒤에는 같은 계정으로 대화를 이어가 프롬프트 캐시를 지킵니다. 계정은 목록 순서대로 씁니다.", "providers.addChatgptAccount": "+ ChatGPT 계정 추가", "providers.chatgptAddWaiting": "브라우저에서 추가할 계정으로 로그인하세요. 이미 로그인한 계정이 나오면 다른 계정을 고르세요.", "providers.chatgptAdded": "ChatGPT 계정을 추가했습니다.", "providers.chatgptNoAccounts": "로그인한 ChatGPT 계정이 없습니다.", "providers.chatgptAccountCount": "계정 {count}개", "providers.chatgptCodexHelp": "Codex CLI에 로그인한 계정입니다. 리플은 로그인 정보를 읽기만 하고 자동으로 갱신하지 않습니다. 다른 계정을 모두 쓴 뒤 마지막에 씁니다.", "providers.chatgptOwnHelp": "리플이 이 컴퓨터의 현재 사용자만 읽을 수 있는 파일에 계정 정보를 저장하고 자동으로 갱신합니다.", "providers.chatgptInUse": "현재 사용 중", "providers.chatgptStandby": "대기", "providers.chatgptPause": "일시 중지", "providers.chatgptResume": "다시 사용", "providers.chatgptPaused": "일시 중지됨", "providers.chatgptClearCooldown": "대기 해제", "providers.chatgptRemoveConfirm": "리플에서 ChatGPT 계정 {name}을(를) 삭제할까요?", "providers.chatgptReauthHelp": "로그인이 만료되었거나 거부되었습니다. 같은 계정으로 다시 로그인하면 사용할 수 있습니다.", "pool.coolingFor": "{duration} 후 다시 사용", "time.hoursMinutes": "{h}시간 {m}분", "time.minutes": "{m}분", "time.seconds": "{s}초", "quota.window5h": "5시간", "quota.windowWeek": "주간", "quota.windowHours": "{hours}시간", "quota.line": "{window} {percent}%", "quota.resetAt": "{time} 초기화", "providers.effortLevels": "추론 강도: {levels}", "providers.effortNone": "지원 안 함", "providers.modelEffort": "추론 강도 설정", "providers.modelNoEffort": "추론 강도 지원 안 함", "providers.defaultEffort": "기본 추론 강도 (effort)", "providers.defaultEffortHelp": "모델 매핑이나 앱에서 추론 강도를 따로 정하지 않았을 때 쓸 값입니다.", "providers.identity": "모델에 자기 이름 알려 주기 (identity)", "providers.append": "시스템 프롬프트 추가 문구", "providers.appendHelp": "모든 요청의 시스템 프롬프트 끝에 덧붙이는 고정 문구입니다. 자주 바꾸면 프롬프트 캐시를 쓸 수 없어 비용이 늘 수 있습니다.",
14
18
  "providerStatus.connected": "연결됨", "providerStatus.keyNeeded": "API 키 확인 필요", "providerStatus.loginNeeded": "로그인 필요", "providerStatus.disconnected": "연결 안 됨", "providerStatus.checking": "확인 중",
15
19
  "clients.title": "클라이언트", "clients.subtitle": "리플에 연결해서 쓸 앱을 설정합니다.", "clients.desktop.title": "Claude Desktop", "clients.desktop.help": "프로바이더의 모델을 피커에 실제 이름으로 표시할지 정합니다.", "clients.codex.title": "Codex (앱과 CLI)", "clients.codex.help": "Codex 설정(~/.codex/config.toml)에 리플 프로바이더를 추가합니다. 터미널에서는 `codex --profile clauderipple -m <모델>`을 실행하고, Codex 앱에서는 설정에서 clauderipple 프로바이더를 기본값으로 고르세요.", "clients.codex.note": "켜면 Codex의 GPT 요청도 리플이 처리합니다. Codex 로그인은 그대로입니다. ChatGPT 계정을 여러 개 추가해 두면 한 계정이 한도에 걸려도 로그아웃할 필요 없이 다음 계정이 이어받습니다. 아래는 Codex에서 더 쓸 수 있는 Claude 모델과 Anthropic 호환 모델입니다.", "clients.codex.turnOn": "Codex에서 리플 사용","clients.claudeCode.title": "터미널 Claude Code", "clients.claudeCode.help": "터미널의 `claude`도 같은 리플에 연결됩니다. `/model <이름>` 명령어로 모델을 고르세요.",
@@ -194,6 +198,7 @@ const I18N = {
194
198
  "mod.modeDelegation": "위임 강도",
195
199
  "mod.modeNormal": "보통",
196
200
  "mod.modeEager": "적극 — 메인 대화는 지휘만",
201
+ "mod.baseDelegationHelp": "모드가 꺼져 있을 때 적용됩니다. 켠 모드는 자기 위임 강도를 씁니다.",
197
202
  "mod.modeUntil": "종료 시점",
198
203
  "mod.modeUntilNone": "끄기 전까지",
199
204
  "mod.modeUntilTime": "시각 지정",
@@ -214,6 +219,8 @@ const I18N = {
214
219
  "mod.modeRemoveConfirm": "‘{name}’ 모드를 삭제할까요?",
215
220
  },
216
221
  en: {
222
+ "update.auto": "Automatic updates",
223
+ "update.autoHelp": "Automatically installs new versions while ClaudeRipple is idle, then restarts once requests in flight have finished.",
217
224
  "brand": "ClaudeRipple",
218
225
  "nav.health": "Status", "nav.slots": "Model mapping", "nav.providers": "Providers", "nav.clients": "Clients", "nav.logs": "Logs", "nav.about": "About",
219
226
  "lang.label": "Language",
@@ -221,7 +228,9 @@ const I18N = {
221
228
  "health.title": "Status", "health.subtitle": "Claude Desktop connection, providers and the model picker, refreshed every 5s.", "health.desktop": "Claude Desktop connection", "health.connection": "Connection", "health.connected": "Connected", "health.notConnected": "Not connected", "health.disconnected": "Not connected", "health.connectedHelp": "Claude Desktop's Code tab requests go through ClaudeRipple.", "health.notConnectedHelp": "Claude Desktop is not going through ClaudeRipple. Start the router from the menu-bar app or run clauderipple install.", "health.requests": "Requests (since router start)", "health.requestCount": "Request count", "health.requestsFmt": "{completed} done · {failed} failed · {inFlight} in progress", "health.providers": "Providers", "health.noProviders": "No providers connected. Add one under Providers.", "health.quota": "{percent}% used{reset}","health.version": "Version", "health.runtime": "Router files · started", "health.routes": "Mappings", "health.cli": "Claude Code version",
222
229
  "update.check": "Check for updates", "update.checking": "Checking…", "update.latest": "This is the latest version.", "update.install": "Update to {latest}", "update.download": "Get {latest}", "update.checkout": "{latest} is out. This is a source checkout; update it with git pull.", "update.running": "Updating…", "update.checkFailed": "Could not check for updates.", "update.confirm": "Update ClaudeRipple {current} → {latest}? The router restarts on the new version once requests in flight have finished, and this window reloads by itself.", "update.started": "The update has started. This window reloads when the router is back on the new version. Its output is in ~/.clauderipple/logs/update.log.", "update.failed": "Could not start the update.",
223
230
  "picker.title": "Model picker","picker.restartHelp": "After turning it on, quit Claude Desktop completely and open it again.", "picker.confirmOn": "Your operating system will ask you to confirm trusting the certificate (macOS asks for your login password, Windows shows a certificate dialog). ClaudeRipple never sees your password. Continue?", "picker.working": "Working.", "picker.doneOn": "Turned on. Quit Claude Desktop completely and open it again.", "picker.doneOff": "Turned off. Quit Claude Desktop completely and open it again.",
224
- "slots.title": "Model mapping", "slots.subtitle": "Decide which model actually answers when you pick a Claude model in the app. Changes save automatically.","slots.pickerModels": "Models to put in the picker", "slots.pickerModelsHelp": "Checked models appear in the Claude Desktop picker; picking one makes it answer directly. Newly added models are selectable from the next session (a running session keeps the list it started with).", "slots.add": "+ Add mapping", "slots.th.claude": "Picked in the Claude app", "slots.th.target": "Actually answered by", "slots.th.effort": "Reasoning effort", "slots.passthrough": "Claude answers as usual", "slots.noProviderModels": "Connect a provider and choose its models first.", "slots.noChanges": "No mappings. Every model answers as Claude.", "slots.duplicate": "A Claude model can be mapped only once.", "slots.effortHint": "If the effort chosen in the app is unsupported by the model, ClaudeRipple sends the nearest level instead. (For example: xhigh → high)", "slots.noEffort": "This model does not take a reasoning effort", "pool.ready": "in use", "pool.cooling": "back in {seconds}s", "pool.quarantined": "rejected — check the key", "slots.modelSlots": "Claude Code's own model slots", "slots.modelSlotsHelp": "Claude Code chooses these before a request exists, so a mapping cannot reach them. Left empty, Claude answers them and they cost Claude quota. Web search in particular runs on the small, fast model, so a routed session still searches on Claude quota until that one is moved. Changes are written to settings.json on install.", "slots.noWebSearch": "no web search here", "slots.noWebSearchWarn": "Saved, but web search will fail on this model — ClaudeRipple cannot run a search through it. The model itself may be able to; leaving this on the default lets Claude search.", "slots.slotDefault": "default (Claude)", "slots.slot.smallFast": "Small, fast model", "slots.slot.subagent": "Subagents", "slots.slot.main": "Session default", "slots.slotHelp.smallFast": "web search, titles, classifiers", "slots.slotHelp.subagent": "a subagent that names no model of its own", "slots.slotHelp.main": "what a session starts on", "slots.slotSaved": "Model slots saved. They take effect in a new session.", "slots.windowGlobal": "default", "slots.windowHelp": "This model's context window, in tokens. Routed models do not share one, so each carries its own. Left blank it takes what the provider reported, and otherwise the global default. Enter the model's real window, not a compaction point.",
231
+ "slots.title": "Model mapping", "slots.subtitle": "Decide which model actually answers when you pick a Claude model in the app. Changes save automatically.","slots.pickerModels": "Models to put in the picker", "slots.pickerModelsHelp": "Checked models appear in the Claude Desktop picker; picking one makes it answer directly. Newly added models are selectable from the next session (a running session keeps the list it started with).", "slots.add": "+ Add mapping", "slots.th.claude": "Picked in the Claude app", "slots.th.target": "Actually answered by", "slots.th.effort": "Reasoning effort", "slots.passthrough": "Claude answers as usual", "slots.noProviderModels": "Connect a provider and choose its models first.", "slots.noChanges": "No mappings. Every model answers as Claude.", "slots.duplicate": "A Claude model can be mapped only once.", "slots.effortHint": "If the effort chosen in the app is unsupported by the model, ClaudeRipple sends the nearest level instead. (For example: xhigh → high)", "slots.noEffort": "This model does not take a reasoning effort", "pool.ready": "in use", "pool.cooling": "back in {seconds}s", "pool.quarantined": "rejected — check the key", "slots.modelSlots": "Claude Code's own model slots", "slots.modelSlotsHelp": "Claude Code chooses these before a request exists, so a mapping cannot reach them. Left empty, Claude answers them and they cost Claude quota. Web search runs on the small, fast model unless one is chosen for it below, so a routed session still searches on Claude quota until one of them is moved. Changes are written to settings.json on install.", "slots.noWebSearch": "no web search here", "slots.noWebSearchWarn": "Saved, but web search will fail on this model — ClaudeRipple cannot run a search through it. The model itself may be able to; leaving this on the default lets Claude search.", "slots.slotDefault": "default (Claude)", "slots.slot.smallFast": "Small, fast model", "slots.slot.subagent": "Subagents", "slots.slot.main": "Session default", "slots.slotHelp.smallFast": "web search, titles, classifiers", "slots.slotHelp.subagent": "a subagent that names no model of its own", "slots.slotHelp.main": "what a session starts on", "slots.slotSaved": "Model slots saved. They take effect in a new session.",
232
+ "slots.webSearch": "Web search", "slots.webSearchFollow": "Follow the small, fast model", "slots.webSearchHelp": "Only web search is answered by this model. It takes effect immediately, with no new session. If the chosen model cannot complete a search, the search falls back to the small, fast model.", "slots.webSearchSaved": "Web search model saved. It takes effect immediately.",
233
+ "slots.windowGlobal": "default", "slots.windowHelp": "This model's context window, in tokens. Routed models do not share one, so each carries its own. Left blank it takes what the provider reported, and otherwise the global default. Enter the model's real window, not a compaction point.",
225
234
  "providers.title": "Providers", "providers.subtitle": "Manage connected services and accounts in one place. Pick a provider to open its settings and accounts.", "providers.search": "Search providers…", "providers.overview": "Overview", "providers.accounts": "Accounts","providers.modelsTab": "Models", "providers.selectedModels": "Selected models", "providers.modelsCountShort": "{count} models", "providers.noSelection": "Select a provider on the left.", "providers.rotationOn": "Rotation on", "providers.rotationOff": "Rotation off", "providers.rotationSaving": "Saving rotation…", "providers.rotationSaved": "Account rotation saved.", "providers.accountCount": "{count} connected accounts", "providers.accountCountHelp": "Includes the current Claude login and accounts added to ClaudeRipple.", "providers.accountPoolTitle": "Claude accounts", "providers.accountPoolSubtitle": "A conversation stays on one account. ClaudeRipple switches only when quota or authentication fails before output starts.", "providers.addClaudeAccount": "+ Add Claude account", "providers.currentAccountHelp": "The external sign-in currently used by Claude Code or Claude Desktop. ClaudeRipple cannot remove it.", "providers.addedAccountHelp": "ClaudeRipple stores this OAuth grant on this computer and refreshes it automatically.", "providers.reauthAction": "Sign in again", "providers.preferBadge": "First", "providers.preferAction": "Use first", "providers.preferHelp": "New conversations start on the account at the top. When it hits its limit they move to the next one, and once it resets new conversations go back to it. A conversation already running stays on its account to keep its cache.","providers.dangerZone": "Added account management","providers.connection": "Connection","providers.rotationRequired": "Added accounts are used for Claude app and Claude Code requests only when account rotation is on.", "providers.anthropic": "Claude (Anthropic)", "providers.anthropicHelp": "Rotate Claude subscription accounts, or serve Claude models to OpenAI-style tools such as Codex.", "providers.anthropicIngressOnly": "With account rotation off, the models chosen here are served only to OpenAI-style tools such as Codex and do not appear in Claude app mappings.", "providers.anthropicPoolRouting": "With account rotation on, Claude app and Claude Code requests stay on one account per conversation to preserve the prompt cache. A 429 or authentication refusal before the response starts moves the request to the next account. Using multiple subscriptions may be subject to Anthropic's terms and account restrictions; use only your own authorized accounts.","providers.anthropicPool": "Rotate Claude accounts automatically","providers.anthropicCurrent": "Current login", "providers.anthropicReauth": "Sign in again", "providers.anthropicNoAccounts": "No accounts have been added to ClaudeRipple.", "providers.anthropicRenamePrompt": "Display name for this account", "providers.anthropicRemoveConfirm": "Remove Claude account {name} from ClaudeRipple?", "providers.anthropicLogoutAll": "Remove all added accounts", "providers.anthropicLogoutAllConfirm": "Remove every Claude account added to ClaudeRipple? The current Claude Code and Claude Desktop login is not changed.", "providers.anthropicAuthClaudeCode": "Reuse Claude Code sign-in (automatic)", "providers.anthropicAuthApiKey": "API key", "providers.anthropicCredentialsHelp": "Choose your Claude Code sign-in or an Anthropic API key.", "providers.anthropicSourceObserved": "Detected from a Claude Desktop session", "providers.anthropicSourceClaudeCode": "Terminal Claude sign-in", "providers.anthropicSourceTokenFile": "ClaudeRipple token", "providers.anthropicSourceMissing": "None — connect below","providers.anthropicOAuthTitle": "Add Claude account", "providers.anthropicOAuthWarning": "Using Claude subscription OAuth through a third-party proxy is not an Anthropic-supported integration and may be subject to terms or account restrictions. Use only your own accounts for authorized purposes.", "providers.anthropicOAuthAccept": "I understand the risk and want to continue with OAuth.", "providers.anthropicOAuthContinue": "Continue with OAuth","providers.anthropicLoginDone": "Claude subscription connected.", "providers.anthropicSignInBrowser": "Sign in to Claude in your browser. If no window opened, use the link below.", "providers.anthropicSignInLink": "Open the sign-in page", "providers.anthropicSignInPaste": "After signing in, paste the code the page shows here.", "providers.anthropicSignInSubmit": "Submit code", "providers.anthropicSignInManual": "Try again by pasting a code", "providers.anthropicSignInWaiting": "Waiting for the browser… (up to 5 minutes)", "providers.anthropicSignInFailed": "Sign-in failed","providers.add": "+ Add provider", "providers.refresh": "Test all", "providers.check": "Test connection", "providers.checking": "Checking", "providers.empty": "No providers connected yet. Add one with the button above.","providers.noModels": "No models chosen. Pick some under Edit.", "providers.removeConfirm": "Remove provider {name}? Model mappings that use it are removed too.", "providers.choose": "Add provider", "providers.chooseHelp": "Pick the service to connect.", "providers.chatgpt": "ChatGPT subscription", "providers.chatgptHelp": "Use GPT models through any ChatGPT plan that includes Codex (Plus, Pro, Business…). Sign-in only, no API key.", "providers.presetHelp": "Paste an API key and you are done.", "providers.openaiGroup": "OpenAI-compatible", "providers.openaiGroupHelp": "ClaudeRipple translates requests into the OpenAI API format.","providers.custom": "Enter manually", "providers.customName": "New provider", "providers.customHelp": "Connect any Anthropic- or OpenAI-compatible API by URL and key.", "providers.formatHelp": "Pick the format the service docs name. A service whose endpoint ends in /chat/completions is OpenAI Chat Completions.", "providers.addTitle": "Add provider", "providers.edit": "Edit provider", "providers.name": "Name", "providers.nameHelp": "How this provider is called in lists and mappings.", "providers.nameRequired": "Enter a name.", "providers.apiKey": "API key", "providers.keyHelp": "The API key issued on the provider's site. Stored only in this computer's config file.", "providers.keyPlaceholder": "Paste your API key", "providers.keySaved": "The saved key stays in use. Paste a new key to replace it.", "providers.url": "API base URL", "providers.urlHelp": "Base URL of the Anthropic-compatible endpoint. Change only if the service docs differ.", "providers.openaiUrlHelp": "Base URL of the OpenAI-compatible API. Requests are translated to its Chat Completions or Responses endpoint.", "providers.wire": "API format", "providers.wireHelp": "Use Chat Completions for most providers. Select Responses only when the provider supports the stateless Responses API.", "providers.urlRequired": "Enter a valid URL.", "providers.keyType": "Auth header", "providers.keyTypeHelp": "Usually x-api-key. Pick Authorization: Bearer only if the service docs say so.", "providers.extraHeaders": "Extra HTTP headers", "providers.extraHeadersHelp": "Only headers the service requires, one 'name: value' per line.", "providers.models": "Models to use", "providers.modelsHelp": "Checked models appear in the Model mapping list.", "providers.modelsFound": "Model list reported by the provider.", "providers.modelsFallback": "Could not fetch the list; showing known default models.", "measure.running": "Measuring model capabilities… ({done}/{total})", "measure.help": "Checks which API format each model answers in and which reasoning efforts it takes — from the public model catalogue (models.dev) when it lists the model, otherwise by asking the model.", "measure.done": "Measured: {summary}", "measure.nothing": "Nothing was measured: no model answered.", "measure.notEntitled": "{models}: usable only from the provider\u2019s own app. Ticking it here will not make it work.", "measure.failed": "Could not measure model capabilities.", "measure.authFailed": "The API key was refused, so nothing could be measured.", "providers.probeNotEntitled": "The key was accepted; the plan refused.", "providerStatus.notEntitled": "Plan refused", "providers.probeOk": "Connected.", "providers.probeFailed": "Could not connect.", "providers.apiSoon": "Please try again shortly.", "providers.showInPicker": "Also show by real name in the Claude app picker", "providers.pickerOffHint": "Picker mode is off right now, so ticking this does not show them yet. You will be asked to turn it on when you save.", "providers.pickerOffPrompt": "Picker mode is off, so these models will not appear in the Claude Desktop picker yet. Turn it on now? Your operating system will ask you to confirm trusting the certificate.", "providers.credentials": "Sign-in source", "providers.credentialsHelp": "Where the ChatGPT account token comes from.", "providers.authAuto": "Auto (own sign-in if present, else Codex CLI's)", "providers.authOwn": "Sign in with ClaudeRipple", "providers.authBorrow": "Reuse Codex CLI sign-in (~/.codex/auth.json)", "providers.login": "How to sign in", "providers.loginHelp": "'Sign in to ChatGPT…' in the menu-bar app or clauderipple login in a terminal opens the browser sign-in. Tokens stay on this computer.","providers.chatgptLogin": "Sign in to ChatGPT", "providers.chatgptLoginWaiting": "Finish signing in in your browser. This will update when it is done.", "providers.chatgptLoginDone": "ChatGPT sign-in complete.", "providers.chatgptAccountsTitle": "ChatGPT accounts", "providers.chatgptAccountsSubtitle": "With several accounts, when one hits its limit the next one takes over the same request. A conversation stays on the account that answered, to keep its cache. Accounts are used in list order.", "providers.addChatgptAccount": "+ Add ChatGPT account", "providers.chatgptAddWaiting": "Sign in with the account to add in your browser. If it shows an account you already added, pick another.", "providers.chatgptAdded": "ChatGPT account added.", "providers.chatgptNoAccounts": "No ChatGPT account is signed in.", "providers.chatgptAccountCount": "{count} accounts", "providers.chatgptCodexHelp": "The Codex CLI's own login. ClaudeRipple only reads it, never refreshes it, and uses it last.", "providers.chatgptOwnHelp": "ClaudeRipple stores this login in a file only your user can read and refreshes it automatically.", "providers.chatgptInUse": "in use now", "providers.chatgptStandby": "standby", "providers.chatgptPause": "Pause", "providers.chatgptResume": "Resume", "providers.chatgptPaused": "paused", "providers.chatgptClearCooldown": "Use now", "providers.chatgptRemoveConfirm": "Remove ChatGPT account {name} from ClaudeRipple?", "providers.chatgptReauthHelp": "This login expired or was refused. Signing in again with the same account restores it in place.", "pool.coolingFor": "back in {duration}", "time.hoursMinutes": "{h}h {m}m", "time.minutes": "{m}m", "time.seconds": "{s}s", "quota.window5h": "5h", "quota.windowWeek": "weekly", "quota.windowHours": "{hours}h", "quota.line": "{window} {percent}%", "quota.resetAt": "resets {time}", "providers.effortLevels": "Reasoning effort: {levels}", "providers.effortNone": "Does not take reasoning effort", "providers.modelEffort": "effort", "providers.modelNoEffort": "no effort", "providers.defaultEffort": "Default reasoning effort", "providers.defaultEffortHelp": "Used when neither the mapping nor the app sets an effort.", "providers.identity": "Tell the model what it is (identity)", "providers.append": "System prompt addendum", "providers.appendHelp": "Fixed text appended to every request's system prompt. Changing it often breaks the prompt cache and raises cost.",
226
235
  "providerStatus.connected": "Connected", "providerStatus.keyNeeded": "Key needed", "providerStatus.loginNeeded": "Sign-in needed", "providerStatus.disconnected": "Not connected", "providerStatus.checking": "Checking",
227
236
  "clients.title": "Clients", "clients.subtitle": "Set up the apps that use ClaudeRipple.", "clients.desktop.title": "Claude Desktop", "clients.desktop.help": "Choose whether provider models appear by real name in the model picker.", "clients.codex.title": "Codex (app and CLI)", "clients.codex.help": "Adds ClaudeRipple as a provider to Codex's config (~/.codex/config.toml). Terminal: `codex --profile clauderipple -m <model>`; Codex app: make clauderipple the default provider in that config.", "clients.codex.note": "When on, Codex's GPT requests go through ClaudeRipple too. Codex keeps its own sign-in, and with several ChatGPT accounts added, the next one takes over when one hits its limit — no sign-out. The list below is the Claude and Anthropic-compatible models Codex gains.", "clients.codex.turnOn": "Use ClaudeRipple in Codex","clients.claudeCode.title": "Terminal Claude Code", "clients.claudeCode.help": "Terminal `claude` uses the same router. Choose with `/model <name>`.",
@@ -406,6 +415,7 @@ const I18N = {
406
415
  "mod.modeDelegation": "Delegation",
407
416
  "mod.modeNormal": "Normal",
408
417
  "mod.modeEager": "Eager — main chat only directs",
418
+ "mod.baseDelegationHelp": "Applies while no mode is on. A mode that is on uses its own delegation.",
409
419
  "mod.modeUntil": "End",
410
420
  "mod.modeUntilNone": "Until turned off",
411
421
  "mod.modeUntilTime": "At a time",
@@ -426,6 +436,8 @@ const I18N = {
426
436
  "mod.modeRemoveConfirm": "Delete the mode “{name}”?",
427
437
  },
428
438
  zh: {
439
+ "update.auto": "自动更新",
440
+ "update.autoHelp": "在 ClaudeRipple 空闲时自动安装新版本,待进行中的请求完成后重启。",
429
441
  "brand": "ClaudeRipple",
430
442
  "nav.health": "状态",
431
443
  "nav.slots": "模型映射",
@@ -500,7 +512,7 @@ const I18N = {
500
512
  "pool.cooling": "{seconds} 秒后恢复",
501
513
  "pool.quarantined": "已被拒绝 — 请检查密钥",
502
514
  "slots.modelSlots": "Claude Code 自带的模型槽位",
503
- "slots.modelSlotsHelp": "Claude Code 在请求产生前就已选定这些模型,因此模型映射对它们不起作用。留空时由 Claude 作答,并消耗 Claude 额度。网页搜索尤其如此:它使用小型快速模型,因此即使会话已路由到其他模型,在更改该槽位前,搜索仍会消耗 Claude 额度。更改会在安装时写入 settings.json。",
515
+ "slots.modelSlotsHelp": "Claude Code 在请求产生前就已选定这些模型,因此模型映射对它们不起作用。留空时由 Claude 作答,并消耗 Claude 额度。网页搜索除非在下方单独选择模型,否则使用小型快速模型;因此即使会话已路由到其他模型,在更改该槽位前,搜索仍会消耗 Claude 额度。更改会在安装时写入 settings.json。",
504
516
  "slots.noWebSearch": "此处不支持网页搜索",
505
517
  "slots.noWebSearchWarn": "已保存,但在该模型上网页搜索会失败——ClaudeRipple 无法通过它执行搜索。模型本身或许可以;保持默认则由 Claude 负责搜索。",
506
518
  "slots.slotDefault": "默认(Claude)",
@@ -511,6 +523,7 @@ const I18N = {
511
523
  "slots.slotHelp.subagent": "未自行指定模型的子代理",
512
524
  "slots.slotHelp.main": "会话启动时使用的模型",
513
525
  "slots.slotSaved": "模型槽位已保存,在新会话中生效。",
526
+ "slots.webSearch": "网页搜索", "slots.webSearchFollow": "跟随小型快速模型", "slots.webSearchHelp": "仅网页搜索由该模型处理。保存后立即生效,无需新会话。如果所选模型搜索失败,会回退到小型快速模型。", "slots.webSearchSaved": "已保存网页搜索模型,立即生效。",
514
527
  "slots.windowGlobal": "默认",
515
528
  "slots.windowHelp": "该模型的上下文窗口,单位为 token。经路由服务转发的模型不共用上下文窗口,而是各自使用自己的窗口。留空时采用供应商报告的值,若未报告则使用全局默认值。请填写模型的实际窗口大小,而不是压缩触发点。",
516
529
  "providers.title": "供应商",
@@ -919,6 +932,7 @@ const I18N = {
919
932
  "mod.modeDelegation": "委派强度",
920
933
  "mod.modeNormal": "普通",
921
934
  "mod.modeEager": "积极 — 主对话只指挥",
935
+ "mod.baseDelegationHelp": "没有启用模式时生效。已启用的模式会使用自己的委派强度。",
922
936
  "mod.modeUntil": "结束时间",
923
937
  "mod.modeUntilNone": "手动关闭前",
924
938
  "mod.modeUntilTime": "指定时间",
@@ -939,6 +953,8 @@ const I18N = {
939
953
  "mod.modeRemoveConfirm": "删除模式“{name}”?",
940
954
  },
941
955
  ja: {
956
+ "update.auto": "自動アップデート",
957
+ "update.autoHelp": "ClaudeRipple が待機中に新しいバージョンを自動でインストールし、処理中のリクエストが終わってから再起動します。",
942
958
  "brand": "ClaudeRipple",
943
959
  "nav.health": "ステータス",
944
960
  "nav.slots": "モデルマッピング",
@@ -1013,7 +1029,7 @@ const I18N = {
1013
1029
  "pool.cooling": "{seconds}秒後に復帰",
1014
1030
  "pool.quarantined": "拒否されました — キーを確認してください",
1015
1031
  "slots.modelSlots": "Claude Code自身のモデルスロット",
1016
- "slots.modelSlotsHelp": "これらはリクエストが作られる前にClaude Codeが決めるため、マッピングでは変更できません。空欄のままだとClaudeが応答し、Claudeのクォータを消費します。特にWeb検索は小型・高速モデルで動くため、そのスロットを移さない限り、ルーティング済みのセッションでも検索はClaudeのクォータで行われます。変更はインストール時にsettings.jsonへ書き込まれます。",
1032
+ "slots.modelSlotsHelp": "これらはリクエストが作られる前にClaude Codeが決めるため、マッピングでは変更できません。空欄のままだとClaudeが応答し、Claudeのクォータを消費します。Web検索は下で別に選ばない限り小型・高速モデルで動くため、そのスロットを移さない限り、ルーティング済みのセッションでも検索はClaudeのクォータで行われます。変更はインストール時にsettings.jsonへ書き込まれます。",
1017
1033
  "slots.noWebSearch": "Web検索は使えません",
1018
1034
  "slots.noWebSearchWarn": "保存しましたが、このモデルではWeb検索が失敗します。ClaudeRippleはこのモデル経由で検索を実行できません。モデル自体は検索できる場合もあります。デフォルトのままにしておけば、Claudeが検索します。",
1019
1035
  "slots.slotDefault": "デフォルト(Claude)",
@@ -1024,6 +1040,7 @@ const I18N = {
1024
1040
  "slots.slotHelp.subagent": "モデルを指定していないサブエージェント",
1025
1041
  "slots.slotHelp.main": "セッション開始時のモデル",
1026
1042
  "slots.slotSaved": "モデルスロットを保存しました。新しいセッションから有効になります。",
1043
+ "slots.webSearch": "Web検索", "slots.webSearchFollow": "小型・高速モデルに従う", "slots.webSearchHelp": "Web検索だけをこのモデルで処理します。保存すると新しいセッションなしで即座に反映されます。選んだモデルで検索に失敗した場合は「小型・高速モデル」に戻ります。", "slots.webSearchSaved": "Web検索モデルを保存しました。すぐに反映されます。",
1027
1044
  "slots.windowGlobal": "デフォルト",
1028
1045
  "slots.windowHelp": "このモデルのコンテキストウィンドウ(トークン数)。ルーティングされるモデルはそれぞれ異なるため、モデルごとに持ちます。空欄の場合はプロバイダーが報告した値、それもなければ全体のデフォルト値を使います。コンパクションが始まる位置ではなく、モデル本来のウィンドウサイズを入力してください。",
1029
1046
  "providers.title": "プロバイダー",
@@ -1432,6 +1449,7 @@ const I18N = {
1432
1449
  "mod.modeDelegation": "委任の強さ",
1433
1450
  "mod.modeNormal": "通常",
1434
1451
  "mod.modeEager": "積極的 — メイン会話は指揮のみ",
1452
+ "mod.baseDelegationHelp": "モードがオフのときに適用されます。オンのモードは自分の委任の強さを使います。",
1435
1453
  "mod.modeUntil": "終了時点",
1436
1454
  "mod.modeUntilNone": "オフにするまで",
1437
1455
  "mod.modeUntilTime": "時刻指定",
@@ -1452,6 +1470,8 @@ const I18N = {
1452
1470
  "mod.modeRemoveConfirm": "モード「{name}」を削除しますか?",
1453
1471
  },
1454
1472
  es: {
1473
+ "update.auto": "Actualizaciones automáticas",
1474
+ "update.autoHelp": "Instala nuevas versiones automáticamente cuando ClaudeRipple está inactivo y se reinicia cuando terminan las solicitudes en curso.",
1455
1475
  "brand": "ClaudeRipple",
1456
1476
  "nav.health": "Estado",
1457
1477
  "nav.slots": "Asignación de modelos",
@@ -1526,7 +1546,7 @@ const I18N = {
1526
1546
  "pool.cooling": "vuelve en {seconds} s",
1527
1547
  "pool.quarantined": "rechazada — revisa la clave",
1528
1548
  "slots.modelSlots": "Slots de modelos de Claude Code",
1529
- "slots.modelSlotsHelp": "Claude Code selecciona estos modelos antes de crear la solicitud, así que las asignaciones no se aplican a ellos. Si los slots se dejan vacíos, responde Claude y se consume su cuota. En particular, la búsqueda web usa el modelo pequeño y rápido, así que una sesión redirigida sigue consumiendo cuota de Claude al buscar hasta que se cambie ese slot. Los cambios se escriben en settings.json durante la instalación.",
1549
+ "slots.modelSlotsHelp": "Claude Code selecciona estos modelos antes de crear la solicitud, así que las asignaciones no se aplican a ellos. Si los slots se dejan vacíos, responde Claude y se consume su cuota. En particular, la búsqueda web usa el modelo pequeño y rápido salvo que se elija uno abajo, así que una sesión redirigida sigue consumiendo cuota de Claude al buscar hasta que se cambie uno de esos slots. Los cambios se escriben en settings.json durante la instalación.",
1530
1550
  "slots.noWebSearch": "sin búsqueda web aquí",
1531
1551
  "slots.noWebSearchWarn": "Guardado, pero la búsqueda web fallará con este modelo: ClaudeRipple no puede hacer búsquedas a través de él. Puede que el propio modelo sí pueda; si lo dejas en el valor predeterminado, Claude se encarga de buscar.",
1532
1552
  "slots.slotDefault": "predeterminado (Claude)",
@@ -1537,6 +1557,7 @@ const I18N = {
1537
1557
  "slots.slotHelp.subagent": "un subagente que no indica un modelo propio",
1538
1558
  "slots.slotHelp.main": "con el que empieza una sesión",
1539
1559
  "slots.slotSaved": "Slots de modelos guardados. Se aplican en una sesión nueva.",
1560
+ "slots.webSearch": "Búsqueda web", "slots.webSearchFollow": "Seguir el modelo pequeño y rápido", "slots.webSearchHelp": "Solo la búsqueda web la responde este modelo. Se aplica al instante, sin sesión nueva. Si el modelo elegido no logra buscar, la búsqueda pasa al modelo pequeño y rápido.", "slots.webSearchSaved": "Modelo de búsqueda web guardado. Se aplica al instante.",
1540
1561
  "slots.windowGlobal": "predeterminado",
1541
1562
  "slots.windowHelp": "La ventana de contexto de este modelo, en tokens. Los modelos redirigidos no comparten una, así que cada uno tiene la suya. Si se deja en blanco, se usa la que indicó el proveedor o, si no la indicó, el valor predeterminado global. Introduce la ventana real del modelo, no un punto de compactación.",
1542
1563
  "providers.title": "Proveedores",
@@ -1945,6 +1966,7 @@ const I18N = {
1945
1966
  "mod.modeDelegation": "Delegación",
1946
1967
  "mod.modeNormal": "Normal",
1947
1968
  "mod.modeEager": "Activa — el chat principal solo dirige",
1969
+ "mod.baseDelegationHelp": "Se aplica cuando no hay ningún modo activo. Un modo activo usa su propia delegación.",
1948
1970
  "mod.modeUntil": "Fin",
1949
1971
  "mod.modeUntilNone": "Hasta apagarlo",
1950
1972
  "mod.modeUntilTime": "A una hora",
@@ -1965,6 +1987,8 @@ const I18N = {
1965
1987
  "mod.modeRemoveConfirm": "¿Eliminar el modo «{name}»?",
1966
1988
  },
1967
1989
  pt: {
1990
+ "update.auto": "Atualizações automáticas",
1991
+ "update.autoHelp": "Instala novas versões automaticamente enquanto o ClaudeRipple está ocioso e reinicia quando as requisições em andamento terminam.",
1968
1992
  "brand": "ClaudeRipple",
1969
1993
  "nav.health": "Status",
1970
1994
  "nav.slots": "Mapeamento de modelos",
@@ -2039,7 +2063,7 @@ const I18N = {
2039
2063
  "pool.cooling": "volta em {seconds}s",
2040
2064
  "pool.quarantined": "recusado — verifique a chave",
2041
2065
  "slots.modelSlots": "Slots de modelo do próprio Claude Code",
2042
- "slots.modelSlotsHelp": "O Claude Code escolhe estes modelos antes de existir uma requisição, então um mapeamento não os alcança. Se ficarem vazios, o Claude responde por eles e eles consomem cota do Claude. A busca na web, em especial, roda no modelo pequeno e rápido, então uma sessão roteada continua buscando com cota do Claude até que esse slot seja trocado. As alterações são gravadas no settings.json na instalação.",
2066
+ "slots.modelSlotsHelp": "O Claude Code escolhe estes modelos antes de existir uma requisição, então um mapeamento não os alcança. Se ficarem vazios, o Claude responde por eles e eles consomem cota do Claude. A busca na web, em especial, roda no modelo pequeno e rápido a menos que um seja escolhido abaixo, então uma sessão roteada continua buscando com cota do Claude até que um desses slots seja trocado. As alterações são gravadas no settings.json na instalação.",
2043
2067
  "slots.noWebSearch": "sem busca na web aqui",
2044
2068
  "slots.noWebSearchWarn": "Salvo, mas a busca na web vai falhar neste modelo — o ClaudeRipple não consegue fazer uma busca por ele. O modelo em si talvez consiga; deixar este slot no padrão faz o Claude buscar.",
2045
2069
  "slots.slotDefault": "padrão (Claude)",
@@ -2050,6 +2074,7 @@ const I18N = {
2050
2074
  "slots.slotHelp.subagent": "subagente que não define um modelo próprio",
2051
2075
  "slots.slotHelp.main": "o modelo com que a sessão começa",
2052
2076
  "slots.slotSaved": "Slots de modelo salvos. Valem a partir de uma nova sessão.",
2077
+ "slots.webSearch": "Pesquisa na web", "slots.webSearchFollow": "Seguir o modelo pequeno e rápido", "slots.webSearchHelp": "Somente a pesquisa na web é respondida por este modelo. Vale imediatamente, sem nova sessão. Se o modelo escolhido não conseguir pesquisar, a pesquisa volta ao modelo pequeno e rápido.", "slots.webSearchSaved": "Modelo de pesquisa na web salvo. Vale imediatamente.",
2053
2078
  "slots.windowGlobal": "padrão",
2054
2079
  "slots.windowHelp": "A janela de contexto deste modelo, em tokens. Modelos roteados não compartilham uma janela, então cada um tem a sua. Em branco, usa o valor informado pelo provedor e, na falta dele, o padrão global. Informe a janela real do modelo, não um ponto de compactação.",
2055
2080
  "providers.title": "Provedores",
@@ -2458,6 +2483,7 @@ const I18N = {
2458
2483
  "mod.modeDelegation": "Delegação",
2459
2484
  "mod.modeNormal": "Normal",
2460
2485
  "mod.modeEager": "Ativa — o chat principal só dirige",
2486
+ "mod.baseDelegationHelp": "Aplica-se quando nenhum modo está ativo. Um modo ativo usa a própria delegação.",
2461
2487
  "mod.modeUntil": "Fim",
2462
2488
  "mod.modeUntilNone": "Até desligar",
2463
2489
  "mod.modeUntilTime": "Em um horário",
@@ -2479,84 +2505,194 @@ const I18N = {
2479
2505
  },
2480
2506
  };
2481
2507
 
2508
+ // Optional domain ladders use the same editor in Base and every saved mode.
2509
+ Object.assign(I18N.ko, {
2510
+ "mod.workerDomain": "작업 분야", "mod.domain.general": "전체", "mod.domain.ui": "프론트엔드·디자인", "mod.domain.backend": "백엔드",
2511
+ "mod.domainSeparate": "따로 정하기", "mod.domainFollowsGeneral": "전체 설정을 따릅니다.",
2512
+ "mod.domainLevel.light": "가벼운 작업", "mod.domainLevel.heavy": "무거운 작업",
2513
+ "mod.domainHint.ui.light": "문구·스타일 수정, 시안대로 화면 구현, 컴포넌트 정리",
2514
+ "mod.domainHint.ui.heavy": "레이아웃·상태 버그, 화면 설계, 디자인 판단",
2515
+ "mod.domainHint.backend.light": "설정·로그 확인, 명세대로 API·로직 구현, 테스트 추가",
2516
+ "mod.domainHint.backend.heavy": "동시성·정합성 디버깅, 성능·보안, 데이터 설계",
2517
+ });
2518
+ Object.assign(I18N.en, {
2519
+ "mod.workerDomain": "Task domain", "mod.domain.general": "General", "mod.domain.ui": "Frontend & design", "mod.domain.backend": "Backend",
2520
+ "mod.domainSeparate": "Set separately", "mod.domainFollowsGeneral": "Follows this set's general settings.",
2521
+ "mod.domainLevel.light": "Light work", "mod.domainLevel.heavy": "Heavy work",
2522
+ "mod.domainHint.ui.light": "Copy and style tweaks, screens from a design, tidy components",
2523
+ "mod.domainHint.ui.heavy": "Layout and state bugs, screen design, design system, brand",
2524
+ "mod.domainHint.backend.light": "Config and log checks, specified APIs and logic, tests",
2525
+ "mod.domainHint.backend.heavy": "Concurrency and integrity bugs, performance, security, data models",
2526
+ });
2527
+ Object.assign(I18N.zh, {
2528
+ "mod.workerDomain": "任务领域", "mod.domain.general": "全部", "mod.domain.ui": "前端与设计", "mod.domain.backend": "后端",
2529
+ "mod.domainSeparate": "单独设置", "mod.domainFollowsGeneral": "沿用此组的通用设置。",
2530
+ "mod.domainLevel.light": "轻松工作", "mod.domainLevel.heavy": "繁重工作",
2531
+ "mod.domainHint.ui.light": "文案样式修改、按设计实现页面、整理组件",
2532
+ "mod.domainHint.ui.heavy": "布局与状态缺陷、页面设计、设计系统与品牌",
2533
+ "mod.domainHint.backend.light": "查看配置与日志、按规格实现接口、补充测试",
2534
+ "mod.domainHint.backend.heavy": "并发与一致性缺陷、性能安全、数据模型设计",
2535
+ });
2536
+ Object.assign(I18N.ja, {
2537
+ "mod.workerDomain": "作業分野", "mod.domain.general": "全体", "mod.domain.ui": "フロントエンド・デザイン", "mod.domain.backend": "バックエンド",
2538
+ "mod.domainSeparate": "個別に設定", "mod.domainFollowsGeneral": "このセットの全体設定に従います。",
2539
+ "mod.domainLevel.light": "軽い作業", "mod.domainLevel.heavy": "重い作業",
2540
+ "mod.domainHint.ui.light": "文言・スタイル修正、設計どおりの実装、部品整理",
2541
+ "mod.domainHint.ui.heavy": "レイアウトや状態バグ、画面設計、デザインシステム",
2542
+ "mod.domainHint.backend.light": "設定・ログ確認、仕様どおりのAPI実装、テスト追加",
2543
+ "mod.domainHint.backend.heavy": "並行処理・整合性デバッグ、性能・セキュリティ、DB設計",
2544
+ });
2545
+ Object.assign(I18N.es, {
2546
+ "mod.workerDomain": "Área de la tarea", "mod.domain.general": "General", "mod.domain.ui": "Frontend y diseño", "mod.domain.backend": "Backend",
2547
+ "mod.domainSeparate": "Configurar por separado", "mod.domainFollowsGeneral": "Usa los ajustes generales de este conjunto.",
2548
+ "mod.domainLevel.light": "Trabajo ligero", "mod.domainLevel.heavy": "Trabajo pesado",
2549
+ "mod.domainHint.ui.light": "Ajustes de texto y estilo, pantallas según diseño, ordenar componentes",
2550
+ "mod.domainHint.ui.heavy": "Fallos de maquetación y estado, diseño de pantallas, sistemas de diseño",
2551
+ "mod.domainHint.backend.light": "Revisar configuración y logs, APIs especificadas, añadir pruebas",
2552
+ "mod.domainHint.backend.heavy": "Concurrencia e integridad, rendimiento y seguridad, modelos de datos",
2553
+ });
2554
+ Object.assign(I18N.pt, {
2555
+ "mod.workerDomain": "Área da tarefa", "mod.domain.general": "Geral", "mod.domain.ui": "Frontend e design", "mod.domain.backend": "Backend",
2556
+ "mod.domainSeparate": "Configurar separadamente", "mod.domainFollowsGeneral": "Usa as configurações gerais deste conjunto.",
2557
+ "mod.domainLevel.light": "Trabalho leve", "mod.domainLevel.heavy": "Trabalho pesado",
2558
+ "mod.domainHint.ui.light": "Ajustes de texto e estilo, telas pelo design, organizar componentes",
2559
+ "mod.domainHint.ui.heavy": "Falhas de layout e estado, design de telas, design systems e marca",
2560
+ "mod.domainHint.backend.light": "Revisar configuração e logs, APIs especificadas, adicionar testes",
2561
+ "mod.domainHint.backend.heavy": "Concorrência e integridade, desempenho e segurança, modelos de dados",
2562
+ });
2563
+
2482
2564
  // Phone control vocabulary stays together across the dashboard's six languages.
2483
2565
  Object.assign(I18N.ko, {
2484
2566
  "nav.phone": "휴대폰", "phone.subtitle": "Ripple Remote 앱을 연결하고, 휴대폰 연결과 알림을 관리합니다.",
2485
2567
  "phone.refresh": "새로고침", "phone.status": "연결 상태", "phone.host": "원격 호스트", "phone.running": "실행 중", "phone.stopped": "중지됨",
2486
2568
  "phone.mode": "연결 방식", "phone.relayStatus": "Relay — 어디서나 + 알림", "phone.directStatus": "같은 네트워크", "phone.localStatus": "꺼짐",
2487
2569
  "phone.notifications": "알림", "phone.ready": "준비됨", "phone.needsRelay": "Relay 필요", "phone.install": "저장소 폴더에서 터미널을 열고 호스트를 설치·시작하세요.",
2488
- "phone.pairing": "휴대폰 연결 (QR)", "phone.scan": "휴대폰 카메라로 스캔", "phone.showQr": "QR 코드 보기", "phone.copyLink": "링크 복사", "phone.copied": "링크를 복사했습니다.", "phone.copyFailed": "링크를 복사하지 못했습니다. 브라우저의 클립보드 권한을 확인하세요.", "phone.qrAlt": "Ripple Remote 연결 QR 코드",
2570
+ "phone.pairing": "휴대폰 연결 (QR)", "phone.scan": "Ripple Remote 앱으로 스캔", "phone.scanHelp": "앱에서 'QR 코드 스캔'을 누르세요. 앱을 받는 링크가 아닙니다.", "phone.showQr": "QR 코드 보기", "phone.copyLink": "링크 복사", "phone.copied": "링크를 복사했습니다.", "phone.copyFailed": "링크를 복사하지 못했습니다. 브라우저의 클립보드 권한을 확인하세요.", "phone.qrAlt": "Ripple Remote 연결 QR 코드",
2489
2571
  "phone.qrWarning": "코드와 링크에 페어링 키가 들어 있습니다. 본인 휴대폰에만 보여 주세요.", "phone.unreachable": "지금은 이 컴퓨터에서만 연결됩니다. 휴대폰으로 연결하려면 아래에서 Relay나 네트워크 연결을 설정하세요.",
2490
2572
  "phone.settings": "휴대폰 접속", "phone.choice.relay": "켜기 — Relay (권장)", "phone.help.relay": "어디서나 연결되고 알림도 받습니다. 대화는 종단간 암호화됩니다.", "phone.choice.local": "끄기", "phone.help.local": "휴대폰에서 이 컴퓨터에 연결할 수 없습니다.",
2491
2573
  "phone.apply": "변경 사항 적용",
2492
2574
  "phone.restartRequired": "호스트를 다시 시작해야 적용됩니다", "phone.restart": "재시작", "phone.restartConfirm": "호스트를 다시 시작할까요? 휴대폰 연결과 진행 중인 원격 작업이 끊길 수 있습니다.", "phone.restarting": "호스트를 다시 시작했습니다. 잠시 후 상태를 확인합니다.", "phone.manualRestart": "이 운영체제에서는 터미널에서 실행하세요: {command}",
2493
- "phone.phones": "연결된 휴대폰", "phone.connected": "연결됨", "phone.offline": "오프라인", "phone.lastSeen": "마지막 접속: {time}", "phone.unknownSeen": "마지막 접속 기록 없음", "phone.pushOn": "알림 켜짐", "phone.pushOff": "알림 꺼짐", "phone.empty": "아직 연결된 휴대폰이 없습니다. QR 코드로 연결하세요.",
2575
+ "phone.phones": "연결된 휴대폰", "phone.connected": "연결됨", "phone.offline": "오프라인", "phone.lastSeen": "마지막 접속: {time}", "phone.unknownSeen": "마지막 접속 기록 없음", "phone.pushOn": "알림 켜짐", "phone.pushOff": "알림 꺼짐", "phone.empty": "아직 연결된 휴대폰이 없습니다. Ripple Remote 앱에서 QR 코드를 스캔하세요.",
2494
2576
  "phone.rotate": "모든 휴대폰 연결 끊기 (새 키)", "phone.rotateConfirm": "모든 휴대폰의 연결과 알림을 끊고 새 키를 발급할까요? 기존 QR과 링크는 쓸 수 없게 되고, 휴대폰을 다시 연결해야 합니다.", "phone.rotated": "모든 휴대폰 연결을 끊고 키를 바꿨습니다.", "phone.sharedKey": "모든 휴대폰이 페어링 키 하나를 함께 씁니다. 하나만 연결 해제하는 것은 지원하지 않습니다.",
2577
+ "phone.promo.eyebrow": "Ripple Remote · 무료 휴대폰 앱", "phone.promo.title": "GPT·DeepSeek·Gemini 세션을 휴대폰에서 이어 가세요",
2578
+ "phone.promo.lead": "리플이 돌리는 세션이라면 어떤 모델이든 휴대폰에서 그대로 다룹니다. 30분이 지나도 연결이 끊기지 않습니다.",
2579
+ "phone.promo.models": "모델을 가리지 않습니다.", "phone.promo.modelsBody": "GPT·DeepSeek·Gemini·Claude, 어떤 세션이든 똑같이 보이고 똑같이 다룹니다. 다음 턴의 모델과 추론 강도도 휴대폰에서 바꿉니다.",
2580
+ "phone.promo.hours": "몇 분이 아니라 몇 시간.", "phone.promo.hoursBody": "컴퓨터가 켜져 있는 동안 연결이 유지됩니다. 두 시간짜리 리팩터링도 끝까지 지켜보세요.",
2581
+ "phone.promo.private": "비공개, 무료.", "phone.promo.privateBody": "메시지·첨부·알림은 종단간 암호화됩니다. 회원가입·광고·추적 없음.",
2582
+ "phone.promo.appStore": "App Store에서 받기", "phone.promo.appStoreShort": "App Store", "phone.promo.android": "안드로이드: Google Play 비공개 테스트 중", "phone.promo.androidLink": "디스코드에서 신청",
2583
+ "phone.promo.nextQr": "설치한 뒤 앱에서 'QR 코드 스캔'을 누르고 아래 휴대폰 연결 QR을 비추면 끝입니다.",
2584
+ "phone.promo.nextHost": "앱을 설치하고 아래 안내대로 원격 호스트를 시작하면, 이 화면에서 휴대폰 연결 QR을 띄울 수 있습니다.",
2585
+ "phone.promo.compact": "다른 휴대폰에도 Ripple Remote 설치",
2495
2586
  });
2496
2587
  Object.assign(I18N.en, {
2497
2588
  "nav.phone": "Phone", "phone.subtitle": "Pair Ripple Remote and manage phone connections and notifications.",
2498
2589
  "phone.refresh": "Refresh", "phone.status": "Connection status", "phone.host": "Remote host", "phone.running": "Running", "phone.stopped": "Stopped",
2499
2590
  "phone.mode": "Connection mode", "phone.relayStatus": "Relay — anywhere + notifications", "phone.directStatus": "Same network", "phone.localStatus": "Off",
2500
2591
  "phone.notifications": "Notifications", "phone.ready": "Ready", "phone.needsRelay": "Relay required", "phone.install": "Install and start the host from a terminal in the repository folder.",
2501
- "phone.pairing": "Pair a phone (QR)", "phone.scan": "Scan with your phone camera", "phone.showQr": "Show QR code", "phone.copyLink": "Copy link", "phone.copied": "Link copied.", "phone.copyFailed": "Could not copy the link. Check the browser's clipboard permission.", "phone.qrAlt": "Ripple Remote pairing QR code",
2592
+ "phone.pairing": "Pair a phone (QR)", "phone.scan": "Scan with the Ripple Remote app", "phone.scanHelp": "Tap “Scan QR code” in the app. This is not a download link.", "phone.showQr": "Show QR code", "phone.copyLink": "Copy link", "phone.copied": "Link copied.", "phone.copyFailed": "Could not copy the link. Check the browser's clipboard permission.", "phone.qrAlt": "Ripple Remote pairing QR code",
2502
2593
  "phone.qrWarning": "The code and link carry the pairing key. Show them only to your own phone.", "phone.unreachable": "Only this computer can connect right now. Choose Relay or a network connection below to reach it from your phone.",
2503
2594
  "phone.settings": "Phone access", "phone.choice.relay": "On — Relay (recommended)", "phone.help.relay": "Connect from anywhere, with notifications. Conversations are end-to-end encrypted.", "phone.choice.local": "Off", "phone.help.local": "Phones cannot connect to this computer.",
2504
2595
  "phone.apply": "Apply changes",
2505
2596
  "phone.restartRequired": "Restart the host to apply changes", "phone.restart": "Restart", "phone.restartConfirm": "Restart the host? Phone connections and active remote work may be interrupted.", "phone.restarting": "Host restarted. Checking its status shortly.", "phone.manualRestart": "On this operating system, run in a terminal: {command}",
2506
- "phone.phones": "Connected phones", "phone.connected": "Connected", "phone.offline": "Offline", "phone.lastSeen": "Last seen: {time}", "phone.unknownSeen": "Last seen unknown", "phone.pushOn": "Notifications on", "phone.pushOff": "Notifications off", "phone.empty": "No phones yet. Pair one with the QR code.",
2597
+ "phone.phones": "Connected phones", "phone.connected": "Connected", "phone.offline": "Offline", "phone.lastSeen": "Last seen: {time}", "phone.unknownSeen": "Last seen unknown", "phone.pushOn": "Notifications on", "phone.pushOff": "Notifications off", "phone.empty": "No phones yet. Scan the QR code from the Ripple Remote app.",
2507
2598
  "phone.rotate": "Disconnect all phones (new key)", "phone.rotateConfirm": "Disconnect every phone, remove notifications and issue a new key? Existing QR codes and links will stop working. Each phone must pair again.", "phone.rotated": "All phones disconnected and pairing key replaced.", "phone.sharedKey": "All phones share one pairing key. Individual unpairing is not supported.",
2599
+ "phone.promo.eyebrow": "Ripple Remote · free phone app", "phone.promo.title": "Your GPT, DeepSeek and Gemini sessions, on your phone",
2600
+ "phone.promo.lead": "Any session ClaudeRipple runs, whichever model runs it, works on your phone as it is. And it does not drop you after 30 minutes.",
2601
+ "phone.promo.models": "Any model you route.", "phone.promo.modelsBody": "GPT, DeepSeek, Gemini or Claude: every session looks and works the same. Switch the next turn's model and reasoning effort from your phone.",
2602
+ "phone.promo.hours": "Hours, not minutes.", "phone.promo.hoursBody": "The connection holds as long as your computer is on. Watch a two-hour refactor through to the end.",
2603
+ "phone.promo.private": "Private and free.", "phone.promo.privateBody": "Messages, attachments and notifications are end-to-end encrypted. No account, no ads, no tracking.",
2604
+ "phone.promo.appStore": "Get it on the App Store", "phone.promo.appStoreShort": "App Store", "phone.promo.android": "Android: closed test on Google Play", "phone.promo.androidLink": "ask on Discord",
2605
+ "phone.promo.nextQr": "Once it is installed, tap “Scan QR code” in the app and point it at the pairing QR below.",
2606
+ "phone.promo.nextHost": "Once it is installed, start the remote host as shown below, and this page can show the pairing QR.",
2607
+ "phone.promo.compact": "Install Ripple Remote on another phone",
2508
2608
  });
2509
2609
  Object.assign(I18N.zh, {
2510
2610
  "nav.phone": "手机", "phone.subtitle": "配对 Ripple Remote,管理手机连接和通知。",
2511
2611
  "phone.refresh": "刷新", "phone.status": "连接状态", "phone.host": "远程主机", "phone.running": "运行中", "phone.stopped": "已停止",
2512
2612
  "phone.mode": "连接方式", "phone.relayStatus": "Relay — 随处连接 + 通知", "phone.directStatus": "同一网络", "phone.localStatus": "仅此电脑",
2513
2613
  "phone.notifications": "通知", "phone.ready": "已就绪", "phone.needsRelay": "需要 Relay", "phone.install": "在仓库文件夹中打开终端,安装并启动主机。",
2514
- "phone.pairing": "配对手机 (QR)", "phone.scan": "用手机相机扫描", "phone.showQr": "显示 QR 码", "phone.copyLink": "复制链接", "phone.copied": "已复制链接。", "phone.copyFailed": "无法复制链接,请检查浏览器剪贴板权限。", "phone.qrAlt": "Ripple Remote 配对 QR 码",
2614
+ "phone.pairing": "配对手机 (QR)", "phone.scan": "用 Ripple Remote 应用扫描", "phone.scanHelp": "请在应用内扫描。这不是下载链接。", "phone.showQr": "显示 QR 码", "phone.copyLink": "复制链接", "phone.copied": "已复制链接。", "phone.copyFailed": "无法复制链接,请检查浏览器剪贴板权限。", "phone.qrAlt": "Ripple Remote 配对 QR 码",
2515
2615
  "phone.qrWarning": "QR 码和链接包含配对密钥,仅向您自己的手机展示。", "phone.unreachable": "当前仅此电脑可连接。请在下方选择 Relay 或网络连接以连接手机。",
2516
2616
  "phone.settings": "手机访问", "phone.choice.relay": "开启 — Relay(推荐)", "phone.help.relay": "随处连接并接收通知。对话端到端加密。", "phone.choice.local": "关闭", "phone.help.local": "手机无法连接到此电脑。",
2517
2617
  "phone.apply": "应用更改",
2518
2618
  "phone.restartRequired": "重启主机后更改生效", "phone.restart": "重启", "phone.restartConfirm": "重启主机?手机连接和正在进行的远程任务可能会中断。", "phone.restarting": "主机已重启,稍后检查状态。", "phone.manualRestart": "请在此操作系统的终端中运行:{command}",
2519
- "phone.phones": "已连接的手机", "phone.connected": "已连接", "phone.offline": "离线", "phone.lastSeen": "最后连接:{time}", "phone.unknownSeen": "无最后连接记录", "phone.pushOn": "通知已开启", "phone.pushOff": "通知已关闭", "phone.empty": "尚无手机,请使用 QR 码配对。",
2619
+ "phone.phones": "已连接的手机", "phone.connected": "已连接", "phone.offline": "离线", "phone.lastSeen": "最后连接:{time}", "phone.unknownSeen": "无最后连接记录", "phone.pushOn": "通知已开启", "phone.pushOff": "通知已关闭", "phone.empty": "尚无手机,请在 Ripple Remote 应用中扫描 QR 码。",
2520
2620
  "phone.rotate": "断开所有手机(新密钥)", "phone.rotateConfirm": "断开所有手机、移除通知订阅并生成新密钥?现有 QR 码和链接将失效,所有手机都需要重新配对。", "phone.rotated": "已断开所有手机并更换配对密钥。", "phone.sharedKey": "所有手机共用一个配对密钥,不支持单独解除配对。",
2621
+ "phone.promo.eyebrow": "Ripple Remote · 免费手机应用", "phone.promo.title": "GPT、DeepSeek、Gemini 会话,在手机上接着用",
2622
+ "phone.promo.lead": "只要是 ClaudeRipple 在跑的会话,不管哪个模型,都能在手机上原样操作。过了 30 分钟也不断线。",
2623
+ "phone.promo.models": "路由哪个模型都行。", "phone.promo.modelsBody": "GPT、DeepSeek、Gemini 还是 Claude:每个会话看起来、用起来都一样。下一轮的模型和推理强度也在手机上切换。",
2624
+ "phone.promo.hours": "按小时算,不是按分钟。", "phone.promo.hoursBody": "只要电脑开着,连接就一直保持。两小时的代码重构也能盯到最后。",
2625
+ "phone.promo.private": "私密,免费。", "phone.promo.privateBody": "消息、附件和通知端到端加密。无需注册、无广告、无跟踪。",
2626
+ "phone.promo.appStore": "在 App Store 获取", "phone.promo.appStoreShort": "App Store", "phone.promo.android": "Android:Google Play 封闭测试中", "phone.promo.androidLink": "到 Discord 申请",
2627
+ "phone.promo.nextQr": "安装后,在应用内扫描下方的配对 QR 码即可。",
2628
+ "phone.promo.nextHost": "安装后,按下方说明启动远程主机,此页面即可显示配对 QR 码。",
2629
+ "phone.promo.compact": "在另一部手机上安装 Ripple Remote",
2521
2630
  });
2522
2631
  Object.assign(I18N.ja, {
2523
2632
  "nav.phone": "スマートフォン", "phone.subtitle": "Ripple Remote をペアリングし、接続と通知を管理します。",
2524
2633
  "phone.refresh": "更新", "phone.status": "接続状況", "phone.host": "リモートホスト", "phone.running": "実行中", "phone.stopped": "停止中",
2525
2634
  "phone.mode": "接続方式", "phone.relayStatus": "Relay — どこからでも + 通知", "phone.directStatus": "同じネットワーク", "phone.localStatus": "このコンピュータのみ",
2526
2635
  "phone.notifications": "通知", "phone.ready": "準備完了", "phone.needsRelay": "Relay が必要", "phone.install": "リポジトリのフォルダ内でターミナルを開き、ホストをインストールして起動してください。",
2527
- "phone.pairing": "スマートフォンの接続 (QR)", "phone.scan": "スマートフォンのカメラでスキャン", "phone.showQr": "QR コードを表示", "phone.copyLink": "リンクをコピー", "phone.copied": "リンクをコピーしました。", "phone.copyFailed": "コピーできませんでした。ブラウザのクリップボード権限を確認してください。", "phone.qrAlt": "Ripple Remote ペアリング QR コード",
2636
+ "phone.pairing": "スマートフォンの接続 (QR)", "phone.scan": "Ripple Remote アプリでスキャン", "phone.scanHelp": "アプリ内でスキャンします。ダウンロード用のリンクではありません。", "phone.showQr": "QR コードを表示", "phone.copyLink": "リンクをコピー", "phone.copied": "リンクをコピーしました。", "phone.copyFailed": "コピーできませんでした。ブラウザのクリップボード権限を確認してください。", "phone.qrAlt": "Ripple Remote ペアリング QR コード",
2528
2637
  "phone.qrWarning": "コードとリンクにはペアリングキーが含まれています。ご自身のスマートフォンにのみ見せてください。", "phone.unreachable": "現在はこのコンピュータのみ接続できます。スマートフォンから接続するには、下で Relay またはネットワーク接続を選んでください。",
2529
2638
  "phone.settings": "スマートフォンからのアクセス", "phone.choice.relay": "オン — Relay(推奨)", "phone.help.relay": "どこからでも接続でき、通知も届きます。会話はエンドツーエンドで暗号化されます。", "phone.choice.local": "オフ", "phone.help.local": "スマートフォンからこのコンピュータに接続できません。",
2530
2639
  "phone.apply": "変更を適用",
2531
2640
  "phone.restartRequired": "適用するにはホストの再起動が必要です", "phone.restart": "再起動", "phone.restartConfirm": "ホストを再起動しますか?スマートフォンの接続と実行中のリモート作業が中断する場合があります。", "phone.restarting": "ホストを再起動しました。まもなく状態を確認します。", "phone.manualRestart": "この OS ではターミナルで実行してください:{command}",
2532
- "phone.phones": "接続済みスマートフォン", "phone.connected": "接続中", "phone.offline": "オフライン", "phone.lastSeen": "最終接続:{time}", "phone.unknownSeen": "最終接続の記録なし", "phone.pushOn": "通知オン", "phone.pushOff": "通知オフ", "phone.empty": "スマートフォンはまだありません。QR コードで接続してください。",
2641
+ "phone.phones": "接続済みスマートフォン", "phone.connected": "接続中", "phone.offline": "オフライン", "phone.lastSeen": "最終接続:{time}", "phone.unknownSeen": "最終接続の記録なし", "phone.pushOn": "通知オン", "phone.pushOff": "通知オフ", "phone.empty": "スマートフォンはまだありません。Ripple Remote アプリで QR コードをスキャンしてください。",
2533
2642
  "phone.rotate": "すべての接続を解除(新しいキー)", "phone.rotateConfirm": "すべての接続と通知を解除し、新しいキーを発行しますか?既存の QR コードとリンクは無効になり、再ペアリングが必要です。", "phone.rotated": "すべての接続を解除し、キーを更新しました。", "phone.sharedKey": "すべての端末で一つのキーを共有します。個別のペアリング解除はできません。",
2643
+ "phone.promo.eyebrow": "Ripple Remote · 無料のスマートフォンアプリ", "phone.promo.title": "GPT・DeepSeek・Gemini のセッションを、スマートフォンで",
2644
+ "phone.promo.lead": "ClaudeRipple が動かすセッションなら、どのモデルでもスマートフォンからそのまま操作できます。30 分たっても接続は切れません。",
2645
+ "phone.promo.models": "モデルを問いません。", "phone.promo.modelsBody": "GPT、DeepSeek、Gemini、Claude——どのセッションも同じ見た目で同じように動きます。次のターンのモデルと推論強度もスマートフォンから切り替えられます。",
2646
+ "phone.promo.hours": "数分ではなく数時間。", "phone.promo.hoursBody": "コンピュータの電源が入っている間、接続は保たれます。2 時間のリファクタリングも最後まで見守れます。",
2647
+ "phone.promo.private": "プライベート、無料。", "phone.promo.privateBody": "メッセージ、添付ファイル、通知はエンドツーエンドで暗号化されます。アカウント登録・広告・トラッキングなし。",
2648
+ "phone.promo.appStore": "App Store で入手", "phone.promo.appStoreShort": "App Store", "phone.promo.android": "Android:Google Play のクローズドテスト中", "phone.promo.androidLink": "Discord で申請",
2649
+ "phone.promo.nextQr": "インストールしたら、アプリで下のペアリング QR コードをスキャンするだけです。",
2650
+ "phone.promo.nextHost": "インストールしたら、下の案内どおりにリモートホストを起動してください。この画面にペアリング QR コードを表示できるようになります。",
2651
+ "phone.promo.compact": "別のスマートフォンに Ripple Remote をインストール",
2534
2652
  });
2535
2653
  Object.assign(I18N.es, {
2536
2654
  "nav.phone": "Teléfono", "phone.subtitle": "Vincula Ripple Remote y gestiona las conexiones y notificaciones del teléfono.",
2537
2655
  "phone.refresh": "Actualizar", "phone.status": "Estado de conexión", "phone.host": "Host remoto", "phone.running": "En ejecución", "phone.stopped": "Detenido",
2538
2656
  "phone.mode": "Modo de conexión", "phone.relayStatus": "Relay — desde cualquier lugar + avisos", "phone.directStatus": "Misma red", "phone.localStatus": "Solo este equipo",
2539
2657
  "phone.notifications": "Notificaciones", "phone.ready": "Listas", "phone.needsRelay": "Requiere Relay", "phone.install": "Instala e inicia el host desde una terminal en la carpeta del repositorio.",
2540
- "phone.pairing": "Vincular teléfono (QR)", "phone.scan": "Escanea con la cámara del teléfono", "phone.showQr": "Mostrar código QR", "phone.copyLink": "Copiar enlace", "phone.copied": "Enlace copiado.", "phone.copyFailed": "No se pudo copiar. Revisa el permiso del portapapeles del navegador.", "phone.qrAlt": "Código QR de vinculación de Ripple Remote",
2658
+ "phone.pairing": "Vincular teléfono (QR)", "phone.scan": "Escanea con la app Ripple Remote", "phone.scanHelp": "Escanéalo desde la app. No es un enlace de descarga.", "phone.showQr": "Mostrar código QR", "phone.copyLink": "Copiar enlace", "phone.copied": "Enlace copiado.", "phone.copyFailed": "No se pudo copiar. Revisa el permiso del portapapeles del navegador.", "phone.qrAlt": "Código QR de vinculación de Ripple Remote",
2541
2659
  "phone.qrWarning": "El código y el enlace contienen la clave de vinculación. Muéstralos solo a tu propio teléfono.", "phone.unreachable": "Ahora solo puede conectarse este equipo. Elige Relay o una conexión de red para conectar tu teléfono.",
2542
2660
  "phone.settings": "Acceso desde el teléfono", "phone.choice.relay": "Activado — Relay (recomendado)", "phone.help.relay": "Conéctate desde cualquier lugar, con avisos. Las conversaciones se cifran de extremo a extremo.", "phone.choice.local": "Desactivado", "phone.help.local": "Los teléfonos no pueden conectarse a este equipo.",
2543
2661
  "phone.apply": "Aplicar cambios",
2544
2662
  "phone.restartRequired": "Reinicia el host para aplicar los cambios", "phone.restart": "Reiniciar", "phone.restartConfirm": "¿Reiniciar el host? Las conexiones y tareas remotas activas podrían interrumpirse.", "phone.restarting": "Host reiniciado. Comprobaremos su estado en breve.", "phone.manualRestart": "En este sistema operativo, ejecuta en una terminal: {command}",
2545
- "phone.phones": "Teléfonos conectados", "phone.connected": "Conectado", "phone.offline": "Sin conexión", "phone.lastSeen": "Última conexión: {time}", "phone.unknownSeen": "Última conexión desconocida", "phone.pushOn": "Notificaciones activadas", "phone.pushOff": "Notificaciones desactivadas", "phone.empty": "Aún no hay teléfonos. Vincula uno con el QR.",
2663
+ "phone.phones": "Teléfonos conectados", "phone.connected": "Conectado", "phone.offline": "Sin conexión", "phone.lastSeen": "Última conexión: {time}", "phone.unknownSeen": "Última conexión desconocida", "phone.pushOn": "Notificaciones activadas", "phone.pushOff": "Notificaciones desactivadas", "phone.empty": "Aún no hay teléfonos. Escanea el QR desde la app Ripple Remote.",
2546
2664
  "phone.rotate": "Desconectar todos (nueva clave)", "phone.rotateConfirm": "¿Desconectar todos los teléfonos, eliminar sus avisos y emitir una nueva clave? Los QR y enlaces actuales dejarán de funcionar. Habrá que vincular cada teléfono de nuevo.", "phone.rotated": "Todos los teléfonos desconectados y clave reemplazada.", "phone.sharedKey": "Todos comparten una clave. No se admite desvincular teléfonos individualmente.",
2665
+ "phone.promo.eyebrow": "Ripple Remote · app gratuita para el teléfono", "phone.promo.title": "Tus sesiones de GPT, DeepSeek y Gemini, en el teléfono",
2666
+ "phone.promo.lead": "Cualquier sesión que ejecute ClaudeRipple, sea cual sea el modelo, se maneja desde el teléfono tal cual. Y no se corta a los 30 minutos.",
2667
+ "phone.promo.models": "Cualquier modelo que enrutes.", "phone.promo.modelsBody": "GPT, DeepSeek, Gemini o Claude: todas las sesiones se ven y funcionan igual. Cambia el modelo y el esfuerzo de razonamiento del siguiente turno desde el teléfono.",
2668
+ "phone.promo.hours": "Horas, no minutos.", "phone.promo.hoursBody": "La conexión se mantiene mientras tu computadora esté encendida. Sigue una refactorización de dos horas hasta el final.",
2669
+ "phone.promo.private": "Privada y gratis.", "phone.promo.privateBody": "Los mensajes, adjuntos y notificaciones van cifrados de extremo a extremo. Sin cuenta, sin anuncios, sin rastreo.",
2670
+ "phone.promo.appStore": "Descargar en App Store", "phone.promo.appStoreShort": "App Store", "phone.promo.android": "Android: prueba cerrada en Google Play", "phone.promo.androidLink": "pide acceso en Discord",
2671
+ "phone.promo.nextQr": "Cuando la tengas, escanea desde la app el código QR de vinculación de abajo.",
2672
+ "phone.promo.nextHost": "Cuando la tengas, inicia el host remoto como se indica abajo y esta página podrá mostrar el código QR de vinculación.",
2673
+ "phone.promo.compact": "Instala Ripple Remote en otro teléfono",
2547
2674
  });
2548
2675
  Object.assign(I18N.pt, {
2549
2676
  "nav.phone": "Celular", "phone.subtitle": "Pareie o Ripple Remote e gerencie conexões e notificações do celular.",
2550
2677
  "phone.refresh": "Atualizar", "phone.status": "Status da conexão", "phone.host": "Host remoto", "phone.running": "Em execução", "phone.stopped": "Parado",
2551
2678
  "phone.mode": "Modo de conexão", "phone.relayStatus": "Relay — de qualquer lugar + avisos", "phone.directStatus": "Mesma rede", "phone.localStatus": "Somente este computador",
2552
2679
  "phone.notifications": "Notificações", "phone.ready": "Prontas", "phone.needsRelay": "Requer Relay", "phone.install": "Instale e inicie o host em um terminal na pasta do repositório.",
2553
- "phone.pairing": "Parear celular (QR)", "phone.scan": "Escaneie com a câmera do celular", "phone.showQr": "Mostrar QR code", "phone.copyLink": "Copiar link", "phone.copied": "Link copiado.", "phone.copyFailed": "Não foi possível copiar. Verifique a permissão da área de transferência do navegador.", "phone.qrAlt": "QR code de pareamento do Ripple Remote",
2680
+ "phone.pairing": "Parear celular (QR)", "phone.scan": "Escaneie com o app Ripple Remote", "phone.scanHelp": "Escaneie pelo app. Não é um link de download.", "phone.showQr": "Mostrar QR code", "phone.copyLink": "Copiar link", "phone.copied": "Link copiado.", "phone.copyFailed": "Não foi possível copiar. Verifique a permissão da área de transferência do navegador.", "phone.qrAlt": "QR code de pareamento do Ripple Remote",
2554
2681
  "phone.qrWarning": "O código e o link contêm a chave de pareamento. Mostre-os apenas ao seu próprio celular.", "phone.unreachable": "Por enquanto, só este computador pode se conectar. Escolha Relay ou uma conexão de rede abaixo para usar o celular.",
2555
2682
  "phone.settings": "Acesso pelo celular", "phone.choice.relay": "Ativado — Relay (recomendado)", "phone.help.relay": "Conecte de qualquer lugar, com notificações. As conversas têm criptografia de ponta a ponta.", "phone.choice.local": "Desativado", "phone.help.local": "Celulares não podem se conectar a este computador.",
2556
2683
  "phone.apply": "Aplicar alterações",
2557
2684
  "phone.restartRequired": "Reinicie o host para aplicar as alterações", "phone.restart": "Reiniciar", "phone.restartConfirm": "Reiniciar o host? As conexões e tarefas remotas em andamento podem ser interrompidas.", "phone.restarting": "Host reiniciado. Verificaremos o status em breve.", "phone.manualRestart": "Neste sistema operacional, execute em um terminal: {command}",
2558
- "phone.phones": "Celulares conectados", "phone.connected": "Conectado", "phone.offline": "Offline", "phone.lastSeen": "Última conexão: {time}", "phone.unknownSeen": "Última conexão desconhecida", "phone.pushOn": "Notificações ativadas", "phone.pushOff": "Notificações desativadas", "phone.empty": "Nenhum celular ainda. Pareie um com o QR code.",
2685
+ "phone.phones": "Celulares conectados", "phone.connected": "Conectado", "phone.offline": "Offline", "phone.lastSeen": "Última conexão: {time}", "phone.unknownSeen": "Última conexão desconhecida", "phone.pushOn": "Notificações ativadas", "phone.pushOff": "Notificações desativadas", "phone.empty": "Nenhum celular ainda. Escaneie o QR code pelo app Ripple Remote.",
2559
2686
  "phone.rotate": "Desconectar todos (nova chave)", "phone.rotateConfirm": "Desconectar todos os celulares, remover as notificações e gerar uma nova chave? Os QR codes e links atuais deixarão de funcionar. Será necessário parear cada celular novamente.", "phone.rotated": "Todos os celulares desconectados e chave substituída.", "phone.sharedKey": "Todos compartilham uma chave. Não há suporte para desemparelhar celulares individualmente.",
2687
+ "phone.promo.eyebrow": "Ripple Remote · app de celular gratuito", "phone.promo.title": "Suas sessões de GPT, DeepSeek e Gemini, no celular",
2688
+ "phone.promo.lead": "Qualquer sessão que o ClaudeRipple rode, seja qual for o modelo, funciona no celular como está. E a conexão não cai depois de 30 minutos.",
2689
+ "phone.promo.models": "Qualquer modelo que você rotear.", "phone.promo.modelsBody": "GPT, DeepSeek, Gemini ou Claude: toda sessão aparece e funciona igual. Troque o modelo e o esforço de raciocínio do próximo turno pelo celular.",
2690
+ "phone.promo.hours": "Horas, não minutos.", "phone.promo.hoursBody": "A conexão se mantém enquanto o computador estiver ligado. Acompanhe uma refatoração de duas horas até o fim.",
2691
+ "phone.promo.private": "Privado e grátis.", "phone.promo.privateBody": "Mensagens, anexos e notificações têm criptografia de ponta a ponta. Sem conta, sem anúncios, sem rastreamento.",
2692
+ "phone.promo.appStore": "Baixar na App Store", "phone.promo.appStoreShort": "App Store", "phone.promo.android": "Android: teste fechado no Google Play", "phone.promo.androidLink": "peça acesso no Discord",
2693
+ "phone.promo.nextQr": "Depois de instalar, escaneie pelo app o QR code de pareamento abaixo.",
2694
+ "phone.promo.nextHost": "Depois de instalar, inicie o host remoto como indicado abaixo, e esta página poderá mostrar o QR code de pareamento.",
2695
+ "phone.promo.compact": "Instale o Ripple Remote em outro celular",
2560
2696
  });
2561
2697
 
2562
2698
  // The connection card and the "Ripple is down" pages, drawn only inside the ClaudeRipple app.