From 621853cbfb4233b34cb8cc2e1ed10917ba424352 Mon Sep 17 00:00:00 2001 From: Igor Zarubin <132256588+itsmepicus@users.noreply.github.com> Date: Wed, 11 Mar 2026 15:09:54 +0800 Subject: [PATCH] feat(i18n): localize plugin settings for all languages (#515) * chore(gitignore): add .worktrees/ to .gitignore * fix(gitignore): add .worktrees/ to .gitignore * feat(i18n): localize plugin settings - Add missing mainTabs.plugins key in Russian locale. - Add useTranslation to PluginSettingsTab and MobileNav. - Add pluginSettings translations for en, ru, ja, ko, zh-CN. - Localize the mobile navigation More button. * fix: remove Japanese symbols in Rorean translate * fix: fix Korean typo and localize starter plugin error * fix(plugins): localize toggle labels and fix translation issues * refactor(plugins): extract inline onToggle to named handleToggle * fix(plugins): localize repo input aria-label and "tab" badge - Replace hardcoded aria-label with t('pluginSettings.installAriaLabel') - Replace hardcoded "tab" badge text with t('pluginSettings.tab') - Add missing keys to all settings.json locale files * fix(plugins): localize "running" status badge --- .gitignore | 5 +- src/components/app/MobileNav.tsx | 14 +- .../plugins/view/PluginSettingsTab.tsx | 120 +++++++++--------- src/i18n/locales/en/settings.json | 38 +++++- src/i18n/locales/ja/settings.json | 38 +++++- src/i18n/locales/ko/settings.json | 38 +++++- src/i18n/locales/ru/settings.json | 41 +++++- src/i18n/locales/zh-CN/settings.json | 38 +++++- 8 files changed, 261 insertions(+), 71 deletions(-) diff --git a/.gitignore b/.gitignore index 76793a8..5ee3dbb 100755 --- a/.gitignore +++ b/.gitignore @@ -134,4 +134,7 @@ tasks/ # Translations !src/i18n/locales/en/tasks.json !src/i18n/locales/ja/tasks.json -!src/i18n/locales/ru/tasks.json \ No newline at end of file +!src/i18n/locales/ru/tasks.json + +# Git worktrees +.worktrees/ \ No newline at end of file diff --git a/src/components/app/MobileNav.tsx b/src/components/app/MobileNav.tsx index ea49f00..8a672be 100644 --- a/src/components/app/MobileNav.tsx +++ b/src/components/app/MobileNav.tsx @@ -1,4 +1,5 @@ import { useState, useRef, useEffect, type Dispatch, type SetStateAction } from 'react'; +import { useTranslation } from 'react-i18next'; import { MessageSquare, Folder, @@ -37,6 +38,7 @@ type MobileNavProps = { }; export default function MobileNav({ activeTab, setActiveTab, isInputFocused }: MobileNavProps) { + const { t } = useTranslation(['common', 'settings']); const { tasksEnabled, isTaskMasterInstalled } = useTasksSettings(); const shouldShowTasksTab = Boolean(tasksEnabled && isTaskMasterInstalled); const { plugins } = usePlugins(); @@ -126,11 +128,10 @@ export default function MobileNav({ activeTab, setActiveTab, isInputFocused }: M e.preventDefault(); setMoreOpen((v) => !v); }} - className={`relative flex w-full touch-manipulation flex-col items-center justify-center gap-0.5 rounded-xl px-3 py-2 transition-all duration-200 active:scale-95 ${ - isPluginActive || moreOpen + className={`relative flex w-full touch-manipulation flex-col items-center justify-center gap-0.5 rounded-xl px-3 py-2 transition-all duration-200 active:scale-95 ${isPluginActive || moreOpen ? 'text-primary' : 'text-muted-foreground hover:text-foreground' - }`} + }`} aria-label="More plugins" aria-expanded={moreOpen} > @@ -142,7 +143,7 @@ export default function MobileNav({ activeTab, setActiveTab, isInputFocused }: M strokeWidth={isPluginActive ? 2.4 : 1.8} /> - More + {t('settings:pluginSettings.morePlugins')} @@ -157,11 +158,10 @@ export default function MobileNav({ activeTab, setActiveTab, isInputFocused }: M - + @@ -175,20 +176,20 @@ function PluginCard({ {confirmingUninstall && (
- Remove {plugin.displayName}? This cannot be undone. + {t('pluginSettings.confirmUninstallMessage', { name: plugin.displayName })}
@@ -208,6 +209,8 @@ function PluginCard({ /* ─── Starter Plugin Card ───────────────────────────────────────────────── */ function StarterPluginCard({ onInstall, installing }: { onInstall: () => void; installing: boolean }) { + const { t } = useTranslation('settings'); + return (
@@ -220,17 +223,17 @@ function StarterPluginCard({ onInstall, installing }: { onInstall: () => void; i
- Project Stats + {t('pluginSettings.starterPlugin.name')} - starter + {t('pluginSettings.starterPlugin.badge')} - tab + {t('pluginSettings.tab')}

- File counts, lines of code, file-type breakdown, and recent activity for your project. + {t('pluginSettings.starterPlugin.description')}

void; i ) : ( )} - {installing ? 'Installing…' : 'Install'} + {installing ? t('pluginSettings.installing') : t('pluginSettings.starterPlugin.install')}
@@ -263,6 +266,7 @@ function StarterPluginCard({ onInstall, installing }: { onInstall: () => void; i /* ─── Main Component ────────────────────────────────────────────────────── */ export default function PluginSettingsTab() { + const { t } = useTranslation('settings'); const { plugins, loading, installPlugin, uninstallPlugin, updatePlugin, togglePlugin } = usePlugins(); @@ -279,7 +283,7 @@ export default function PluginSettingsTab() { setUpdateErrors((prev) => { const next = { ...prev }; delete next[name]; return next; }); const result = await updatePlugin(name); if (!result.success) { - setUpdateErrors((prev) => ({ ...prev, [name]: result.error || 'Update failed' })); + setUpdateErrors((prev) => ({ ...prev, [name]: result.error || t('pluginSettings.updateFailed') })); } setUpdatingPlugins((prev) => { const next = new Set(prev); next.delete(name); return next; }); }; @@ -292,7 +296,7 @@ export default function PluginSettingsTab() { if (result.success) { setGitUrl(''); } else { - setInstallError(result.error || 'Installation failed'); + setInstallError(result.error || t('pluginSettings.installFailed')); } setInstalling(false); }; @@ -302,7 +306,7 @@ export default function PluginSettingsTab() { setInstallError(null); const result = await installPlugin(STARTER_PLUGIN_URL); if (!result.success) { - setInstallError(result.error || 'Installation failed'); + setInstallError(result.error || t('pluginSettings.installFailed')); } setInstallingStarter(false); }; @@ -316,7 +320,7 @@ export default function PluginSettingsTab() { if (result.success) { setConfirmUninstall(null); } else { - setInstallError(result.error || 'Uninstall failed'); + setInstallError(result.error || t('pluginSettings.uninstallFailed')); setConfirmUninstall(null); } }; @@ -328,17 +332,10 @@ export default function PluginSettingsTab() { {/* Header */}

- Plugins + {t('pluginSettings.title')}

- Extend the interface with custom plugins. Install from{' '} - - git - {' '} - or drop a folder in{' '} - - ~/.claude-code-ui/plugins/ - + {t('pluginSettings.description')}

@@ -354,8 +351,8 @@ export default function PluginSettingsTab() { setGitUrl(e.target.value); setInstallError(null); }} - placeholder="https://github.com/user/my-plugin" - aria-label="Plugin git repository URL" + placeholder={t('pluginSettings.installPlaceholder')} + aria-label={t('pluginSettings.installAriaLabel')} className="flex-1 bg-transparent px-2 py-2.5 text-sm text-foreground placeholder:text-muted-foreground/40 focus:outline-none" onKeyDown={(e) => { if (e.key === 'Enter') void handleInstall(); @@ -369,7 +366,7 @@ export default function PluginSettingsTab() { {installing ? ( ) : ( - 'Install' + t('pluginSettings.installButton') )}
@@ -381,7 +378,7 @@ export default function PluginSettingsTab() {

- Only install plugins whose source code you have reviewed or from authors you trust. + {t('pluginSettings.securityWarning')}

@@ -394,26 +391,35 @@ export default function PluginSettingsTab() { {loading ? (
- Scanning plugins… + {t('pluginSettings.scanningPlugins')}
) : plugins.length === 0 ? ( -

No plugins installed

+

{t('pluginSettings.noPluginsInstalled')}

) : (
- {plugins.map((plugin, index) => ( - void togglePlugin(plugin.name, enabled).then(r => { if (!r.success) setInstallError(r.error || 'Toggle failed'); })} - onUpdate={() => void handleUpdate(plugin.name)} - onUninstall={() => void handleUninstall(plugin.name)} - updating={updatingPlugins.has(plugin.name)} - confirmingUninstall={confirmUninstall === plugin.name} - onCancelUninstall={() => setConfirmUninstall(null)} - updateError={updateErrors[plugin.name] ?? null} - /> - ))} + {plugins.map((plugin, index) => { + const handleToggle = async (enabled: boolean) => { + const r = await togglePlugin(plugin.name, enabled); + if (!r.success) { + setInstallError(r.error || t('pluginSettings.toggleFailed')); + } + }; + + return ( + void handleToggle(enabled)} + onUpdate={() => void handleUpdate(plugin.name)} + onUninstall={() => void handleUninstall(plugin.name)} + updating={updatingPlugins.has(plugin.name)} + confirmingUninstall={confirmUninstall === plugin.name} + onCancelUninstall={() => setConfirmUninstall(null)} + updateError={updateErrors[plugin.name] ?? null} + /> + ); + })}
)} @@ -422,7 +428,7 @@ export default function PluginSettingsTab() {
- Build your own plugin + {t('pluginSettings.buildYourOwn')}
@@ -432,7 +438,7 @@ export default function PluginSettingsTab() { rel="noopener noreferrer" className="inline-flex items-center gap-1 text-xs text-muted-foreground/60 transition-colors hover:text-foreground" > - Starter + {t('pluginSettings.starter')} · - Docs + {t('pluginSettings.docs')}
diff --git a/src/i18n/locales/en/settings.json b/src/i18n/locales/en/settings.json index fdb2f7f..00e7eaa 100644 --- a/src/i18n/locales/en/settings.json +++ b/src/i18n/locales/en/settings.json @@ -434,5 +434,41 @@ "title": "About Codex MCP", "description": "Codex supports stdio-based MCP servers. You can add servers that extend Codex's capabilities with additional tools and resources." } + }, + "pluginSettings": { + "title": "Plugins", + "description": "Extend the interface with custom plugins. Install from git or drop a folder in ~/.claude-code-ui/plugins/", + "installPlaceholder": "https://github.com/user/my-plugin", + "installButton": "Install", + "installing": "Installing…", + "securityWarning": "Only install plugins whose source code you have reviewed or from authors you trust.", + "scanningPlugins": "Scanning plugins…", + "noPluginsInstalled": "No plugins installed", + "pullLatest": "Pull latest from git", + "noGitRemote": "No git remote — update not available", + "uninstallPlugin": "Uninstall plugin", + "confirmUninstall": "Click again to confirm", + "confirmUninstallMessage": "Remove {{name}}? This cannot be undone.", + "cancel": "Cancel", + "remove": "Remove", + "updateFailed": "Update failed", + "installFailed": "Installation failed", + "uninstallFailed": "Uninstall failed", + "toggleFailed": "Toggle failed", + "buildYourOwn": "Build your own plugin", + "starter": "Starter", + "docs": "Docs", + "starterPlugin": { + "name": "Project Stats", + "badge": "starter", + "description": "File counts, lines of code, file-type breakdown, and recent activity for your project.", + "install": "Install" + }, + "morePlugins": "More", + "enable": "Enable", + "disable": "Disable", + "installAriaLabel": "Plugin git repository URL", + "tab": "tab", + "runningStatus": "running" } -} +} \ No newline at end of file diff --git a/src/i18n/locales/ja/settings.json b/src/i18n/locales/ja/settings.json index 927056e..5b01a5c 100644 --- a/src/i18n/locales/ja/settings.json +++ b/src/i18n/locales/ja/settings.json @@ -434,5 +434,41 @@ "title": "Codex MCPについて", "description": "Codexはstdioベースのツールサーバーをサポートしています。追加のツールやリソースでCodexの機能を拡張するサーバーを追加できます。" } + }, + "pluginSettings": { + "title": "プラグイン", + "description": "カスタムプラグインでインターフェースを拡張します。gitからインストールするか、~/.claude-code-ui/plugins/ にフォルダを配置してください。", + "installPlaceholder": "https://github.com/user/my-plugin", + "installButton": "インストール", + "installing": "インストール中…", + "securityWarning": "信頼できる作成者のプラグイン、またはソースコードを確認済みのプラグインのみをインストールしてください。", + "scanningPlugins": "プラグインをスキャン中…", + "noPluginsInstalled": "プラグインがインストールされていません", + "pullLatest": "gitから最新を取得", + "noGitRemote": "リモートgitリポジトリがありません — アップデート不可", + "uninstallPlugin": "プラグインを削除", + "confirmUninstall": "クリックして確定", + "confirmUninstallMessage": "{{name}} を削除しますか?この操作は取り消せません。", + "cancel": "キャンセル", + "remove": "削除", + "updateFailed": "アップデートに失敗しました", + "installFailed": "インストールに失敗しました", + "uninstallFailed": "削除に失敗しました", + "toggleFailed": "切り替えに失敗しました", + "buildYourOwn": "プラグインを自作する", + "starter": "スターター", + "docs": "ドキュメント", + "starterPlugin": { + "name": "プロジェクト統計", + "badge": "スターター", + "description": "プロジェクトのファイル数、コード行数、ファイルタイプの内訳、最近のアクティビティを表示します。", + "install": "インストール" + }, + "morePlugins": "詳細", + "enable": "有効にする", + "disable": "無効にする", + "installAriaLabel": "プラグインのgitリポジトリURL", + "tab": "タブ", + "runningStatus": "実行中" } -} +} \ No newline at end of file diff --git a/src/i18n/locales/ko/settings.json b/src/i18n/locales/ko/settings.json index bd42a77..468f480 100644 --- a/src/i18n/locales/ko/settings.json +++ b/src/i18n/locales/ko/settings.json @@ -434,5 +434,41 @@ "title": "Codex MCP 정보", "description": "Codex는 stdio 기반 MCP 서버를 지원합니다. 추가 도구와 리소스로 Codex의 기능을 확장하는 서버를 추가할 수 있습니다." } + }, + "pluginSettings": { + "title": "플러그인", + "description": "커스텀 플러그인으로 인터페이스를 확장하세요. git에서 설치하거나 ~/.claude-code-ui/plugins/ 폴더에 직접 추가할 수 있습니다.", + "installPlaceholder": "https://github.com/user/my-plugin", + "installButton": "설치", + "installing": "설치 중…", + "securityWarning": "소스 코드를 검토했거나 신뢰할 수 있는 작성자의 플러그인만 설치하세요.", + "scanningPlugins": "플러그인 스캔 중…", + "noPluginsInstalled": "설치된 플러그인이 없습니다", + "pullLatest": "git에서 최신 버전 가져오기", + "noGitRemote": "git 리모트가 없음 — 업데이트 불가", + "uninstallPlugin": "플러그인 삭제", + "confirmUninstall": "다시 클릭하여 확인", + "confirmUninstallMessage": "{{name}} 플러그인을 삭제하시겠습니까? 이 작업은 되돌릴 수 없습니다.", + "cancel": "취소", + "remove": "삭제", + "updateFailed": "업데이트 실패", + "installFailed": "설치 실패", + "uninstallFailed": "삭제 실패", + "toggleFailed": "토글 실패", + "buildYourOwn": "나만의 플러그인 만들기", + "starter": "스타터", + "docs": "문서", + "starterPlugin": { + "name": "프로젝트 통계", + "badge": "스타터", + "description": "프로젝트의 파일 수, 코드 라인 수, 파일 유형별 분석 및 최근 활동을 확인합니다.", + "install": "설치" + }, + "morePlugins": "더 보기", + "enable": "활성화", + "disable": "비활성화", + "installAriaLabel": "플러그인 git 저장소 URL", + "tab": "탭", + "runningStatus": "실행 중" } -} +} \ No newline at end of file diff --git a/src/i18n/locales/ru/settings.json b/src/i18n/locales/ru/settings.json index 5ba9f46..f8991ab 100644 --- a/src/i18n/locales/ru/settings.json +++ b/src/i18n/locales/ru/settings.json @@ -104,7 +104,8 @@ "appearance": "Внешний вид", "git": "Git", "apiTokens": "API и токены", - "tasks": "Задачи" + "tasks": "Задачи", + "plugins": "Плагины" }, "appearanceSettings": { "darkMode": { @@ -433,5 +434,41 @@ "title": "О Codex MCP", "description": "Codex поддерживает MCP серверы на основе stdio. Вы можете добавлять серверы, которые расширяют возможности Codex дополнительными инструментами и ресурсами." } + }, + "pluginSettings": { + "title": "Плагины", + "description": "Расширяйте интерфейс с помощью кастомных плагинов. Установите из git или добавьте папку в ~/.claude-code-ui/plugins/", + "installPlaceholder": "https://github.com/user/my-plugin", + "installButton": "Установить", + "installing": "Установка…", + "securityWarning": "Устанавливайте только те плагины, исходный код которых вы проверили или от авторов, которым вы доверяете.", + "scanningPlugins": "Сканирование плагинов…", + "noPluginsInstalled": "Плагины не установлены", + "pullLatest": "Получить обновления из git", + "noGitRemote": "Нет удаленного git-репозитория — обновление недоступно", + "uninstallPlugin": "Удалить плагин", + "confirmUninstall": "Нажмите еще раз для подтверждения", + "confirmUninstallMessage": "Удалить {{name}}? Это действие нельзя отменить.", + "cancel": "Отмена", + "remove": "Удалить", + "updateFailed": "Ошибка обновления", + "installFailed": "Ошибка установки", + "uninstallFailed": "Ошибка удаления", + "toggleFailed": "Ошибка переключения", + "buildYourOwn": "Создайте свой плагин", + "starter": "Шаблон", + "docs": "Документация", + "starterPlugin": { + "name": "Статистика проекта", + "badge": "шаблон", + "description": "Количество файлов, строк кода, разбивка по типам файлов и недавняя активность в вашем проекте.", + "install": "Установить" + }, + "morePlugins": "Ещё", + "enable": "Включить", + "disable": "Выключить", + "installAriaLabel": "URL git-репозитория плагина", + "tab": "вкладка", + "runningStatus": "запущен" } -} +} \ No newline at end of file diff --git a/src/i18n/locales/zh-CN/settings.json b/src/i18n/locales/zh-CN/settings.json index 555bb82..0bd7731 100644 --- a/src/i18n/locales/zh-CN/settings.json +++ b/src/i18n/locales/zh-CN/settings.json @@ -434,5 +434,41 @@ "title": "关于 Codex MCP", "description": "Codex 支持基于 stdio 的 MCP 服务器。您可以添加服务器,通过额外的工具和资源来扩展 Codex 的功能。" } + }, + "pluginSettings": { + "title": "插件", + "description": "通过自定义插件扩展界面。从 git 安装或直接将文件夹放入 ~/.claude-code-ui/plugins/", + "installPlaceholder": "https://github.com/user/my-plugin", + "installButton": "安装", + "installing": "安装中…", + "securityWarning": "仅安装您已审查过源代码或信任作者的插件。", + "scanningPlugins": "正在扫描插件…", + "noPluginsInstalled": "未安装插件", + "pullLatest": "从 git 拉取最新内容", + "noGitRemote": "无 git 远程仓库 — 无法更新", + "uninstallPlugin": "卸载插件", + "confirmUninstall": "再次点击确认", + "confirmUninstallMessage": "移除 {{name}}?此操作无法撤销。", + "cancel": "取消", + "remove": "移除", + "updateFailed": "更新失败", + "installFailed": "安装失败", + "uninstallFailed": "卸载失败", + "toggleFailed": "切换失败", + "buildYourOwn": "构建您自己的插件", + "starter": "入门模板", + "docs": "文档", + "starterPlugin": { + "name": "项目统计", + "badge": "入门", + "description": "查看项目的文件数、代码行数、文件类型分布以及最近活动。", + "install": "安装" + }, + "morePlugins": "更多", + "enable": "启用", + "disable": "禁用", + "installAriaLabel": "插件 git 仓库 URL", + "tab": "标签", + "runningStatus": "运行中" } -} +} \ No newline at end of file