From c19383d7c5b10b297be7d124b42b56cbe343d188 Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Sat, 13 Jun 2026 06:10:33 +0100 Subject: [PATCH 01/10] refac --- .../src/lib/components/Admin/Settings.svelte | 255 -------------- .../src/lib/components/Admin/Users.svelte | 47 ++- .../src/lib/components/Admin/Web.svelte | 314 ++++++++++++++++++ cptr/frontend/src/lib/components/Icon.svelte | 7 + .../lib/components/Settings/Browser.svelte | 254 -------------- .../src/lib/components/SettingsModal.svelte | 15 +- cptr/frontend/src/lib/i18n/locales/en.json | 1 - cptr/frontend/src/lib/i18n/locales/fr.json | 1 - 8 files changed, 371 insertions(+), 523 deletions(-) delete mode 100644 cptr/frontend/src/lib/components/Admin/Settings.svelte create mode 100644 cptr/frontend/src/lib/components/Admin/Web.svelte delete mode 100644 cptr/frontend/src/lib/components/Settings/Browser.svelte diff --git a/cptr/frontend/src/lib/components/Admin/Settings.svelte b/cptr/frontend/src/lib/components/Admin/Settings.svelte deleted file mode 100644 index 592f7c5..0000000 --- a/cptr/frontend/src/lib/components/Admin/Settings.svelte +++ /dev/null @@ -1,255 +0,0 @@ - - -
-

{$t('admin.settings')}

- - {#if loading} -
- -
- {:else} - -

{$t('admin.authentication')}

- -
- {$t('admin.allowSignUp')} - updateConfig('auth.signup_enabled', v)} - disabled={saving} - /> -
-

- {config['auth.signup_enabled'] ? $t('admin.signUpEnabled') : $t('admin.signUpDisabled')} -

- - -

{$t('admin.web')}

- - -
- {$t('admin.webEnabled')} - updateConfig('web.enabled', config['web.enabled'] === false)} - disabled={saving} - /> -
-

- {config['web.enabled'] !== false ? $t('admin.webEnabledHint') : $t('admin.webDisabledHint')} -

- - {#if config['web.enabled'] !== false} - -
- {$t('admin.webSearchProvider')} - -
- - - {@const provider = config['web.search_provider'] || 'auto'} - - {#if provider === 'auto'} -

{$t('admin.webAutoHint')}

- {:else if provider === 'exa'} -
- - saveKey('web.exa_api_key', exaKey)} - disabled={saving} - /> -

- {$t('admin.webExaHint')} -

-
- {:else if provider === 'tavily'} -
- - saveKey('web.tavily_api_key', tavilyKey)} - disabled={saving} - /> -

- {$t('admin.webTavilyHint')} -

-
- {:else if provider === 'brave'} -
- - saveKey('web.brave_api_key', braveKey)} - disabled={saving} - /> -

- {$t('admin.webBraveHint')} -

-
- {:else if provider === 'duckduckgo'} -

- {$t('admin.webDuckDuckGoNote')} -

- {:else if provider === 'perplexity'} -
- - saveKey('web.perplexity_api_key', perplexityKey)} - disabled={saving} - /> -

- {$t('admin.webPerplexityHint')} -

-
- {:else if provider === 'chat_completions'} -
-
- - saveKey('web.chat_completions_base_url', ccBaseUrl)} - disabled={saving} - /> -
-
- - saveKey('web.chat_completions_api_key', ccKey)} - disabled={saving} - /> -
-
- - saveKey('web.chat_completions_model', ccModel)} - disabled={saving} - /> -
-

- {$t('admin.webCcHint')} -

-
- {/if} - {/if} - -
- -
- {/if} -
diff --git a/cptr/frontend/src/lib/components/Admin/Users.svelte b/cptr/frontend/src/lib/components/Admin/Users.svelte index e9c7030..91de2d8 100644 --- a/cptr/frontend/src/lib/components/Admin/Users.svelte +++ b/cptr/frontend/src/lib/components/Admin/Users.svelte @@ -4,10 +4,11 @@ import CreateUserModal from './CreateUserModal.svelte'; import EditUserModal from './EditUserModal.svelte'; import { onMount } from 'svelte'; - import { listUsers } from '$lib/apis/admin'; + import { listUsers, getAdminConfig, updateConfig } from '$lib/apis/admin'; import { session } from '$lib/session'; import { t } from '$lib/i18n'; import Spinner from '$lib/components/common/Spinner.svelte'; + import ToggleSwitch from '$lib/components/common/ToggleSwitch.svelte'; interface User { user_id: string; @@ -24,6 +25,10 @@ let loading = $state(true); let page = $state(0); + // Auth config + let signupEnabled = $state(false); + let savingConfig = $state(false); + let showCreate = $state(false); let editUser = $state(null); @@ -41,6 +46,26 @@ } } + async function loadAuthConfig() { + try { + const config = await getAdminConfig(); + signupEnabled = config['auth.signup_enabled'] === true; + } catch {} + } + + async function toggleSignup(value: boolean) { + savingConfig = true; + try { + await updateConfig({ 'auth.signup_enabled': value }); + signupEnabled = value; + toast.success($t('settings.saved')); + } catch { + toast.error($t('admin.failedToSave')); + } finally { + savingConfig = false; + } + } + function handleCreated() { showCreate = false; loadUsers(); @@ -52,7 +77,10 @@ if (page >= totalPages) page = Math.max(0, totalPages - 1); } - onMount(loadUsers); + onMount(() => { + loadUsers(); + loadAuthConfig(); + });
@@ -65,6 +93,21 @@
+ +
+ +

+ {signupEnabled ? $t('admin.signUpEnabled') : $t('admin.signUpDisabled')} +

+
+ {#if loading}
diff --git a/cptr/frontend/src/lib/components/Admin/Web.svelte b/cptr/frontend/src/lib/components/Admin/Web.svelte new file mode 100644 index 0000000..40a12a5 --- /dev/null +++ b/cptr/frontend/src/lib/components/Admin/Web.svelte @@ -0,0 +1,314 @@ + + +
+

{$t('admin.web')}

+ + {#if loading} +
+ {:else} + +

Search

+ +
+ +

+ {webEnabled ? $t('admin.webEnabledHint') : $t('admin.webDisabledHint')} +

+ + {#if webEnabled} +
+ {$t('admin.webSearchProvider')} + +
+

+ {#if searchProvider === 'auto'} + {$t('admin.webAutoHint')} + {:else if searchProvider === 'duckduckgo'} + {$t('admin.webDuckDuckGoNote')} + {/if} +

+ + {#if searchProvider === 'exa'} +
+ + +

{$t('admin.webExaHint')}

+
+ {:else if searchProvider === 'tavily'} +
+ + +

{$t('admin.webTavilyHint')}

+
+ {:else if searchProvider === 'brave'} +
+ + +

{$t('admin.webBraveHint')}

+
+ {:else if searchProvider === 'perplexity'} +
+ + +

{$t('admin.webPerplexityHint')}

+
+ {:else if searchProvider === 'chat_completions'} +
+ + +
+
+ + +
+
+ + +
+

{$t('admin.webCcHint')}

+ {/if} + {/if} +
+ + +

Browser

+ +
+ +

+ Give the AI access to a web browser for navigating pages, clicking elements, and taking screenshots. +

+ + {#if browserEnabled} +
+ Provider + +
+

+ {#if browserProvider === 'local'} + Connects to Chrome via DevTools Protocol. Full interactive browsing with clicking, typing, and screenshots. + {:else if browserProvider === 'firecrawl'} + Cloud API that converts web pages to markdown. Fast extraction, no interactive browsing. + {:else} + Cloud API for LLM-driven browser tasks. Describe what you need in natural language. + {/if} +

+ + {#if browserProvider === 'local'} + + +
+ +
+ + +
+ {#if testResult} +

+ {testResult.message} +

+ {/if} +
+ +
+ +
+ + minutes +
+
+ {:else if browserProvider === 'firecrawl'} +
+ + +
+
+ + +

Change for self-hosted Firecrawl instances

+
+ {:else if browserProvider === 'browser_use'} +
+ + +
+
+ + +
+ {/if} + {/if} +
+ + +
+ +
+ {/if} +
diff --git a/cptr/frontend/src/lib/components/Icon.svelte b/cptr/frontend/src/lib/components/Icon.svelte index c9261a6..3137450 100644 --- a/cptr/frontend/src/lib/components/Icon.svelte +++ b/cptr/frontend/src/lib/components/Icon.svelte @@ -360,6 +360,13 @@ + {:else if name === 'globe'} + + + + + + {:else if name === 'microphone'} - import { toast } from 'svelte-sonner'; - import ToggleSwitch from '../common/ToggleSwitch.svelte'; - import Spinner from '../common/Spinner.svelte'; - import { onMount } from 'svelte'; - import { getAdminConfig, updateConfig } from '$lib/apis/admin'; - import { t } from '$lib/i18n'; - - let loading = $state(true); - let saving = $state(false); - let testing = $state(false); - let testResult = $state<{ ok: boolean; message: string } | null>(null); - - // Config state - let enabled = $state(false); - let provider = $state<'local' | 'firecrawl' | 'browser_use'>('local'); - let cdpUrl = $state('http://localhost:9222'); - let autoLaunch = $state(true); - let sessionTimeout = $state(10); - let firecrawlApiKey = $state(''); - let firecrawlBaseUrl = $state('https://api.firecrawl.dev'); - let browserUseApiKey = $state(''); - let browserUseBaseUrl = $state('https://api.browser-use.com'); - - onMount(async () => { - try { - const config = await getAdminConfig(); - enabled = config['browser.enabled'] === true || config['browser.enabled'] === 'true'; - provider = (config['browser.provider'] as typeof provider) || 'local'; - cdpUrl = (config['browser.cdp_url'] as string) || 'http://localhost:9222'; - autoLaunch = config['browser.auto_launch'] !== false && config['browser.auto_launch'] !== 'false'; - sessionTimeout = Number(config['browser.session_timeout_minutes']) || 10; - firecrawlApiKey = (config['browser.firecrawl_api_key'] as string) || ''; - firecrawlBaseUrl = (config['browser.firecrawl_base_url'] as string) || 'https://api.firecrawl.dev'; - browserUseApiKey = (config['browser.browser_use_api_key'] as string) || ''; - browserUseBaseUrl = (config['browser.browser_use_base_url'] as string) || 'https://api.browser-use.com'; - } catch {} - loading = false; - }); - - async function save() { - saving = true; - try { - await updateConfig({ - 'browser.enabled': enabled, - 'browser.provider': provider, - 'browser.cdp_url': cdpUrl, - 'browser.auto_launch': autoLaunch, - 'browser.session_timeout_minutes': sessionTimeout, - 'browser.firecrawl_api_key': firecrawlApiKey, - 'browser.firecrawl_base_url': firecrawlBaseUrl, - 'browser.browser_use_api_key': browserUseApiKey, - 'browser.browser_use_base_url': browserUseBaseUrl - }); - toast.success($t('settings.saved')); - } catch { - toast.error('Failed to save browser settings'); - } finally { - saving = false; - } - } - - async function testConnection() { - testing = true; - testResult = null; - try { - const resp = await fetch(`${cdpUrl}/json/version`); - if (resp.ok) { - const data = await resp.json(); - testResult = { ok: true, message: data.Browser || 'Connected' }; - } else { - testResult = { ok: false, message: `HTTP ${resp.status}` }; - } - } catch { - testResult = { ok: false, message: 'Could not connect' }; - } finally { - testing = false; - } - } - - -
-

Browser

- - {#if loading} -
- {:else} - -

Enable

- -
- -

- Give the AI access to a web browser for navigating pages, clicking elements, and taking screenshots. -

-
- - {#if enabled} - -

Provider

- -
- {#each [ - { value: 'local' as const, label: 'Local CDP' }, - { value: 'firecrawl' as const, label: 'Firecrawl' }, - { value: 'browser_use' as const, label: 'Browser-Use' } - ] as opt} - - {/each} -
-

- {#if provider === 'local'} - Connects to Chrome via DevTools Protocol. Full interactive browsing with clicking, typing, and screenshots. - {:else if provider === 'firecrawl'} - Cloud API that converts web pages to markdown. Fast extraction, no interactive browsing. - {:else} - Cloud API for LLM-driven browser tasks. Describe what you need in natural language. - {/if} -

- - - {#if provider === 'local'} -

Connection

- -
- - -
- -
- - -
- {#if testResult} -

- {testResult.message} -

- {/if} -
- -
- -
- - minutes -
-
-
- {/if} - - - {#if provider === 'firecrawl'} -

Firecrawl

- -
-
- - -
-
- - -

Change for self-hosted Firecrawl instances

-
-
- {/if} - - - {#if provider === 'browser_use'} -

Browser-Use

- -
-
- - -
-
- - -
-
- {/if} - {/if} - - -
- -
- {/if} -
diff --git a/cptr/frontend/src/lib/components/SettingsModal.svelte b/cptr/frontend/src/lib/components/SettingsModal.svelte index 936a54b..07a2b2c 100644 --- a/cptr/frontend/src/lib/components/SettingsModal.svelte +++ b/cptr/frontend/src/lib/components/SettingsModal.svelte @@ -4,7 +4,6 @@ import General from './Settings/General.svelte'; import Account from './Settings/Account.svelte'; import Keyboard from './Settings/Keyboard.svelte'; - import Browser from './Settings/Browser.svelte'; import About from './Settings/About.svelte'; import Users from './Admin/Users.svelte'; import Connections from './Admin/Connections.svelte'; @@ -12,14 +11,13 @@ import Messaging from './Admin/Messaging.svelte'; import Gateway from './Admin/Gateway.svelte'; import AudioSettings from './Admin/AudioSettings.svelte'; - import AdminSettings from './Admin/Settings.svelte'; + import AdminWeb from './Admin/Web.svelte'; import { session } from '$lib/session'; import { t } from '$lib/i18n'; type Tab = | 'general' | 'keyboard' - | 'browser' | 'account' | 'about' | 'users' @@ -28,7 +26,7 @@ | 'messaging' | 'gateway' | 'audio' - | 'admin_settings'; + | 'web'; interface Props { onclose: () => void; @@ -55,8 +53,7 @@ { id: 'messaging', label: $t('admin.messaging'), icon: 'chat-bubble' }, { id: 'gateway', label: $t('admin.gateway.tab'), icon: 'gateway' }, { id: 'audio', label: 'Audio', icon: 'microphone' }, - { id: 'browser', label: 'Browser', icon: 'browser' }, - { id: 'admin_settings', label: $t('settings.configuration'), icon: 'shield' } + { id: 'web', label: $t('admin.web'), icon: 'globe' } ]); @@ -117,8 +114,6 @@ {:else if activeTab === 'keyboard'} - {:else if activeTab === 'browser'} - {:else if activeTab === 'account'} {:else if activeTab === 'about'} @@ -135,8 +130,8 @@ {:else if activeTab === 'audio'} - {:else if activeTab === 'admin_settings'} - + {:else if activeTab === 'web'} + {/if}
diff --git a/cptr/frontend/src/lib/i18n/locales/en.json b/cptr/frontend/src/lib/i18n/locales/en.json index f0b91e0..d5b945d 100644 --- a/cptr/frontend/src/lib/i18n/locales/en.json +++ b/cptr/frontend/src/lib/i18n/locales/en.json @@ -322,7 +322,6 @@ "general.interruptDesc": "Sending a message cancels the current generation.", "settings.keyboard": "Keyboard", - "settings.configuration": "Configuration", "keyboard.title": "Keyboard Shortcuts", "keyboard.resetDefaults": "Reset defaults", diff --git a/cptr/frontend/src/lib/i18n/locales/fr.json b/cptr/frontend/src/lib/i18n/locales/fr.json index 5b6498b..02908e2 100644 --- a/cptr/frontend/src/lib/i18n/locales/fr.json +++ b/cptr/frontend/src/lib/i18n/locales/fr.json @@ -295,7 +295,6 @@ "general.queueDesc": "Les messages envoyés pendant la génération sont mis en file et envoyés après la fin.", "general.interruptDesc": "L'envoi d'un message annule la génération en cours.", "settings.keyboard": "Clavier", - "settings.configuration": "Configuration", "keyboard.title": "Raccourcis clavier", "keyboard.resetDefaults": "Réinitialiser", "keyboard.command": "Commande", From fce849d1b19e1f7f990b30774f988b5afda34549 Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Sat, 13 Jun 2026 07:39:38 +0100 Subject: [PATCH 02/10] refac --- .../lib/components/Admin/AudioSettings.svelte | 34 ++--- .../src/lib/components/GitView.svelte | 5 +- .../src/lib/components/GroupTabBar.svelte | 8 +- .../lib/components/NotificationToast.svelte | 3 +- .../src/lib/components/PortPreview.svelte | 3 +- .../lib/components/Settings/Keyboard.svelte | 2 +- .../src/lib/components/VoiceMemoModal.svelte | 23 +-- .../components/chat/AssistantMessage.svelte | 75 +++++----- .../src/lib/components/chat/ChatInput.svelte | 3 +- .../src/lib/components/chat/ChatPanel.svelte | 5 +- .../lib/components/chat/OutputEditView.svelte | 5 +- .../src/lib/components/chat/PlusMenu.svelte | 37 ++--- .../components/chat/QueuedMessageItem.svelte | 8 +- .../lib/components/chat/UserMessage.svelte | 15 +- .../src/lib/components/common/ChatItem.svelte | 3 +- .../lib/components/common/Pagination.svelte | 5 +- .../components/markdown/BlockRenderer.svelte | 11 +- .../lib/components/preview/HtmlPreview.svelte | 3 +- .../components/preview/OfficePreview.svelte | 3 +- .../lib/components/preview/SqliteView.svelte | 17 +-- cptr/frontend/src/lib/i18n/locales/de.json | 133 +++++++++++++++++- cptr/frontend/src/lib/i18n/locales/en.json | 131 ++++++++++++++++- cptr/frontend/src/lib/i18n/locales/es.json | 133 +++++++++++++++++- cptr/frontend/src/lib/i18n/locales/fr.json | 132 ++++++++++++++++- cptr/frontend/src/lib/i18n/locales/ja.json | 119 +++++++++++++++- cptr/frontend/src/lib/i18n/locales/ko.json | 119 +++++++++++++++- cptr/frontend/src/lib/i18n/locales/pt-BR.json | 119 +++++++++++++++- cptr/frontend/src/lib/i18n/locales/ru.json | 119 +++++++++++++++- cptr/frontend/src/lib/i18n/locales/zh-CN.json | 119 +++++++++++++++- cptr/frontend/src/lib/i18n/locales/zh-TW.json | 119 +++++++++++++++- 30 files changed, 1375 insertions(+), 136 deletions(-) diff --git a/cptr/frontend/src/lib/components/Admin/AudioSettings.svelte b/cptr/frontend/src/lib/components/Admin/AudioSettings.svelte index 8ae2435..32b6c71 100644 --- a/cptr/frontend/src/lib/components/Admin/AudioSettings.svelte +++ b/cptr/frontend/src/lib/components/Admin/AudioSettings.svelte @@ -52,7 +52,7 @@ toast.success($t('settings.saved')); refreshAudioState(); } catch { - toast.error('Failed to save audio settings'); + toast.error($t('admin.audio.saveFailed')); } finally { saving = false; } @@ -60,53 +60,53 @@
-

Audio

+

{$t('admin.audio.title')}

{#if loading}
{:else} -

Voice Memos

+

{$t('admin.audio.voiceMemos')}

- Record voice memos from the "+" menu. + {$t('admin.audio.voiceMemosHint')}

- {transcribeEnabled ? 'Recordings are transcribed to markdown via STT.' : 'Recordings are saved as audio only.'} + {transcribeEnabled ? $t('admin.audio.transcribeOnHint') : $t('admin.audio.transcribeOffHint')}

- Recording quality + {$t('admin.audio.recordingQuality')}

- {quality === 'high' ? 'Best quality, larger files.' : quality === 'medium' ? 'Balanced quality and size.' : 'Smallest files, optimized for speech.'} + {quality === 'high' ? $t('admin.audio.qualityHintHigh') : quality === 'medium' ? $t('admin.audio.qualityHintMedium') : $t('admin.audio.qualityHintLow')}

-

Speech-to-Text

+

{$t('admin.audio.stt')}

- +
- +
- +

- Compatible with OpenAI's audio/transcriptions API. + {$t('admin.audio.sttHint')}

diff --git a/cptr/frontend/src/lib/components/GitView.svelte b/cptr/frontend/src/lib/components/GitView.svelte index 2084745..d782100 100644 --- a/cptr/frontend/src/lib/components/GitView.svelte +++ b/cptr/frontend/src/lib/components/GitView.svelte @@ -4,6 +4,7 @@ import { gitStatusStore, type GitStatus, type GitFile } from '$lib/stores/gitStatus.svelte'; import { tooltip } from '$lib/tooltip'; + import { t } from '$lib/i18n'; import Icon from './Icon.svelte'; import Spinner from '$lib/components/common/Spinner.svelte'; @@ -309,8 +310,8 @@ class="flex h-6 w-6 items-center justify-center rounded-md text-gray-400 transition-colors hover:bg-gray-100 hover:text-gray-600 dark:hover:bg-white/6 dark:hover:text-gray-300" onclick={() => refreshReview(false)} disabled={refreshing} - aria-label="Refresh changes" - use:tooltip={'Refresh changes'} + aria-label={$t('a11y.refreshChanges')} + use:tooltip={$t('a11y.refreshChanges')} > diff --git a/cptr/frontend/src/lib/components/GroupTabBar.svelte b/cptr/frontend/src/lib/components/GroupTabBar.svelte index cf4e553..a307a84 100644 --- a/cptr/frontend/src/lib/components/GroupTabBar.svelte +++ b/cptr/frontend/src/lib/components/GroupTabBar.svelte @@ -354,8 +354,8 @@ ? 'bg-gray-200/50 text-gray-900 dark:bg-white/8 dark:text-white' : 'text-gray-400 hover:text-gray-600 dark:hover:text-gray-300'}" onclick={() => (showSplitMenu = !showSplitMenu)} - aria-label="Split Editor" - use:tooltip={'Split Editor'} + aria-label={$t('a11y.splitEditor')} + use:tooltip={$t('a11y.splitEditor')} > closeGroup(group.id)} - aria-label="Close pane" - use:tooltip={'Close pane'} + aria-label={$t('a11y.closePane')} + use:tooltip={$t('a11y.closePane')} > diff --git a/cptr/frontend/src/lib/components/NotificationToast.svelte b/cptr/frontend/src/lib/components/NotificationToast.svelte index 2d1622f..a423cb8 100644 --- a/cptr/frontend/src/lib/components/NotificationToast.svelte +++ b/cptr/frontend/src/lib/components/NotificationToast.svelte @@ -1,5 +1,6 @@
@@ -35,7 +37,7 @@
{$t('common.cancel')} {$t('chat.send')}
@@ -186,7 +187,7 @@ class="p-0.5 rounded text-gray-400 dark:text-gray-600 hover:text-gray-600 dark:hover:text-gray-300 disabled:opacity-30 disabled:cursor-default transition-colors duration-100" disabled={siblingIndex === 0} onclick={() => onnavigate?.(-1)} - aria-label="Previous message" + aria-label={$t('chat.prevMessage')} > onnavigate?.(1)} - aria-label="Next message" + aria-label={$t('chat.nextMessage')} > {#if copied} { e.stopPropagation(); onmenu?.(e); }} - aria-label="Chat options" + aria-label={$t('a11y.chatOptions')} > diff --git a/cptr/frontend/src/lib/components/common/Pagination.svelte b/cptr/frontend/src/lib/components/common/Pagination.svelte index 80c5a71..48dc6ae 100644 --- a/cptr/frontend/src/lib/components/common/Pagination.svelte +++ b/cptr/frontend/src/lib/components/common/Pagination.svelte @@ -5,6 +5,7 @@ onpagechange: (page: number) => void; } let { page, totalPages, onpagechange }: Props = $props(); + import { t } from '$lib/i18n'; // Build page numbers with ellipsis for large page counts const pages = $derived.by((): (number | 'ellipsis')[] => { @@ -28,7 +29,7 @@ class="pagination-btn" disabled={page <= 1} onclick={() => onpagechange(page - 1)} - aria-label="Previous page" + aria-label={$t('a11y.prevPage')} > = totalPages} onclick={() => onpagechange(page + 1)} - aria-label="Next page" + aria-label={$t('a11y.nextPage')} >

Empty sheet

'; + if (rows.length === 0) return `

${$t('preview.emptySheet')}

`; const colCount = rows.reduce((max, row) => Math.max(max, row.length), 0); const colLetter = (i: number) => { diff --git a/cptr/frontend/src/lib/components/preview/SqliteView.svelte b/cptr/frontend/src/lib/components/preview/SqliteView.svelte index a646cc1..476fa0c 100644 --- a/cptr/frontend/src/lib/components/preview/SqliteView.svelte +++ b/cptr/frontend/src/lib/components/preview/SqliteView.svelte @@ -2,6 +2,7 @@ import { onMount, onDestroy } from 'svelte'; import { fetchHandler } from '$lib/apis'; import Spinner from '$lib/components/common/Spinner.svelte'; + import { t } from '$lib/i18n'; interface Props { src: string; @@ -47,7 +48,7 @@ } } catch (e: any) { console.error('SQLite load error:', e); - error = e.message || 'Failed to load database.'; + error = e.message || $t('sqlite.loadError'); } finally { loading = false; } @@ -122,8 +123,8 @@ } function formatCell(val: unknown): string { - if (val === null) return 'NULL'; - if (val instanceof Uint8Array) return `[BLOB ${val.length}B]`; + if (val === null) return $t('sqlite.null'); + if (val instanceof Uint8Array) return $t('sqlite.blob', { size: val.length }); return String(val); } @@ -168,7 +169,7 @@ class:active={queryMode} onclick={() => { queryMode = true; - }}>SQL{$t('sqlite.sql')}
@@ -184,7 +185,7 @@ if ((e.metaKey || e.ctrlKey) && e.key === 'Enter') runQuery(); }} > - + {/if} @@ -219,12 +220,12 @@ {#if !queryMode && totalRows > PAGE_SIZE} {/if} diff --git a/cptr/frontend/src/lib/i18n/locales/de.json b/cptr/frontend/src/lib/i18n/locales/de.json index 964c9c4..af77dca 100644 --- a/cptr/frontend/src/lib/i18n/locales/de.json +++ b/cptr/frontend/src/lib/i18n/locales/de.json @@ -373,6 +373,135 @@ "automationModal.cancel": "Cancel", "automationModal.saving": "Saving...", "automationModal.save": "Save", - "automationModal.createBtn": "Create", - "automationModal.failedToSave": "Failed to save automation" + "automationModal.createBtn": "Erstellen", + "automationModal.failedToSave": "Automatisierung konnte nicht gespeichert werden", + + "common.cancel": "Abbrechen", + "common.save": "Speichern", + "common.edit": "Bearbeiten", + "common.copy": "Kopieren", + "common.remove": "Entfernen", + "common.close": "Schließen", + "common.dismiss": "Verwerfen", + "common.loading": "Laden", + "common.downloadCsv": "CSV herunterladen", + + "chat.tool.readFile": "Lesen {{path}}", + "chat.tool.readFileRange": "Lesen {{path}} Z{{range}}", + "chat.tool.editFile": "Bearbeiten {{path}}", + "chat.tool.multiEditFile": "Mehrfachbearbeitung {{path}}", + "chat.tool.createFile": "Erstellen {{path}}", + "chat.tool.writeFile": "Schreiben {{path}}", + "chat.tool.listDirectory": "Auflisten {{path}}", + "chat.tool.listDirectoryRecursive": "Auflisten {{path}} (rekursiv)", + "chat.tool.searchFiles": "Suche \"{{query}}\"{{scope}}", + "chat.tool.searchFilesScope": " in {{include}}", + "chat.tool.backgroundCommand": "Hintergrund: {{command}}", + "chat.tool.checkTask": "Aufgabe prüfen {{id}}", + "chat.tool.killTask": "Aufgabe beenden {{id}}", + "chat.tool.webSearch": "Websuche: \"{{query}}\"", + "chat.tool.fetchUrl": "Abrufen {{hostname}}", + "chat.tool.fetchUrlFallback": "URL abrufen", + "chat.saveAs": "Speichern unter", + "chat.send": "Senden", + "chat.artifact": "Artefakt", + "chat.exploring": "Wird erkundet", + "chat.explored": "Erkundet", + "chat.allow": "Erlauben", + "chat.deny": "Ablehnen", + "chat.toolInput": "Eingabe", + "chat.toolOutput": "Ausgabe", + "chat.toolLines": "Zeilen {{start}}–{{end}}", + "chat.totalChars": "{{count}} Zeichen insgesamt", + "chat.placeholder": "Frag alles über {{name}}...", + "chat.prevResponse": "Vorherige Antwort", + "chat.nextResponse": "Nächste Antwort", + "chat.editResponse": "Antwort bearbeiten", + "chat.copyResponse": "Antwort kopieren", + "chat.regenerateResponse": "Antwort neu generieren", + "chat.usageInfo": "Nutzungsinfo", + "chat.toggleToolCalls": "Tool-Aufrufe umschalten", + "chat.scrollToBottom": "Nach unten scrollen", + "chat.removeUpload": "Upload entfernen", + "chat.sendNow": "Jetzt senden", + "chat.prevMessage": "Vorherige Nachricht", + "chat.nextMessage": "Nächste Nachricht", + "chat.editMessage": "Nachricht bearbeiten", + "chat.copyMessage": "Nachricht kopieren", + "chat.editMessagePlaceholder": "Nachrichtentext...", + "chat.editReasoningPlaceholder": "Begründungstext...", + + "plusMenu.askApproval": "Um Genehmigung bitten", + "plusMenu.askApprovalDesc": "Jeden Tool-Aufruf vor der Ausführung bestätigen", + "plusMenu.autoApprove": "Automatisch genehmigen", + "plusMenu.autoApproveDesc": "Sichere Aktionen genehmigen, bei riskanten nachfragen", + "plusMenu.fullAccess": "Vollzugriff", + "plusMenu.fullAccessDesc": "Alle Tool-Aufrufe ohne Bestätigung ausführen", + "plusMenu.attachFiles": "Dateien anhängen", + "plusMenu.capture": "Aufnehmen", + "plusMenu.planMode": "Planungsmodus", + "plusMenu.toolPermissions": "Tool-Berechtigungen", + "plusMenu.parameters": "Parameter", + "plusMenu.noParams": "Keine Parameter konfiguriert", + "plusMenu.paramKey": "Schlüssel", + "plusMenu.paramValue": "Wert", + "plusMenu.removeParam": "Parameter entfernen", + "plusMenu.addParam": "Parameter hinzufügen", + + "voiceMemo.microphoneError": "Mikrofon konnte nicht zugegriffen werden", + "voiceMemo.uploadFailed": "Audio lokal gespeichert. Server-Upload fehlgeschlagen.", + "voiceMemo.sttNotConfigured": "STT nicht konfiguriert. Einrichten unter Einstellungen → Audio.", + "voiceMemo.writeFailed": "Transkript konnte nicht geschrieben werden. Audio ist sicher.", + "voiceMemo.done": "Fertig →", + "voiceMemo.processing": "Verarbeitung…", + "voiceMemo.filename": "Dateiname", + "voiceMemo.filenamePlaceholder": "aufnahme-name", + "voiceMemo.saved": "Gespeichert ✓", + + "admin.audio.title": "Audio", + "admin.audio.voiceMemos": "Sprachnotizen", + "admin.audio.enableVoiceMemos": "Sprachnotizen aktivieren", + "admin.audio.voiceMemosHint": "Sprachnotizen über das \"+\"-Menü aufnehmen.", + "admin.audio.autoTranscribe": "Automatisch transkribieren", + "admin.audio.transcribeOnHint": "Aufnahmen werden per STT in Markdown transkribiert.", + "admin.audio.transcribeOffHint": "Aufnahmen werden nur als Audio gespeichert.", + "admin.audio.recordingQuality": "Aufnahmequalität", + "admin.audio.qualityHigh": "Hoch (128kbps)", + "admin.audio.qualityMedium": "Mittel (64kbps)", + "admin.audio.qualityLow": "Niedrig (32kbps)", + "admin.audio.qualityHintHigh": "Beste Qualität, größere Dateien.", + "admin.audio.qualityHintMedium": "Ausgewogene Qualität und Größe.", + "admin.audio.qualityHintLow": "Kleinste Dateien, optimiert für Sprache.", + "admin.audio.stt": "Sprache-zu-Text", + "admin.audio.sttHint": "Kompatibel mit OpenAIs Audio-/Transkriptions-API.", + "admin.audio.saveFailed": "Audioeinstellungen konnten nicht gespeichert werden", + + "sqlite.loadError": "Datenbank konnte nicht geladen werden.", + "sqlite.null": "NULL", + "sqlite.blob": "[BLOB {{size}}B]", + "sqlite.sql": "SQL", + "sqlite.queryPlaceholder": "SELECT * FROM ...", + "sqlite.run": "Ausführen ⌘↵", + "sqlite.pageInfo": "{{start}}–{{end}} von {{total}}", + "sqlite.prev": "← Zurück", + "sqlite.next": "Weiter →", + + "preview.htmlTitle": "HTML-Vorschau", + "preview.emptySheet": "Leeres Blatt", + + "port.cannotConnect": "Verbindung nicht möglich", + + "connections.optional": "Optional", + "connections.leaveBlankPlaceholder": "•••••••• (leer lassen zum Beibehalten)", + + "a11y.dismissNotification": "Benachrichtigung verwerfen", + "a11y.chatOptions": "Chat-Optionen", + "a11y.prevPage": "Vorherige Seite", + "a11y.nextPage": "Nächste Seite", + "a11y.splitEditor": "Editor teilen", + "a11y.closePane": "Bereich schließen", + "a11y.refreshChanges": "Änderungen aktualisieren", + + "keyboard.clickToRebind": "Klicken zum Neuzuweisen" } + diff --git a/cptr/frontend/src/lib/i18n/locales/en.json b/cptr/frontend/src/lib/i18n/locales/en.json index d5b945d..9170de0 100644 --- a/cptr/frontend/src/lib/i18n/locales/en.json +++ b/cptr/frontend/src/lib/i18n/locales/en.json @@ -451,5 +451,134 @@ "messaging.namePlaceholder": "Bot name...", "messaging.workspacePlaceholder": "Workspace path (e.g. /Users/you/project)", "messaging.modelPlaceholder": "Model ID (e.g. openrouter/claude-sonnet-4-20250514)", - "messaging.sendersPlaceholder": "Allowed user IDs (comma-separated, leave empty for all)" + "messaging.sendersPlaceholder": "Allowed user IDs (comma-separated, leave empty for all)", + + "common.cancel": "Cancel", + "common.save": "Save", + "common.edit": "Edit", + "common.copy": "Copy", + "common.remove": "Remove", + "common.close": "Close", + "common.dismiss": "Dismiss", + "common.loading": "Loading", + "common.downloadCsv": "Download CSV", + + "chat.tool.readFile": "Read {{path}}", + "chat.tool.readFileRange": "Read {{path}} L{{range}}", + "chat.tool.editFile": "Edit {{path}}", + "chat.tool.multiEditFile": "Multi-edit {{path}}", + "chat.tool.createFile": "Create {{path}}", + "chat.tool.writeFile": "Write {{path}}", + "chat.tool.listDirectory": "List {{path}}", + "chat.tool.listDirectoryRecursive": "List {{path}} (recursive)", + "chat.tool.searchFiles": "Search \"{{query}}\"{{scope}}", + "chat.tool.searchFilesScope": " in {{include}}", + "chat.tool.backgroundCommand": "Background: {{command}}", + "chat.tool.checkTask": "Check task {{id}}", + "chat.tool.killTask": "Kill task {{id}}", + "chat.tool.webSearch": "Search web: \"{{query}}\"", + "chat.tool.fetchUrl": "Fetch {{hostname}}", + "chat.tool.fetchUrlFallback": "Fetch URL", + "chat.saveAs": "Save As", + "chat.send": "Send", + "chat.artifact": "Artifact", + "chat.exploring": "Exploring", + "chat.explored": "Explored", + "chat.allow": "Allow", + "chat.deny": "Deny", + "chat.toolInput": "Input", + "chat.toolOutput": "Output", + "chat.toolLines": "Lines {{start}}–{{end}}", + "chat.totalChars": "{{count}} total characters", + "chat.placeholder": "Ask anything about {{name}}...", + "chat.prevResponse": "Previous response", + "chat.nextResponse": "Next response", + "chat.editResponse": "Edit response", + "chat.copyResponse": "Copy response", + "chat.regenerateResponse": "Regenerate response", + "chat.usageInfo": "Usage info", + "chat.toggleToolCalls": "Toggle tool calls", + "chat.scrollToBottom": "Scroll to bottom", + "chat.removeUpload": "Remove upload", + "chat.sendNow": "Send now", + "chat.prevMessage": "Previous message", + "chat.nextMessage": "Next message", + "chat.editMessage": "Edit message", + "chat.copyMessage": "Copy message", + "chat.editMessagePlaceholder": "Message text...", + "chat.editReasoningPlaceholder": "Reasoning text...", + + "plusMenu.askApproval": "Ask for approval", + "plusMenu.askApprovalDesc": "Confirm each tool call before it runs", + "plusMenu.autoApprove": "Auto-approve", + "plusMenu.autoApproveDesc": "Approve safe actions, ask for risky ones", + "plusMenu.fullAccess": "Full access", + "plusMenu.fullAccessDesc": "All tool calls run without confirmation", + "plusMenu.attachFiles": "Attach files", + "plusMenu.capture": "Capture", + "plusMenu.planMode": "Plan mode", + "plusMenu.toolPermissions": "Tool permissions", + "plusMenu.parameters": "Parameters", + "plusMenu.noParams": "No parameters configured", + "plusMenu.paramKey": "key", + "plusMenu.paramValue": "value", + "plusMenu.removeParam": "Remove parameter", + "plusMenu.addParam": "Add parameter", + + "voiceMemo.microphoneError": "Could not access microphone", + "voiceMemo.uploadFailed": "Audio saved locally. Server upload failed.", + "voiceMemo.sttNotConfigured": "STT not configured. Set up in Settings → Audio.", + "voiceMemo.writeFailed": "Failed to write transcript. Audio is safe.", + "voiceMemo.done": "Done →", + "voiceMemo.processing": "Processing…", + "voiceMemo.filename": "Filename", + "voiceMemo.filenamePlaceholder": "recording-name", + "voiceMemo.saved": "Saved ✓", + + "admin.audio.title": "Audio", + "admin.audio.voiceMemos": "Voice Memos", + "admin.audio.enableVoiceMemos": "Enable Voice Memos", + "admin.audio.voiceMemosHint": "Record voice memos from the \"+\" menu.", + "admin.audio.autoTranscribe": "Auto-transcribe", + "admin.audio.transcribeOnHint": "Recordings are transcribed to markdown via STT.", + "admin.audio.transcribeOffHint": "Recordings are saved as audio only.", + "admin.audio.recordingQuality": "Recording quality", + "admin.audio.qualityHigh": "High (128kbps)", + "admin.audio.qualityMedium": "Medium (64kbps)", + "admin.audio.qualityLow": "Low (32kbps)", + "admin.audio.qualityHintHigh": "Best quality, larger files.", + "admin.audio.qualityHintMedium": "Balanced quality and size.", + "admin.audio.qualityHintLow": "Smallest files, optimized for speech.", + "admin.audio.stt": "Speech-to-Text", + "admin.audio.sttHint": "Compatible with OpenAI's audio/transcriptions API.", + "admin.audio.saveFailed": "Failed to save audio settings", + + "sqlite.loadError": "Failed to load database.", + "sqlite.null": "NULL", + "sqlite.blob": "[BLOB {{size}}B]", + "sqlite.sql": "SQL", + "sqlite.queryPlaceholder": "SELECT * FROM ...", + "sqlite.run": "Run ⌘↵", + "sqlite.pageInfo": "{{start}}–{{end}} of {{total}}", + "sqlite.prev": "← Prev", + "sqlite.next": "Next →", + + "preview.htmlTitle": "HTML Preview", + "preview.emptySheet": "Empty sheet", + + "port.cannotConnect": "Cannot connect", + + "connections.optional": "Optional", + "connections.leaveBlankPlaceholder": "•••••••• (leave blank to keep)", + + "a11y.dismissNotification": "Dismiss notification", + "a11y.chatOptions": "Chat options", + "a11y.prevPage": "Previous page", + "a11y.nextPage": "Next page", + "a11y.splitEditor": "Split Editor", + "a11y.closePane": "Close pane", + "a11y.refreshChanges": "Refresh changes", + + "keyboard.clickToRebind": "Click to rebind" } + diff --git a/cptr/frontend/src/lib/i18n/locales/es.json b/cptr/frontend/src/lib/i18n/locales/es.json index 274bb79..64a32ce 100644 --- a/cptr/frontend/src/lib/i18n/locales/es.json +++ b/cptr/frontend/src/lib/i18n/locales/es.json @@ -373,6 +373,135 @@ "automationModal.cancel": "Cancel", "automationModal.saving": "Saving...", "automationModal.save": "Save", - "automationModal.createBtn": "Create", - "automationModal.failedToSave": "Failed to save automation" + "automationModal.createBtn": "Crear", + "automationModal.failedToSave": "No se pudo guardar la automatización", + + "common.cancel": "Cancelar", + "common.save": "Guardar", + "common.edit": "Editar", + "common.copy": "Copiar", + "common.remove": "Eliminar", + "common.close": "Cerrar", + "common.dismiss": "Descartar", + "common.loading": "Cargando", + "common.downloadCsv": "Descargar CSV", + + "chat.tool.readFile": "Leer {{path}}", + "chat.tool.readFileRange": "Leer {{path}} L{{range}}", + "chat.tool.editFile": "Editar {{path}}", + "chat.tool.multiEditFile": "Edición múltiple {{path}}", + "chat.tool.createFile": "Crear {{path}}", + "chat.tool.writeFile": "Escribir {{path}}", + "chat.tool.listDirectory": "Listar {{path}}", + "chat.tool.listDirectoryRecursive": "Listar {{path}} (recursivo)", + "chat.tool.searchFiles": "Buscar \"{{query}}\"{{scope}}", + "chat.tool.searchFilesScope": " en {{include}}", + "chat.tool.backgroundCommand": "Segundo plano: {{command}}", + "chat.tool.checkTask": "Verificar tarea {{id}}", + "chat.tool.killTask": "Terminar tarea {{id}}", + "chat.tool.webSearch": "Buscar en web: \"{{query}}\"", + "chat.tool.fetchUrl": "Obtener {{hostname}}", + "chat.tool.fetchUrlFallback": "Obtener URL", + "chat.saveAs": "Guardar como", + "chat.send": "Enviar", + "chat.artifact": "Artefacto", + "chat.exploring": "Explorando", + "chat.explored": "Explorado", + "chat.allow": "Permitir", + "chat.deny": "Denegar", + "chat.toolInput": "Entrada", + "chat.toolOutput": "Salida", + "chat.toolLines": "Líneas {{start}}–{{end}}", + "chat.totalChars": "{{count}} caracteres en total", + "chat.placeholder": "Pregunta cualquier cosa sobre {{name}}...", + "chat.prevResponse": "Respuesta anterior", + "chat.nextResponse": "Siguiente respuesta", + "chat.editResponse": "Editar respuesta", + "chat.copyResponse": "Copiar respuesta", + "chat.regenerateResponse": "Regenerar respuesta", + "chat.usageInfo": "Info de uso", + "chat.toggleToolCalls": "Alternar llamadas a herramientas", + "chat.scrollToBottom": "Desplazar al final", + "chat.removeUpload": "Eliminar archivo subido", + "chat.sendNow": "Enviar ahora", + "chat.prevMessage": "Mensaje anterior", + "chat.nextMessage": "Siguiente mensaje", + "chat.editMessage": "Editar mensaje", + "chat.copyMessage": "Copiar mensaje", + "chat.editMessagePlaceholder": "Texto del mensaje...", + "chat.editReasoningPlaceholder": "Texto del razonamiento...", + + "plusMenu.askApproval": "Pedir aprobación", + "plusMenu.askApprovalDesc": "Confirmar cada llamada a herramienta antes de ejecutarla", + "plusMenu.autoApprove": "Aprobar automáticamente", + "plusMenu.autoApproveDesc": "Aprobar acciones seguras, preguntar por las riesgosas", + "plusMenu.fullAccess": "Acceso completo", + "plusMenu.fullAccessDesc": "Todas las llamadas se ejecutan sin confirmación", + "plusMenu.attachFiles": "Adjuntar archivos", + "plusMenu.capture": "Capturar", + "plusMenu.planMode": "Modo plan", + "plusMenu.toolPermissions": "Permisos de herramientas", + "plusMenu.parameters": "Parámetros", + "plusMenu.noParams": "No hay parámetros configurados", + "plusMenu.paramKey": "clave", + "plusMenu.paramValue": "valor", + "plusMenu.removeParam": "Eliminar parámetro", + "plusMenu.addParam": "Añadir parámetro", + + "voiceMemo.microphoneError": "No se pudo acceder al micrófono", + "voiceMemo.uploadFailed": "Audio guardado localmente. La subida al servidor falló.", + "voiceMemo.sttNotConfigured": "STT no configurado. Configúralo en Ajustes → Audio.", + "voiceMemo.writeFailed": "No se pudo escribir la transcripción. El audio está seguro.", + "voiceMemo.done": "Listo →", + "voiceMemo.processing": "Procesando…", + "voiceMemo.filename": "Nombre de archivo", + "voiceMemo.filenamePlaceholder": "nombre-grabación", + "voiceMemo.saved": "Guardado ✓", + + "admin.audio.title": "Audio", + "admin.audio.voiceMemos": "Notas de voz", + "admin.audio.enableVoiceMemos": "Activar notas de voz", + "admin.audio.voiceMemosHint": "Graba notas de voz desde el menú \"+\".", + "admin.audio.autoTranscribe": "Transcripción automática", + "admin.audio.transcribeOnHint": "Las grabaciones se transcriben a markdown mediante STT.", + "admin.audio.transcribeOffHint": "Las grabaciones se guardan solo como audio.", + "admin.audio.recordingQuality": "Calidad de grabación", + "admin.audio.qualityHigh": "Alta (128kbps)", + "admin.audio.qualityMedium": "Media (64kbps)", + "admin.audio.qualityLow": "Baja (32kbps)", + "admin.audio.qualityHintHigh": "Mejor calidad, archivos más grandes.", + "admin.audio.qualityHintMedium": "Calidad y tamaño equilibrados.", + "admin.audio.qualityHintLow": "Archivos más pequeños, optimizado para voz.", + "admin.audio.stt": "Voz a texto", + "admin.audio.sttHint": "Compatible con la API de audio/transcripciones de OpenAI.", + "admin.audio.saveFailed": "No se pudieron guardar los ajustes de audio", + + "sqlite.loadError": "No se pudo cargar la base de datos.", + "sqlite.null": "NULL", + "sqlite.blob": "[BLOB {{size}}B]", + "sqlite.sql": "SQL", + "sqlite.queryPlaceholder": "SELECT * FROM ...", + "sqlite.run": "Ejecutar ⌘↵", + "sqlite.pageInfo": "{{start}}–{{end}} de {{total}}", + "sqlite.prev": "← Anterior", + "sqlite.next": "Siguiente →", + + "preview.htmlTitle": "Vista previa HTML", + "preview.emptySheet": "Hoja vacía", + + "port.cannotConnect": "No se puede conectar", + + "connections.optional": "Opcional", + "connections.leaveBlankPlaceholder": "•••••••• (dejar en blanco para mantener)", + + "a11y.dismissNotification": "Descartar notificación", + "a11y.chatOptions": "Opciones de chat", + "a11y.prevPage": "Página anterior", + "a11y.nextPage": "Página siguiente", + "a11y.splitEditor": "Dividir editor", + "a11y.closePane": "Cerrar panel", + "a11y.refreshChanges": "Actualizar cambios", + + "keyboard.clickToRebind": "Clic para reasignar" } + diff --git a/cptr/frontend/src/lib/i18n/locales/fr.json b/cptr/frontend/src/lib/i18n/locales/fr.json index 02908e2..0e83f85 100644 --- a/cptr/frontend/src/lib/i18n/locales/fr.json +++ b/cptr/frontend/src/lib/i18n/locales/fr.json @@ -372,6 +372,134 @@ "automationModal.cancel": "Cancel", "automationModal.saving": "Saving...", "automationModal.save": "Save", - "automationModal.createBtn": "Create", - "automationModal.failedToSave": "Failed to save automation" + "automationModal.createBtn": "Créer", + "automationModal.failedToSave": "Échec de la sauvegarde de l'automatisation", + + "common.cancel": "Annuler", + "common.save": "Enregistrer", + "common.edit": "Modifier", + "common.copy": "Copier", + "common.remove": "Supprimer", + "common.close": "Fermer", + "common.dismiss": "Ignorer", + "common.loading": "Chargement", + "common.downloadCsv": "Télécharger CSV", + + "chat.tool.readFile": "Lire {{path}}", + "chat.tool.readFileRange": "Lire {{path}} L{{range}}", + "chat.tool.editFile": "Modifier {{path}}", + "chat.tool.multiEditFile": "Édition multiple {{path}}", + "chat.tool.createFile": "Créer {{path}}", + "chat.tool.writeFile": "Écrire {{path}}", + "chat.tool.listDirectory": "Lister {{path}}", + "chat.tool.listDirectoryRecursive": "Lister {{path}} (récursif)", + "chat.tool.searchFiles": "Rechercher \"{{query}}\"{{scope}}", + "chat.tool.searchFilesScope": " dans {{include}}", + "chat.tool.backgroundCommand": "Arrière-plan : {{command}}", + "chat.tool.checkTask": "Vérifier la tâche {{id}}", + "chat.tool.killTask": "Terminer la tâche {{id}}", + "chat.tool.webSearch": "Recherche web : \"{{query}}\"", + "chat.tool.fetchUrl": "Récupérer {{hostname}}", + "chat.tool.fetchUrlFallback": "Récupérer l'URL", + "chat.saveAs": "Enregistrer sous", + "chat.send": "Envoyer", + "chat.artifact": "Artefact", + "chat.exploring": "Exploration", + "chat.explored": "Exploré", + "chat.allow": "Autoriser", + "chat.deny": "Refuser", + "chat.toolInput": "Entrée", + "chat.toolOutput": "Sortie", + "chat.toolLines": "Lignes {{start}}–{{end}}", + "chat.totalChars": "{{count}} caractères au total", + "chat.placeholder": "Posez une question sur {{name}}...", + "chat.prevResponse": "Réponse précédente", + "chat.nextResponse": "Réponse suivante", + "chat.editResponse": "Modifier la réponse", + "chat.copyResponse": "Copier la réponse", + "chat.regenerateResponse": "Régénérer la réponse", + "chat.usageInfo": "Informations d'utilisation", + "chat.toggleToolCalls": "Afficher/masquer les appels d'outils", + "chat.scrollToBottom": "Défiler vers le bas", + "chat.removeUpload": "Supprimer le fichier", + "chat.sendNow": "Envoyer maintenant", + "chat.prevMessage": "Message précédent", + "chat.nextMessage": "Message suivant", + "chat.editMessage": "Modifier le message", + "chat.copyMessage": "Copier le message", + "chat.editMessagePlaceholder": "Texte du message...", + "chat.editReasoningPlaceholder": "Texte du raisonnement...", + + "plusMenu.askApproval": "Demander l'approbation", + "plusMenu.askApprovalDesc": "Confirmer chaque appel d'outil avant son exécution", + "plusMenu.autoApprove": "Approbation automatique", + "plusMenu.autoApproveDesc": "Approuver les actions sûres, demander pour les risquées", + "plusMenu.fullAccess": "Accès complet", + "plusMenu.fullAccessDesc": "Tous les appels s'exécutent sans confirmation", + "plusMenu.attachFiles": "Joindre des fichiers", + "plusMenu.capture": "Capturer", + "plusMenu.planMode": "Mode plan", + "plusMenu.toolPermissions": "Permissions des outils", + "plusMenu.parameters": "Paramètres", + "plusMenu.noParams": "Aucun paramètre configuré", + "plusMenu.paramKey": "clé", + "plusMenu.paramValue": "valeur", + "plusMenu.removeParam": "Supprimer le paramètre", + "plusMenu.addParam": "Ajouter un paramètre", + + "voiceMemo.microphoneError": "Impossible d'accéder au microphone", + "voiceMemo.uploadFailed": "Audio sauvegardé localement. Échec de l'envoi au serveur.", + "voiceMemo.sttNotConfigured": "STT non configuré. Configurez dans Paramètres → Audio.", + "voiceMemo.writeFailed": "Échec de l'écriture du transcript. L'audio est en sécurité.", + "voiceMemo.done": "Terminé →", + "voiceMemo.processing": "Traitement…", + "voiceMemo.filename": "Nom du fichier", + "voiceMemo.filenamePlaceholder": "nom-enregistrement", + "voiceMemo.saved": "Enregistré ✓", + + "admin.audio.title": "Audio", + "admin.audio.voiceMemos": "Notes vocales", + "admin.audio.enableVoiceMemos": "Activer les notes vocales", + "admin.audio.voiceMemosHint": "Enregistrer des notes vocales depuis le menu \"+\".", + "admin.audio.autoTranscribe": "Transcription automatique", + "admin.audio.transcribeOnHint": "Les enregistrements sont transcrits en markdown via STT.", + "admin.audio.transcribeOffHint": "Les enregistrements sont sauvegardés en audio uniquement.", + "admin.audio.recordingQuality": "Qualité d'enregistrement", + "admin.audio.qualityHigh": "Haute (128kbps)", + "admin.audio.qualityMedium": "Moyenne (64kbps)", + "admin.audio.qualityLow": "Basse (32kbps)", + "admin.audio.qualityHintHigh": "Meilleure qualité, fichiers plus volumineux.", + "admin.audio.qualityHintMedium": "Qualité et taille équilibrées.", + "admin.audio.qualityHintLow": "Fichiers les plus petits, optimisé pour la parole.", + "admin.audio.stt": "Reconnaissance vocale", + "admin.audio.sttHint": "Compatible avec l'API audio/transcriptions d'OpenAI.", + "admin.audio.saveFailed": "Échec de la sauvegarde des paramètres audio", + + "sqlite.loadError": "Impossible de charger la base de données.", + "sqlite.null": "NULL", + "sqlite.blob": "[BLOB {{size}}o]", + "sqlite.sql": "SQL", + "sqlite.queryPlaceholder": "SELECT * FROM ...", + "sqlite.run": "Exécuter ⌘↵", + "sqlite.pageInfo": "{{start}}–{{end}} sur {{total}}", + "sqlite.prev": "← Précédent", + "sqlite.next": "Suivant →", + + "preview.htmlTitle": "Aperçu HTML", + "preview.emptySheet": "Feuille vide", + + "port.cannotConnect": "Connexion impossible", + + "connections.optional": "Facultatif", + "connections.leaveBlankPlaceholder": "•••••••• (laisser vide pour conserver)", + + "a11y.dismissNotification": "Ignorer la notification", + "a11y.chatOptions": "Options du chat", + "a11y.prevPage": "Page précédente", + "a11y.nextPage": "Page suivante", + "a11y.splitEditor": "Diviser l'éditeur", + "a11y.closePane": "Fermer le panneau", + "a11y.refreshChanges": "Actualiser les modifications", + + "keyboard.clickToRebind": "Cliquer pour réassigner" } diff --git a/cptr/frontend/src/lib/i18n/locales/ja.json b/cptr/frontend/src/lib/i18n/locales/ja.json index 87b1d3c..8fce805 100644 --- a/cptr/frontend/src/lib/i18n/locales/ja.json +++ b/cptr/frontend/src/lib/i18n/locales/ja.json @@ -374,5 +374,122 @@ "automationModal.saving": "Saving...", "automationModal.save": "Save", "automationModal.createBtn": "Create", - "automationModal.failedToSave": "Failed to save automation" + "automationModal.failedToSave": "Failed to save automation", + "common.cancel": "キャンセル", + "common.save": "保存", + "common.edit": "編集", + "common.copy": "コピー", + "common.remove": "削除", + "common.close": "閉じる", + "common.dismiss": "閉じる", + "common.loading": "読み込み中", + "common.downloadCsv": "CSVをダウンロード", + "chat.tool.readFile": "読み取り {{path}}", + "chat.tool.readFileRange": "読み取り {{path}} L{{range}}", + "chat.tool.editFile": "編集 {{path}}", + "chat.tool.multiEditFile": "複数編集 {{path}}", + "chat.tool.createFile": "作成 {{path}}", + "chat.tool.writeFile": "書き込み {{path}}", + "chat.tool.listDirectory": "一覧 {{path}}", + "chat.tool.listDirectoryRecursive": "一覧 {{path}} (再帰)", + "chat.tool.searchFiles": "検索 \"{{query}}\"{{scope}}", + "chat.tool.searchFilesScope": " ({{include}}内)", + "chat.tool.backgroundCommand": "バックグラウンド: {{command}}", + "chat.tool.checkTask": "タスク確認 {{id}}", + "chat.tool.killTask": "タスク終了 {{id}}", + "chat.tool.webSearch": "Web検索: \"{{query}}\"", + "chat.tool.fetchUrl": "取得 {{hostname}}", + "chat.tool.fetchUrlFallback": "URL取得", + "chat.saveAs": "名前を付けて保存", + "chat.send": "送信", + "chat.artifact": "アーティファクト", + "chat.exploring": "探索中", + "chat.explored": "探索済み", + "chat.allow": "許可", + "chat.deny": "拒否", + "chat.toolInput": "入力", + "chat.toolOutput": "出力", + "chat.toolLines": "行 {{start}}–{{end}}", + "chat.totalChars": "合計 {{count}} 文字", + "chat.placeholder": "{{name}}について何でも聞いてください...", + "chat.prevResponse": "前の応答", + "chat.nextResponse": "次の応答", + "chat.editResponse": "応答を編集", + "chat.copyResponse": "応答をコピー", + "chat.regenerateResponse": "応答を再生成", + "chat.usageInfo": "使用状況", + "chat.toggleToolCalls": "ツール呼び出しの表示切替", + "chat.scrollToBottom": "一番下にスクロール", + "chat.removeUpload": "アップロードを削除", + "chat.sendNow": "今すぐ送信", + "chat.prevMessage": "前のメッセージ", + "chat.nextMessage": "次のメッセージ", + "chat.editMessage": "メッセージを編集", + "chat.copyMessage": "メッセージをコピー", + "chat.editMessagePlaceholder": "メッセージテキスト...", + "chat.editReasoningPlaceholder": "推論テキスト...", + "plusMenu.askApproval": "承認を求める", + "plusMenu.askApprovalDesc": "各ツール呼び出しの実行前に確認", + "plusMenu.autoApprove": "自動承認", + "plusMenu.autoApproveDesc": "安全な操作は承認、リスクのあるものは確認", + "plusMenu.fullAccess": "フルアクセス", + "plusMenu.fullAccessDesc": "すべてのツール呼び出しを確認なしで実行", + "plusMenu.attachFiles": "ファイルを添付", + "plusMenu.capture": "キャプチャ", + "plusMenu.planMode": "プランモード", + "plusMenu.toolPermissions": "ツール権限", + "plusMenu.parameters": "パラメータ", + "plusMenu.noParams": "パラメータは設定されていません", + "plusMenu.paramKey": "キー", + "plusMenu.paramValue": "値", + "plusMenu.removeParam": "パラメータを削除", + "plusMenu.addParam": "パラメータを追加", + "voiceMemo.microphoneError": "マイクにアクセスできません", + "voiceMemo.uploadFailed": "音声はローカルに保存されました。サーバーへのアップロードに失敗しました。", + "voiceMemo.sttNotConfigured": "STTが未設定です。設定 → オーディオで設定してください。", + "voiceMemo.writeFailed": "文字起こしの書き込みに失敗しました。音声は安全です。", + "voiceMemo.done": "完了 →", + "voiceMemo.processing": "処理中…", + "voiceMemo.filename": "ファイル名", + "voiceMemo.filenamePlaceholder": "録音名", + "voiceMemo.saved": "保存済み ✓", + "admin.audio.title": "オーディオ", + "admin.audio.voiceMemos": "音声メモ", + "admin.audio.enableVoiceMemos": "音声メモを有効にする", + "admin.audio.voiceMemosHint": "\"+\"メニューから音声メモを録音します。", + "admin.audio.autoTranscribe": "自動文字起こし", + "admin.audio.transcribeOnHint": "録音はSTTでMarkdownに文字起こしされます。", + "admin.audio.transcribeOffHint": "録音は音声のみで保存されます。", + "admin.audio.recordingQuality": "録音品質", + "admin.audio.qualityHigh": "高品質 (128kbps)", + "admin.audio.qualityMedium": "中品質 (64kbps)", + "admin.audio.qualityLow": "低品質 (32kbps)", + "admin.audio.qualityHintHigh": "最高品質、ファイルサイズ大。", + "admin.audio.qualityHintMedium": "品質とサイズのバランス。", + "admin.audio.qualityHintLow": "最小ファイル、音声に最適化。", + "admin.audio.stt": "音声テキスト変換", + "admin.audio.sttHint": "OpenAIのaudio/transcriptions APIと互換性があります。", + "admin.audio.saveFailed": "オーディオ設定の保存に失敗しました", + "sqlite.loadError": "データベースの読み込みに失敗しました。", + "sqlite.null": "NULL", + "sqlite.blob": "[BLOB {{size}}B]", + "sqlite.sql": "SQL", + "sqlite.queryPlaceholder": "SELECT * FROM ...", + "sqlite.run": "実行 ⌘↵", + "sqlite.pageInfo": "{{start}}–{{end}} / {{total}}", + "sqlite.prev": "← 前へ", + "sqlite.next": "次へ →", + "preview.htmlTitle": "HTMLプレビュー", + "preview.emptySheet": "空のシート", + "port.cannotConnect": "接続できません", + "connections.optional": "任意", + "connections.leaveBlankPlaceholder": "•••••••• (空欄で現在の値を保持)", + "a11y.dismissNotification": "通知を閉じる", + "a11y.chatOptions": "チャットオプション", + "a11y.prevPage": "前のページ", + "a11y.nextPage": "次のページ", + "a11y.splitEditor": "エディタを分割", + "a11y.closePane": "パネルを閉じる", + "a11y.refreshChanges": "変更を更新", + "keyboard.clickToRebind": "クリックして再割り当て" } diff --git a/cptr/frontend/src/lib/i18n/locales/ko.json b/cptr/frontend/src/lib/i18n/locales/ko.json index bcf9602..ca639b3 100644 --- a/cptr/frontend/src/lib/i18n/locales/ko.json +++ b/cptr/frontend/src/lib/i18n/locales/ko.json @@ -374,5 +374,122 @@ "automationModal.saving": "Saving...", "automationModal.save": "Save", "automationModal.createBtn": "Create", - "automationModal.failedToSave": "Failed to save automation" + "automationModal.failedToSave": "Failed to save automation", + "common.cancel": "취소", + "common.save": "저장", + "common.edit": "편집", + "common.copy": "복사", + "common.remove": "삭제", + "common.close": "닫기", + "common.dismiss": "닫기", + "common.loading": "로딩 중", + "common.downloadCsv": "CSV 다운로드", + "chat.tool.readFile": "읽기 {{path}}", + "chat.tool.readFileRange": "읽기 {{path}} L{{range}}", + "chat.tool.editFile": "편집 {{path}}", + "chat.tool.multiEditFile": "다중 편집 {{path}}", + "chat.tool.createFile": "생성 {{path}}", + "chat.tool.writeFile": "쓰기 {{path}}", + "chat.tool.listDirectory": "목록 {{path}}", + "chat.tool.listDirectoryRecursive": "목록 {{path}} (재귀)", + "chat.tool.searchFiles": "검색 \"{{query}}\"{{scope}}", + "chat.tool.searchFilesScope": " ({{include}} 내)", + "chat.tool.backgroundCommand": "백그라운드: {{command}}", + "chat.tool.checkTask": "작업 확인 {{id}}", + "chat.tool.killTask": "작업 종료 {{id}}", + "chat.tool.webSearch": "웹 검색: \"{{query}}\"", + "chat.tool.fetchUrl": "가져오기 {{hostname}}", + "chat.tool.fetchUrlFallback": "URL 가져오기", + "chat.saveAs": "다른 이름으로 저장", + "chat.send": "전송", + "chat.artifact": "아티팩트", + "chat.exploring": "탐색 중", + "chat.explored": "탐색 완료", + "chat.allow": "허용", + "chat.deny": "거부", + "chat.toolInput": "입력", + "chat.toolOutput": "출력", + "chat.toolLines": "줄 {{start}}–{{end}}", + "chat.totalChars": "총 {{count}}자", + "chat.placeholder": "{{name}}에 대해 무엇이든 물어보세요...", + "chat.prevResponse": "이전 응답", + "chat.nextResponse": "다음 응답", + "chat.editResponse": "응답 편집", + "chat.copyResponse": "응답 복사", + "chat.regenerateResponse": "응답 재생성", + "chat.usageInfo": "사용량 정보", + "chat.toggleToolCalls": "도구 호출 표시 전환", + "chat.scrollToBottom": "맨 아래로 스크롤", + "chat.removeUpload": "업로드 삭제", + "chat.sendNow": "지금 전송", + "chat.prevMessage": "이전 메시지", + "chat.nextMessage": "다음 메시지", + "chat.editMessage": "메시지 편집", + "chat.copyMessage": "메시지 복사", + "chat.editMessagePlaceholder": "메시지 텍스트...", + "chat.editReasoningPlaceholder": "추론 텍스트...", + "plusMenu.askApproval": "승인 요청", + "plusMenu.askApprovalDesc": "각 도구 호출 실행 전 확인", + "plusMenu.autoApprove": "자동 승인", + "plusMenu.autoApproveDesc": "안전한 작업은 승인, 위험한 작업은 확인", + "plusMenu.fullAccess": "전체 접근", + "plusMenu.fullAccessDesc": "모든 도구 호출을 확인 없이 실행", + "plusMenu.attachFiles": "파일 첨부", + "plusMenu.capture": "캡처", + "plusMenu.planMode": "계획 모드", + "plusMenu.toolPermissions": "도구 권한", + "plusMenu.parameters": "매개변수", + "plusMenu.noParams": "설정된 매개변수 없음", + "plusMenu.paramKey": "키", + "plusMenu.paramValue": "값", + "plusMenu.removeParam": "매개변수 삭제", + "plusMenu.addParam": "매개변수 추가", + "voiceMemo.microphoneError": "마이크에 접근할 수 없습니다", + "voiceMemo.uploadFailed": "오디오가 로컬에 저장되었습니다. 서버 업로드에 실패했습니다.", + "voiceMemo.sttNotConfigured": "STT가 설정되지 않았습니다. 설정 → 오디오에서 설정하세요.", + "voiceMemo.writeFailed": "자막 작성에 실패했습니다. 오디오는 안전합니다.", + "voiceMemo.done": "완료 →", + "voiceMemo.processing": "처리 중…", + "voiceMemo.filename": "파일 이름", + "voiceMemo.filenamePlaceholder": "녹음-이름", + "voiceMemo.saved": "저장됨 ✓", + "admin.audio.title": "오디오", + "admin.audio.voiceMemos": "음성 메모", + "admin.audio.enableVoiceMemos": "음성 메모 활성화", + "admin.audio.voiceMemosHint": "\"+\" 메뉴에서 음성 메모를 녹음합니다.", + "admin.audio.autoTranscribe": "자동 전사", + "admin.audio.transcribeOnHint": "녹음이 STT를 통해 마크다운으로 전사됩니다.", + "admin.audio.transcribeOffHint": "녹음이 오디오로만 저장됩니다.", + "admin.audio.recordingQuality": "녹음 품질", + "admin.audio.qualityHigh": "고품질 (128kbps)", + "admin.audio.qualityMedium": "중간 (64kbps)", + "admin.audio.qualityLow": "저품질 (32kbps)", + "admin.audio.qualityHintHigh": "최고 품질, 파일 크기 큼.", + "admin.audio.qualityHintMedium": "품질과 크기의 균형.", + "admin.audio.qualityHintLow": "가장 작은 파일, 음성에 최적화.", + "admin.audio.stt": "음성-텍스트 변환", + "admin.audio.sttHint": "OpenAI의 audio/transcriptions API와 호환됩니다.", + "admin.audio.saveFailed": "오디오 설정 저장에 실패했습니다", + "sqlite.loadError": "데이터베이스를 불러오지 못했습니다.", + "sqlite.null": "NULL", + "sqlite.blob": "[BLOB {{size}}B]", + "sqlite.sql": "SQL", + "sqlite.queryPlaceholder": "SELECT * FROM ...", + "sqlite.run": "실행 ⌘↵", + "sqlite.pageInfo": "{{start}}–{{end}} / {{total}}", + "sqlite.prev": "← 이전", + "sqlite.next": "다음 →", + "preview.htmlTitle": "HTML 미리보기", + "preview.emptySheet": "빈 시트", + "port.cannotConnect": "연결할 수 없습니다", + "connections.optional": "선택사항", + "connections.leaveBlankPlaceholder": "•••••••• (비워두면 현재 값 유지)", + "a11y.dismissNotification": "알림 닫기", + "a11y.chatOptions": "채팅 옵션", + "a11y.prevPage": "이전 페이지", + "a11y.nextPage": "다음 페이지", + "a11y.splitEditor": "편집기 분할", + "a11y.closePane": "패널 닫기", + "a11y.refreshChanges": "변경사항 새로고침", + "keyboard.clickToRebind": "클릭하여 재할당" } diff --git a/cptr/frontend/src/lib/i18n/locales/pt-BR.json b/cptr/frontend/src/lib/i18n/locales/pt-BR.json index fcf57e6..f6108e5 100644 --- a/cptr/frontend/src/lib/i18n/locales/pt-BR.json +++ b/cptr/frontend/src/lib/i18n/locales/pt-BR.json @@ -374,5 +374,122 @@ "automationModal.saving": "Saving...", "automationModal.save": "Save", "automationModal.createBtn": "Create", - "automationModal.failedToSave": "Failed to save automation" + "automationModal.failedToSave": "Failed to save automation", + "common.cancel": "Cancelar", + "common.save": "Salvar", + "common.edit": "Editar", + "common.copy": "Copiar", + "common.remove": "Remover", + "common.close": "Fechar", + "common.dismiss": "Dispensar", + "common.loading": "Carregando", + "common.downloadCsv": "Baixar CSV", + "chat.tool.readFile": "Ler {{path}}", + "chat.tool.readFileRange": "Ler {{path}} L{{range}}", + "chat.tool.editFile": "Editar {{path}}", + "chat.tool.multiEditFile": "Edição múltipla {{path}}", + "chat.tool.createFile": "Criar {{path}}", + "chat.tool.writeFile": "Escrever {{path}}", + "chat.tool.listDirectory": "Listar {{path}}", + "chat.tool.listDirectoryRecursive": "Listar {{path}} (recursivo)", + "chat.tool.searchFiles": "Buscar \"{{query}}\"{{scope}}", + "chat.tool.searchFilesScope": " em {{include}}", + "chat.tool.backgroundCommand": "Segundo plano: {{command}}", + "chat.tool.checkTask": "Verificar tarefa {{id}}", + "chat.tool.killTask": "Encerrar tarefa {{id}}", + "chat.tool.webSearch": "Busca web: \"{{query}}\"", + "chat.tool.fetchUrl": "Buscar {{hostname}}", + "chat.tool.fetchUrlFallback": "Buscar URL", + "chat.saveAs": "Salvar como", + "chat.send": "Enviar", + "chat.artifact": "Artefato", + "chat.exploring": "Explorando", + "chat.explored": "Explorado", + "chat.allow": "Permitir", + "chat.deny": "Negar", + "chat.toolInput": "Entrada", + "chat.toolOutput": "Saída", + "chat.toolLines": "Linhas {{start}}–{{end}}", + "chat.totalChars": "{{count}} caracteres no total", + "chat.placeholder": "Pergunte qualquer coisa sobre {{name}}...", + "chat.prevResponse": "Resposta anterior", + "chat.nextResponse": "Próxima resposta", + "chat.editResponse": "Editar resposta", + "chat.copyResponse": "Copiar resposta", + "chat.regenerateResponse": "Regenerar resposta", + "chat.usageInfo": "Informações de uso", + "chat.toggleToolCalls": "Alternar chamadas de ferramentas", + "chat.scrollToBottom": "Rolar para o final", + "chat.removeUpload": "Remover upload", + "chat.sendNow": "Enviar agora", + "chat.prevMessage": "Mensagem anterior", + "chat.nextMessage": "Próxima mensagem", + "chat.editMessage": "Editar mensagem", + "chat.copyMessage": "Copiar mensagem", + "chat.editMessagePlaceholder": "Texto da mensagem...", + "chat.editReasoningPlaceholder": "Texto do raciocínio...", + "plusMenu.askApproval": "Pedir aprovação", + "plusMenu.askApprovalDesc": "Confirmar cada chamada de ferramenta antes da execução", + "plusMenu.autoApprove": "Aprovação automática", + "plusMenu.autoApproveDesc": "Aprovar ações seguras, perguntar para as arriscadas", + "plusMenu.fullAccess": "Acesso total", + "plusMenu.fullAccessDesc": "Todas as chamadas executam sem confirmação", + "plusMenu.attachFiles": "Anexar arquivos", + "plusMenu.capture": "Capturar", + "plusMenu.planMode": "Modo planejamento", + "plusMenu.toolPermissions": "Permissões de ferramentas", + "plusMenu.parameters": "Parâmetros", + "plusMenu.noParams": "Nenhum parâmetro configurado", + "plusMenu.paramKey": "chave", + "plusMenu.paramValue": "valor", + "plusMenu.removeParam": "Remover parâmetro", + "plusMenu.addParam": "Adicionar parâmetro", + "voiceMemo.microphoneError": "Não foi possível acessar o microfone", + "voiceMemo.uploadFailed": "Áudio salvo localmente. Falha no upload para o servidor.", + "voiceMemo.sttNotConfigured": "STT não configurado. Configure em Configurações → Áudio.", + "voiceMemo.writeFailed": "Falha ao escrever a transcrição. O áudio está seguro.", + "voiceMemo.done": "Concluído →", + "voiceMemo.processing": "Processando…", + "voiceMemo.filename": "Nome do arquivo", + "voiceMemo.filenamePlaceholder": "nome-gravação", + "voiceMemo.saved": "Salvo ✓", + "admin.audio.title": "Áudio", + "admin.audio.voiceMemos": "Notas de voz", + "admin.audio.enableVoiceMemos": "Ativar notas de voz", + "admin.audio.voiceMemosHint": "Grave notas de voz pelo menu \"+\".", + "admin.audio.autoTranscribe": "Transcrição automática", + "admin.audio.transcribeOnHint": "As gravações são transcritas para markdown via STT.", + "admin.audio.transcribeOffHint": "As gravações são salvas apenas como áudio.", + "admin.audio.recordingQuality": "Qualidade da gravação", + "admin.audio.qualityHigh": "Alta (128kbps)", + "admin.audio.qualityMedium": "Média (64kbps)", + "admin.audio.qualityLow": "Baixa (32kbps)", + "admin.audio.qualityHintHigh": "Melhor qualidade, arquivos maiores.", + "admin.audio.qualityHintMedium": "Qualidade e tamanho equilibrados.", + "admin.audio.qualityHintLow": "Arquivos menores, otimizado para voz.", + "admin.audio.stt": "Voz para texto", + "admin.audio.sttHint": "Compatível com a API audio/transcriptions da OpenAI.", + "admin.audio.saveFailed": "Falha ao salvar as configurações de áudio", + "sqlite.loadError": "Falha ao carregar o banco de dados.", + "sqlite.null": "NULL", + "sqlite.blob": "[BLOB {{size}}B]", + "sqlite.sql": "SQL", + "sqlite.queryPlaceholder": "SELECT * FROM ...", + "sqlite.run": "Executar ⌘↵", + "sqlite.pageInfo": "{{start}}–{{end}} de {{total}}", + "sqlite.prev": "← Anterior", + "sqlite.next": "Próximo →", + "preview.htmlTitle": "Pré-visualização HTML", + "preview.emptySheet": "Planilha vazia", + "port.cannotConnect": "Não é possível conectar", + "connections.optional": "Opcional", + "connections.leaveBlankPlaceholder": "•••••••• (deixe em branco para manter)", + "a11y.dismissNotification": "Dispensar notificação", + "a11y.chatOptions": "Opções do chat", + "a11y.prevPage": "Página anterior", + "a11y.nextPage": "Próxima página", + "a11y.splitEditor": "Dividir editor", + "a11y.closePane": "Fechar painel", + "a11y.refreshChanges": "Atualizar alterações", + "keyboard.clickToRebind": "Clique para reatribuir" } diff --git a/cptr/frontend/src/lib/i18n/locales/ru.json b/cptr/frontend/src/lib/i18n/locales/ru.json index 2c01255..71392b5 100644 --- a/cptr/frontend/src/lib/i18n/locales/ru.json +++ b/cptr/frontend/src/lib/i18n/locales/ru.json @@ -374,5 +374,122 @@ "automationModal.saving": "Saving...", "automationModal.save": "Save", "automationModal.createBtn": "Create", - "automationModal.failedToSave": "Failed to save automation" + "automationModal.failedToSave": "Failed to save automation", + "common.cancel": "Отмена", + "common.save": "Сохранить", + "common.edit": "Редактировать", + "common.copy": "Копировать", + "common.remove": "Удалить", + "common.close": "Закрыть", + "common.dismiss": "Закрыть", + "common.loading": "Загрузка", + "common.downloadCsv": "Скачать CSV", + "chat.tool.readFile": "Чтение {{path}}", + "chat.tool.readFileRange": "Чтение {{path}} L{{range}}", + "chat.tool.editFile": "Редактирование {{path}}", + "chat.tool.multiEditFile": "Множественное редактирование {{path}}", + "chat.tool.createFile": "Создание {{path}}", + "chat.tool.writeFile": "Запись {{path}}", + "chat.tool.listDirectory": "Список {{path}}", + "chat.tool.listDirectoryRecursive": "Список {{path}} (рекурсивно)", + "chat.tool.searchFiles": "Поиск \"{{query}}\"{{scope}}", + "chat.tool.searchFilesScope": " в {{include}}", + "chat.tool.backgroundCommand": "Фоновый: {{command}}", + "chat.tool.checkTask": "Проверить задачу {{id}}", + "chat.tool.killTask": "Завершить задачу {{id}}", + "chat.tool.webSearch": "Поиск в сети: \"{{query}}\"", + "chat.tool.fetchUrl": "Получить {{hostname}}", + "chat.tool.fetchUrlFallback": "Получить URL", + "chat.saveAs": "Сохранить как", + "chat.send": "Отправить", + "chat.artifact": "Артефакт", + "chat.exploring": "Исследование", + "chat.explored": "Исследовано", + "chat.allow": "Разрешить", + "chat.deny": "Отклонить", + "chat.toolInput": "Ввод", + "chat.toolOutput": "Вывод", + "chat.toolLines": "Строки {{start}}–{{end}}", + "chat.totalChars": "{{count}} символов всего", + "chat.placeholder": "Спросите что-нибудь о {{name}}...", + "chat.prevResponse": "Предыдущий ответ", + "chat.nextResponse": "Следующий ответ", + "chat.editResponse": "Редактировать ответ", + "chat.copyResponse": "Копировать ответ", + "chat.regenerateResponse": "Перегенерировать ответ", + "chat.usageInfo": "Информация об использовании", + "chat.toggleToolCalls": "Переключить вызовы инструментов", + "chat.scrollToBottom": "Прокрутить вниз", + "chat.removeUpload": "Удалить загрузку", + "chat.sendNow": "Отправить сейчас", + "chat.prevMessage": "Предыдущее сообщение", + "chat.nextMessage": "Следующее сообщение", + "chat.editMessage": "Редактировать сообщение", + "chat.copyMessage": "Копировать сообщение", + "chat.editMessagePlaceholder": "Текст сообщения...", + "chat.editReasoningPlaceholder": "Текст рассуждения...", + "plusMenu.askApproval": "Запросить одобрение", + "plusMenu.askApprovalDesc": "Подтверждать каждый вызов инструмента перед выполнением", + "plusMenu.autoApprove": "Автоодобрение", + "plusMenu.autoApproveDesc": "Одобрять безопасные действия, спрашивать о рискованных", + "plusMenu.fullAccess": "Полный доступ", + "plusMenu.fullAccessDesc": "Все вызовы выполняются без подтверждения", + "plusMenu.attachFiles": "Прикрепить файлы", + "plusMenu.capture": "Захват", + "plusMenu.planMode": "Режим планирования", + "plusMenu.toolPermissions": "Разрешения инструментов", + "plusMenu.parameters": "Параметры", + "plusMenu.noParams": "Параметры не настроены", + "plusMenu.paramKey": "ключ", + "plusMenu.paramValue": "значение", + "plusMenu.removeParam": "Удалить параметр", + "plusMenu.addParam": "Добавить параметр", + "voiceMemo.microphoneError": "Не удалось получить доступ к микрофону", + "voiceMemo.uploadFailed": "Аудио сохранено локально. Загрузка на сервер не удалась.", + "voiceMemo.sttNotConfigured": "STT не настроен. Настройте в Настройки → Аудио.", + "voiceMemo.writeFailed": "Не удалось записать транскрипцию. Аудио в безопасности.", + "voiceMemo.done": "Готово →", + "voiceMemo.processing": "Обработка…", + "voiceMemo.filename": "Имя файла", + "voiceMemo.filenamePlaceholder": "название-записи", + "voiceMemo.saved": "Сохранено ✓", + "admin.audio.title": "Аудио", + "admin.audio.voiceMemos": "Голосовые заметки", + "admin.audio.enableVoiceMemos": "Включить голосовые заметки", + "admin.audio.voiceMemosHint": "Записывайте голосовые заметки из меню \"+\".", + "admin.audio.autoTranscribe": "Автоматическая транскрипция", + "admin.audio.transcribeOnHint": "Записи транскрибируются в markdown через STT.", + "admin.audio.transcribeOffHint": "Записи сохраняются только как аудио.", + "admin.audio.recordingQuality": "Качество записи", + "admin.audio.qualityHigh": "Высокое (128кбит/с)", + "admin.audio.qualityMedium": "Среднее (64кбит/с)", + "admin.audio.qualityLow": "Низкое (32кбит/с)", + "admin.audio.qualityHintHigh": "Лучшее качество, большие файлы.", + "admin.audio.qualityHintMedium": "Сбалансированное качество и размер.", + "admin.audio.qualityHintLow": "Наименьшие файлы, оптимизировано для речи.", + "admin.audio.stt": "Распознавание речи", + "admin.audio.sttHint": "Совместимо с API audio/transcriptions OpenAI.", + "admin.audio.saveFailed": "Не удалось сохранить настройки аудио", + "sqlite.loadError": "Не удалось загрузить базу данных.", + "sqlite.null": "NULL", + "sqlite.blob": "[BLOB {{size}}Б]", + "sqlite.sql": "SQL", + "sqlite.queryPlaceholder": "SELECT * FROM ...", + "sqlite.run": "Выполнить ⌘↵", + "sqlite.pageInfo": "{{start}}–{{end}} из {{total}}", + "sqlite.prev": "← Назад", + "sqlite.next": "Далее →", + "preview.htmlTitle": "Предпросмотр HTML", + "preview.emptySheet": "Пустой лист", + "port.cannotConnect": "Не удается подключиться", + "connections.optional": "Необязательно", + "connections.leaveBlankPlaceholder": "•••••••• (оставьте пустым для сохранения)", + "a11y.dismissNotification": "Закрыть уведомление", + "a11y.chatOptions": "Параметры чата", + "a11y.prevPage": "Предыдущая страница", + "a11y.nextPage": "Следующая страница", + "a11y.splitEditor": "Разделить редактор", + "a11y.closePane": "Закрыть панель", + "a11y.refreshChanges": "Обновить изменения", + "keyboard.clickToRebind": "Нажмите для переназначения" } diff --git a/cptr/frontend/src/lib/i18n/locales/zh-CN.json b/cptr/frontend/src/lib/i18n/locales/zh-CN.json index 3433a9f..dd055d7 100644 --- a/cptr/frontend/src/lib/i18n/locales/zh-CN.json +++ b/cptr/frontend/src/lib/i18n/locales/zh-CN.json @@ -374,5 +374,122 @@ "automationModal.saving": "Saving...", "automationModal.save": "Save", "automationModal.createBtn": "Create", - "automationModal.failedToSave": "Failed to save automation" + "automationModal.failedToSave": "Failed to save automation", + "common.cancel": "取消", + "common.save": "保存", + "common.edit": "编辑", + "common.copy": "复制", + "common.remove": "移除", + "common.close": "关闭", + "common.dismiss": "忽略", + "common.loading": "加载中", + "common.downloadCsv": "下载 CSV", + "chat.tool.readFile": "读取 {{path}}", + "chat.tool.readFileRange": "读取 {{path}} L{{range}}", + "chat.tool.editFile": "编辑 {{path}}", + "chat.tool.multiEditFile": "多处编辑 {{path}}", + "chat.tool.createFile": "创建 {{path}}", + "chat.tool.writeFile": "写入 {{path}}", + "chat.tool.listDirectory": "列出 {{path}}", + "chat.tool.listDirectoryRecursive": "列出 {{path}} (递归)", + "chat.tool.searchFiles": "搜索 \"{{query}}\"{{scope}}", + "chat.tool.searchFilesScope": " 在 {{include}} 中", + "chat.tool.backgroundCommand": "后台: {{command}}", + "chat.tool.checkTask": "检查任务 {{id}}", + "chat.tool.killTask": "终止任务 {{id}}", + "chat.tool.webSearch": "网络搜索: \"{{query}}\"", + "chat.tool.fetchUrl": "获取 {{hostname}}", + "chat.tool.fetchUrlFallback": "获取 URL", + "chat.saveAs": "另存为", + "chat.send": "发送", + "chat.artifact": "工件", + "chat.exploring": "探索中", + "chat.explored": "已探索", + "chat.allow": "允许", + "chat.deny": "拒绝", + "chat.toolInput": "输入", + "chat.toolOutput": "输出", + "chat.toolLines": "第 {{start}}–{{end}} 行", + "chat.totalChars": "共 {{count}} 个字符", + "chat.placeholder": "关于 {{name}} 的任何问题...", + "chat.prevResponse": "上一个回复", + "chat.nextResponse": "下一个回复", + "chat.editResponse": "编辑回复", + "chat.copyResponse": "复制回复", + "chat.regenerateResponse": "重新生成回复", + "chat.usageInfo": "使用信息", + "chat.toggleToolCalls": "切换工具调用显示", + "chat.scrollToBottom": "滚动到底部", + "chat.removeUpload": "移除上传", + "chat.sendNow": "立即发送", + "chat.prevMessage": "上一条消息", + "chat.nextMessage": "下一条消息", + "chat.editMessage": "编辑消息", + "chat.copyMessage": "复制消息", + "chat.editMessagePlaceholder": "消息文本...", + "chat.editReasoningPlaceholder": "推理文本...", + "plusMenu.askApproval": "请求批准", + "plusMenu.askApprovalDesc": "每次工具调用前确认", + "plusMenu.autoApprove": "自动批准", + "plusMenu.autoApproveDesc": "批准安全操作,询问风险操作", + "plusMenu.fullAccess": "完全访问", + "plusMenu.fullAccessDesc": "所有工具调用无需确认即可执行", + "plusMenu.attachFiles": "附加文件", + "plusMenu.capture": "截图", + "plusMenu.planMode": "计划模式", + "plusMenu.toolPermissions": "工具权限", + "plusMenu.parameters": "参数", + "plusMenu.noParams": "未配置参数", + "plusMenu.paramKey": "键", + "plusMenu.paramValue": "值", + "plusMenu.removeParam": "移除参数", + "plusMenu.addParam": "添加参数", + "voiceMemo.microphoneError": "无法访问麦克风", + "voiceMemo.uploadFailed": "音频已保存到本地。服务器上传失败。", + "voiceMemo.sttNotConfigured": "STT 未配置。请在设置 → 音频中配置。", + "voiceMemo.writeFailed": "无法写入转录文本。音频已安全保存。", + "voiceMemo.done": "完成 →", + "voiceMemo.processing": "处理中…", + "voiceMemo.filename": "文件名", + "voiceMemo.filenamePlaceholder": "录音名称", + "voiceMemo.saved": "已保存 ✓", + "admin.audio.title": "音频", + "admin.audio.voiceMemos": "语音备忘录", + "admin.audio.enableVoiceMemos": "启用语音备忘录", + "admin.audio.voiceMemosHint": "从菜单录制语音备忘录。", + "admin.audio.autoTranscribe": "自动转录", + "admin.audio.transcribeOnHint": "录音通过 STT 转录为 Markdown。", + "admin.audio.transcribeOffHint": "录音仅保存为音频。", + "admin.audio.recordingQuality": "录音质量", + "admin.audio.qualityHigh": "高质量 (128kbps)", + "admin.audio.qualityMedium": "中等 (64kbps)", + "admin.audio.qualityLow": "低质量 (32kbps)", + "admin.audio.qualityHintHigh": "最佳质量,文件较大。", + "admin.audio.qualityHintMedium": "质量与大小均衡。", + "admin.audio.qualityHintLow": "最小文件,针对语音优化。", + "admin.audio.stt": "语音转文字", + "admin.audio.sttHint": "兼容 OpenAI 的 audio/transcriptions API。", + "admin.audio.saveFailed": "保存音频设置失败", + "sqlite.loadError": "无法加载数据库。", + "sqlite.null": "NULL", + "sqlite.blob": "[BLOB {{size}}B]", + "sqlite.sql": "SQL", + "sqlite.queryPlaceholder": "SELECT * FROM ...", + "sqlite.run": "运行 ⌘↵", + "sqlite.pageInfo": "{{start}}–{{end}} / {{total}}", + "sqlite.prev": "← 上一页", + "sqlite.next": "下一页 →", + "preview.htmlTitle": "HTML 预览", + "preview.emptySheet": "空表格", + "port.cannotConnect": "无法连接", + "connections.optional": "可选", + "connections.leaveBlankPlaceholder": "•••••••• (留空以保留当前值)", + "a11y.dismissNotification": "关闭通知", + "a11y.chatOptions": "聊天选项", + "a11y.prevPage": "上一页", + "a11y.nextPage": "下一页", + "a11y.splitEditor": "分割编辑器", + "a11y.closePane": "关闭面板", + "a11y.refreshChanges": "刷新更改", + "keyboard.clickToRebind": "点击重新绑定" } diff --git a/cptr/frontend/src/lib/i18n/locales/zh-TW.json b/cptr/frontend/src/lib/i18n/locales/zh-TW.json index 4f313cf..6f218e0 100644 --- a/cptr/frontend/src/lib/i18n/locales/zh-TW.json +++ b/cptr/frontend/src/lib/i18n/locales/zh-TW.json @@ -374,5 +374,122 @@ "automationModal.saving": "Saving...", "automationModal.save": "Save", "automationModal.createBtn": "Create", - "automationModal.failedToSave": "Failed to save automation" + "automationModal.failedToSave": "Failed to save automation", + "common.cancel": "取消", + "common.save": "儲存", + "common.edit": "編輯", + "common.copy": "複製", + "common.remove": "移除", + "common.close": "關閉", + "common.dismiss": "忽略", + "common.loading": "載入中", + "common.downloadCsv": "下載 CSV", + "chat.tool.readFile": "讀取 {{path}}", + "chat.tool.readFileRange": "讀取 {{path}} L{{range}}", + "chat.tool.editFile": "編輯 {{path}}", + "chat.tool.multiEditFile": "多處編輯 {{path}}", + "chat.tool.createFile": "建立 {{path}}", + "chat.tool.writeFile": "寫入 {{path}}", + "chat.tool.listDirectory": "列出 {{path}}", + "chat.tool.listDirectoryRecursive": "列出 {{path}} (遞迴)", + "chat.tool.searchFiles": "搜尋 \"{{query}}\"{{scope}}", + "chat.tool.searchFilesScope": " 在 {{include}} 中", + "chat.tool.backgroundCommand": "背景: {{command}}", + "chat.tool.checkTask": "檢查工作 {{id}}", + "chat.tool.killTask": "終止工作 {{id}}", + "chat.tool.webSearch": "網路搜尋: \"{{query}}\"", + "chat.tool.fetchUrl": "取得 {{hostname}}", + "chat.tool.fetchUrlFallback": "取得 URL", + "chat.saveAs": "另存為", + "chat.send": "傳送", + "chat.artifact": "成品", + "chat.exploring": "探索中", + "chat.explored": "已探索", + "chat.allow": "允許", + "chat.deny": "拒絕", + "chat.toolInput": "輸入", + "chat.toolOutput": "輸出", + "chat.toolLines": "第 {{start}}–{{end}} 行", + "chat.totalChars": "共 {{count}} 個字元", + "chat.placeholder": "關於 {{name}} 的任何問題...", + "chat.prevResponse": "上一個回覆", + "chat.nextResponse": "下一個回覆", + "chat.editResponse": "編輯回覆", + "chat.copyResponse": "複製回覆", + "chat.regenerateResponse": "重新產生回覆", + "chat.usageInfo": "使用資訊", + "chat.toggleToolCalls": "切換工具呼叫顯示", + "chat.scrollToBottom": "捲動至底部", + "chat.removeUpload": "移除上傳", + "chat.sendNow": "立即傳送", + "chat.prevMessage": "上一則訊息", + "chat.nextMessage": "下一則訊息", + "chat.editMessage": "編輯訊息", + "chat.copyMessage": "複製訊息", + "chat.editMessagePlaceholder": "訊息文字...", + "chat.editReasoningPlaceholder": "推理文字...", + "plusMenu.askApproval": "請求核准", + "plusMenu.askApprovalDesc": "每次工具呼叫前確認", + "plusMenu.autoApprove": "自動核准", + "plusMenu.autoApproveDesc": "核准安全操作,詢問風險操作", + "plusMenu.fullAccess": "完整存取", + "plusMenu.fullAccessDesc": "所有工具呼叫無需確認即可執行", + "plusMenu.attachFiles": "附加檔案", + "plusMenu.capture": "擷取", + "plusMenu.planMode": "計畫模式", + "plusMenu.toolPermissions": "工具權限", + "plusMenu.parameters": "參數", + "plusMenu.noParams": "未設定參數", + "plusMenu.paramKey": "鍵", + "plusMenu.paramValue": "值", + "plusMenu.removeParam": "移除參數", + "plusMenu.addParam": "新增參數", + "voiceMemo.microphoneError": "無法存取麥克風", + "voiceMemo.uploadFailed": "音訊已儲存至本機。伺服器上傳失敗。", + "voiceMemo.sttNotConfigured": "STT 未設定。請在設定 → 音訊中設定。", + "voiceMemo.writeFailed": "無法寫入轉錄文字。音訊已安全儲存。", + "voiceMemo.done": "完成 →", + "voiceMemo.processing": "處理中…", + "voiceMemo.filename": "檔案名稱", + "voiceMemo.filenamePlaceholder": "錄音名稱", + "voiceMemo.saved": "已儲存 ✓", + "admin.audio.title": "音訊", + "admin.audio.voiceMemos": "語音備忘錄", + "admin.audio.enableVoiceMemos": "啟用語音備忘錄", + "admin.audio.voiceMemosHint": "從「+」選單錄製語音備忘錄。", + "admin.audio.autoTranscribe": "自動轉錄", + "admin.audio.transcribeOnHint": "錄音透過 STT 轉錄為 Markdown。", + "admin.audio.transcribeOffHint": "錄音僅儲存為音訊。", + "admin.audio.recordingQuality": "錄音品質", + "admin.audio.qualityHigh": "高品質 (128kbps)", + "admin.audio.qualityMedium": "中等 (64kbps)", + "admin.audio.qualityLow": "低品質 (32kbps)", + "admin.audio.qualityHintHigh": "最佳品質,檔案較大。", + "admin.audio.qualityHintMedium": "品質與大小均衡。", + "admin.audio.qualityHintLow": "最小檔案,針對語音最佳化。", + "admin.audio.stt": "語音轉文字", + "admin.audio.sttHint": "相容 OpenAI 的 audio/transcriptions API。", + "admin.audio.saveFailed": "儲存音訊設定失敗", + "sqlite.loadError": "無法載入資料庫。", + "sqlite.null": "NULL", + "sqlite.blob": "[BLOB {{size}}B]", + "sqlite.sql": "SQL", + "sqlite.queryPlaceholder": "SELECT * FROM ...", + "sqlite.run": "執行 ⌘↵", + "sqlite.pageInfo": "{{start}}–{{end}} / {{total}}", + "sqlite.prev": "← 上一頁", + "sqlite.next": "下一頁 →", + "preview.htmlTitle": "HTML 預覽", + "preview.emptySheet": "空白工作表", + "port.cannotConnect": "無法連線", + "connections.optional": "選填", + "connections.leaveBlankPlaceholder": "•••••••• (留空以保留目前的值)", + "a11y.dismissNotification": "關閉通知", + "a11y.chatOptions": "聊天選項", + "a11y.prevPage": "上一頁", + "a11y.nextPage": "下一頁", + "a11y.splitEditor": "分割編輯器", + "a11y.closePane": "關閉面板", + "a11y.refreshChanges": "重新整理變更", + "keyboard.clickToRebind": "點擊重新繫結" } From a6faac1200c885eaf310c748ab75f22b98086875 Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Sat, 13 Jun 2026 07:50:47 +0100 Subject: [PATCH 03/10] refac --- .../components/Admin/CreateBotModal.svelte | 36 ++--- .../src/lib/components/Admin/Gateway.svelte | 12 +- .../src/lib/components/Admin/Messaging.svelte | 8 +- .../src/lib/components/GroupTabBar.svelte | 12 +- .../src/lib/components/SettingsModal.svelte | 2 +- .../src/lib/components/Sidebar.svelte | 8 +- cptr/frontend/src/lib/i18n/locales/de.json | 133 +++++++++-------- cptr/frontend/src/lib/i18n/locales/en.json | 37 ++++- cptr/frontend/src/lib/i18n/locales/es.json | 135 ++++++++++-------- cptr/frontend/src/lib/i18n/locales/fr.json | 130 ++++++++++------- cptr/frontend/src/lib/i18n/locales/ja.json | 131 ++++++++++------- cptr/frontend/src/lib/i18n/locales/ko.json | 133 ++++++++++------- cptr/frontend/src/lib/i18n/locales/pt-BR.json | 125 ++++++++++------ cptr/frontend/src/lib/i18n/locales/ru.json | 131 ++++++++++------- cptr/frontend/src/lib/i18n/locales/zh-CN.json | 131 ++++++++++------- cptr/frontend/src/lib/i18n/locales/zh-TW.json | 131 ++++++++++------- 16 files changed, 796 insertions(+), 499 deletions(-) diff --git a/cptr/frontend/src/lib/components/Admin/CreateBotModal.svelte b/cptr/frontend/src/lib/components/Admin/CreateBotModal.svelte index 31d8dbf..209c07a 100644 --- a/cptr/frontend/src/lib/components/Admin/CreateBotModal.svelte +++ b/cptr/frontend/src/lib/components/Admin/CreateBotModal.svelte @@ -54,16 +54,16 @@ ); let selectedWsName = $derived( - $workspaceList.find((w) => w.path === workspace)?.name || workspace.split('/').pop() || 'Select workspace' + $workspaceList.find((w) => w.path === workspace)?.name || workspace.split('/').pop() || $t('automationModal.selectWorkspace') ); - const platformHints: Record = { - telegram: 'Create a bot via @BotFather', - discord: 'Create a bot in the Developer Portal', - slack: 'Bot Token | App Token (pipe-separated)', - whatsapp: 'Access Token | Phone Number ID (pipe-separated)', - signal: 'signal-cli URL | Phone Number (pipe-separated)' - }; + const platformHints: Record = $derived({ + telegram: $t('messaging.hint.telegram'), + discord: $t('messaging.hint.discord'), + slack: $t('messaging.hint.slack'), + whatsapp: $t('messaging.hint.whatsapp'), + signal: $t('messaging.hint.signal') + }); async function handleVerify() { if (!token.trim()) return; @@ -106,7 +106,7 @@ onsave(); onclose(); } catch (e: any) { - toast.error(e.message || 'Failed to save'); + toast.error(e.message || $t('messaging.failedToSave')); } finally { saving = false; } @@ -122,14 +122,14 @@ }} >

- {bot ? 'Edit Bot' : 'Add Bot'} + {bot ? $t('messaging.editBot') : $t('messaging.addBot')}

{#if !bot}
- +
- + @@ -191,7 +191,7 @@ {#if verifying} {:else} - Verify + {$t('messaging.verify')} {/if} {/if} @@ -208,8 +208,8 @@ {/if} - -

Comma-separated user IDs. Leave empty to allow all.

+ +

{$t('messaging.allowedSendersHint')}

{:else} - {bot ? 'Save' : 'Add'} → + {bot ? $t('messaging.save') : $t('messaging.add')} {/if}
diff --git a/cptr/frontend/src/lib/components/Admin/Gateway.svelte b/cptr/frontend/src/lib/components/Admin/Gateway.svelte index 868127c..bd2c26e 100644 --- a/cptr/frontend/src/lib/components/Admin/Gateway.svelte +++ b/cptr/frontend/src/lib/components/Admin/Gateway.svelte @@ -167,7 +167,7 @@ class="shrink-0 text-[11px] text-gray-500 hover:text-gray-900 dark:text-gray-500 dark:hover:text-white transition-colors" onclick={copyKey} > - Copy + {$t('admin.gateway.copy')}
@@ -183,7 +183,7 @@
{/if} -

Keys

+

{$t('admin.gateway.keys')}

- Base URL: + {$t('admin.gateway.baseUrl')} {`${typeof window !== 'undefined' ? window.location.origin : ''}/v1`}
- API Key: + {$t('admin.gateway.apiKey')} sk-cptr-...
- Headers: + {$t('admin.gateway.headers')}
{/if} diff --git a/cptr/frontend/src/lib/components/GroupTabBar.svelte b/cptr/frontend/src/lib/components/GroupTabBar.svelte index a307a84..583648c 100644 --- a/cptr/frontend/src/lib/components/GroupTabBar.svelte +++ b/cptr/frontend/src/lib/components/GroupTabBar.svelte @@ -165,7 +165,7 @@ ...($voiceMemosEnabled ? [ { - label: 'Voice Memo', + label: $t('bar.voiceMemo'), icon: 'microphone', shortcut: formatChord($keybindings.voiceMemo), onclick: () => { @@ -183,12 +183,12 @@ if (isWideScreen && tab.type === 'file' && tab.filePath && !$splitActive) { items.push({ - label: 'Split Right', + label: $t('bar.splitRight'), icon: 'split-horizontal', onclick: () => openInSplit(tab.filePath!, 'horizontal') }); items.push({ - label: 'Split Down', + label: $t('bar.splitDown'), icon: 'split-vertical', onclick: () => openInSplit(tab.filePath!, 'vertical') }); @@ -210,7 +210,7 @@ const direction = $activeWorkspace?.splitDirection ?? 'horizontal'; return [ { - label: 'Split Right', + label: $t('bar.splitRight'), icon: 'split-horizontal', active: direction === 'horizontal', onclick: () => { @@ -219,7 +219,7 @@ } }, { - label: 'Split Down', + label: $t('bar.splitDown'), icon: 'split-vertical', active: direction === 'vertical', onclick: () => { @@ -309,7 +309,7 @@ {/if} - {tab.type === 'files' ? ($activeWorkspace?.name ?? 'Files') : tab.label} + {tab.type === 'files' ? ($activeWorkspace?.name ?? $t('bar.files')) : tab.label} {#if tab.unsaved}{/if} {#if !tab.permanent} diff --git a/cptr/frontend/src/lib/components/SettingsModal.svelte b/cptr/frontend/src/lib/components/SettingsModal.svelte index 07a2b2c..27adebe 100644 --- a/cptr/frontend/src/lib/components/SettingsModal.svelte +++ b/cptr/frontend/src/lib/components/SettingsModal.svelte @@ -52,7 +52,7 @@ { id: 'models', label: $t('admin.models'), icon: 'cube' }, { id: 'messaging', label: $t('admin.messaging'), icon: 'chat-bubble' }, { id: 'gateway', label: $t('admin.gateway.tab'), icon: 'gateway' }, - { id: 'audio', label: 'Audio', icon: 'microphone' }, + { id: 'audio', label: $t('admin.audio.title'), icon: 'microphone' }, { id: 'web', label: $t('admin.web'), icon: 'globe' } ]); diff --git a/cptr/frontend/src/lib/components/Sidebar.svelte b/cptr/frontend/src/lib/components/Sidebar.svelte index 74f8a44..f20f902 100644 --- a/cptr/frontend/src/lib/components/Sidebar.svelte +++ b/cptr/frontend/src/lib/components/Sidebar.svelte @@ -343,7 +343,7 @@ }} > - Automations + {$t('automations.title')}
{/if} @@ -388,7 +388,7 @@ e.preventDefault(); toggleWorkspaceExpand(ws.path); }} - aria-label={isExpanded ? 'Collapse' : 'Expand'} + aria-label={isExpanded ? $t('sidebar.collapse') : $t('sidebar.addWorkspace')} > @@ -417,8 +417,8 @@ role="button" tabindex="-1" onclick={() => handleNewChat(ws.path)} - aria-label="New Chat" - use:tooltip={'New Chat'} + aria-label={$t('bar.newChat')} + use:tooltip={$t('bar.newChat')} > diff --git a/cptr/frontend/src/lib/i18n/locales/de.json b/cptr/frontend/src/lib/i18n/locales/de.json index af77dca..7cc3f83 100644 --- a/cptr/frontend/src/lib/i18n/locales/de.json +++ b/cptr/frontend/src/lib/i18n/locales/de.json @@ -327,55 +327,54 @@ "keyboard.openSettings": "Open the settings panel", "keyboard.toggleSplit": "Toggle split editor view", "keyboard.toggleSidebar": "Show or hide the sidebar", - "automations.title": "Automations", - "automations.filter": "Filter...", - "automations.noMatches": "No matches", - "automations.noAutomations": "No automations yet", - "automations.create": "Create automation", + "automations.title": "Automatisierungen", + "automations.filter": "Filtern...", + "automations.noMatches": "Keine Treffer", + "automations.noAutomations": "Noch keine Automatisierungen", + "automations.create": "Automatisierung erstellen", "automations.status": "Status", - "automations.active": "Active", - "automations.paused": "Paused", - "automations.all": "All", - "automations.schedule": "Schedule", - "automations.model": "Model", - "automations.nextRun": "Next run", - "automations.lastRun": "Last run", + "automations.active": "Aktiv", + "automations.paused": "Pausiert", + "automations.all": "Alle", + "automations.schedule": "Zeitplan", + "automations.model": "Modell", + "automations.nextRun": "Nächste Ausführung", + "automations.lastRun": "Letzte Ausführung", "automations.webhook": "Webhook", - "automations.enabled": "Enabled", - "automations.regenerate": "Regenerate", - "automations.disable": "Disable", - "automations.enable": "Enable", - "automations.copied": "Copied!", - "automations.copy": "Copy", + "automations.enabled": "Aktiviert", + "automations.regenerate": "Neu generieren", + "automations.disable": "Deaktivieren", + "automations.enable": "Aktivieren", + "automations.copied": "Kopiert!", + "automations.copy": "Kopieren", "automations.prompt": "Prompt", - "automations.runs": "Runs", - "automations.noRuns": "No runs yet", - "automations.viewChat": "view chat →", - "automations.deleted": "Deleted", - "automations.webhookEnabled": "Webhook enabled", - "automations.webhookDisabled": "Webhook disabled", - "automations.failedToLoad": "Failed to load automations", - "automations.failedToLoadOne": "Failed to load automation", - "automations.failedToToggle": "Failed to toggle", - "automations.failedToDelete": "Failed to delete", - "automations.failedToRun": "Failed to run", - "automations.failedToGenerateWebhook": "Failed to generate webhook", - "automations.failedToRevokeWebhook": "Failed to revoke webhook", - "automations.triggered": "\"{{name}}\" triggered", - "automations.deleteConfirm": "Delete \"{{name}}\"?", - "automations.runNow": "Run now", - "automations.newAutomation": "New automation", - "automations.toggleSidebar": "Toggle sidebar", - "automationModal.titlePlaceholder": "Automation title", - "automationModal.instructions": "Instructions", - "automationModal.promptPlaceholder": "Enter prompt here...", - "automationModal.selectWorkspace": "Select workspace", - "automationModal.cancel": "Cancel", - "automationModal.saving": "Saving...", - "automationModal.save": "Save", + "automations.runs": "Ausführungen", + "automations.noRuns": "Noch keine Ausführungen", + "automations.viewChat": "Chat ansehen →", + "automations.deleted": "Gelöscht", + "automations.webhookEnabled": "Webhook aktiviert", + "automations.webhookDisabled": "Webhook deaktiviert", + "automations.failedToLoad": "Automatisierungen konnten nicht geladen werden", + "automations.failedToLoadOne": "Automatisierung konnte nicht geladen werden", + "automations.failedToToggle": "Umschalten fehlgeschlagen", + "automations.failedToDelete": "Löschen fehlgeschlagen", + "automations.failedToRun": "Ausführung fehlgeschlagen", + "automations.failedToGenerateWebhook": "Webhook konnte nicht generiert werden", + "automations.failedToRevokeWebhook": "Webhook konnte nicht widerrufen werden", + "automations.triggered": "\"{{name}}\" ausgelöst", + "automations.deleteConfirm": "\"{{name}}\" löschen?", + "automations.runNow": "Jetzt ausführen", + "automations.newAutomation": "Neue Automatisierung", + "automations.toggleSidebar": "Seitenleiste umschalten", + "automationModal.titlePlaceholder": "Titel der Automatisierung", + "automationModal.instructions": "Anweisungen", + "automationModal.promptPlaceholder": "Prompt hier eingeben...", + "automationModal.selectWorkspace": "Arbeitsbereich auswählen", + "automationModal.cancel": "Abbrechen", + "automationModal.saving": "Speichern...", + "automationModal.save": "Speichern", "automationModal.createBtn": "Erstellen", "automationModal.failedToSave": "Automatisierung konnte nicht gespeichert werden", - "common.cancel": "Abbrechen", "common.save": "Speichern", "common.edit": "Bearbeiten", @@ -385,7 +384,6 @@ "common.dismiss": "Verwerfen", "common.loading": "Laden", "common.downloadCsv": "CSV herunterladen", - "chat.tool.readFile": "Lesen {{path}}", "chat.tool.readFileRange": "Lesen {{path}} Z{{range}}", "chat.tool.editFile": "Bearbeiten {{path}}", @@ -430,7 +428,6 @@ "chat.copyMessage": "Nachricht kopieren", "chat.editMessagePlaceholder": "Nachrichtentext...", "chat.editReasoningPlaceholder": "Begründungstext...", - "plusMenu.askApproval": "Um Genehmigung bitten", "plusMenu.askApprovalDesc": "Jeden Tool-Aufruf vor der Ausführung bestätigen", "plusMenu.autoApprove": "Automatisch genehmigen", @@ -447,7 +444,6 @@ "plusMenu.paramValue": "Wert", "plusMenu.removeParam": "Parameter entfernen", "plusMenu.addParam": "Parameter hinzufügen", - "voiceMemo.microphoneError": "Mikrofon konnte nicht zugegriffen werden", "voiceMemo.uploadFailed": "Audio lokal gespeichert. Server-Upload fehlgeschlagen.", "voiceMemo.sttNotConfigured": "STT nicht konfiguriert. Einrichten unter Einstellungen → Audio.", @@ -457,7 +453,6 @@ "voiceMemo.filename": "Dateiname", "voiceMemo.filenamePlaceholder": "aufnahme-name", "voiceMemo.saved": "Gespeichert ✓", - "admin.audio.title": "Audio", "admin.audio.voiceMemos": "Sprachnotizen", "admin.audio.enableVoiceMemos": "Sprachnotizen aktivieren", @@ -475,7 +470,6 @@ "admin.audio.stt": "Sprache-zu-Text", "admin.audio.sttHint": "Kompatibel mit OpenAIs Audio-/Transkriptions-API.", "admin.audio.saveFailed": "Audioeinstellungen konnten nicht gespeichert werden", - "sqlite.loadError": "Datenbank konnte nicht geladen werden.", "sqlite.null": "NULL", "sqlite.blob": "[BLOB {{size}}B]", @@ -485,15 +479,11 @@ "sqlite.pageInfo": "{{start}}–{{end}} von {{total}}", "sqlite.prev": "← Zurück", "sqlite.next": "Weiter →", - "preview.htmlTitle": "HTML-Vorschau", "preview.emptySheet": "Leeres Blatt", - "port.cannotConnect": "Verbindung nicht möglich", - "connections.optional": "Optional", "connections.leaveBlankPlaceholder": "•••••••• (leer lassen zum Beibehalten)", - "a11y.dismissNotification": "Benachrichtigung verwerfen", "a11y.chatOptions": "Chat-Optionen", "a11y.prevPage": "Vorherige Seite", @@ -501,7 +491,38 @@ "a11y.splitEditor": "Editor teilen", "a11y.closePane": "Bereich schließen", "a11y.refreshChanges": "Änderungen aktualisieren", - - "keyboard.clickToRebind": "Klicken zum Neuzuweisen" + "keyboard.clickToRebind": "Klicken zum Neuzuweisen", + "bar.splitRight": "Rechts teilen", + "bar.splitDown": "Unten teilen", + "bar.voiceMemo": "Sprachnotiz", + "bar.files": "Dateien", + "messaging.noBots": "Keine Messaging-Bots konfiguriert.", + "messaging.failedToLoad": "Bots konnten nicht geladen werden", + "messaging.failedToToggle": "Bot konnte nicht umgeschaltet werden", + "messaging.failedToDelete": "Bot konnte nicht gelöscht werden", + "messaging.editBot": "Bot bearbeiten", + "messaging.addBot": "Bot hinzufügen", + "messaging.name": "Name", + "messaging.platform": "Plattform", + "messaging.token": "Token", + "messaging.tokenKeep": "Leer lassen zum Beibehalten", + "messaging.tokenPaste": "Token einfügen", + "messaging.verify": "Überprüfen", + "messaging.allowedSenders": "Erlaubte Absender", + "messaging.allowedSendersHint": "Kommagetrennte Benutzer-IDs. Leer lassen für alle.", + "messaging.save": "Speichern →", + "messaging.add": "Hinzufügen →", + "messaging.failedToSave": "Speichern fehlgeschlagen", + "messaging.hint.telegram": "Bot über @BotFather erstellen", + "messaging.hint.discord": "Bot im Developer Portal erstellen", + "messaging.hint.slack": "Bot Token | App Token (pipe-getrennt)", + "messaging.hint.whatsapp": "Access Token | Phone Number ID (pipe-getrennt)", + "messaging.hint.signal": "signal-cli URL | Telefonnummer (pipe-getrennt)", + "admin.gateway.keys": "Schlüssel", + "admin.gateway.copy": "Kopieren", + "admin.gateway.baseUrl": "Basis-URL:", + "admin.gateway.apiKey": "API-Schlüssel:", + "admin.gateway.headers": "Header:", + "admin.messaging": "Nachrichten", + "admin.gateway.tab": "Gateway" } - diff --git a/cptr/frontend/src/lib/i18n/locales/en.json b/cptr/frontend/src/lib/i18n/locales/en.json index 9170de0..b501692 100644 --- a/cptr/frontend/src/lib/i18n/locales/en.json +++ b/cptr/frontend/src/lib/i18n/locales/en.json @@ -579,6 +579,41 @@ "a11y.closePane": "Close pane", "a11y.refreshChanges": "Refresh changes", - "keyboard.clickToRebind": "Click to rebind" + "keyboard.clickToRebind": "Click to rebind", + + "bar.splitRight": "Split Right", + "bar.splitDown": "Split Down", + "bar.voiceMemo": "Voice Memo", + "bar.files": "Files", + + "messaging.noBots": "No messaging bots configured.", + "messaging.failedToLoad": "Failed to load bots", + "messaging.failedToToggle": "Failed to toggle bot", + "messaging.failedToDelete": "Failed to delete bot", + "messaging.editBot": "Edit Bot", + "messaging.addBot": "Add Bot", + "messaging.name": "Name", + "messaging.platform": "Platform", + "messaging.token": "Token", + "messaging.tokenKeep": "Leave empty to keep current", + "messaging.tokenPaste": "Paste token", + "messaging.verify": "Verify", + "messaging.allowedSenders": "Allowed senders", + "messaging.allowedSendersHint": "Comma-separated user IDs. Leave empty to allow all.", + "messaging.save": "Save →", + "messaging.add": "Add →", + "messaging.failedToSave": "Failed to save", + + "messaging.hint.telegram": "Create a bot via @BotFather", + "messaging.hint.discord": "Create a bot in the Developer Portal", + "messaging.hint.slack": "Bot Token | App Token (pipe-separated)", + "messaging.hint.whatsapp": "Access Token | Phone Number ID (pipe-separated)", + "messaging.hint.signal": "signal-cli URL | Phone Number (pipe-separated)", + + "admin.gateway.keys": "Keys", + "admin.gateway.copy": "Copy", + "admin.gateway.baseUrl": "Base URL:", + "admin.gateway.apiKey": "API Key:", + "admin.gateway.headers": "Headers:" } diff --git a/cptr/frontend/src/lib/i18n/locales/es.json b/cptr/frontend/src/lib/i18n/locales/es.json index 64a32ce..110b106 100644 --- a/cptr/frontend/src/lib/i18n/locales/es.json +++ b/cptr/frontend/src/lib/i18n/locales/es.json @@ -327,55 +327,54 @@ "keyboard.openSettings": "Open the settings panel", "keyboard.toggleSplit": "Toggle split editor view", "keyboard.toggleSidebar": "Show or hide the sidebar", - "automations.title": "Automations", - "automations.filter": "Filter...", - "automations.noMatches": "No matches", - "automations.noAutomations": "No automations yet", - "automations.create": "Create automation", - "automations.status": "Status", - "automations.active": "Active", - "automations.paused": "Paused", - "automations.all": "All", - "automations.schedule": "Schedule", - "automations.model": "Model", - "automations.nextRun": "Next run", - "automations.lastRun": "Last run", + "automations.title": "Automatizaciones", + "automations.filter": "Filtrar...", + "automations.noMatches": "Sin resultados", + "automations.noAutomations": "Aún no hay automatizaciones", + "automations.create": "Crear automatización", + "automations.status": "Estado", + "automations.active": "Activa", + "automations.paused": "Pausada", + "automations.all": "Todas", + "automations.schedule": "Programación", + "automations.model": "Modelo", + "automations.nextRun": "Próxima ejecución", + "automations.lastRun": "Última ejecución", "automations.webhook": "Webhook", - "automations.enabled": "Enabled", - "automations.regenerate": "Regenerate", - "automations.disable": "Disable", - "automations.enable": "Enable", - "automations.copied": "Copied!", - "automations.copy": "Copy", + "automations.enabled": "Habilitado", + "automations.regenerate": "Regenerar", + "automations.disable": "Desactivar", + "automations.enable": "Activar", + "automations.copied": "¡Copiado!", + "automations.copy": "Copiar", "automations.prompt": "Prompt", - "automations.runs": "Runs", - "automations.noRuns": "No runs yet", - "automations.viewChat": "view chat →", - "automations.deleted": "Deleted", - "automations.webhookEnabled": "Webhook enabled", - "automations.webhookDisabled": "Webhook disabled", - "automations.failedToLoad": "Failed to load automations", - "automations.failedToLoadOne": "Failed to load automation", - "automations.failedToToggle": "Failed to toggle", - "automations.failedToDelete": "Failed to delete", - "automations.failedToRun": "Failed to run", - "automations.failedToGenerateWebhook": "Failed to generate webhook", - "automations.failedToRevokeWebhook": "Failed to revoke webhook", - "automations.triggered": "\"{{name}}\" triggered", - "automations.deleteConfirm": "Delete \"{{name}}\"?", - "automations.runNow": "Run now", - "automations.newAutomation": "New automation", - "automations.toggleSidebar": "Toggle sidebar", - "automationModal.titlePlaceholder": "Automation title", - "automationModal.instructions": "Instructions", - "automationModal.promptPlaceholder": "Enter prompt here...", - "automationModal.selectWorkspace": "Select workspace", - "automationModal.cancel": "Cancel", - "automationModal.saving": "Saving...", - "automationModal.save": "Save", + "automations.runs": "Ejecuciones", + "automations.noRuns": "Aún no hay ejecuciones", + "automations.viewChat": "ver chat →", + "automations.deleted": "Eliminada", + "automations.webhookEnabled": "Webhook habilitado", + "automations.webhookDisabled": "Webhook deshabilitado", + "automations.failedToLoad": "Error al cargar las automatizaciones", + "automations.failedToLoadOne": "Error al cargar la automatización", + "automations.failedToToggle": "Error al cambiar", + "automations.failedToDelete": "Error al eliminar", + "automations.failedToRun": "Error al ejecutar", + "automations.failedToGenerateWebhook": "Error al generar webhook", + "automations.failedToRevokeWebhook": "Error al revocar webhook", + "automations.triggered": "\"{{name}}\" ejecutada", + "automations.deleteConfirm": "¿Eliminar \"{{name}}\"?", + "automations.runNow": "Ejecutar ahora", + "automations.newAutomation": "Nueva automatización", + "automations.toggleSidebar": "Alternar barra lateral", + "automationModal.titlePlaceholder": "Título de la automatización", + "automationModal.instructions": "Instrucciones", + "automationModal.promptPlaceholder": "Ingrese el prompt aquí...", + "automationModal.selectWorkspace": "Seleccionar espacio de trabajo", + "automationModal.cancel": "Cancelar", + "automationModal.saving": "Guardando...", + "automationModal.save": "Guardar", "automationModal.createBtn": "Crear", "automationModal.failedToSave": "No se pudo guardar la automatización", - "common.cancel": "Cancelar", "common.save": "Guardar", "common.edit": "Editar", @@ -385,7 +384,6 @@ "common.dismiss": "Descartar", "common.loading": "Cargando", "common.downloadCsv": "Descargar CSV", - "chat.tool.readFile": "Leer {{path}}", "chat.tool.readFileRange": "Leer {{path}} L{{range}}", "chat.tool.editFile": "Editar {{path}}", @@ -430,7 +428,6 @@ "chat.copyMessage": "Copiar mensaje", "chat.editMessagePlaceholder": "Texto del mensaje...", "chat.editReasoningPlaceholder": "Texto del razonamiento...", - "plusMenu.askApproval": "Pedir aprobación", "plusMenu.askApprovalDesc": "Confirmar cada llamada a herramienta antes de ejecutarla", "plusMenu.autoApprove": "Aprobar automáticamente", @@ -447,7 +444,6 @@ "plusMenu.paramValue": "valor", "plusMenu.removeParam": "Eliminar parámetro", "plusMenu.addParam": "Añadir parámetro", - "voiceMemo.microphoneError": "No se pudo acceder al micrófono", "voiceMemo.uploadFailed": "Audio guardado localmente. La subida al servidor falló.", "voiceMemo.sttNotConfigured": "STT no configurado. Configúralo en Ajustes → Audio.", @@ -457,7 +453,6 @@ "voiceMemo.filename": "Nombre de archivo", "voiceMemo.filenamePlaceholder": "nombre-grabación", "voiceMemo.saved": "Guardado ✓", - "admin.audio.title": "Audio", "admin.audio.voiceMemos": "Notas de voz", "admin.audio.enableVoiceMemos": "Activar notas de voz", @@ -475,7 +470,6 @@ "admin.audio.stt": "Voz a texto", "admin.audio.sttHint": "Compatible con la API de audio/transcripciones de OpenAI.", "admin.audio.saveFailed": "No se pudieron guardar los ajustes de audio", - "sqlite.loadError": "No se pudo cargar la base de datos.", "sqlite.null": "NULL", "sqlite.blob": "[BLOB {{size}}B]", @@ -485,15 +479,11 @@ "sqlite.pageInfo": "{{start}}–{{end}} de {{total}}", "sqlite.prev": "← Anterior", "sqlite.next": "Siguiente →", - "preview.htmlTitle": "Vista previa HTML", "preview.emptySheet": "Hoja vacía", - "port.cannotConnect": "No se puede conectar", - "connections.optional": "Opcional", "connections.leaveBlankPlaceholder": "•••••••• (dejar en blanco para mantener)", - "a11y.dismissNotification": "Descartar notificación", "a11y.chatOptions": "Opciones de chat", "a11y.prevPage": "Página anterior", @@ -501,7 +491,38 @@ "a11y.splitEditor": "Dividir editor", "a11y.closePane": "Cerrar panel", "a11y.refreshChanges": "Actualizar cambios", - - "keyboard.clickToRebind": "Clic para reasignar" + "keyboard.clickToRebind": "Clic para reasignar", + "bar.splitRight": "Dividir a la derecha", + "bar.splitDown": "Dividir abajo", + "bar.voiceMemo": "Nota de voz", + "bar.files": "Archivos", + "messaging.noBots": "No hay bots de mensajería configurados.", + "messaging.failedToLoad": "Error al cargar los bots", + "messaging.failedToToggle": "Error al cambiar el estado del bot", + "messaging.failedToDelete": "Error al eliminar el bot", + "messaging.editBot": "Editar Bot", + "messaging.addBot": "Añadir Bot", + "messaging.name": "Nombre", + "messaging.platform": "Plataforma", + "messaging.token": "Token", + "messaging.tokenKeep": "Dejar vacío para mantener", + "messaging.tokenPaste": "Pegar token", + "messaging.verify": "Verificar", + "messaging.allowedSenders": "Remitentes permitidos", + "messaging.allowedSendersHint": "IDs de usuario separados por comas. Dejar vacío para permitir todos.", + "messaging.save": "Guardar →", + "messaging.add": "Añadir →", + "messaging.failedToSave": "Error al guardar", + "messaging.hint.telegram": "Crear un bot con @BotFather", + "messaging.hint.discord": "Crear un bot en el Portal de Desarrolladores", + "messaging.hint.slack": "Bot Token | App Token (separados por pipe)", + "messaging.hint.whatsapp": "Access Token | Phone Number ID (separados por pipe)", + "messaging.hint.signal": "URL de signal-cli | Teléfono (separados por pipe)", + "admin.gateway.keys": "Claves", + "admin.gateway.copy": "Copiar", + "admin.gateway.baseUrl": "URL base:", + "admin.gateway.apiKey": "Clave API:", + "admin.gateway.headers": "Encabezados:", + "admin.messaging": "Mensajería", + "admin.gateway.tab": "Gateway" } - diff --git a/cptr/frontend/src/lib/i18n/locales/fr.json b/cptr/frontend/src/lib/i18n/locales/fr.json index 0e83f85..af39f5b 100644 --- a/cptr/frontend/src/lib/i18n/locales/fr.json +++ b/cptr/frontend/src/lib/i18n/locales/fr.json @@ -326,55 +326,54 @@ "keyboard.openSettings": "Open the settings panel", "keyboard.toggleSplit": "Toggle split editor view", "keyboard.toggleSidebar": "Show or hide the sidebar", - "automations.title": "Automations", - "automations.filter": "Filter...", - "automations.noMatches": "No matches", - "automations.noAutomations": "No automations yet", - "automations.create": "Create automation", - "automations.status": "Status", + "automations.title": "Automatisations", + "automations.filter": "Filtrer...", + "automations.noMatches": "Aucun résultat", + "automations.noAutomations": "Aucune automatisation", + "automations.create": "Créer une automatisation", + "automations.status": "Statut", "automations.active": "Active", - "automations.paused": "Paused", - "automations.all": "All", - "automations.schedule": "Schedule", - "automations.model": "Model", - "automations.nextRun": "Next run", - "automations.lastRun": "Last run", + "automations.paused": "En pause", + "automations.all": "Toutes", + "automations.schedule": "Planification", + "automations.model": "Modèle", + "automations.nextRun": "Prochaine exécution", + "automations.lastRun": "Dernière exécution", "automations.webhook": "Webhook", - "automations.enabled": "Enabled", - "automations.regenerate": "Regenerate", - "automations.disable": "Disable", - "automations.enable": "Enable", - "automations.copied": "Copied!", - "automations.copy": "Copy", + "automations.enabled": "Activé", + "automations.regenerate": "Régénérer", + "automations.disable": "Désactiver", + "automations.enable": "Activer", + "automations.copied": "Copié !", + "automations.copy": "Copier", "automations.prompt": "Prompt", - "automations.runs": "Runs", - "automations.noRuns": "No runs yet", - "automations.viewChat": "view chat →", - "automations.deleted": "Deleted", - "automations.webhookEnabled": "Webhook enabled", - "automations.webhookDisabled": "Webhook disabled", - "automations.failedToLoad": "Failed to load automations", - "automations.failedToLoadOne": "Failed to load automation", - "automations.failedToToggle": "Failed to toggle", - "automations.failedToDelete": "Failed to delete", - "automations.failedToRun": "Failed to run", - "automations.failedToGenerateWebhook": "Failed to generate webhook", - "automations.failedToRevokeWebhook": "Failed to revoke webhook", - "automations.triggered": "\"{{name}}\" triggered", - "automations.deleteConfirm": "Delete \"{{name}}\"?", - "automations.runNow": "Run now", - "automations.newAutomation": "New automation", - "automations.toggleSidebar": "Toggle sidebar", - "automationModal.titlePlaceholder": "Automation title", + "automations.runs": "Exécutions", + "automations.noRuns": "Aucune exécution", + "automations.viewChat": "voir le chat →", + "automations.deleted": "Supprimée", + "automations.webhookEnabled": "Webhook activé", + "automations.webhookDisabled": "Webhook désactivé", + "automations.failedToLoad": "Échec du chargement des automatisations", + "automations.failedToLoadOne": "Échec du chargement de l'automatisation", + "automations.failedToToggle": "Échec du basculement", + "automations.failedToDelete": "Échec de la suppression", + "automations.failedToRun": "Échec de l'exécution", + "automations.failedToGenerateWebhook": "Échec de la génération du webhook", + "automations.failedToRevokeWebhook": "Échec de la révocation du webhook", + "automations.triggered": "\"{{name}}\" déclenchée", + "automations.deleteConfirm": "Supprimer \"{{name}}\" ?", + "automations.runNow": "Exécuter maintenant", + "automations.newAutomation": "Nouvelle automatisation", + "automations.toggleSidebar": "Afficher/masquer la barre latérale", + "automationModal.titlePlaceholder": "Titre de l'automatisation", "automationModal.instructions": "Instructions", - "automationModal.promptPlaceholder": "Enter prompt here...", - "automationModal.selectWorkspace": "Select workspace", - "automationModal.cancel": "Cancel", - "automationModal.saving": "Saving...", - "automationModal.save": "Save", + "automationModal.promptPlaceholder": "Saisissez le prompt ici...", + "automationModal.selectWorkspace": "Sélectionner un espace de travail", + "automationModal.cancel": "Annuler", + "automationModal.saving": "Enregistrement...", + "automationModal.save": "Enregistrer", "automationModal.createBtn": "Créer", "automationModal.failedToSave": "Échec de la sauvegarde de l'automatisation", - "common.cancel": "Annuler", "common.save": "Enregistrer", "common.edit": "Modifier", @@ -384,7 +383,6 @@ "common.dismiss": "Ignorer", "common.loading": "Chargement", "common.downloadCsv": "Télécharger CSV", - "chat.tool.readFile": "Lire {{path}}", "chat.tool.readFileRange": "Lire {{path}} L{{range}}", "chat.tool.editFile": "Modifier {{path}}", @@ -429,7 +427,6 @@ "chat.copyMessage": "Copier le message", "chat.editMessagePlaceholder": "Texte du message...", "chat.editReasoningPlaceholder": "Texte du raisonnement...", - "plusMenu.askApproval": "Demander l'approbation", "plusMenu.askApprovalDesc": "Confirmer chaque appel d'outil avant son exécution", "plusMenu.autoApprove": "Approbation automatique", @@ -446,7 +443,6 @@ "plusMenu.paramValue": "valeur", "plusMenu.removeParam": "Supprimer le paramètre", "plusMenu.addParam": "Ajouter un paramètre", - "voiceMemo.microphoneError": "Impossible d'accéder au microphone", "voiceMemo.uploadFailed": "Audio sauvegardé localement. Échec de l'envoi au serveur.", "voiceMemo.sttNotConfigured": "STT non configuré. Configurez dans Paramètres → Audio.", @@ -456,7 +452,6 @@ "voiceMemo.filename": "Nom du fichier", "voiceMemo.filenamePlaceholder": "nom-enregistrement", "voiceMemo.saved": "Enregistré ✓", - "admin.audio.title": "Audio", "admin.audio.voiceMemos": "Notes vocales", "admin.audio.enableVoiceMemos": "Activer les notes vocales", @@ -474,7 +469,6 @@ "admin.audio.stt": "Reconnaissance vocale", "admin.audio.sttHint": "Compatible avec l'API audio/transcriptions d'OpenAI.", "admin.audio.saveFailed": "Échec de la sauvegarde des paramètres audio", - "sqlite.loadError": "Impossible de charger la base de données.", "sqlite.null": "NULL", "sqlite.blob": "[BLOB {{size}}o]", @@ -484,15 +478,11 @@ "sqlite.pageInfo": "{{start}}–{{end}} sur {{total}}", "sqlite.prev": "← Précédent", "sqlite.next": "Suivant →", - "preview.htmlTitle": "Aperçu HTML", "preview.emptySheet": "Feuille vide", - "port.cannotConnect": "Connexion impossible", - "connections.optional": "Facultatif", "connections.leaveBlankPlaceholder": "•••••••• (laisser vide pour conserver)", - "a11y.dismissNotification": "Ignorer la notification", "a11y.chatOptions": "Options du chat", "a11y.prevPage": "Page précédente", @@ -500,6 +490,38 @@ "a11y.splitEditor": "Diviser l'éditeur", "a11y.closePane": "Fermer le panneau", "a11y.refreshChanges": "Actualiser les modifications", - - "keyboard.clickToRebind": "Cliquer pour réassigner" + "keyboard.clickToRebind": "Cliquer pour réassigner", + "bar.splitRight": "Diviser à droite", + "bar.splitDown": "Diviser en bas", + "bar.voiceMemo": "Note vocale", + "bar.files": "Fichiers", + "messaging.noBots": "Aucun bot de messagerie configuré.", + "messaging.failedToLoad": "Échec du chargement des bots", + "messaging.failedToToggle": "Échec du basculement du bot", + "messaging.failedToDelete": "Échec de la suppression du bot", + "messaging.editBot": "Modifier le bot", + "messaging.addBot": "Ajouter un bot", + "messaging.name": "Nom", + "messaging.platform": "Plateforme", + "messaging.token": "Jeton", + "messaging.tokenKeep": "Laisser vide pour conserver", + "messaging.tokenPaste": "Coller le jeton", + "messaging.verify": "Vérifier", + "messaging.allowedSenders": "Expéditeurs autorisés", + "messaging.allowedSendersHint": "IDs d'utilisateurs séparés par des virgules. Laisser vide pour autoriser tous.", + "messaging.save": "Enregistrer →", + "messaging.add": "Ajouter →", + "messaging.failedToSave": "Échec de la sauvegarde", + "messaging.hint.telegram": "Créer un bot via @BotFather", + "messaging.hint.discord": "Créer un bot dans le Portail Développeur", + "messaging.hint.slack": "Bot Token | App Token (séparés par pipe)", + "messaging.hint.whatsapp": "Access Token | Phone Number ID (séparés par pipe)", + "messaging.hint.signal": "URL signal-cli | Numéro de téléphone (séparés par pipe)", + "admin.gateway.keys": "Clés", + "admin.gateway.copy": "Copier", + "admin.gateway.baseUrl": "URL de base :", + "admin.gateway.apiKey": "Clé API :", + "admin.gateway.headers": "En-têtes :", + "admin.messaging": "Messagerie", + "admin.gateway.tab": "Passerelle" } diff --git a/cptr/frontend/src/lib/i18n/locales/ja.json b/cptr/frontend/src/lib/i18n/locales/ja.json index 8fce805..d3de12b 100644 --- a/cptr/frontend/src/lib/i18n/locales/ja.json +++ b/cptr/frontend/src/lib/i18n/locales/ja.json @@ -235,7 +235,7 @@ "system.disk": "ディスク", "system.cores": "{{count}} コア", "system.process": "プロセス", - "admin.web": "Web", + "admin.web": "ウェブ", "admin.webEnabled": "Webアクセスを有効にする", "admin.webEnabledHint": "AIがWebを検索し、URLを取得できます。", "admin.webDisabledHint": "Web検索とURL取得は無効です。", @@ -327,54 +327,54 @@ "keyboard.openSettings": "Open the settings panel", "keyboard.toggleSplit": "Toggle split editor view", "keyboard.toggleSidebar": "Show or hide the sidebar", - "automations.title": "Automations", - "automations.filter": "Filter...", - "automations.noMatches": "No matches", - "automations.noAutomations": "No automations yet", - "automations.create": "Create automation", - "automations.status": "Status", - "automations.active": "Active", - "automations.paused": "Paused", - "automations.all": "All", - "automations.schedule": "Schedule", - "automations.model": "Model", - "automations.nextRun": "Next run", - "automations.lastRun": "Last run", + "automations.title": "自動化", + "automations.filter": "フィルター...", + "automations.noMatches": "一致なし", + "automations.noAutomations": "自動化はまだありません", + "automations.create": "自動化を作成", + "automations.status": "ステータス", + "automations.active": "有効", + "automations.paused": "一時停止", + "automations.all": "すべて", + "automations.schedule": "スケジュール", + "automations.model": "モデル", + "automations.nextRun": "次の実行", + "automations.lastRun": "前回の実行", "automations.webhook": "Webhook", - "automations.enabled": "Enabled", - "automations.regenerate": "Regenerate", - "automations.disable": "Disable", - "automations.enable": "Enable", - "automations.copied": "Copied!", - "automations.copy": "Copy", - "automations.prompt": "Prompt", - "automations.runs": "Runs", - "automations.noRuns": "No runs yet", - "automations.viewChat": "view chat →", - "automations.deleted": "Deleted", - "automations.webhookEnabled": "Webhook enabled", - "automations.webhookDisabled": "Webhook disabled", - "automations.failedToLoad": "Failed to load automations", - "automations.failedToLoadOne": "Failed to load automation", - "automations.failedToToggle": "Failed to toggle", - "automations.failedToDelete": "Failed to delete", - "automations.failedToRun": "Failed to run", - "automations.failedToGenerateWebhook": "Failed to generate webhook", - "automations.failedToRevokeWebhook": "Failed to revoke webhook", - "automations.triggered": "\"{{name}}\" triggered", - "automations.deleteConfirm": "Delete \"{{name}}\"?", - "automations.runNow": "Run now", - "automations.newAutomation": "New automation", - "automations.toggleSidebar": "Toggle sidebar", - "automationModal.titlePlaceholder": "Automation title", - "automationModal.instructions": "Instructions", - "automationModal.promptPlaceholder": "Enter prompt here...", - "automationModal.selectWorkspace": "Select workspace", - "automationModal.cancel": "Cancel", - "automationModal.saving": "Saving...", - "automationModal.save": "Save", - "automationModal.createBtn": "Create", - "automationModal.failedToSave": "Failed to save automation", + "automations.enabled": "有効", + "automations.regenerate": "再生成", + "automations.disable": "無効にする", + "automations.enable": "有効にする", + "automations.copied": "コピーしました!", + "automations.copy": "コピー", + "automations.prompt": "プロンプト", + "automations.runs": "実行履歴", + "automations.noRuns": "まだ実行されていません", + "automations.viewChat": "チャットを見る →", + "automations.deleted": "削除済み", + "automations.webhookEnabled": "Webhook有効", + "automations.webhookDisabled": "Webhook無効", + "automations.failedToLoad": "自動化の読み込みに失敗しました", + "automations.failedToLoadOne": "自動化の読み込みに失敗しました", + "automations.failedToToggle": "切り替えに失敗しました", + "automations.failedToDelete": "削除に失敗しました", + "automations.failedToRun": "実行に失敗しました", + "automations.failedToGenerateWebhook": "Webhookの生成に失敗しました", + "automations.failedToRevokeWebhook": "Webhookの取り消しに失敗しました", + "automations.triggered": "\"{{name}}\" が実行されました", + "automations.deleteConfirm": "\"{{name}}\" を削除しますか?", + "automations.runNow": "今すぐ実行", + "automations.newAutomation": "新しい自動化", + "automations.toggleSidebar": "サイドバーを切り替え", + "automationModal.titlePlaceholder": "自動化のタイトル", + "automationModal.instructions": "手順", + "automationModal.promptPlaceholder": "プロンプトを入力...", + "automationModal.selectWorkspace": "ワークスペースを選択", + "automationModal.cancel": "キャンセル", + "automationModal.saving": "保存中...", + "automationModal.save": "保存", + "automationModal.createBtn": "作成", + "automationModal.failedToSave": "自動化の保存に失敗しました", "common.cancel": "キャンセル", "common.save": "保存", "common.edit": "編集", @@ -491,5 +491,38 @@ "a11y.splitEditor": "エディタを分割", "a11y.closePane": "パネルを閉じる", "a11y.refreshChanges": "変更を更新", - "keyboard.clickToRebind": "クリックして再割り当て" + "keyboard.clickToRebind": "クリックして再割り当て", + "bar.splitRight": "右に分割", + "bar.splitDown": "下に分割", + "bar.voiceMemo": "音声メモ", + "bar.files": "ファイル", + "messaging.noBots": "メッセージングボットが設定されていません。", + "messaging.failedToLoad": "ボットの読み込みに失敗しました", + "messaging.failedToToggle": "ボットの切り替えに失敗しました", + "messaging.failedToDelete": "ボットの削除に失敗しました", + "messaging.editBot": "ボットを編集", + "messaging.addBot": "ボットを追加", + "messaging.name": "名前", + "messaging.platform": "プラットフォーム", + "messaging.token": "トークン", + "messaging.tokenKeep": "現在の値を保持するには空欄", + "messaging.tokenPaste": "トークンを貼り付け", + "messaging.verify": "確認", + "messaging.allowedSenders": "許可された送信者", + "messaging.allowedSendersHint": "カンマ区切りのユーザーID。空欄で全員を許可。", + "messaging.save": "保存 →", + "messaging.add": "追加 →", + "messaging.failedToSave": "保存に失敗しました", + "messaging.hint.telegram": "@BotFatherでボットを作成", + "messaging.hint.discord": "Developer Portalでボットを作成", + "messaging.hint.slack": "Bot Token | App Token (パイプ区切り)", + "messaging.hint.whatsapp": "Access Token | Phone Number ID (パイプ区切り)", + "messaging.hint.signal": "signal-cli URL | 電話番号 (パイプ区切り)", + "admin.gateway.keys": "キー", + "admin.gateway.copy": "コピー", + "admin.gateway.baseUrl": "ベースURL:", + "admin.gateway.apiKey": "APIキー:", + "admin.gateway.headers": "ヘッダー:", + "admin.messaging": "メッセージング", + "admin.gateway.tab": "ゲートウェイ" } diff --git a/cptr/frontend/src/lib/i18n/locales/ko.json b/cptr/frontend/src/lib/i18n/locales/ko.json index ca639b3..23c7f38 100644 --- a/cptr/frontend/src/lib/i18n/locales/ko.json +++ b/cptr/frontend/src/lib/i18n/locales/ko.json @@ -174,7 +174,7 @@ "admin.displayName": "표시 이름", "admin.passwordLabel": "비밀번호", "admin.role": "역할", - "admin.optional": "선택 사항", + "admin.optional": "선택사항", "admin.minChars": "최소 6자", "admin.enterUsername": "사용자 이름 입력", "admin.create": "생성", @@ -327,54 +327,54 @@ "keyboard.openSettings": "Open the settings panel", "keyboard.toggleSplit": "Toggle split editor view", "keyboard.toggleSidebar": "Show or hide the sidebar", - "automations.title": "Automations", - "automations.filter": "Filter...", - "automations.noMatches": "No matches", - "automations.noAutomations": "No automations yet", - "automations.create": "Create automation", - "automations.status": "Status", - "automations.active": "Active", - "automations.paused": "Paused", - "automations.all": "All", - "automations.schedule": "Schedule", - "automations.model": "Model", - "automations.nextRun": "Next run", - "automations.lastRun": "Last run", - "automations.webhook": "Webhook", - "automations.enabled": "Enabled", - "automations.regenerate": "Regenerate", - "automations.disable": "Disable", - "automations.enable": "Enable", - "automations.copied": "Copied!", - "automations.copy": "Copy", - "automations.prompt": "Prompt", - "automations.runs": "Runs", - "automations.noRuns": "No runs yet", - "automations.viewChat": "view chat →", - "automations.deleted": "Deleted", - "automations.webhookEnabled": "Webhook enabled", - "automations.webhookDisabled": "Webhook disabled", - "automations.failedToLoad": "Failed to load automations", - "automations.failedToLoadOne": "Failed to load automation", - "automations.failedToToggle": "Failed to toggle", - "automations.failedToDelete": "Failed to delete", - "automations.failedToRun": "Failed to run", - "automations.failedToGenerateWebhook": "Failed to generate webhook", - "automations.failedToRevokeWebhook": "Failed to revoke webhook", - "automations.triggered": "\"{{name}}\" triggered", - "automations.deleteConfirm": "Delete \"{{name}}\"?", - "automations.runNow": "Run now", - "automations.newAutomation": "New automation", - "automations.toggleSidebar": "Toggle sidebar", - "automationModal.titlePlaceholder": "Automation title", - "automationModal.instructions": "Instructions", - "automationModal.promptPlaceholder": "Enter prompt here...", - "automationModal.selectWorkspace": "Select workspace", - "automationModal.cancel": "Cancel", - "automationModal.saving": "Saving...", - "automationModal.save": "Save", - "automationModal.createBtn": "Create", - "automationModal.failedToSave": "Failed to save automation", + "automations.title": "자동화", + "automations.filter": "필터...", + "automations.noMatches": "일치 항목 없음", + "automations.noAutomations": "자동화가 아직 없습니다", + "automations.create": "자동화 만들기", + "automations.status": "상태", + "automations.active": "활성", + "automations.paused": "일시중지", + "automations.all": "전체", + "automations.schedule": "일정", + "automations.model": "모델", + "automations.nextRun": "다음 실행", + "automations.lastRun": "마지막 실행", + "automations.webhook": "웹훅", + "automations.enabled": "활성화됨", + "automations.regenerate": "재생성", + "automations.disable": "비활성화", + "automations.enable": "활성화", + "automations.copied": "복사됨!", + "automations.copy": "복사", + "automations.prompt": "프롬프트", + "automations.runs": "실행 기록", + "automations.noRuns": "아직 실행 기록 없음", + "automations.viewChat": "채팅 보기 →", + "automations.deleted": "삭제됨", + "automations.webhookEnabled": "웹훅 활성화됨", + "automations.webhookDisabled": "웹훅 비활성화됨", + "automations.failedToLoad": "자동화 로드에 실패했습니다", + "automations.failedToLoadOne": "자동화 로드에 실패했습니다", + "automations.failedToToggle": "전환에 실패했습니다", + "automations.failedToDelete": "삭제에 실패했습니다", + "automations.failedToRun": "실행에 실패했습니다", + "automations.failedToGenerateWebhook": "웹훅 생성에 실패했습니다", + "automations.failedToRevokeWebhook": "웹훅 해지에 실패했습니다", + "automations.triggered": "\"{{name}}\" 실행됨", + "automations.deleteConfirm": "\"{{name}}\"을(를) 삭제하시겠습니까?", + "automations.runNow": "지금 실행", + "automations.newAutomation": "새 자동화", + "automations.toggleSidebar": "사이드바 전환", + "automationModal.titlePlaceholder": "자동화 제목", + "automationModal.instructions": "지침", + "automationModal.promptPlaceholder": "프롬프트를 입력하세요...", + "automationModal.selectWorkspace": "작업 공간 선택", + "automationModal.cancel": "취소", + "automationModal.saving": "저장 중...", + "automationModal.save": "저장", + "automationModal.createBtn": "만들기", + "automationModal.failedToSave": "자동화 저장에 실패했습니다", "common.cancel": "취소", "common.save": "저장", "common.edit": "편집", @@ -491,5 +491,38 @@ "a11y.splitEditor": "편집기 분할", "a11y.closePane": "패널 닫기", "a11y.refreshChanges": "변경사항 새로고침", - "keyboard.clickToRebind": "클릭하여 재할당" + "keyboard.clickToRebind": "클릭하여 재할당", + "bar.splitRight": "오른쪽으로 분할", + "bar.splitDown": "아래로 분할", + "bar.voiceMemo": "음성 메모", + "bar.files": "파일", + "messaging.noBots": "구성된 메시징 봇이 없습니다.", + "messaging.failedToLoad": "봇 로드에 실패했습니다", + "messaging.failedToToggle": "봇 전환에 실패했습니다", + "messaging.failedToDelete": "봇 삭제에 실패했습니다", + "messaging.editBot": "봇 편집", + "messaging.addBot": "봇 추가", + "messaging.name": "이름", + "messaging.platform": "플랫폼", + "messaging.token": "토큰", + "messaging.tokenKeep": "현재 값을 유지하려면 비워두세요", + "messaging.tokenPaste": "토큰 붙여넣기", + "messaging.verify": "확인", + "messaging.allowedSenders": "허용된 발신자", + "messaging.allowedSendersHint": "쉼표로 구분된 사용자 ID. 비워두면 전체 허용.", + "messaging.save": "저장 →", + "messaging.add": "추가 →", + "messaging.failedToSave": "저장에 실패했습니다", + "messaging.hint.telegram": "@BotFather에서 봇 생성", + "messaging.hint.discord": "개발자 포털에서 봇 생성", + "messaging.hint.slack": "Bot Token | App Token (파이프 구분)", + "messaging.hint.whatsapp": "Access Token | Phone Number ID (파이프 구분)", + "messaging.hint.signal": "signal-cli URL | 전화번호 (파이프 구분)", + "admin.gateway.keys": "키", + "admin.gateway.copy": "복사", + "admin.gateway.baseUrl": "기본 URL:", + "admin.gateway.apiKey": "API 키:", + "admin.gateway.headers": "헤더:", + "admin.messaging": "메시징", + "admin.gateway.tab": "게이트웨이" } diff --git a/cptr/frontend/src/lib/i18n/locales/pt-BR.json b/cptr/frontend/src/lib/i18n/locales/pt-BR.json index f6108e5..c82d79f 100644 --- a/cptr/frontend/src/lib/i18n/locales/pt-BR.json +++ b/cptr/frontend/src/lib/i18n/locales/pt-BR.json @@ -327,54 +327,54 @@ "keyboard.openSettings": "Open the settings panel", "keyboard.toggleSplit": "Toggle split editor view", "keyboard.toggleSidebar": "Show or hide the sidebar", - "automations.title": "Automations", - "automations.filter": "Filter...", - "automations.noMatches": "No matches", - "automations.noAutomations": "No automations yet", - "automations.create": "Create automation", + "automations.title": "Automações", + "automations.filter": "Filtrar...", + "automations.noMatches": "Sem resultados", + "automations.noAutomations": "Nenhuma automação ainda", + "automations.create": "Criar automação", "automations.status": "Status", - "automations.active": "Active", - "automations.paused": "Paused", - "automations.all": "All", - "automations.schedule": "Schedule", - "automations.model": "Model", - "automations.nextRun": "Next run", - "automations.lastRun": "Last run", + "automations.active": "Ativa", + "automations.paused": "Pausada", + "automations.all": "Todas", + "automations.schedule": "Agendamento", + "automations.model": "Modelo", + "automations.nextRun": "Próxima execução", + "automations.lastRun": "Última execução", "automations.webhook": "Webhook", - "automations.enabled": "Enabled", - "automations.regenerate": "Regenerate", - "automations.disable": "Disable", - "automations.enable": "Enable", - "automations.copied": "Copied!", - "automations.copy": "Copy", + "automations.enabled": "Habilitado", + "automations.regenerate": "Regenerar", + "automations.disable": "Desativar", + "automations.enable": "Ativar", + "automations.copied": "Copiado!", + "automations.copy": "Copiar", "automations.prompt": "Prompt", - "automations.runs": "Runs", - "automations.noRuns": "No runs yet", - "automations.viewChat": "view chat →", - "automations.deleted": "Deleted", - "automations.webhookEnabled": "Webhook enabled", - "automations.webhookDisabled": "Webhook disabled", - "automations.failedToLoad": "Failed to load automations", - "automations.failedToLoadOne": "Failed to load automation", - "automations.failedToToggle": "Failed to toggle", - "automations.failedToDelete": "Failed to delete", - "automations.failedToRun": "Failed to run", - "automations.failedToGenerateWebhook": "Failed to generate webhook", - "automations.failedToRevokeWebhook": "Failed to revoke webhook", - "automations.triggered": "\"{{name}}\" triggered", - "automations.deleteConfirm": "Delete \"{{name}}\"?", - "automations.runNow": "Run now", - "automations.newAutomation": "New automation", - "automations.toggleSidebar": "Toggle sidebar", - "automationModal.titlePlaceholder": "Automation title", - "automationModal.instructions": "Instructions", - "automationModal.promptPlaceholder": "Enter prompt here...", - "automationModal.selectWorkspace": "Select workspace", - "automationModal.cancel": "Cancel", - "automationModal.saving": "Saving...", - "automationModal.save": "Save", - "automationModal.createBtn": "Create", - "automationModal.failedToSave": "Failed to save automation", + "automations.runs": "Execuções", + "automations.noRuns": "Nenhuma execução ainda", + "automations.viewChat": "ver chat →", + "automations.deleted": "Excluída", + "automations.webhookEnabled": "Webhook habilitado", + "automations.webhookDisabled": "Webhook desabilitado", + "automations.failedToLoad": "Falha ao carregar automações", + "automations.failedToLoadOne": "Falha ao carregar automação", + "automations.failedToToggle": "Falha ao alternar", + "automations.failedToDelete": "Falha ao excluir", + "automations.failedToRun": "Falha ao executar", + "automations.failedToGenerateWebhook": "Falha ao gerar webhook", + "automations.failedToRevokeWebhook": "Falha ao revogar webhook", + "automations.triggered": "\"{{name}}\" executada", + "automations.deleteConfirm": "Excluir \"{{name}}\"?", + "automations.runNow": "Executar agora", + "automations.newAutomation": "Nova automação", + "automations.toggleSidebar": "Alternar barra lateral", + "automationModal.titlePlaceholder": "Título da automação", + "automationModal.instructions": "Instruções", + "automationModal.promptPlaceholder": "Digite o prompt aqui...", + "automationModal.selectWorkspace": "Selecionar workspace", + "automationModal.cancel": "Cancelar", + "automationModal.saving": "Salvando...", + "automationModal.save": "Salvar", + "automationModal.createBtn": "Criar", + "automationModal.failedToSave": "Falha ao salvar automação", "common.cancel": "Cancelar", "common.save": "Salvar", "common.edit": "Editar", @@ -491,5 +491,38 @@ "a11y.splitEditor": "Dividir editor", "a11y.closePane": "Fechar painel", "a11y.refreshChanges": "Atualizar alterações", - "keyboard.clickToRebind": "Clique para reatribuir" + "keyboard.clickToRebind": "Clique para reatribuir", + "bar.splitRight": "Dividir à direita", + "bar.splitDown": "Dividir abaixo", + "bar.voiceMemo": "Nota de voz", + "bar.files": "Arquivos", + "messaging.noBots": "Nenhum bot de mensagens configurado.", + "messaging.failedToLoad": "Falha ao carregar os bots", + "messaging.failedToToggle": "Falha ao alternar o bot", + "messaging.failedToDelete": "Falha ao excluir o bot", + "messaging.editBot": "Editar Bot", + "messaging.addBot": "Adicionar Bot", + "messaging.name": "Nome", + "messaging.platform": "Plataforma", + "messaging.token": "Token", + "messaging.tokenKeep": "Deixe vazio para manter", + "messaging.tokenPaste": "Colar token", + "messaging.verify": "Verificar", + "messaging.allowedSenders": "Remetentes permitidos", + "messaging.allowedSendersHint": "IDs de usuários separados por vírgula. Deixe vazio para permitir todos.", + "messaging.save": "Salvar →", + "messaging.add": "Adicionar →", + "messaging.failedToSave": "Falha ao salvar", + "messaging.hint.telegram": "Criar um bot via @BotFather", + "messaging.hint.discord": "Criar um bot no Portal do Desenvolvedor", + "messaging.hint.slack": "Bot Token | App Token (separados por pipe)", + "messaging.hint.whatsapp": "Access Token | Phone Number ID (separados por pipe)", + "messaging.hint.signal": "URL do signal-cli | Telefone (separados por pipe)", + "admin.gateway.keys": "Chaves", + "admin.gateway.copy": "Copiar", + "admin.gateway.baseUrl": "URL base:", + "admin.gateway.apiKey": "Chave API:", + "admin.gateway.headers": "Cabeçalhos:", + "admin.messaging": "Mensagens", + "admin.gateway.tab": "Gateway" } diff --git a/cptr/frontend/src/lib/i18n/locales/ru.json b/cptr/frontend/src/lib/i18n/locales/ru.json index 71392b5..56ffd64 100644 --- a/cptr/frontend/src/lib/i18n/locales/ru.json +++ b/cptr/frontend/src/lib/i18n/locales/ru.json @@ -327,54 +327,54 @@ "keyboard.openSettings": "Open the settings panel", "keyboard.toggleSplit": "Toggle split editor view", "keyboard.toggleSidebar": "Show or hide the sidebar", - "automations.title": "Automations", - "automations.filter": "Filter...", - "automations.noMatches": "No matches", - "automations.noAutomations": "No automations yet", - "automations.create": "Create automation", - "automations.status": "Status", - "automations.active": "Active", - "automations.paused": "Paused", - "automations.all": "All", - "automations.schedule": "Schedule", - "automations.model": "Model", - "automations.nextRun": "Next run", - "automations.lastRun": "Last run", - "automations.webhook": "Webhook", - "automations.enabled": "Enabled", - "automations.regenerate": "Regenerate", - "automations.disable": "Disable", - "automations.enable": "Enable", - "automations.copied": "Copied!", - "automations.copy": "Copy", - "automations.prompt": "Prompt", - "automations.runs": "Runs", - "automations.noRuns": "No runs yet", - "automations.viewChat": "view chat →", - "automations.deleted": "Deleted", - "automations.webhookEnabled": "Webhook enabled", - "automations.webhookDisabled": "Webhook disabled", - "automations.failedToLoad": "Failed to load automations", - "automations.failedToLoadOne": "Failed to load automation", - "automations.failedToToggle": "Failed to toggle", - "automations.failedToDelete": "Failed to delete", - "automations.failedToRun": "Failed to run", - "automations.failedToGenerateWebhook": "Failed to generate webhook", - "automations.failedToRevokeWebhook": "Failed to revoke webhook", - "automations.triggered": "\"{{name}}\" triggered", - "automations.deleteConfirm": "Delete \"{{name}}\"?", - "automations.runNow": "Run now", - "automations.newAutomation": "New automation", - "automations.toggleSidebar": "Toggle sidebar", - "automationModal.titlePlaceholder": "Automation title", - "automationModal.instructions": "Instructions", - "automationModal.promptPlaceholder": "Enter prompt here...", - "automationModal.selectWorkspace": "Select workspace", - "automationModal.cancel": "Cancel", - "automationModal.saving": "Saving...", - "automationModal.save": "Save", - "automationModal.createBtn": "Create", - "automationModal.failedToSave": "Failed to save automation", + "automations.title": "Автоматизации", + "automations.filter": "Фильтр...", + "automations.noMatches": "Нет совпадений", + "automations.noAutomations": "Автоматизаций пока нет", + "automations.create": "Создать автоматизацию", + "automations.status": "Статус", + "automations.active": "Активна", + "automations.paused": "Приостановлена", + "automations.all": "Все", + "automations.schedule": "Расписание", + "automations.model": "Модель", + "automations.nextRun": "Следующий запуск", + "automations.lastRun": "Последний запуск", + "automations.webhook": "Вебхук", + "automations.enabled": "Включено", + "automations.regenerate": "Перегенерировать", + "automations.disable": "Отключить", + "automations.enable": "Включить", + "automations.copied": "Скопировано!", + "automations.copy": "Копировать", + "automations.prompt": "Промпт", + "automations.runs": "Запуски", + "automations.noRuns": "Запусков пока нет", + "automations.viewChat": "открыть чат →", + "automations.deleted": "Удалено", + "automations.webhookEnabled": "Вебхук включён", + "automations.webhookDisabled": "Вебхук отключён", + "automations.failedToLoad": "Не удалось загрузить автоматизации", + "automations.failedToLoadOne": "Не удалось загрузить автоматизацию", + "automations.failedToToggle": "Не удалось переключить", + "automations.failedToDelete": "Не удалось удалить", + "automations.failedToRun": "Не удалось запустить", + "automations.failedToGenerateWebhook": "Не удалось сгенерировать вебхук", + "automations.failedToRevokeWebhook": "Не удалось отозвать вебхук", + "automations.triggered": "\"{{name}}\" запущена", + "automations.deleteConfirm": "Удалить \"{{name}}\"?", + "automations.runNow": "Запустить сейчас", + "automations.newAutomation": "Новая автоматизация", + "automations.toggleSidebar": "Переключить боковую панель", + "automationModal.titlePlaceholder": "Название автоматизации", + "automationModal.instructions": "Инструкции", + "automationModal.promptPlaceholder": "Введите промпт...", + "automationModal.selectWorkspace": "Выбрать рабочее пространство", + "automationModal.cancel": "Отмена", + "automationModal.saving": "Сохранение...", + "automationModal.save": "Сохранить", + "automationModal.createBtn": "Создать", + "automationModal.failedToSave": "Не удалось сохранить автоматизацию", "common.cancel": "Отмена", "common.save": "Сохранить", "common.edit": "Редактировать", @@ -491,5 +491,38 @@ "a11y.splitEditor": "Разделить редактор", "a11y.closePane": "Закрыть панель", "a11y.refreshChanges": "Обновить изменения", - "keyboard.clickToRebind": "Нажмите для переназначения" + "keyboard.clickToRebind": "Нажмите для переназначения", + "bar.splitRight": "Разделить вправо", + "bar.splitDown": "Разделить вниз", + "bar.voiceMemo": "Голосовая заметка", + "bar.files": "Файлы", + "messaging.noBots": "Боты для обмена сообщениями не настроены.", + "messaging.failedToLoad": "Не удалось загрузить ботов", + "messaging.failedToToggle": "Не удалось переключить бота", + "messaging.failedToDelete": "Не удалось удалить бота", + "messaging.editBot": "Редактировать бота", + "messaging.addBot": "Добавить бота", + "messaging.name": "Имя", + "messaging.platform": "Платформа", + "messaging.token": "Токен", + "messaging.tokenKeep": "Оставьте пустым для сохранения", + "messaging.tokenPaste": "Вставить токен", + "messaging.verify": "Проверить", + "messaging.allowedSenders": "Разрешённые отправители", + "messaging.allowedSendersHint": "ID пользователей через запятую. Оставьте пустым для разрешения всем.", + "messaging.save": "Сохранить →", + "messaging.add": "Добавить →", + "messaging.failedToSave": "Не удалось сохранить", + "messaging.hint.telegram": "Создайте бота через @BotFather", + "messaging.hint.discord": "Создайте бота на портале разработчиков", + "messaging.hint.slack": "Bot Token | App Token (через pipe)", + "messaging.hint.whatsapp": "Access Token | Phone Number ID (через pipe)", + "messaging.hint.signal": "URL signal-cli | Номер телефона (через pipe)", + "admin.gateway.keys": "Ключи", + "admin.gateway.copy": "Копировать", + "admin.gateway.baseUrl": "Базовый URL:", + "admin.gateway.apiKey": "API-ключ:", + "admin.gateway.headers": "Заголовки:", + "admin.messaging": "Сообщения", + "admin.gateway.tab": "Шлюз" } diff --git a/cptr/frontend/src/lib/i18n/locales/zh-CN.json b/cptr/frontend/src/lib/i18n/locales/zh-CN.json index dd055d7..b0bb31d 100644 --- a/cptr/frontend/src/lib/i18n/locales/zh-CN.json +++ b/cptr/frontend/src/lib/i18n/locales/zh-CN.json @@ -235,7 +235,7 @@ "system.disk": "磁盘", "system.cores": "{{count}} 核", "system.process": "进程", - "admin.web": "网页", + "admin.web": "网络", "admin.webEnabled": "启用网页访问", "admin.webEnabledHint": "AI 可以搜索网页并获取 URL。", "admin.webDisabledHint": "网页搜索和 URL 获取已禁用。", @@ -327,54 +327,54 @@ "keyboard.openSettings": "Open the settings panel", "keyboard.toggleSplit": "Toggle split editor view", "keyboard.toggleSidebar": "Show or hide the sidebar", - "automations.title": "Automations", - "automations.filter": "Filter...", - "automations.noMatches": "No matches", - "automations.noAutomations": "No automations yet", - "automations.create": "Create automation", - "automations.status": "Status", - "automations.active": "Active", - "automations.paused": "Paused", - "automations.all": "All", - "automations.schedule": "Schedule", - "automations.model": "Model", - "automations.nextRun": "Next run", - "automations.lastRun": "Last run", + "automations.title": "自动化", + "automations.filter": "筛选...", + "automations.noMatches": "无匹配", + "automations.noAutomations": "暂无自动化", + "automations.create": "创建自动化", + "automations.status": "状态", + "automations.active": "活动", + "automations.paused": "已暂停", + "automations.all": "全部", + "automations.schedule": "计划", + "automations.model": "模型", + "automations.nextRun": "下次运行", + "automations.lastRun": "上次运行", "automations.webhook": "Webhook", - "automations.enabled": "Enabled", - "automations.regenerate": "Regenerate", - "automations.disable": "Disable", - "automations.enable": "Enable", - "automations.copied": "Copied!", - "automations.copy": "Copy", - "automations.prompt": "Prompt", - "automations.runs": "Runs", - "automations.noRuns": "No runs yet", - "automations.viewChat": "view chat →", - "automations.deleted": "Deleted", - "automations.webhookEnabled": "Webhook enabled", - "automations.webhookDisabled": "Webhook disabled", - "automations.failedToLoad": "Failed to load automations", - "automations.failedToLoadOne": "Failed to load automation", - "automations.failedToToggle": "Failed to toggle", - "automations.failedToDelete": "Failed to delete", - "automations.failedToRun": "Failed to run", - "automations.failedToGenerateWebhook": "Failed to generate webhook", - "automations.failedToRevokeWebhook": "Failed to revoke webhook", - "automations.triggered": "\"{{name}}\" triggered", - "automations.deleteConfirm": "Delete \"{{name}}\"?", - "automations.runNow": "Run now", - "automations.newAutomation": "New automation", - "automations.toggleSidebar": "Toggle sidebar", - "automationModal.titlePlaceholder": "Automation title", - "automationModal.instructions": "Instructions", - "automationModal.promptPlaceholder": "Enter prompt here...", - "automationModal.selectWorkspace": "Select workspace", - "automationModal.cancel": "Cancel", - "automationModal.saving": "Saving...", - "automationModal.save": "Save", - "automationModal.createBtn": "Create", - "automationModal.failedToSave": "Failed to save automation", + "automations.enabled": "已启用", + "automations.regenerate": "重新生成", + "automations.disable": "禁用", + "automations.enable": "启用", + "automations.copied": "已复制!", + "automations.copy": "复制", + "automations.prompt": "提示词", + "automations.runs": "运行记录", + "automations.noRuns": "暂无运行记录", + "automations.viewChat": "查看对话 →", + "automations.deleted": "已删除", + "automations.webhookEnabled": "Webhook 已启用", + "automations.webhookDisabled": "Webhook 已禁用", + "automations.failedToLoad": "加载自动化失败", + "automations.failedToLoadOne": "加载自动化失败", + "automations.failedToToggle": "切换失败", + "automations.failedToDelete": "删除失败", + "automations.failedToRun": "运行失败", + "automations.failedToGenerateWebhook": "生成 Webhook 失败", + "automations.failedToRevokeWebhook": "撤销 Webhook 失败", + "automations.triggered": "\"{{name}}\" 已触发", + "automations.deleteConfirm": "删除 \"{{name}}\"?", + "automations.runNow": "立即运行", + "automations.newAutomation": "新建自动化", + "automations.toggleSidebar": "切换侧边栏", + "automationModal.titlePlaceholder": "自动化标题", + "automationModal.instructions": "说明", + "automationModal.promptPlaceholder": "在此输入提示词...", + "automationModal.selectWorkspace": "选择工作区", + "automationModal.cancel": "取消", + "automationModal.saving": "保存中...", + "automationModal.save": "保存", + "automationModal.createBtn": "创建", + "automationModal.failedToSave": "保存自动化失败", "common.cancel": "取消", "common.save": "保存", "common.edit": "编辑", @@ -491,5 +491,38 @@ "a11y.splitEditor": "分割编辑器", "a11y.closePane": "关闭面板", "a11y.refreshChanges": "刷新更改", - "keyboard.clickToRebind": "点击重新绑定" + "keyboard.clickToRebind": "点击重新绑定", + "bar.splitRight": "向右分割", + "bar.splitDown": "向下分割", + "bar.voiceMemo": "语音备忘录", + "bar.files": "文件", + "messaging.noBots": "未配置消息机器人。", + "messaging.failedToLoad": "加载机器人失败", + "messaging.failedToToggle": "切换机器人失败", + "messaging.failedToDelete": "删除机器人失败", + "messaging.editBot": "编辑机器人", + "messaging.addBot": "添加机器人", + "messaging.name": "名称", + "messaging.platform": "平台", + "messaging.token": "令牌", + "messaging.tokenKeep": "留空以保留当前值", + "messaging.tokenPaste": "粘贴令牌", + "messaging.verify": "验证", + "messaging.allowedSenders": "允许的发送者", + "messaging.allowedSendersHint": "逗号分隔的用户 ID。留空允许所有人。", + "messaging.save": "保存 →", + "messaging.add": "添加 →", + "messaging.failedToSave": "保存失败", + "messaging.hint.telegram": "通过 @BotFather 创建机器人", + "messaging.hint.discord": "在开发者门户创建机器人", + "messaging.hint.slack": "Bot Token | App Token (竖线分隔)", + "messaging.hint.whatsapp": "Access Token | Phone Number ID (竖线分隔)", + "messaging.hint.signal": "signal-cli URL | 电话号码 (竖线分隔)", + "admin.gateway.keys": "密钥", + "admin.gateway.copy": "复制", + "admin.gateway.baseUrl": "基础 URL:", + "admin.gateway.apiKey": "API 密钥:", + "admin.gateway.headers": "请求头:", + "admin.messaging": "消息", + "admin.gateway.tab": "网关" } diff --git a/cptr/frontend/src/lib/i18n/locales/zh-TW.json b/cptr/frontend/src/lib/i18n/locales/zh-TW.json index 6f218e0..a8d8053 100644 --- a/cptr/frontend/src/lib/i18n/locales/zh-TW.json +++ b/cptr/frontend/src/lib/i18n/locales/zh-TW.json @@ -235,7 +235,7 @@ "system.disk": "磁碟", "system.cores": "{{count}} 核心", "system.process": "處理程序", - "admin.web": "網頁", + "admin.web": "網路", "admin.webEnabled": "啟用網頁存取", "admin.webEnabledHint": "AI 可以搜尋網頁並擷取 URL。", "admin.webDisabledHint": "網頁搜尋與 URL 擷取已停用。", @@ -327,54 +327,54 @@ "keyboard.openSettings": "Open the settings panel", "keyboard.toggleSplit": "Toggle split editor view", "keyboard.toggleSidebar": "Show or hide the sidebar", - "automations.title": "Automations", - "automations.filter": "Filter...", - "automations.noMatches": "No matches", - "automations.noAutomations": "No automations yet", - "automations.create": "Create automation", - "automations.status": "Status", - "automations.active": "Active", - "automations.paused": "Paused", - "automations.all": "All", - "automations.schedule": "Schedule", - "automations.model": "Model", - "automations.nextRun": "Next run", - "automations.lastRun": "Last run", + "automations.title": "自動化", + "automations.filter": "篩選...", + "automations.noMatches": "無符合項目", + "automations.noAutomations": "尚無自動化", + "automations.create": "建立自動化", + "automations.status": "狀態", + "automations.active": "活動中", + "automations.paused": "已暫停", + "automations.all": "全部", + "automations.schedule": "排程", + "automations.model": "模型", + "automations.nextRun": "下次執行", + "automations.lastRun": "上次執行", "automations.webhook": "Webhook", - "automations.enabled": "Enabled", - "automations.regenerate": "Regenerate", - "automations.disable": "Disable", - "automations.enable": "Enable", - "automations.copied": "Copied!", - "automations.copy": "Copy", - "automations.prompt": "Prompt", - "automations.runs": "Runs", - "automations.noRuns": "No runs yet", - "automations.viewChat": "view chat →", - "automations.deleted": "Deleted", - "automations.webhookEnabled": "Webhook enabled", - "automations.webhookDisabled": "Webhook disabled", - "automations.failedToLoad": "Failed to load automations", - "automations.failedToLoadOne": "Failed to load automation", - "automations.failedToToggle": "Failed to toggle", - "automations.failedToDelete": "Failed to delete", - "automations.failedToRun": "Failed to run", - "automations.failedToGenerateWebhook": "Failed to generate webhook", - "automations.failedToRevokeWebhook": "Failed to revoke webhook", - "automations.triggered": "\"{{name}}\" triggered", - "automations.deleteConfirm": "Delete \"{{name}}\"?", - "automations.runNow": "Run now", - "automations.newAutomation": "New automation", - "automations.toggleSidebar": "Toggle sidebar", - "automationModal.titlePlaceholder": "Automation title", - "automationModal.instructions": "Instructions", - "automationModal.promptPlaceholder": "Enter prompt here...", - "automationModal.selectWorkspace": "Select workspace", - "automationModal.cancel": "Cancel", - "automationModal.saving": "Saving...", - "automationModal.save": "Save", - "automationModal.createBtn": "Create", - "automationModal.failedToSave": "Failed to save automation", + "automations.enabled": "已啟用", + "automations.regenerate": "重新產生", + "automations.disable": "停用", + "automations.enable": "啟用", + "automations.copied": "已複製!", + "automations.copy": "複製", + "automations.prompt": "提示詞", + "automations.runs": "執行記錄", + "automations.noRuns": "尚無執行記錄", + "automations.viewChat": "檢視對話 →", + "automations.deleted": "已刪除", + "automations.webhookEnabled": "Webhook 已啟用", + "automations.webhookDisabled": "Webhook 已停用", + "automations.failedToLoad": "載入自動化失敗", + "automations.failedToLoadOne": "載入自動化失敗", + "automations.failedToToggle": "切換失敗", + "automations.failedToDelete": "刪除失敗", + "automations.failedToRun": "執行失敗", + "automations.failedToGenerateWebhook": "產生 Webhook 失敗", + "automations.failedToRevokeWebhook": "撤銷 Webhook 失敗", + "automations.triggered": "\"{{name}}\" 已觸發", + "automations.deleteConfirm": "刪除 \"{{name}}\"?", + "automations.runNow": "立即執行", + "automations.newAutomation": "新增自動化", + "automations.toggleSidebar": "切換側邊欄", + "automationModal.titlePlaceholder": "自動化標題", + "automationModal.instructions": "說明", + "automationModal.promptPlaceholder": "在此輸入提示詞...", + "automationModal.selectWorkspace": "選擇工作區", + "automationModal.cancel": "取消", + "automationModal.saving": "儲存中...", + "automationModal.save": "儲存", + "automationModal.createBtn": "建立", + "automationModal.failedToSave": "儲存自動化失敗", "common.cancel": "取消", "common.save": "儲存", "common.edit": "編輯", @@ -491,5 +491,38 @@ "a11y.splitEditor": "分割編輯器", "a11y.closePane": "關閉面板", "a11y.refreshChanges": "重新整理變更", - "keyboard.clickToRebind": "點擊重新繫結" + "keyboard.clickToRebind": "點擊重新繫結", + "bar.splitRight": "向右分割", + "bar.splitDown": "向下分割", + "bar.voiceMemo": "語音備忘錄", + "bar.files": "檔案", + "messaging.noBots": "未設定訊息機器人。", + "messaging.failedToLoad": "載入機器人失敗", + "messaging.failedToToggle": "切換機器人失敗", + "messaging.failedToDelete": "刪除機器人失敗", + "messaging.editBot": "編輯機器人", + "messaging.addBot": "新增機器人", + "messaging.name": "名稱", + "messaging.platform": "平台", + "messaging.token": "權杖", + "messaging.tokenKeep": "留空以保留目前的值", + "messaging.tokenPaste": "貼上權杖", + "messaging.verify": "驗證", + "messaging.allowedSenders": "允許的傳送者", + "messaging.allowedSendersHint": "逗號分隔的使用者 ID。留空允許所有人。", + "messaging.save": "儲存 →", + "messaging.add": "新增 →", + "messaging.failedToSave": "儲存失敗", + "messaging.hint.telegram": "透過 @BotFather 建立機器人", + "messaging.hint.discord": "在開發者入口網站建立機器人", + "messaging.hint.slack": "Bot Token | App Token (管線符號分隔)", + "messaging.hint.whatsapp": "Access Token | Phone Number ID (管線符號分隔)", + "messaging.hint.signal": "signal-cli URL | 電話號碼 (管線符號分隔)", + "admin.gateway.keys": "金鑰", + "admin.gateway.copy": "複製", + "admin.gateway.baseUrl": "基礎 URL:", + "admin.gateway.apiKey": "API 金鑰:", + "admin.gateway.headers": "標頭:", + "admin.messaging": "訊息", + "admin.gateway.tab": "閘道" } From 83c456a10cbb3bdeae3b11cd4b989f739a7f60e0 Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Sat, 13 Jun 2026 07:56:56 +0100 Subject: [PATCH 04/10] refac --- .../lib/components/Settings/Keyboard.svelte | 19 +++++- .../lib/components/chat/ChatHistory.svelte | 15 +++-- .../src/lib/components/chat/ChatPanel.svelte | 6 +- .../lib/components/chat/DictateButton.svelte | 3 +- .../lib/components/chat/OutputEditView.svelte | 8 +-- cptr/frontend/src/lib/i18n/locales/de.json | 66 +++++++++++++++---- cptr/frontend/src/lib/i18n/locales/en.json | 32 ++++++++- cptr/frontend/src/lib/i18n/locales/es.json | 66 +++++++++++++++---- cptr/frontend/src/lib/i18n/locales/fr.json | 66 +++++++++++++++---- cptr/frontend/src/lib/i18n/locales/ja.json | 66 +++++++++++++++---- cptr/frontend/src/lib/i18n/locales/ko.json | 66 +++++++++++++++---- cptr/frontend/src/lib/i18n/locales/pt-BR.json | 66 +++++++++++++++---- cptr/frontend/src/lib/i18n/locales/ru.json | 66 +++++++++++++++---- cptr/frontend/src/lib/i18n/locales/zh-CN.json | 66 +++++++++++++++---- cptr/frontend/src/lib/i18n/locales/zh-TW.json | 66 +++++++++++++++---- 15 files changed, 560 insertions(+), 117 deletions(-) diff --git a/cptr/frontend/src/lib/components/Settings/Keyboard.svelte b/cptr/frontend/src/lib/components/Settings/Keyboard.svelte index 09dfff4..6aed5ad 100644 --- a/cptr/frontend/src/lib/components/Settings/Keyboard.svelte +++ b/cptr/frontend/src/lib/components/Settings/Keyboard.svelte @@ -2,7 +2,6 @@ import { keybindings, ACTION_IDS, - ACTION_LABELS, DEFAULT_KEYBINDINGS, formatChord, eventToChord, @@ -26,6 +25,22 @@ toggleSidebar: $t('keyboard.toggleSidebar') }); + /** Translated action labels for display. */ + const ACTION_LABELS: Record = $derived({ + newFile: $t('keyboard.action.newFile'), + newTerminal: $t('keyboard.action.newTerminal'), + newChat: $t('keyboard.action.newChat'), + closeTab: $t('keyboard.action.closeTab'), + nextTab: $t('keyboard.action.nextTab'), + prevTab: $t('keyboard.action.prevTab'), + quickOpen: $t('keyboard.action.quickOpen'), + searchAll: $t('keyboard.action.searchAll'), + openSettings: $t('keyboard.action.openSettings'), + toggleSplit: $t('keyboard.action.toggleSplit'), + toggleSidebar: $t('keyboard.action.toggleSidebar'), + voiceMemo: $t('keyboard.action.voiceMemo') + }); + let recordingAction = $state(null); function startRecording(actionId: ActionId) { @@ -130,7 +145,7 @@ {#if conflict} !! {/if}
diff --git a/cptr/frontend/src/lib/components/chat/ChatHistory.svelte b/cptr/frontend/src/lib/components/chat/ChatHistory.svelte index 4824db3..9b5f74f 100644 --- a/cptr/frontend/src/lib/components/chat/ChatHistory.svelte +++ b/cptr/frontend/src/lib/components/chat/ChatHistory.svelte @@ -1,5 +1,6 @@ diff --git a/cptr/frontend/src/lib/i18n/locales/de.json b/cptr/frontend/src/lib/i18n/locales/de.json index 7cc3f83..7c89e6c 100644 --- a/cptr/frontend/src/lib/i18n/locales/de.json +++ b/cptr/frontend/src/lib/i18n/locales/de.json @@ -317,16 +317,16 @@ "about.copyright": "Copyright © 2026 Open WebUI Inc. Alle Rechte vorbehalten.", "about.updateAvailable": "v{{version}} verfügbar", "connections.failedToUpdate": "Verbindung konnte nicht aktualisiert werden", - "keyboard.newFile": "Open a new untitled file", - "keyboard.newTerminal": "Open a new terminal session", - "keyboard.newChat": "Start a new AI chat", - "keyboard.closeTab": "Close the active tab", - "keyboard.nextTab": "Switch to the next tab", - "keyboard.prevTab": "Switch to the previous tab", - "keyboard.quickOpen": "Search and open files quickly", - "keyboard.openSettings": "Open the settings panel", - "keyboard.toggleSplit": "Toggle split editor view", - "keyboard.toggleSidebar": "Show or hide the sidebar", + "keyboard.newFile": "Eine neue leere Datei öffnen", + "keyboard.newTerminal": "Eine neue Terminalsitzung öffnen", + "keyboard.newChat": "Einen neuen KI-Chat starten", + "keyboard.closeTab": "Den aktiven Tab schließen", + "keyboard.nextTab": "Zum nächsten Tab wechseln", + "keyboard.prevTab": "Zum vorherigen Tab wechseln", + "keyboard.quickOpen": "Dateien schnell suchen und öffnen", + "keyboard.openSettings": "Einstellungen öffnen", + "keyboard.toggleSplit": "Geteilte Ansicht umschalten", + "keyboard.toggleSidebar": "Seitenleiste ein-/ausblenden", "automations.title": "Automatisierungen", "automations.filter": "Filtern...", "automations.noMatches": "Keine Treffer", @@ -524,5 +524,49 @@ "admin.gateway.apiKey": "API-Schlüssel:", "admin.gateway.headers": "Header:", "admin.messaging": "Nachrichten", - "admin.gateway.tab": "Gateway" + "admin.gateway.tab": "Gateway", + "keyboard.action.newFile": "Neue Datei", + "keyboard.action.newTerminal": "Neues Terminal", + "keyboard.action.newChat": "Neuer Chat", + "keyboard.action.closeTab": "Tab schließen", + "keyboard.action.nextTab": "Nächster Tab", + "keyboard.action.prevTab": "Vorheriger Tab", + "keyboard.action.quickOpen": "Schnell öffnen", + "keyboard.action.searchAll": "Suche", + "keyboard.action.openSettings": "Einstellungen öffnen", + "keyboard.action.toggleSplit": "Teilung umschalten", + "keyboard.action.toggleSidebar": "Seitenleiste umschalten", + "keyboard.action.voiceMemo": "Sprachnotiz", + "keyboard.conflict": "Auch belegt von {{action}}", + "chat.history.justNow": "Gerade eben", + "chat.history.minutesAgo": "vor {{count}}min", + "chat.history.hoursAgo": "vor {{count}}h", + "chat.history.daysAgo": "vor {{count}}T", + "chat.history.title": "Titel", + "chat.history.updated": "Aktualisiert", + "chat.history.delete": "Löschen", + "chat.edit.text": "Text", + "chat.edit.thought": "Gedanke", + "chat.edit.tool": "Werkzeug", + "chat.edit.item": "Element", + "chat.dictate.unsupported": "Spracherkennung wird in diesem Browser nicht unterstützt.", + "chat.fallbackTitle": "Chat", + "admin.gateway.copied": "In die Zwischenablage kopiert", + "admin.gateway.createError": "Schlüssel konnte nicht erstellt werden", + "admin.gateway.createKey": "Schlüssel erstellen", + "admin.gateway.deleteError": "Schlüssel konnte nicht gelöscht werden", + "admin.gateway.description": "Generieren Sie API-Schlüssel, um Open WebUI oder einen OpenAI-kompatiblen Client mit Ihren Arbeitsbereichen zu verbinden.", + "admin.gateway.howToConnect": "Von Open WebUI verbinden", + "admin.gateway.keyCreated": "API-Schlüssel erstellt", + "admin.gateway.keyDeleted": "API-Schlüssel gelöscht", + "admin.gateway.keyNamePlaceholder": "Schlüsselname (z.B. open-webui)", + "admin.gateway.keyWarning": "Dieser Schlüssel wird nur einmal angezeigt. Speichern Sie ihn sicher.", + "admin.gateway.loadError": "API-Schlüssel konnten nicht geladen werden", + "admin.gateway.model": "Antwortmodell", + "admin.gateway.modelDescription": "Gateway-Clients wählen einen Arbeitsbereich; cptr verwendet dieses Modell für die Antwort.", + "admin.gateway.modelFallback": "Arbeitsbereich-/Standardmodell verwenden", + "admin.gateway.modelSaveError": "Gateway-Modell konnte nicht gespeichert werden", + "admin.gateway.newKey": "Neuer API-Schlüssel erstellt. Jetzt kopieren", + "admin.gateway.noKeys": "Noch keine API-Schlüssel", + "admin.gateway.title": "API-Gateway" } diff --git a/cptr/frontend/src/lib/i18n/locales/en.json b/cptr/frontend/src/lib/i18n/locales/en.json index b501692..8e87a66 100644 --- a/cptr/frontend/src/lib/i18n/locales/en.json +++ b/cptr/frontend/src/lib/i18n/locales/en.json @@ -614,6 +614,36 @@ "admin.gateway.copy": "Copy", "admin.gateway.baseUrl": "Base URL:", "admin.gateway.apiKey": "API Key:", - "admin.gateway.headers": "Headers:" + "admin.gateway.headers": "Headers:", + + "keyboard.action.newFile": "New File", + "keyboard.action.newTerminal": "New Terminal", + "keyboard.action.newChat": "New Chat", + "keyboard.action.closeTab": "Close Tab", + "keyboard.action.nextTab": "Next Tab", + "keyboard.action.prevTab": "Previous Tab", + "keyboard.action.quickOpen": "Quick Open", + "keyboard.action.searchAll": "Search", + "keyboard.action.openSettings": "Open Settings", + "keyboard.action.toggleSplit": "Toggle Split", + "keyboard.action.toggleSidebar": "Toggle Sidebar", + "keyboard.action.voiceMemo": "Voice Memo", + "keyboard.conflict": "Also bound to {{action}}", + + "chat.history.justNow": "Just now", + "chat.history.minutesAgo": "{{count}}m ago", + "chat.history.hoursAgo": "{{count}}h ago", + "chat.history.daysAgo": "{{count}}d ago", + "chat.history.title": "Title", + "chat.history.updated": "Updated", + "chat.history.delete": "Delete", + + "chat.edit.text": "Text", + "chat.edit.thought": "Thought", + "chat.edit.tool": "Tool", + "chat.edit.item": "Item", + + "chat.dictate.unsupported": "Speech recognition not supported in this browser.", + "chat.fallbackTitle": "Chat" } diff --git a/cptr/frontend/src/lib/i18n/locales/es.json b/cptr/frontend/src/lib/i18n/locales/es.json index 110b106..47f37f9 100644 --- a/cptr/frontend/src/lib/i18n/locales/es.json +++ b/cptr/frontend/src/lib/i18n/locales/es.json @@ -317,16 +317,16 @@ "about.copyright": "Copyright © 2026 Open WebUI Inc. Todos los derechos reservados.", "about.updateAvailable": "v{{version}} disponible", "connections.failedToUpdate": "Error al actualizar conexión", - "keyboard.newFile": "Open a new untitled file", - "keyboard.newTerminal": "Open a new terminal session", - "keyboard.newChat": "Start a new AI chat", - "keyboard.closeTab": "Close the active tab", - "keyboard.nextTab": "Switch to the next tab", - "keyboard.prevTab": "Switch to the previous tab", - "keyboard.quickOpen": "Search and open files quickly", - "keyboard.openSettings": "Open the settings panel", - "keyboard.toggleSplit": "Toggle split editor view", - "keyboard.toggleSidebar": "Show or hide the sidebar", + "keyboard.newFile": "Abrir un archivo nuevo vacío", + "keyboard.newTerminal": "Abrir una nueva sesión de terminal", + "keyboard.newChat": "Iniciar un nuevo chat de IA", + "keyboard.closeTab": "Cerrar la pestaña activa", + "keyboard.nextTab": "Cambiar a la siguiente pestaña", + "keyboard.prevTab": "Cambiar a la pestaña anterior", + "keyboard.quickOpen": "Buscar y abrir archivos rápidamente", + "keyboard.openSettings": "Abrir el panel de ajustes", + "keyboard.toggleSplit": "Alternar vista dividida del editor", + "keyboard.toggleSidebar": "Mostrar u ocultar la barra lateral", "automations.title": "Automatizaciones", "automations.filter": "Filtrar...", "automations.noMatches": "Sin resultados", @@ -524,5 +524,49 @@ "admin.gateway.apiKey": "Clave API:", "admin.gateway.headers": "Encabezados:", "admin.messaging": "Mensajería", - "admin.gateway.tab": "Gateway" + "admin.gateway.tab": "Gateway", + "keyboard.action.newFile": "Nuevo archivo", + "keyboard.action.newTerminal": "Nueva terminal", + "keyboard.action.newChat": "Nuevo chat", + "keyboard.action.closeTab": "Cerrar pestaña", + "keyboard.action.nextTab": "Siguiente pestaña", + "keyboard.action.prevTab": "Pestaña anterior", + "keyboard.action.quickOpen": "Apertura rápida", + "keyboard.action.searchAll": "Buscar", + "keyboard.action.openSettings": "Abrir ajustes", + "keyboard.action.toggleSplit": "Alternar división", + "keyboard.action.toggleSidebar": "Alternar barra lateral", + "keyboard.action.voiceMemo": "Nota de voz", + "keyboard.conflict": "También asignado a {{action}}", + "chat.history.justNow": "Ahora mismo", + "chat.history.minutesAgo": "hace {{count}}min", + "chat.history.hoursAgo": "hace {{count}}h", + "chat.history.daysAgo": "hace {{count}}d", + "chat.history.title": "Título", + "chat.history.updated": "Actualizado", + "chat.history.delete": "Eliminar", + "chat.edit.text": "Texto", + "chat.edit.thought": "Pensamiento", + "chat.edit.tool": "Herramienta", + "chat.edit.item": "Elemento", + "chat.dictate.unsupported": "El reconocimiento de voz no es compatible con este navegador.", + "chat.fallbackTitle": "Chat", + "admin.gateway.copied": "Copiado al portapapeles", + "admin.gateway.createError": "Error al crear la clave", + "admin.gateway.createKey": "Crear clave", + "admin.gateway.deleteError": "Error al eliminar la clave", + "admin.gateway.description": "Genera claves API para conectar Open WebUI o cualquier cliente compatible con OpenAI a tus espacios de trabajo.", + "admin.gateway.howToConnect": "Conectar desde Open WebUI", + "admin.gateway.keyCreated": "Clave API creada", + "admin.gateway.keyDeleted": "Clave API eliminada", + "admin.gateway.keyNamePlaceholder": "Nombre de la clave (ej. open-webui)", + "admin.gateway.keyWarning": "Esta clave solo se mostrará una vez. Guárdala de forma segura.", + "admin.gateway.loadError": "Error al cargar las claves API", + "admin.gateway.model": "Modelo de respuesta", + "admin.gateway.modelDescription": "Los clientes gateway eligen un espacio de trabajo; cptr usa este modelo para generar la respuesta.", + "admin.gateway.modelFallback": "Usar modelo del espacio de trabajo/predeterminado", + "admin.gateway.modelSaveError": "Error al guardar el modelo gateway", + "admin.gateway.newKey": "Nueva clave API creada. Cópiala ahora", + "admin.gateway.noKeys": "Aún no hay claves API", + "admin.gateway.title": "API Gateway" } diff --git a/cptr/frontend/src/lib/i18n/locales/fr.json b/cptr/frontend/src/lib/i18n/locales/fr.json index af39f5b..121c915 100644 --- a/cptr/frontend/src/lib/i18n/locales/fr.json +++ b/cptr/frontend/src/lib/i18n/locales/fr.json @@ -316,16 +316,16 @@ "about.copyright": "Copyright © 2026 Open WebUI Inc. Tous droits réservés.", "about.updateAvailable": "v{{version}} disponible", "connections.failedToUpdate": "Échec de la mise à jour de la connexion", - "keyboard.newFile": "Open a new untitled file", - "keyboard.newTerminal": "Open a new terminal session", - "keyboard.newChat": "Start a new AI chat", - "keyboard.closeTab": "Close the active tab", - "keyboard.nextTab": "Switch to the next tab", - "keyboard.prevTab": "Switch to the previous tab", - "keyboard.quickOpen": "Search and open files quickly", - "keyboard.openSettings": "Open the settings panel", - "keyboard.toggleSplit": "Toggle split editor view", - "keyboard.toggleSidebar": "Show or hide the sidebar", + "keyboard.newFile": "Ouvrir un nouveau fichier vide", + "keyboard.newTerminal": "Ouvrir une nouvelle session de terminal", + "keyboard.newChat": "Démarrer un nouveau chat IA", + "keyboard.closeTab": "Fermer l'onglet actif", + "keyboard.nextTab": "Passer à l'onglet suivant", + "keyboard.prevTab": "Passer à l'onglet précédent", + "keyboard.quickOpen": "Rechercher et ouvrir des fichiers rapidement", + "keyboard.openSettings": "Ouvrir le panneau des paramètres", + "keyboard.toggleSplit": "Basculer la vue divisée de l'éditeur", + "keyboard.toggleSidebar": "Afficher ou masquer la barre latérale", "automations.title": "Automatisations", "automations.filter": "Filtrer...", "automations.noMatches": "Aucun résultat", @@ -523,5 +523,49 @@ "admin.gateway.apiKey": "Clé API :", "admin.gateway.headers": "En-têtes :", "admin.messaging": "Messagerie", - "admin.gateway.tab": "Passerelle" + "admin.gateway.tab": "Passerelle", + "keyboard.action.newFile": "Nouveau fichier", + "keyboard.action.newTerminal": "Nouveau terminal", + "keyboard.action.newChat": "Nouveau chat", + "keyboard.action.closeTab": "Fermer l'onglet", + "keyboard.action.nextTab": "Onglet suivant", + "keyboard.action.prevTab": "Onglet précédent", + "keyboard.action.quickOpen": "Ouverture rapide", + "keyboard.action.searchAll": "Rechercher", + "keyboard.action.openSettings": "Ouvrir les paramètres", + "keyboard.action.toggleSplit": "Basculer la division", + "keyboard.action.toggleSidebar": "Basculer la barre latérale", + "keyboard.action.voiceMemo": "Note vocale", + "keyboard.conflict": "Également lié à {{action}}", + "chat.history.justNow": "À l'instant", + "chat.history.minutesAgo": "il y a {{count}}min", + "chat.history.hoursAgo": "il y a {{count}}h", + "chat.history.daysAgo": "il y a {{count}}j", + "chat.history.title": "Titre", + "chat.history.updated": "Mis à jour", + "chat.history.delete": "Supprimer", + "chat.edit.text": "Texte", + "chat.edit.thought": "Réflexion", + "chat.edit.tool": "Outil", + "chat.edit.item": "Élément", + "chat.dictate.unsupported": "La reconnaissance vocale n'est pas prise en charge dans ce navigateur.", + "chat.fallbackTitle": "Chat", + "admin.gateway.copied": "Copié dans le presse-papiers", + "admin.gateway.createError": "Échec de la création de la clé", + "admin.gateway.createKey": "Créer une clé", + "admin.gateway.deleteError": "Échec de la suppression de la clé", + "admin.gateway.description": "Générez des clés API pour connecter Open WebUI ou tout client compatible OpenAI à vos espaces de travail.", + "admin.gateway.howToConnect": "Se connecter depuis Open WebUI", + "admin.gateway.keyCreated": "Clé API créée", + "admin.gateway.keyDeleted": "Clé API supprimée", + "admin.gateway.keyNamePlaceholder": "Nom de la clé (ex. open-webui)", + "admin.gateway.keyWarning": "Cette clé ne sera affichée qu'une seule fois. Conservez-la en sécurité.", + "admin.gateway.loadError": "Échec du chargement des clés API", + "admin.gateway.model": "Modèle de réponse", + "admin.gateway.modelDescription": "Les clients passerelle choisissent un espace de travail ; cptr utilise ce modèle pour générer la réponse.", + "admin.gateway.modelFallback": "Utiliser le modèle de l'espace de travail/par défaut", + "admin.gateway.modelSaveError": "Échec de l'enregistrement du modèle passerelle", + "admin.gateway.newKey": "Nouvelle clé API créée. Copiez-la maintenant", + "admin.gateway.noKeys": "Aucune clé API", + "admin.gateway.title": "Passerelle API" } diff --git a/cptr/frontend/src/lib/i18n/locales/ja.json b/cptr/frontend/src/lib/i18n/locales/ja.json index d3de12b..f872477 100644 --- a/cptr/frontend/src/lib/i18n/locales/ja.json +++ b/cptr/frontend/src/lib/i18n/locales/ja.json @@ -317,16 +317,16 @@ "about.copyright": "Copyright © 2026 Open WebUI Inc. All rights reserved.", "about.updateAvailable": "v{{version}} が利用可能", "connections.failedToUpdate": "接続の更新に失敗しました", - "keyboard.newFile": "Open a new untitled file", - "keyboard.newTerminal": "Open a new terminal session", - "keyboard.newChat": "Start a new AI chat", - "keyboard.closeTab": "Close the active tab", - "keyboard.nextTab": "Switch to the next tab", - "keyboard.prevTab": "Switch to the previous tab", - "keyboard.quickOpen": "Search and open files quickly", - "keyboard.openSettings": "Open the settings panel", - "keyboard.toggleSplit": "Toggle split editor view", - "keyboard.toggleSidebar": "Show or hide the sidebar", + "keyboard.newFile": "新しい空のファイルを開く", + "keyboard.newTerminal": "新しいターミナルセッションを開く", + "keyboard.newChat": "新しいAIチャットを開始", + "keyboard.closeTab": "アクティブなタブを閉じる", + "keyboard.nextTab": "次のタブに切り替える", + "keyboard.prevTab": "前のタブに切り替える", + "keyboard.quickOpen": "ファイルをすばやく検索して開く", + "keyboard.openSettings": "設定パネルを開く", + "keyboard.toggleSplit": "エディタの分割表示を切替", + "keyboard.toggleSidebar": "サイドバーの表示/非表示", "automations.title": "自動化", "automations.filter": "フィルター...", "automations.noMatches": "一致なし", @@ -524,5 +524,49 @@ "admin.gateway.apiKey": "APIキー:", "admin.gateway.headers": "ヘッダー:", "admin.messaging": "メッセージング", - "admin.gateway.tab": "ゲートウェイ" + "admin.gateway.tab": "ゲートウェイ", + "keyboard.action.newFile": "新規ファイル", + "keyboard.action.newTerminal": "新規ターミナル", + "keyboard.action.newChat": "新規チャット", + "keyboard.action.closeTab": "タブを閉じる", + "keyboard.action.nextTab": "次のタブ", + "keyboard.action.prevTab": "前のタブ", + "keyboard.action.quickOpen": "クイックオープン", + "keyboard.action.searchAll": "検索", + "keyboard.action.openSettings": "設定を開く", + "keyboard.action.toggleSplit": "分割を切替", + "keyboard.action.toggleSidebar": "サイドバーを切替", + "keyboard.action.voiceMemo": "音声メモ", + "keyboard.conflict": "{{action}}にも割り当て済み", + "chat.history.justNow": "たった今", + "chat.history.minutesAgo": "{{count}}分前", + "chat.history.hoursAgo": "{{count}}時間前", + "chat.history.daysAgo": "{{count}}日前", + "chat.history.title": "タイトル", + "chat.history.updated": "更新日", + "chat.history.delete": "削除", + "chat.edit.text": "テキスト", + "chat.edit.thought": "思考", + "chat.edit.tool": "ツール", + "chat.edit.item": "項目", + "chat.dictate.unsupported": "このブラウザでは音声認識がサポートされていません。", + "chat.fallbackTitle": "チャット", + "admin.gateway.copied": "クリップボードにコピーしました", + "admin.gateway.createError": "キーの作成に失敗しました", + "admin.gateway.createKey": "キーを作成", + "admin.gateway.deleteError": "キーの削除に失敗しました", + "admin.gateway.description": "APIキーを生成して、Open WebUIまたはOpenAI互換クライアントをワークスペースに接続します。", + "admin.gateway.howToConnect": "Open WebUIから接続", + "admin.gateway.keyCreated": "APIキーが作成されました", + "admin.gateway.keyDeleted": "APIキーが削除されました", + "admin.gateway.keyNamePlaceholder": "キー名(例:open-webui)", + "admin.gateway.keyWarning": "このキーは一度だけ表示されます。安全に保管してください。", + "admin.gateway.loadError": "APIキーの読み込みに失敗しました", + "admin.gateway.model": "応答モデル", + "admin.gateway.modelDescription": "ゲートウェイクライアントがワークスペースを選択し、cptrがこのモデルで応答を生成します。", + "admin.gateway.modelFallback": "ワークスペース/デフォルトモデルを使用", + "admin.gateway.modelSaveError": "ゲートウェイモデルの保存に失敗しました", + "admin.gateway.newKey": "新しいAPIキーが作成されました。今すぐコピーしてください", + "admin.gateway.noKeys": "APIキーがまだありません", + "admin.gateway.title": "APIゲートウェイ" } diff --git a/cptr/frontend/src/lib/i18n/locales/ko.json b/cptr/frontend/src/lib/i18n/locales/ko.json index 23c7f38..212145c 100644 --- a/cptr/frontend/src/lib/i18n/locales/ko.json +++ b/cptr/frontend/src/lib/i18n/locales/ko.json @@ -317,16 +317,16 @@ "about.copyright": "Copyright © 2026 Open WebUI Inc. All rights reserved.", "about.updateAvailable": "v{{version}} 사용 가능", "connections.failedToUpdate": "연결 업데이트에 실패했습니다", - "keyboard.newFile": "Open a new untitled file", - "keyboard.newTerminal": "Open a new terminal session", - "keyboard.newChat": "Start a new AI chat", - "keyboard.closeTab": "Close the active tab", - "keyboard.nextTab": "Switch to the next tab", - "keyboard.prevTab": "Switch to the previous tab", - "keyboard.quickOpen": "Search and open files quickly", - "keyboard.openSettings": "Open the settings panel", - "keyboard.toggleSplit": "Toggle split editor view", - "keyboard.toggleSidebar": "Show or hide the sidebar", + "keyboard.newFile": "새 빈 파일 열기", + "keyboard.newTerminal": "새 터미널 세션 열기", + "keyboard.newChat": "새 AI 채팅 시작", + "keyboard.closeTab": "활성 탭 닫기", + "keyboard.nextTab": "다음 탭으로 전환", + "keyboard.prevTab": "이전 탭으로 전환", + "keyboard.quickOpen": "파일을 빠르게 검색하고 열기", + "keyboard.openSettings": "설정 패널 열기", + "keyboard.toggleSplit": "편집기 분할 보기 전환", + "keyboard.toggleSidebar": "사이드바 표시/숨기기", "automations.title": "자동화", "automations.filter": "필터...", "automations.noMatches": "일치 항목 없음", @@ -524,5 +524,49 @@ "admin.gateway.apiKey": "API 키:", "admin.gateway.headers": "헤더:", "admin.messaging": "메시징", - "admin.gateway.tab": "게이트웨이" + "admin.gateway.tab": "게이트웨이", + "keyboard.action.newFile": "새 파일", + "keyboard.action.newTerminal": "새 터미널", + "keyboard.action.newChat": "새 채팅", + "keyboard.action.closeTab": "탭 닫기", + "keyboard.action.nextTab": "다음 탭", + "keyboard.action.prevTab": "이전 탭", + "keyboard.action.quickOpen": "빠른 열기", + "keyboard.action.searchAll": "검색", + "keyboard.action.openSettings": "설정 열기", + "keyboard.action.toggleSplit": "분할 전환", + "keyboard.action.toggleSidebar": "사이드바 전환", + "keyboard.action.voiceMemo": "음성 메모", + "keyboard.conflict": "{{action}}에도 할당됨", + "chat.history.justNow": "방금", + "chat.history.minutesAgo": "{{count}}분 전", + "chat.history.hoursAgo": "{{count}}시간 전", + "chat.history.daysAgo": "{{count}}일 전", + "chat.history.title": "제목", + "chat.history.updated": "수정됨", + "chat.history.delete": "삭제", + "chat.edit.text": "텍스트", + "chat.edit.thought": "생각", + "chat.edit.tool": "도구", + "chat.edit.item": "항목", + "chat.dictate.unsupported": "이 브라우저에서는 음성 인식이 지원되지 않습니다.", + "chat.fallbackTitle": "채팅", + "admin.gateway.copied": "클립보드에 복사됨", + "admin.gateway.createError": "키 생성에 실패했습니다", + "admin.gateway.createKey": "키 생성", + "admin.gateway.deleteError": "키 삭제에 실패했습니다", + "admin.gateway.description": "API 키를 생성하여 Open WebUI 또는 OpenAI 호환 클라이언트를 작업 공간에 연결합니다.", + "admin.gateway.howToConnect": "Open WebUI에서 연결", + "admin.gateway.keyCreated": "API 키가 생성되었습니다", + "admin.gateway.keyDeleted": "API 키가 삭제되었습니다", + "admin.gateway.keyNamePlaceholder": "키 이름 (예: open-webui)", + "admin.gateway.keyWarning": "이 키는 한 번만 표시됩니다. 안전하게 보관하세요.", + "admin.gateway.loadError": "API 키 로드에 실패했습니다", + "admin.gateway.model": "응답 모델", + "admin.gateway.modelDescription": "게이트웨이 클라이언트가 작업 공간을 선택하고, cptr이 이 모델로 응답을 생성합니다.", + "admin.gateway.modelFallback": "작업 공간/기본 모델 사용", + "admin.gateway.modelSaveError": "게이트웨이 모델 저장에 실패했습니다", + "admin.gateway.newKey": "새 API 키가 생성되었습니다. 지금 복사하세요", + "admin.gateway.noKeys": "API 키가 아직 없습니다", + "admin.gateway.title": "API 게이트웨이" } diff --git a/cptr/frontend/src/lib/i18n/locales/pt-BR.json b/cptr/frontend/src/lib/i18n/locales/pt-BR.json index c82d79f..ccff04e 100644 --- a/cptr/frontend/src/lib/i18n/locales/pt-BR.json +++ b/cptr/frontend/src/lib/i18n/locales/pt-BR.json @@ -317,16 +317,16 @@ "about.copyright": "Copyright © 2026 Open WebUI Inc. Todos os direitos reservados.", "about.updateAvailable": "v{{version}} disponível", "connections.failedToUpdate": "Falha ao atualizar conexão", - "keyboard.newFile": "Open a new untitled file", - "keyboard.newTerminal": "Open a new terminal session", - "keyboard.newChat": "Start a new AI chat", - "keyboard.closeTab": "Close the active tab", - "keyboard.nextTab": "Switch to the next tab", - "keyboard.prevTab": "Switch to the previous tab", - "keyboard.quickOpen": "Search and open files quickly", - "keyboard.openSettings": "Open the settings panel", - "keyboard.toggleSplit": "Toggle split editor view", - "keyboard.toggleSidebar": "Show or hide the sidebar", + "keyboard.newFile": "Abrir um novo arquivo vazio", + "keyboard.newTerminal": "Abrir uma nova sessão de terminal", + "keyboard.newChat": "Iniciar um novo chat de IA", + "keyboard.closeTab": "Fechar a aba ativa", + "keyboard.nextTab": "Mudar para a próxima aba", + "keyboard.prevTab": "Mudar para a aba anterior", + "keyboard.quickOpen": "Pesquisar e abrir arquivos rapidamente", + "keyboard.openSettings": "Abrir o painel de configurações", + "keyboard.toggleSplit": "Alternar visualização dividida do editor", + "keyboard.toggleSidebar": "Mostrar ou ocultar a barra lateral", "automations.title": "Automações", "automations.filter": "Filtrar...", "automations.noMatches": "Sem resultados", @@ -524,5 +524,49 @@ "admin.gateway.apiKey": "Chave API:", "admin.gateway.headers": "Cabeçalhos:", "admin.messaging": "Mensagens", - "admin.gateway.tab": "Gateway" + "admin.gateway.tab": "Gateway", + "keyboard.action.newFile": "Novo arquivo", + "keyboard.action.newTerminal": "Novo terminal", + "keyboard.action.newChat": "Novo chat", + "keyboard.action.closeTab": "Fechar aba", + "keyboard.action.nextTab": "Próxima aba", + "keyboard.action.prevTab": "Aba anterior", + "keyboard.action.quickOpen": "Abertura rápida", + "keyboard.action.searchAll": "Pesquisar", + "keyboard.action.openSettings": "Abrir configurações", + "keyboard.action.toggleSplit": "Alternar divisão", + "keyboard.action.toggleSidebar": "Alternar barra lateral", + "keyboard.action.voiceMemo": "Nota de voz", + "keyboard.conflict": "Também atribuído a {{action}}", + "chat.history.justNow": "Agora", + "chat.history.minutesAgo": "há {{count}}min", + "chat.history.hoursAgo": "há {{count}}h", + "chat.history.daysAgo": "há {{count}}d", + "chat.history.title": "Título", + "chat.history.updated": "Atualizado", + "chat.history.delete": "Excluir", + "chat.edit.text": "Texto", + "chat.edit.thought": "Pensamento", + "chat.edit.tool": "Ferramenta", + "chat.edit.item": "Item", + "chat.dictate.unsupported": "O reconhecimento de voz não é suportado neste navegador.", + "chat.fallbackTitle": "Chat", + "admin.gateway.copied": "Copiado para a área de transferência", + "admin.gateway.createError": "Falha ao criar a chave", + "admin.gateway.createKey": "Criar chave", + "admin.gateway.deleteError": "Falha ao excluir a chave", + "admin.gateway.description": "Gere chaves API para conectar o Open WebUI ou qualquer cliente compatível com OpenAI aos seus workspaces.", + "admin.gateway.howToConnect": "Conectar do Open WebUI", + "admin.gateway.keyCreated": "Chave API criada", + "admin.gateway.keyDeleted": "Chave API excluída", + "admin.gateway.keyNamePlaceholder": "Nome da chave (ex. open-webui)", + "admin.gateway.keyWarning": "Esta chave será exibida apenas uma vez. Guarde-a com segurança.", + "admin.gateway.loadError": "Falha ao carregar as chaves API", + "admin.gateway.model": "Modelo de resposta", + "admin.gateway.modelDescription": "Clientes gateway escolhem um workspace; cptr usa este modelo para gerar a resposta.", + "admin.gateway.modelFallback": "Usar modelo do workspace/padrão", + "admin.gateway.modelSaveError": "Falha ao salvar o modelo gateway", + "admin.gateway.newKey": "Nova chave API criada. Copie-a agora", + "admin.gateway.noKeys": "Nenhuma chave API ainda", + "admin.gateway.title": "API Gateway" } diff --git a/cptr/frontend/src/lib/i18n/locales/ru.json b/cptr/frontend/src/lib/i18n/locales/ru.json index 56ffd64..1f7358a 100644 --- a/cptr/frontend/src/lib/i18n/locales/ru.json +++ b/cptr/frontend/src/lib/i18n/locales/ru.json @@ -317,16 +317,16 @@ "about.copyright": "Copyright © 2026 Open WebUI Inc. Все права защищены.", "about.updateAvailable": "v{{version}} доступна", "connections.failedToUpdate": "Не удалось обновить подключение", - "keyboard.newFile": "Open a new untitled file", - "keyboard.newTerminal": "Open a new terminal session", - "keyboard.newChat": "Start a new AI chat", - "keyboard.closeTab": "Close the active tab", - "keyboard.nextTab": "Switch to the next tab", - "keyboard.prevTab": "Switch to the previous tab", - "keyboard.quickOpen": "Search and open files quickly", - "keyboard.openSettings": "Open the settings panel", - "keyboard.toggleSplit": "Toggle split editor view", - "keyboard.toggleSidebar": "Show or hide the sidebar", + "keyboard.newFile": "Открыть новый пустой файл", + "keyboard.newTerminal": "Открыть новую сессию терминала", + "keyboard.newChat": "Начать новый ИИ-чат", + "keyboard.closeTab": "Закрыть активную вкладку", + "keyboard.nextTab": "Перейти к следующей вкладке", + "keyboard.prevTab": "Перейти к предыдущей вкладке", + "keyboard.quickOpen": "Быстрый поиск и открытие файлов", + "keyboard.openSettings": "Открыть панель настроек", + "keyboard.toggleSplit": "Переключить разделённый вид редактора", + "keyboard.toggleSidebar": "Показать или скрыть боковую панель", "automations.title": "Автоматизации", "automations.filter": "Фильтр...", "automations.noMatches": "Нет совпадений", @@ -524,5 +524,49 @@ "admin.gateway.apiKey": "API-ключ:", "admin.gateway.headers": "Заголовки:", "admin.messaging": "Сообщения", - "admin.gateway.tab": "Шлюз" + "admin.gateway.tab": "Шлюз", + "keyboard.action.newFile": "Новый файл", + "keyboard.action.newTerminal": "Новый терминал", + "keyboard.action.newChat": "Новый чат", + "keyboard.action.closeTab": "Закрыть вкладку", + "keyboard.action.nextTab": "Следующая вкладка", + "keyboard.action.prevTab": "Предыдущая вкладка", + "keyboard.action.quickOpen": "Быстрое открытие", + "keyboard.action.searchAll": "Поиск", + "keyboard.action.openSettings": "Открыть настройки", + "keyboard.action.toggleSplit": "Переключить разделение", + "keyboard.action.toggleSidebar": "Переключить боковую панель", + "keyboard.action.voiceMemo": "Голосовая заметка", + "keyboard.conflict": "Также назначено на {{action}}", + "chat.history.justNow": "Только что", + "chat.history.minutesAgo": "{{count}}мин назад", + "chat.history.hoursAgo": "{{count}}ч назад", + "chat.history.daysAgo": "{{count}}д назад", + "chat.history.title": "Название", + "chat.history.updated": "Обновлено", + "chat.history.delete": "Удалить", + "chat.edit.text": "Текст", + "chat.edit.thought": "Мысль", + "chat.edit.tool": "Инструмент", + "chat.edit.item": "Элемент", + "chat.dictate.unsupported": "Распознавание речи не поддерживается в этом браузере.", + "chat.fallbackTitle": "Чат", + "admin.gateway.copied": "Скопировано в буфер обмена", + "admin.gateway.createError": "Не удалось создать ключ", + "admin.gateway.createKey": "Создать ключ", + "admin.gateway.deleteError": "Не удалось удалить ключ", + "admin.gateway.description": "Генерируйте API-ключи для подключения Open WebUI или любого совместимого с OpenAI клиента к вашим рабочим пространствам.", + "admin.gateway.howToConnect": "Подключение из Open WebUI", + "admin.gateway.keyCreated": "API-ключ создан", + "admin.gateway.keyDeleted": "API-ключ удалён", + "admin.gateway.keyNamePlaceholder": "Имя ключа (напр. open-webui)", + "admin.gateway.keyWarning": "Этот ключ будет показан только один раз. Сохраните его надёжно.", + "admin.gateway.loadError": "Не удалось загрузить API-ключи", + "admin.gateway.model": "Модель ответа", + "admin.gateway.modelDescription": "Клиенты шлюза выбирают рабочее пространство; cptr использует эту модель для генерации ответа.", + "admin.gateway.modelFallback": "Использовать модель рабочего пространства/по умолчанию", + "admin.gateway.modelSaveError": "Не удалось сохранить модель шлюза", + "admin.gateway.newKey": "Новый API-ключ создан. Скопируйте его сейчас", + "admin.gateway.noKeys": "API-ключей пока нет", + "admin.gateway.title": "API-шлюз" } diff --git a/cptr/frontend/src/lib/i18n/locales/zh-CN.json b/cptr/frontend/src/lib/i18n/locales/zh-CN.json index b0bb31d..5b1d16f 100644 --- a/cptr/frontend/src/lib/i18n/locales/zh-CN.json +++ b/cptr/frontend/src/lib/i18n/locales/zh-CN.json @@ -317,16 +317,16 @@ "about.copyright": "Copyright © 2026 Open WebUI Inc. 保留所有权利。", "about.updateAvailable": "v{{version}} 可用", "connections.failedToUpdate": "更新连接失败", - "keyboard.newFile": "Open a new untitled file", - "keyboard.newTerminal": "Open a new terminal session", - "keyboard.newChat": "Start a new AI chat", - "keyboard.closeTab": "Close the active tab", - "keyboard.nextTab": "Switch to the next tab", - "keyboard.prevTab": "Switch to the previous tab", - "keyboard.quickOpen": "Search and open files quickly", - "keyboard.openSettings": "Open the settings panel", - "keyboard.toggleSplit": "Toggle split editor view", - "keyboard.toggleSidebar": "Show or hide the sidebar", + "keyboard.newFile": "打开新的空白文件", + "keyboard.newTerminal": "打开新的终端会话", + "keyboard.newChat": "开始新的 AI 聊天", + "keyboard.closeTab": "关闭活动标签页", + "keyboard.nextTab": "切换到下一个标签页", + "keyboard.prevTab": "切换到上一个标签页", + "keyboard.quickOpen": "快速搜索并打开文件", + "keyboard.openSettings": "打开设置面板", + "keyboard.toggleSplit": "切换编辑器分割视图", + "keyboard.toggleSidebar": "显示或隐藏侧边栏", "automations.title": "自动化", "automations.filter": "筛选...", "automations.noMatches": "无匹配", @@ -524,5 +524,49 @@ "admin.gateway.apiKey": "API 密钥:", "admin.gateway.headers": "请求头:", "admin.messaging": "消息", - "admin.gateway.tab": "网关" + "admin.gateway.tab": "网关", + "keyboard.action.newFile": "新建文件", + "keyboard.action.newTerminal": "新建终端", + "keyboard.action.newChat": "新建聊天", + "keyboard.action.closeTab": "关闭标签页", + "keyboard.action.nextTab": "下一个标签页", + "keyboard.action.prevTab": "上一个标签页", + "keyboard.action.quickOpen": "快速打开", + "keyboard.action.searchAll": "搜索", + "keyboard.action.openSettings": "打开设置", + "keyboard.action.toggleSplit": "切换分割", + "keyboard.action.toggleSidebar": "切换侧边栏", + "keyboard.action.voiceMemo": "语音备忘录", + "keyboard.conflict": "同时绑定到 {{action}}", + "chat.history.justNow": "刚刚", + "chat.history.minutesAgo": "{{count}}分钟前", + "chat.history.hoursAgo": "{{count}}小时前", + "chat.history.daysAgo": "{{count}}天前", + "chat.history.title": "标题", + "chat.history.updated": "已更新", + "chat.history.delete": "删除", + "chat.edit.text": "文本", + "chat.edit.thought": "思考", + "chat.edit.tool": "工具", + "chat.edit.item": "项目", + "chat.dictate.unsupported": "此浏览器不支持语音识别。", + "chat.fallbackTitle": "聊天", + "admin.gateway.copied": "已复制到剪贴板", + "admin.gateway.createError": "创建密钥失败", + "admin.gateway.createKey": "创建密钥", + "admin.gateway.deleteError": "删除密钥失败", + "admin.gateway.description": "生成 API 密钥,将 Open WebUI 或任何兼容 OpenAI 的客户端连接到您的工作区。", + "admin.gateway.howToConnect": "从 Open WebUI 连接", + "admin.gateway.keyCreated": "API 密钥已创建", + "admin.gateway.keyDeleted": "API 密钥已删除", + "admin.gateway.keyNamePlaceholder": "密钥名称(例如 open-webui)", + "admin.gateway.keyWarning": "此密钥只会显示一次。请安全保存。", + "admin.gateway.loadError": "加载 API 密钥失败", + "admin.gateway.model": "响应模型", + "admin.gateway.modelDescription": "网关客户端选择工作区;cptr 使用此模型生成响应。", + "admin.gateway.modelFallback": "使用工作区/默认模型", + "admin.gateway.modelSaveError": "保存网关模型失败", + "admin.gateway.newKey": "新 API 密钥已创建。请立即复制", + "admin.gateway.noKeys": "暂无 API 密钥", + "admin.gateway.title": "API 网关" } diff --git a/cptr/frontend/src/lib/i18n/locales/zh-TW.json b/cptr/frontend/src/lib/i18n/locales/zh-TW.json index a8d8053..bdd3412 100644 --- a/cptr/frontend/src/lib/i18n/locales/zh-TW.json +++ b/cptr/frontend/src/lib/i18n/locales/zh-TW.json @@ -317,16 +317,16 @@ "about.copyright": "Copyright © 2026 Open WebUI Inc. 保留所有權利。", "about.updateAvailable": "v{{version}} 可用", "connections.failedToUpdate": "更新連線失敗", - "keyboard.newFile": "Open a new untitled file", - "keyboard.newTerminal": "Open a new terminal session", - "keyboard.newChat": "Start a new AI chat", - "keyboard.closeTab": "Close the active tab", - "keyboard.nextTab": "Switch to the next tab", - "keyboard.prevTab": "Switch to the previous tab", - "keyboard.quickOpen": "Search and open files quickly", - "keyboard.openSettings": "Open the settings panel", - "keyboard.toggleSplit": "Toggle split editor view", - "keyboard.toggleSidebar": "Show or hide the sidebar", + "keyboard.newFile": "開啟新的空白檔案", + "keyboard.newTerminal": "開啟新的終端機工作階段", + "keyboard.newChat": "開始新的 AI 聊天", + "keyboard.closeTab": "關閉使用中的分頁", + "keyboard.nextTab": "切換到下一個分頁", + "keyboard.prevTab": "切換到上一個分頁", + "keyboard.quickOpen": "快速搜尋並開啟檔案", + "keyboard.openSettings": "開啟設定面板", + "keyboard.toggleSplit": "切換編輯器分割檢視", + "keyboard.toggleSidebar": "顯示或隱藏側邊欄", "automations.title": "自動化", "automations.filter": "篩選...", "automations.noMatches": "無符合項目", @@ -524,5 +524,49 @@ "admin.gateway.apiKey": "API 金鑰:", "admin.gateway.headers": "標頭:", "admin.messaging": "訊息", - "admin.gateway.tab": "閘道" + "admin.gateway.tab": "閘道", + "keyboard.action.newFile": "新增檔案", + "keyboard.action.newTerminal": "新增終端機", + "keyboard.action.newChat": "新增聊天", + "keyboard.action.closeTab": "關閉分頁", + "keyboard.action.nextTab": "下一個分頁", + "keyboard.action.prevTab": "上一個分頁", + "keyboard.action.quickOpen": "快速開啟", + "keyboard.action.searchAll": "搜尋", + "keyboard.action.openSettings": "開啟設定", + "keyboard.action.toggleSplit": "切換分割", + "keyboard.action.toggleSidebar": "切換側邊欄", + "keyboard.action.voiceMemo": "語音備忘錄", + "keyboard.conflict": "同時繫結到 {{action}}", + "chat.history.justNow": "剛才", + "chat.history.minutesAgo": "{{count}}分鐘前", + "chat.history.hoursAgo": "{{count}}小時前", + "chat.history.daysAgo": "{{count}}天前", + "chat.history.title": "標題", + "chat.history.updated": "已更新", + "chat.history.delete": "刪除", + "chat.edit.text": "文字", + "chat.edit.thought": "思考", + "chat.edit.tool": "工具", + "chat.edit.item": "項目", + "chat.dictate.unsupported": "此瀏覽器不支援語音辨識。", + "chat.fallbackTitle": "聊天", + "admin.gateway.copied": "已複製到剪貼簿", + "admin.gateway.createError": "建立金鑰失敗", + "admin.gateway.createKey": "建立金鑰", + "admin.gateway.deleteError": "刪除金鑰失敗", + "admin.gateway.description": "產生 API 金鑰,將 Open WebUI 或任何相容 OpenAI 的客戶端連接到您的工作區。", + "admin.gateway.howToConnect": "從 Open WebUI 連線", + "admin.gateway.keyCreated": "API 金鑰已建立", + "admin.gateway.keyDeleted": "API 金鑰已刪除", + "admin.gateway.keyNamePlaceholder": "金鑰名稱(例如 open-webui)", + "admin.gateway.keyWarning": "此金鑰只會顯示一次。請安全儲存。", + "admin.gateway.loadError": "載入 API 金鑰失敗", + "admin.gateway.model": "回應模型", + "admin.gateway.modelDescription": "閘道客戶端選擇工作區;cptr 使用此模型產生回應。", + "admin.gateway.modelFallback": "使用工作區/預設模型", + "admin.gateway.modelSaveError": "儲存閘道模型失敗", + "admin.gateway.newKey": "新 API 金鑰已建立。請立即複製", + "admin.gateway.noKeys": "尚無 API 金鑰", + "admin.gateway.title": "API 閘道" } From 32ee80d6f0f86137d38688ac6e549f9864c710e8 Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Sat, 13 Jun 2026 07:58:48 +0100 Subject: [PATCH 05/10] refac --- cptr/frontend/src/lib/components/chat/ChatPanel.svelte | 2 +- cptr/frontend/src/lib/i18n/locales/de.json | 3 ++- cptr/frontend/src/lib/i18n/locales/en.json | 3 ++- cptr/frontend/src/lib/i18n/locales/es.json | 3 ++- cptr/frontend/src/lib/i18n/locales/fr.json | 3 ++- cptr/frontend/src/lib/i18n/locales/ja.json | 3 ++- cptr/frontend/src/lib/i18n/locales/ko.json | 3 ++- cptr/frontend/src/lib/i18n/locales/pt-BR.json | 3 ++- cptr/frontend/src/lib/i18n/locales/ru.json | 3 ++- cptr/frontend/src/lib/i18n/locales/zh-CN.json | 3 ++- cptr/frontend/src/lib/i18n/locales/zh-TW.json | 3 ++- 11 files changed, 21 insertions(+), 11 deletions(-) diff --git a/cptr/frontend/src/lib/components/chat/ChatPanel.svelte b/cptr/frontend/src/lib/components/chat/ChatPanel.svelte index 43a7a5a..6c0c7fb 100644 --- a/cptr/frontend/src/lib/components/chat/ChatPanel.svelte +++ b/cptr/frontend/src/lib/components/chat/ChatPanel.svelte @@ -877,7 +877,7 @@

- What can I help you with? + {$t('chat.greeting')}

diff --git a/cptr/frontend/src/lib/i18n/locales/de.json b/cptr/frontend/src/lib/i18n/locales/de.json index 7c89e6c..01656a8 100644 --- a/cptr/frontend/src/lib/i18n/locales/de.json +++ b/cptr/frontend/src/lib/i18n/locales/de.json @@ -568,5 +568,6 @@ "admin.gateway.modelSaveError": "Gateway-Modell konnte nicht gespeichert werden", "admin.gateway.newKey": "Neuer API-Schlüssel erstellt. Jetzt kopieren", "admin.gateway.noKeys": "Noch keine API-Schlüssel", - "admin.gateway.title": "API-Gateway" + "admin.gateway.title": "API-Gateway", + "chat.greeting": "Wie kann ich Ihnen helfen?" } diff --git a/cptr/frontend/src/lib/i18n/locales/en.json b/cptr/frontend/src/lib/i18n/locales/en.json index 8e87a66..c03f4e4 100644 --- a/cptr/frontend/src/lib/i18n/locales/en.json +++ b/cptr/frontend/src/lib/i18n/locales/en.json @@ -644,6 +644,7 @@ "chat.edit.item": "Item", "chat.dictate.unsupported": "Speech recognition not supported in this browser.", - "chat.fallbackTitle": "Chat" + "chat.fallbackTitle": "Chat", + "chat.greeting": "What can I help you with?" } diff --git a/cptr/frontend/src/lib/i18n/locales/es.json b/cptr/frontend/src/lib/i18n/locales/es.json index 47f37f9..9444620 100644 --- a/cptr/frontend/src/lib/i18n/locales/es.json +++ b/cptr/frontend/src/lib/i18n/locales/es.json @@ -568,5 +568,6 @@ "admin.gateway.modelSaveError": "Error al guardar el modelo gateway", "admin.gateway.newKey": "Nueva clave API creada. Cópiala ahora", "admin.gateway.noKeys": "Aún no hay claves API", - "admin.gateway.title": "API Gateway" + "admin.gateway.title": "API Gateway", + "chat.greeting": "¿En qué puedo ayudarte?" } diff --git a/cptr/frontend/src/lib/i18n/locales/fr.json b/cptr/frontend/src/lib/i18n/locales/fr.json index 121c915..3af1263 100644 --- a/cptr/frontend/src/lib/i18n/locales/fr.json +++ b/cptr/frontend/src/lib/i18n/locales/fr.json @@ -567,5 +567,6 @@ "admin.gateway.modelSaveError": "Échec de l'enregistrement du modèle passerelle", "admin.gateway.newKey": "Nouvelle clé API créée. Copiez-la maintenant", "admin.gateway.noKeys": "Aucune clé API", - "admin.gateway.title": "Passerelle API" + "admin.gateway.title": "Passerelle API", + "chat.greeting": "Comment puis-je vous aider ?" } diff --git a/cptr/frontend/src/lib/i18n/locales/ja.json b/cptr/frontend/src/lib/i18n/locales/ja.json index f872477..71b5bbe 100644 --- a/cptr/frontend/src/lib/i18n/locales/ja.json +++ b/cptr/frontend/src/lib/i18n/locales/ja.json @@ -568,5 +568,6 @@ "admin.gateway.modelSaveError": "ゲートウェイモデルの保存に失敗しました", "admin.gateway.newKey": "新しいAPIキーが作成されました。今すぐコピーしてください", "admin.gateway.noKeys": "APIキーがまだありません", - "admin.gateway.title": "APIゲートウェイ" + "admin.gateway.title": "APIゲートウェイ", + "chat.greeting": "何かお手伝いできますか?" } diff --git a/cptr/frontend/src/lib/i18n/locales/ko.json b/cptr/frontend/src/lib/i18n/locales/ko.json index 212145c..29d934e 100644 --- a/cptr/frontend/src/lib/i18n/locales/ko.json +++ b/cptr/frontend/src/lib/i18n/locales/ko.json @@ -568,5 +568,6 @@ "admin.gateway.modelSaveError": "게이트웨이 모델 저장에 실패했습니다", "admin.gateway.newKey": "새 API 키가 생성되었습니다. 지금 복사하세요", "admin.gateway.noKeys": "API 키가 아직 없습니다", - "admin.gateway.title": "API 게이트웨이" + "admin.gateway.title": "API 게이트웨이", + "chat.greeting": "무엇을 도와드릴까요?" } diff --git a/cptr/frontend/src/lib/i18n/locales/pt-BR.json b/cptr/frontend/src/lib/i18n/locales/pt-BR.json index ccff04e..e63b126 100644 --- a/cptr/frontend/src/lib/i18n/locales/pt-BR.json +++ b/cptr/frontend/src/lib/i18n/locales/pt-BR.json @@ -568,5 +568,6 @@ "admin.gateway.modelSaveError": "Falha ao salvar o modelo gateway", "admin.gateway.newKey": "Nova chave API criada. Copie-a agora", "admin.gateway.noKeys": "Nenhuma chave API ainda", - "admin.gateway.title": "API Gateway" + "admin.gateway.title": "API Gateway", + "chat.greeting": "Como posso ajudar?" } diff --git a/cptr/frontend/src/lib/i18n/locales/ru.json b/cptr/frontend/src/lib/i18n/locales/ru.json index 1f7358a..4f2b661 100644 --- a/cptr/frontend/src/lib/i18n/locales/ru.json +++ b/cptr/frontend/src/lib/i18n/locales/ru.json @@ -568,5 +568,6 @@ "admin.gateway.modelSaveError": "Не удалось сохранить модель шлюза", "admin.gateway.newKey": "Новый API-ключ создан. Скопируйте его сейчас", "admin.gateway.noKeys": "API-ключей пока нет", - "admin.gateway.title": "API-шлюз" + "admin.gateway.title": "API-шлюз", + "chat.greeting": "Чем могу помочь?" } diff --git a/cptr/frontend/src/lib/i18n/locales/zh-CN.json b/cptr/frontend/src/lib/i18n/locales/zh-CN.json index 5b1d16f..33ad63f 100644 --- a/cptr/frontend/src/lib/i18n/locales/zh-CN.json +++ b/cptr/frontend/src/lib/i18n/locales/zh-CN.json @@ -568,5 +568,6 @@ "admin.gateway.modelSaveError": "保存网关模型失败", "admin.gateway.newKey": "新 API 密钥已创建。请立即复制", "admin.gateway.noKeys": "暂无 API 密钥", - "admin.gateway.title": "API 网关" + "admin.gateway.title": "API 网关", + "chat.greeting": "有什么可以帮您的?" } diff --git a/cptr/frontend/src/lib/i18n/locales/zh-TW.json b/cptr/frontend/src/lib/i18n/locales/zh-TW.json index bdd3412..9e4b8ef 100644 --- a/cptr/frontend/src/lib/i18n/locales/zh-TW.json +++ b/cptr/frontend/src/lib/i18n/locales/zh-TW.json @@ -568,5 +568,6 @@ "admin.gateway.modelSaveError": "儲存閘道模型失敗", "admin.gateway.newKey": "新 API 金鑰已建立。請立即複製", "admin.gateway.noKeys": "尚無 API 金鑰", - "admin.gateway.title": "API 閘道" + "admin.gateway.title": "API 閘道", + "chat.greeting": "有什麼可以幫您的?" } From aedafae38934a1086c5fae1cb1113b063862dc1c Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Sat, 13 Jun 2026 12:49:25 +0200 Subject: [PATCH 06/10] refac --- cptr/frontend/src/lib/apis/admin.ts | 38 ++ .../lib/components/Admin/ToolServers.svelte | 444 ++++++++++++++++++ .../src/lib/components/SettingsModal.svelte | 9 +- .../components/chat/AssistantMessage.svelte | 10 +- cptr/frontend/src/lib/i18n/locales/en.json | 37 +- cptr/routers/admin.py | 167 +++++++ cptr/utils/chat_task.py | 6 +- cptr/utils/mcp/__init__.py | 1 + cptr/utils/mcp/client.py | 147 ++++++ cptr/utils/openapi.py | 311 ++++++++++++ cptr/utils/tools.py | 201 +++++++- pyproject.toml | 1 + 12 files changed, 1349 insertions(+), 23 deletions(-) create mode 100644 cptr/frontend/src/lib/components/Admin/ToolServers.svelte create mode 100644 cptr/utils/mcp/__init__.py create mode 100644 cptr/utils/mcp/client.py create mode 100644 cptr/utils/openapi.py diff --git a/cptr/frontend/src/lib/apis/admin.ts b/cptr/frontend/src/lib/apis/admin.ts index c6243fb..f3c71b8 100644 --- a/cptr/frontend/src/lib/apis/admin.ts +++ b/cptr/frontend/src/lib/apis/admin.ts @@ -128,3 +128,41 @@ export const updateModelConfig = ( ...jsonBody(update), method: 'PUT' }); + +// ── Tool Servers ──────────────────────────────────────────── + +export interface ToolServer { + id: string; + type: 'openapi' | 'mcp'; + url: string; + path: string; + auth_type: string; + key: string; + name: string; + description: string; + headers: Record | null; + enabled: boolean; +} + +export const listToolServers = async (): Promise => { + const data = await fetchJSON<{ servers: ToolServer[] }>('/api/admin/tools/servers'); + return data.servers; +}; + +export const createToolServer = (server: Omit) => + fetchJSON('/api/admin/tools/servers', jsonBody(server)); + +export const updateToolServer = (id: string, updates: Partial>) => + fetchJSON(`/api/admin/tools/servers/${id}`, { + ...jsonBody(updates), + method: 'PUT' + }); + +export const deleteToolServer = (id: string) => + fetchJSON(`/api/admin/tools/servers/${id}`, { method: 'DELETE' }); + +export const verifyToolServer = (id: string) => + fetchJSON<{ ok: boolean; tools?: { name: string; description: string }[]; message?: string }>( + `/api/admin/tools/servers/${id}/verify`, + { method: 'POST' } + ); diff --git a/cptr/frontend/src/lib/components/Admin/ToolServers.svelte b/cptr/frontend/src/lib/components/Admin/ToolServers.svelte new file mode 100644 index 0000000..4b4e6b4 --- /dev/null +++ b/cptr/frontend/src/lib/components/Admin/ToolServers.svelte @@ -0,0 +1,444 @@ + + +
+

{$t('toolServers.title')}

+ +
+ +{#if loading} +
+ +
+{:else} +
+ {#each servers as s} + + {/each} + + {#if servers.length === 0} +

+ {$t('toolServers.empty')} +

+ {/if} +
+{/if} + +{#if showModal} + (showModal = false)} class="w-full max-w-md mx-4"> + +
{ + if (e.key === 'Enter' && formUrl.trim()) handleSubmit(); + }} + > +

+ {editServer ? $t('toolServers.edit') : $t('toolServers.add')} +

+ + +
+
+ + +
+
+ + +
+
+ + + + + + + + + + + {#if formType === 'openapi'} + + + {/if} + + +
+
+ + +
+ {#if formAuthType === 'bearer'} +
+ + +
+ {/if} +
+ + + + + + + +

{$t('toolServers.headersHint')}

+ + + + + {#if verifyResult?.ok && verifyResult.tools} +
+ {verifyResult.tools.length} + {$t('toolServers.toolsFound')}: + {verifyResult.tools.map((t) => t.name).join(', ')} +
+ {/if} + + +
+
+ {#if editServer} + + + {/if} +
+ +
+
+
+{/if} diff --git a/cptr/frontend/src/lib/components/SettingsModal.svelte b/cptr/frontend/src/lib/components/SettingsModal.svelte index 27adebe..3c341f5 100644 --- a/cptr/frontend/src/lib/components/SettingsModal.svelte +++ b/cptr/frontend/src/lib/components/SettingsModal.svelte @@ -12,6 +12,7 @@ import Gateway from './Admin/Gateway.svelte'; import AudioSettings from './Admin/AudioSettings.svelte'; import AdminWeb from './Admin/Web.svelte'; + import ToolServers from './Admin/ToolServers.svelte'; import { session } from '$lib/session'; import { t } from '$lib/i18n'; @@ -26,7 +27,8 @@ | 'messaging' | 'gateway' | 'audio' - | 'web'; + | 'web' + | 'toolservers'; interface Props { onclose: () => void; @@ -53,7 +55,8 @@ { id: 'messaging', label: $t('admin.messaging'), icon: 'chat-bubble' }, { id: 'gateway', label: $t('admin.gateway.tab'), icon: 'gateway' }, { id: 'audio', label: $t('admin.audio.title'), icon: 'microphone' }, - { id: 'web', label: $t('admin.web'), icon: 'globe' } + { id: 'web', label: $t('admin.web'), icon: 'globe' }, + { id: 'toolservers', label: $t('admin.toolServers'), icon: 'plug' } ]); @@ -132,6 +135,8 @@ {:else if activeTab === 'web'} + {:else if activeTab === 'toolservers'} + {/if} diff --git a/cptr/frontend/src/lib/components/chat/AssistantMessage.svelte b/cptr/frontend/src/lib/components/chat/AssistantMessage.svelte index b0370fa..1da33d5 100644 --- a/cptr/frontend/src/lib/components/chat/AssistantMessage.svelte +++ b/cptr/frontend/src/lib/components/chat/AssistantMessage.svelte @@ -178,8 +178,16 @@ return _t('chat.tool.fetchUrlFallback'); } } - default: + default: { + // External tool: {server_id}_{tool_name} → "tool_name (server_id)" + const idx = name.indexOf('_'); + if (idx > 0) { + const serverId = name.slice(0, idx); + const toolName = name.slice(idx + 1); + return `${toolName} (${serverId})`; + } return name; + } } } diff --git a/cptr/frontend/src/lib/i18n/locales/en.json b/cptr/frontend/src/lib/i18n/locales/en.json index c03f4e4..27c8f4e 100644 --- a/cptr/frontend/src/lib/i18n/locales/en.json +++ b/cptr/frontend/src/lib/i18n/locales/en.json @@ -645,6 +645,41 @@ "chat.dictate.unsupported": "Speech recognition not supported in this browser.", "chat.fallbackTitle": "Chat", - "chat.greeting": "What can I help you with?" + "chat.greeting": "What can I help you with?", + + "admin.toolServers": "Tool Servers", + + "toolServers.title": "Tool Servers", + "toolServers.empty": "No tool servers configured", + "toolServers.add": "Add Server", + "toolServers.edit": "Edit Server", + "toolServers.id": "ID", + "toolServers.name": "Name", + "toolServers.type": "Type", + "toolServers.namePlaceholder": "My MCP Server", + "toolServers.url": "URL", + "toolServers.specPath": "Spec path", + "toolServers.fieldsRequired": "ID and URL are required", + "toolServers.idInvalid": "ID must be lowercase letters, numbers, and underscores only", + "toolServers.auth": "Auth", + "toolServers.authNone": "None", + "toolServers.authBearer": "Bearer", + "toolServers.apiKey": "API Key", + "toolServers.description": "Description", + "toolServers.descriptionPlaceholder": "What does this server do?", + "toolServers.headers": "Headers", + "toolServers.headersHint": "Additional HTTP headers as JSON.", + "toolServers.headersInvalid": "Headers must be a valid JSON object", + "toolServers.verify": "Verify", + "toolServers.connected": "Connected", + "toolServers.toolsFound": "tools found", + "toolServers.delete": "Delete", + "toolServers.loadError": "Failed to load tool servers", + "toolServers.urlRequired": "URL is required", + "toolServers.created": "Tool server added", + "toolServers.updated": "Tool server updated", + "toolServers.deleted": "Tool server deleted", + "toolServers.saveFailed": "Failed to save tool server", + "toolServers.deleteFailed": "Failed to delete tool server" } diff --git a/cptr/routers/admin.py b/cptr/routers/admin.py index 7ab2436..effd75e 100644 --- a/cptr/routers/admin.py +++ b/cptr/routers/admin.py @@ -425,3 +425,170 @@ async def update_model_config( await Config.upsert({CONFIG_KEY_CHAT_MODELS: all_config}) return {"ok": True} + +# ── Tool servers ───────────────────────────────────────────── + +CONFIG_KEY_TOOL_SERVERS = "tool_servers" + + +async def _get_tool_servers() -> list[dict]: + """Get all tool server configs from config store.""" + return await Config.get(CONFIG_KEY_TOOL_SERVERS) or [] + + +async def _save_tool_servers(servers: list[dict]): + """Save tool server configs and invalidate cache.""" + await Config.upsert({CONFIG_KEY_TOOL_SERVERS: servers}) + from cptr.utils.tools import invalidate_tool_server_cache + + invalidate_tool_server_cache() + + +def _mask_tool_server(server: dict) -> dict: + """Return server with masked API key for display.""" + masked = {**server} + if masked.get("key"): + key = masked["key"] + masked["key"] = key[:4] + "****" + key[-4:] if len(key) > 8 else "****" + return masked + + +@router.get("/tools/servers") +async def list_tool_servers(request: Request): + """List all configured tool servers (keys masked).""" + require_admin(request) + servers = await _get_tool_servers() + return {"servers": [_mask_tool_server(s) for s in servers]} + + +class CreateToolServerRequest(BaseModel): + id: str + type: str = "openapi" # "openapi" | "mcp" + url: str + path: str = "openapi.json" # OpenAPI spec path (OpenAPI only) + auth_type: str = "bearer" # "bearer" | "none" + key: Optional[str] = None + name: str = "" + description: str = "" + headers: Optional[dict] = None + enabled: bool = True + + +@router.post("/tools/servers") +async def create_tool_server(body: CreateToolServerRequest, request: Request): + """Add a new external tool server.""" + require_admin(request) + import re as _re + + server_id = body.id.strip() + if not server_id or not _re.fullmatch(r"[a-z0-9_]+", server_id): + raise HTTPException(400, "ID must be lowercase alphanumeric with underscores only") + + servers = await _get_tool_servers() + if any(s["id"] == server_id for s in servers): + raise HTTPException(409, f"Server ID '{server_id}' already exists") + + server = { + "id": server_id, + "type": body.type, + "url": body.url, + "path": body.path, + "auth_type": body.auth_type, + "key": body.key or "", + "name": body.name or server_id, + "description": body.description, + "headers": body.headers, + "enabled": body.enabled, + } + servers.append(server) + await _save_tool_servers(servers) + return {"ok": True, "id": server["id"]} + + +class UpdateToolServerRequest(BaseModel): + type: Optional[str] = None + url: Optional[str] = None + path: Optional[str] = None + auth_type: Optional[str] = None + key: Optional[str] = None + name: Optional[str] = None + description: Optional[str] = None + headers: Optional[dict] = None + enabled: Optional[bool] = None + + +@router.put("/tools/servers/{server_id}") +async def update_tool_server(server_id: str, body: UpdateToolServerRequest, request: Request): + """Update an existing tool server.""" + require_admin(request) + servers = await _get_tool_servers() + server = next((s for s in servers if s["id"] == server_id), None) + if not server: + raise HTTPException(404, "tool server not found") + + for field in ("type", "url", "path", "auth_type", "name", "description"): + val = getattr(body, field) + if val is not None: + server[field] = val + if body.key is not None and body.key != "": + server["key"] = body.key + if body.headers is not None: + server["headers"] = body.headers + if body.enabled is not None: + server["enabled"] = body.enabled + + await _save_tool_servers(servers) + return {"ok": True} + + +@router.delete("/tools/servers/{server_id}") +async def delete_tool_server(server_id: str, request: Request): + """Delete a tool server.""" + require_admin(request) + servers = [s for s in await _get_tool_servers() if s["id"] != server_id] + await _save_tool_servers(servers) + return {"ok": True} + + +@router.post("/tools/servers/{server_id}/verify") +async def verify_tool_server(server_id: str, request: Request): + """Test connectivity to a tool server. Returns discovered tools.""" + require_admin(request) + servers = await _get_tool_servers() + server = next((s for s in servers if s["id"] == server_id), None) + if not server: + raise HTTPException(404, "tool server not found") + + server_type = server.get("type", "openapi") + url = server.get("url", "") + headers = dict(server.get("headers") or {}) + if server.get("auth_type") == "bearer" and server.get("key"): + headers["Authorization"] = f"Bearer {server['key']}" + + try: + if server_type == "mcp": + from cptr.utils.mcp.client import MCPClient + + client = MCPClient() + await client.connect(url, headers or None) + try: + specs = await client.list_tool_specs() + return {"ok": True, "tools": specs} + finally: + await client.disconnect() + + else: # openapi + from cptr.utils.openapi import fetch_openapi_spec, convert_openapi_to_tool_specs + + path = server.get("path", "openapi.json") + if path.startswith("http"): + spec_url = path + else: + spec_url = f"{url.rstrip('/')}/{path.lstrip('/')}" + + spec = await fetch_openapi_spec(spec_url, headers or None) + tools = convert_openapi_to_tool_specs(spec) + return {"ok": True, "tools": tools} + + except Exception as e: + return JSONResponse({"ok": False, "message": str(e)}, 400) diff --git a/cptr/utils/chat_task.py b/cptr/utils/chat_task.py index fa699c7..705b250 100644 --- a/cptr/utils/chat_task.py +++ b/cptr/utils/chat_task.py @@ -26,7 +26,7 @@ ) from cptr.utils.config import _get_jwt_secret, now_ms from cptr.utils.crypto import decrypt_key -from cptr.utils.tools import TOOLS, execute_tool, get_tool_list, _fn_to_schema, create_artifact +from cptr.utils.tools import ALL_TOOLS, execute_tool, get_tool_list, _fn_to_schema, create_artifact from cptr.utils.chat_export import export_chat_to_file from cptr.utils.json_parser import extract_json @@ -891,7 +891,7 @@ def _sync_state(): # Plan mode: strip write tools, inject prompt as user message (not system, to preserve cache) plan_mode = chat_params.get("plan_mode", False) if plan_mode: - tools = [t for t in tools if TOOLS.get(t["name"], {}).get("auto")] + tools = [t for t in tools if ALL_TOOLS.get(t["name"], {}).get("auto")] # Inject create_artifact (only available in plan mode) tools.append(_fn_to_schema("create_artifact", create_artifact)) messages.append({"role": "user", "content": PLAN_MODE_PROMPT}) @@ -1037,7 +1037,7 @@ def _sync_state(): flushed_item = _flush_text() name = event["name"] - tool = TOOLS.get(name) + tool = ALL_TOOLS.get(name) item = { "type": "function_call", "id": str(uuid.uuid4()), diff --git a/cptr/utils/mcp/__init__.py b/cptr/utils/mcp/__init__.py new file mode 100644 index 0000000..aaa1082 --- /dev/null +++ b/cptr/utils/mcp/__init__.py @@ -0,0 +1 @@ +# MCP client utilities diff --git a/cptr/utils/mcp/client.py b/cptr/utils/mcp/client.py new file mode 100644 index 0000000..4df3b52 --- /dev/null +++ b/cptr/utils/mcp/client.py @@ -0,0 +1,147 @@ +"""MCP (Model Context Protocol) client for Streamable HTTP transport. + +Wraps the `mcp` library's ClientSession to provide a simple interface +for connecting to MCP servers, listing tools, and calling tools. + +Usage:: + + client = MCPClient() + await client.connect("https://mcp.example.com/sse", headers={"Authorization": "Bearer ..."}) + specs = await client.list_tool_specs() + result = await client.call_tool("my_tool", {"arg": "value"}) + await client.disconnect() +""" + +from __future__ import annotations + +import asyncio +import logging +from contextlib import AsyncExitStack + +import anyio +from mcp import ClientSession +from mcp.client.streamable_http import streamablehttp_client +from mcp.types import CallToolResult + +logger = logging.getLogger(__name__) + +_INIT_TIMEOUT = 30 # seconds + + +class MCPClient: + """Manages a single MCP server connection over Streamable HTTP.""" + + def __init__(self): + self.session: ClientSession | None = None + self._exit_stack: AsyncExitStack | None = None + + async def connect(self, url: str, headers: dict | None = None) -> None: + """Connect to an MCP server. + + Args: + url: The server's Streamable HTTP endpoint URL. + headers: Optional HTTP headers (e.g. Authorization). + """ + async with AsyncExitStack() as exit_stack: + try: + transport = await exit_stack.enter_async_context( + streamablehttp_client(url=url, headers=headers) + ) + read, write, _ = transport + + self.session = await exit_stack.enter_async_context( + ClientSession(read_stream=read, write_stream=write) + ) + + with anyio.fail_after(_INIT_TIMEOUT): + await self.session.initialize() + + # Transfer ownership — prevent exit_stack.__aexit__ from + # tearing everything down when we leave this block. + self._exit_stack = exit_stack.pop_all() + logger.info("[mcp] Connected to %s", url) + except Exception: + await asyncio.shield(self.disconnect()) + raise + + async def list_tool_specs(self) -> list[dict]: + """List tools from the connected server as OpenAI-compatible schemas. + + Returns a list of dicts, each with: name, description, parameters. + """ + if not self.session: + raise RuntimeError("MCPClient is not connected") + + result = await self.session.list_tools() + specs = [] + + for tool in result.tools: + spec = { + "name": tool.name, + "description": tool.description or "", + "parameters": { + "type": "object", + "properties": {}, + "required": [], + }, + } + + if tool.inputSchema: + schema = tool.inputSchema + spec["parameters"]["properties"] = schema.get("properties", {}) + spec["parameters"]["required"] = schema.get("required", []) + + specs.append(spec) + + return specs + + async def call_tool(self, name: str, function_args: dict | None = None) -> list: + """Call a tool on the connected server. + + Args: + name: The tool name. + function_args: Arguments to pass to the tool. + + Returns: + A list of content items from the tool result. + + Raises: + RuntimeError: If the tool returns an error result. + """ + if not self.session: + raise RuntimeError("MCPClient is not connected") + + result: CallToolResult = await self.session.call_tool( + name, arguments=function_args or {} + ) + + content = [item.model_dump() for item in result.content] + + if result.isError: + raise RuntimeError(f"MCP tool error: {content}") + + return content + + async def disconnect(self) -> None: + """Disconnect from the MCP server. Idempotent. + + IMPORTANT: Do NOT use asyncio.shield() or anyio.CancelScope here. + The MCP SDK requires its TaskGroup to be exited in the same task + that created it. Simply call aclose() directly. + """ + exit_stack = self._exit_stack + if exit_stack is None: + return + + # Prevent double-close from concurrent callers + self._exit_stack = None + self.session = None + + try: + await exit_stack.aclose() + except TimeoutError: + logger.warning("[mcp] disconnect timed out") + except RuntimeError as exc: + logger.debug("[mcp] disconnect suppressed RuntimeError: %s", exc) + except Exception: + logger.debug("[mcp] Error during disconnect", exc_info=True) diff --git a/cptr/utils/openapi.py b/cptr/utils/openapi.py new file mode 100644 index 0000000..faa8725 --- /dev/null +++ b/cptr/utils/openapi.py @@ -0,0 +1,311 @@ +"""OpenAPI tool server client. + +Handles fetching OpenAPI specs, converting them to LLM-compatible tool schemas, +and executing tool calls against OpenAPI servers. +""" + +from __future__ import annotations + +import copy +import json +import logging +from typing import Any + +import httpx + +logger = logging.getLogger(__name__) + +# Valid HTTP methods per OpenAPI 3.x +_HTTP_METHODS = {"get", "put", "post", "delete", "options", "head", "patch", "trace"} + +_TIMEOUT = 15 # seconds + + +# ── Spec fetching ─────────────────────────────────────────── + + +async def fetch_openapi_spec( + url: str, headers: dict | None = None, timeout: float = _TIMEOUT +) -> dict: + """Fetch and parse an OpenAPI spec from a URL. + + Supports JSON and YAML (if PyYAML is installed). Falls back to YAML + parsing for non-JSON content. + + Args: + url: Full URL to the OpenAPI spec endpoint. + headers: Optional HTTP headers (e.g. Authorization). + timeout: Request timeout in seconds. + + Returns: + The parsed OpenAPI spec as a dict. + """ + _headers = {"Accept": "application/json"} + if headers: + _headers.update(headers) + + async with httpx.AsyncClient(timeout=timeout, verify=True) as client: + resp = await client.get(url, headers=_headers) + resp.raise_for_status() + + text = resp.text + if url.lower().endswith((".yaml", ".yml")): + import yaml + + return yaml.safe_load(text) + + try: + return json.loads(text) + except json.JSONDecodeError: + # Fallback to YAML for non-.yml URLs that aren't valid JSON + import yaml + + return yaml.safe_load(text) + + +# ── Schema conversion ────────────────────────────────────── + + +def _resolve_schema( + schema: dict, components: dict, resolved: set | None = None +) -> dict: + """Recursively resolve $ref references in a JSON schema.""" + if not schema: + return {} + + if resolved is None: + resolved = set() + + if "$ref" in schema: + ref_path = schema["$ref"] + schema_name = ref_path.split("/")[-1] + + if schema_name in resolved: + return {} # Avoid infinite recursion + + resolved.add(schema_name) + + ref_parts = ref_path.strip("#/").split("/") + target = components + for part in ref_parts[1:]: # Skip 'components' + target = target.get(part, {}) + return _resolve_schema(target, components, resolved) + + result = copy.deepcopy(schema) + + if "properties" in result: + for prop, prop_schema in result["properties"].items(): + result["properties"][prop] = _resolve_schema(prop_schema, components) + + if "items" in result: + result["items"] = _resolve_schema(result["items"], components) + + for keyword in ("oneOf", "anyOf", "allOf"): + if keyword in result and isinstance(result[keyword], list): + result[keyword] = [ + _resolve_schema(inner, components, resolved) for inner in result[keyword] + ] + + return result + + +def convert_openapi_to_tool_specs(openapi_spec: dict) -> list[dict]: + """Convert an OpenAPI specification into tool schemas for the LLM. + + Each operation with an ``operationId`` becomes a tool. Parameters and + request body properties are flattened into a single ``parameters`` object. + + Args: + openapi_spec: The parsed OpenAPI spec dict. + + Returns: + A list of tool spec dicts with name, description, and parameters. + """ + specs = [] + components = openapi_spec.get("components", {}) + + for path, methods in openapi_spec.get("paths", {}).items(): + if not isinstance(methods, dict): + continue + + path_params = methods.get("parameters", []) + if not isinstance(path_params, list): + path_params = [] + + for method, operation in methods.items(): + if method not in _HTTP_METHODS: + continue + if not isinstance(operation, dict): + continue + if not operation.get("operationId"): + continue + + tool = { + "name": operation["operationId"], + "description": operation.get( + "description", operation.get("summary", "No description available.") + ), + "parameters": {"type": "object", "properties": {}, "required": []}, + } + + # Merge path-level and operation-level params + op_params = operation.get("parameters", []) + if not isinstance(op_params, list): + op_params = [] + + merged = {} + for param in path_params: + if isinstance(param, dict) and param.get("name"): + merged[(param["name"], param.get("in", ""))] = param + for param in op_params: + if isinstance(param, dict) and param.get("name"): + merged[(param["name"], param.get("in", ""))] = param + + for param in merged.values(): + pname = param.get("name") + if not pname: + continue + pschema = param.get("schema", {}) + desc = pschema.get("description", "") or param.get("description", "") + if pschema.get("enum") and isinstance(pschema["enum"], list): + desc += f'. Possible values: {", ".join(str(v) for v in pschema["enum"])}' + + prop = { + "type": pschema.get("type") or "string", + "description": desc, + } + if pschema.get("type") == "array" and "items" in pschema: + prop["items"] = pschema["items"] + + prop = {k: v for k, v in prop.items() if v is not None} + tool["parameters"]["properties"][pname] = prop + if param.get("required"): + tool["parameters"]["required"].append(pname) + + # Extract requestBody + request_body = operation.get("requestBody") + if request_body: + content = request_body.get("content", {}) + json_schema = content.get("application/json", {}).get("schema") + if json_schema: + resolved = _resolve_schema(json_schema, components) + if resolved.get("properties"): + tool["parameters"]["properties"].update(resolved["properties"]) + if "required" in resolved: + tool["parameters"]["required"] = list( + set(tool["parameters"]["required"] + resolved["required"]) + ) + elif resolved.get("type") == "array": + tool["parameters"] = resolved + + specs.append(tool) + + return specs + + +# ── Tool execution ────────────────────────────────────────── + + +async def execute_openapi_tool( + server_url: str, + openapi_spec: dict, + tool_name: str, + args: dict, + headers: dict | None = None, + timeout: float = 60, +) -> str: + """Execute a tool call against an OpenAPI server. + + Resolves the route by operationId, separates path/query/body params, + and makes the appropriate HTTP request. + + Args: + server_url: Base URL of the OpenAPI server. + openapi_spec: The full parsed OpenAPI spec. + tool_name: The operationId to call. + args: Arguments from the LLM tool call. + headers: Optional HTTP headers. + timeout: Request timeout in seconds. + + Returns: + The response body as a string (JSON-serialized if structured). + """ + paths = openapi_spec.get("paths", {}) + + # Find matching route + route_path = None + http_method = None + operation = None + + for rpath, methods in paths.items(): + if not isinstance(methods, dict): + continue + for method, op in methods.items(): + if method not in _HTTP_METHODS: + continue + if isinstance(op, dict) and op.get("operationId") == tool_name: + route_path = rpath + http_method = method + operation = op + break + if route_path: + break + + if not route_path or not operation: + return json.dumps({"error": f"Operation '{tool_name}' not found in OpenAPI spec"}) + + # Classify parameters + path_params = {} + query_params = {} + body_params = dict(args) + + all_params = (operation.get("parameters") or []) + ( + paths.get(route_path, {}).get("parameters") or [] + ) + + for param in all_params: + pname = param.get("name", "") + pin = param.get("in", "") + if pname in body_params: + if pin == "path": + path_params[pname] = body_params.pop(pname) + elif pin == "query": + query_params[pname] = body_params.pop(pname) + elif pin == "header": + body_params.pop(pname) # skip header params for now + + # Build URL with path params + url = server_url.rstrip("/") + route_path + for pname, pval in path_params.items(): + url = url.replace(f"{{{pname}}}", str(pval)) + + _headers = {"Content-Type": "application/json"} + if headers: + _headers.update(headers) + + try: + async with httpx.AsyncClient(timeout=timeout, verify=True) as client: + if http_method in ("get", "head", "options"): + resp = await client.request( + http_method.upper(), url, headers=_headers, params=query_params + ) + else: + resp = await client.request( + http_method.upper(), + url, + headers=_headers, + params=query_params, + json=body_params if body_params else None, + ) + + # Try to parse as JSON + try: + data = resp.json() + return json.dumps(data, indent=2, ensure_ascii=False) + except Exception: + return resp.text + + except httpx.TimeoutException: + return json.dumps({"error": f"Request to {url} timed out after {timeout}s"}) + except Exception as e: + return json.dumps({"error": str(e)}) diff --git a/cptr/utils/tools.py b/cptr/utils/tools.py index 7ca14c1..4cb05f2 100644 --- a/cptr/utils/tools.py +++ b/cptr/utils/tools.py @@ -1198,7 +1198,7 @@ async def browser_evaluate(javascript: str, *, __context__: dict) -> str: "delete_automation": {"fn": delete_automation, "auto": False}, } -# Browser tools — registered conditionally based on browser.enabled config +# Browser tools — conditionally included in schemas based on browser.enabled BROWSER_TOOLS: dict[str, dict] = { "browser_navigate": {"fn": browser_navigate, "auto": False}, "browser_snapshot": {"fn": browser_snapshot, "auto": True}, @@ -1208,6 +1208,157 @@ async def browser_evaluate(javascript: str, *, __context__: dict) -> str: "browser_evaluate": {"fn": browser_evaluate, "auto": False}, } +# Combined lookup for execution and approval (always available regardless of config) +ALL_TOOLS: dict[str, dict] = {**TOOLS, **BROWSER_TOOLS} + + +# ── External tool servers ─────────────────────────────────── + +_tool_server_cache: dict | None = None # {"servers": [...], "tools": {name: {server, spec}}} + + +async def _load_tool_servers() -> dict: + """Load and cache external tool server config + specs. + + Returns a dict with 'servers' (raw config list) and 'tools' mapping + prefixed tool names to {server, spec, type}. + """ + global _tool_server_cache + if _tool_server_cache is not None: + return _tool_server_cache + + from cptr.models import Config + + servers = await Config.get("tool_servers") or [] + tools: dict[str, dict] = {} + + for server in servers: + if not server.get("enabled", True): + continue + + server_id = server.get("id", "") + server_type = server.get("type", "openapi") + + try: + if server_type == "openapi": + from cptr.utils.openapi import fetch_openapi_spec, convert_openapi_to_tool_specs + + url = server.get("url", "").rstrip("/") + path = server.get("path", "openapi.json") + if path.startswith("http"): + spec_url = path + else: + spec_url = f"{url}/{path.lstrip('/')}" + + headers = _build_server_headers(server) + openapi_spec = await fetch_openapi_spec(spec_url, headers) + server["_openapi_spec"] = openapi_spec + + for spec in convert_openapi_to_tool_specs(openapi_spec): + prefixed = f"{server_id}_{spec['name']}" + tools[prefixed] = { + "server": server, + "spec": {**spec, "name": prefixed}, + "original_name": spec["name"], + "type": "openapi", + } + + elif server_type == "mcp": + from cptr.utils.mcp.client import MCPClient + + client = MCPClient() + headers = _build_server_headers(server) + await client.connect(server.get("url", ""), headers) + + for spec in await client.list_tool_specs(): + prefixed = f"{server_id}_{spec['name']}" + tools[prefixed] = { + "server": server, + "spec": {**spec, "name": prefixed}, + "original_name": spec["name"], + "type": "mcp", + } + + await client.disconnect() + + except Exception: + import logging + + logging.getLogger(__name__).warning( + "Failed to load tool server '%s'", server_id, exc_info=True + ) + + _tool_server_cache = {"servers": servers, "tools": tools} + return _tool_server_cache + + +def invalidate_tool_server_cache() -> None: + """Clear the external tool server cache, forcing a reload on next access.""" + global _tool_server_cache + _tool_server_cache = None + + +def _build_server_headers(server: dict) -> dict | None: + """Build auth + custom headers for a tool server connection.""" + headers = dict(server.get("headers") or {}) + auth_type = server.get("auth_type", "bearer") + if auth_type == "bearer": + key = server.get("key", "") + if key: + headers["Authorization"] = f"Bearer {key}" + return headers or None + + +async def _execute_external_tool(name: str, args: dict) -> str: + """Execute an external tool by its prefixed name ({server_id}_{tool_name}).""" + cache = await _load_tool_servers() + tool_info = cache["tools"].get(name) + if not tool_info: + return f"Error: external tool '{name}' not found" + + server = tool_info["server"] + original_name = tool_info["original_name"] + tool_type = tool_info["type"] + headers = _build_server_headers(server) + + try: + if tool_type == "mcp": + from cptr.utils.mcp.client import MCPClient + + client = MCPClient() + await client.connect(server.get("url", ""), headers) + try: + result = await client.call_tool(original_name, args) + # MCP returns a list of content items; extract text + texts = [] + for item in result: + if isinstance(item, dict): + if item.get("type") == "text": + texts.append(item.get("text", "")) + else: + texts.append(json.dumps(item)) + return "\n".join(texts) if texts else "(no output)" + finally: + await client.disconnect() + + elif tool_type == "openapi": + from cptr.utils.openapi import execute_openapi_tool + + openapi_spec = server.get("_openapi_spec", {}) + return await execute_openapi_tool( + server_url=server.get("url", "").rstrip("/"), + openapi_spec=openapi_spec, + tool_name=original_name, + args=args, + headers=headers, + ) + + else: + return f"Error: unknown tool server type: {tool_type}" + + except Exception as e: + return f"Error executing external tool '{name}': {e}" + # ── Schema from function signature ────────────────────────── @@ -1264,7 +1415,8 @@ def _fn_to_schema(name: str, fn) -> dict: async def get_tool_list() -> list[dict]: """Return tool schemas for the LLM. - Automatically includes browser tools when browser.enabled is true in config. + Automatically includes browser tools when browser.enabled is true, + and external tool server tools when configured. """ tools = dict(TOOLS) try: @@ -1274,21 +1426,38 @@ async def get_tool_list() -> list[dict]: tools.update(BROWSER_TOOLS) except Exception: pass - return [_fn_to_schema(name, t["fn"]) for name, t in tools.items()] + + schemas = [_fn_to_schema(name, t["fn"]) for name, t in tools.items()] + + # Add external tool server schemas + try: + cache = await _load_tool_servers() + for tool_info in cache["tools"].values(): + schemas.append(tool_info["spec"]) + except Exception: + pass + + return schemas async def execute_tool(name: str, args: dict, __context__: dict) -> str: """Execute a tool by name, injecting execution context.""" - info = TOOLS.get(name) or BROWSER_TOOLS.get(name) - if not info: - return f"Error: unknown tool: {name}" - fn = info["fn"] - try: - sig = inspect.signature(fn) - if "__context__" in sig.parameters: - return await fn(**args, __context__=__context__) - else: - # Legacy tools: inject workspace directly - return await fn(**args, workspace=__context__["workspace"]) - except Exception as e: - return f"Error executing {name}: {e}" + info = ALL_TOOLS.get(name) + if info: + fn = info["fn"] + try: + sig = inspect.signature(fn) + if "__context__" in sig.parameters: + return await fn(**args, __context__=__context__) + else: + # Legacy tools: inject workspace directly + return await fn(**args, workspace=__context__["workspace"]) + except Exception as e: + return f"Error executing {name}: {e}" + + # Check external tool servers + cache = await _load_tool_servers() + if name in cache["tools"]: + return await _execute_external_tool(name, args) + + return f"Error: unknown tool: {name}" diff --git a/pyproject.toml b/pyproject.toml index bda26ce..d7a9e10 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -24,6 +24,7 @@ dependencies = [ [project.optional-dependencies] pam = ["python-pam>=2.0"] +mcp = ["mcp>=1.8"] [dependency-groups] dev = [ From 82d5167e9918bda5afe2a855af5c0724dafef0d4 Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Sat, 13 Jun 2026 13:12:25 +0200 Subject: [PATCH 07/10] refac --- .../src/lib/components/Admin/Subagents.svelte | 113 ++++++++ .../src/lib/components/SettingsModal.svelte | 9 +- .../components/chat/AssistantMessage.svelte | 4 + cptr/frontend/src/lib/i18n/locales/en.json | 14 +- cptr/utils/chat_task.py | 267 +++++++++++------- cptr/utils/tools.py | 145 +++++++++- 6 files changed, 448 insertions(+), 104 deletions(-) create mode 100644 cptr/frontend/src/lib/components/Admin/Subagents.svelte diff --git a/cptr/frontend/src/lib/components/Admin/Subagents.svelte b/cptr/frontend/src/lib/components/Admin/Subagents.svelte new file mode 100644 index 0000000..a6c0dd1 --- /dev/null +++ b/cptr/frontend/src/lib/components/Admin/Subagents.svelte @@ -0,0 +1,113 @@ + + +
+

{$t('admin.subagents')}

+ + {#if loading} +
+ {:else} +
+ +

+ {$t('admin.subagentsHint')} +

+ + {#if enabled} +
+ +
+ + {$t('admin.subagentsMaxConcurrentHint')} +
+
+ +
+ +
+ + {$t('admin.subagentsMaxIterationsHint')} +
+
+ +
+ +
+ + chars +
+
+ +
+ +