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
This commit is contained in:
Igor Zarubin
2026-03-11 15:09:54 +08:00
committed by GitHub
parent 4d8fb6e30a
commit 621853cbfb
8 changed files with 261 additions and 71 deletions

5
.gitignore vendored
View File

@@ -134,4 +134,7 @@ tasks/
# Translations
!src/i18n/locales/en/tasks.json
!src/i18n/locales/ja/tasks.json
!src/i18n/locales/ru/tasks.json
!src/i18n/locales/ru/tasks.json
# Git worktrees
.worktrees/

View File

@@ -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}
/>
<span className={`relative z-10 text-[10px] font-medium transition-all duration-200 ${isPluginActive || moreOpen ? 'opacity-100' : 'opacity-60'}`}>
More
{t('settings:pluginSettings.morePlugins')}
</span>
</button>
@@ -157,11 +158,10 @@ export default function MobileNav({ activeTab, setActiveTab, isInputFocused }: M
<button
key={p.name}
onClick={() => selectPlugin(p.name)}
className={`flex w-full items-center gap-2.5 px-3.5 py-2.5 text-sm transition-colors ${
isActive
className={`flex w-full items-center gap-2.5 px-3.5 py-2.5 text-sm transition-colors ${isActive
? 'bg-primary/8 text-primary'
: 'text-foreground hover:bg-muted/60'
}`}
}`}
>
<Icon className="h-4 w-4 flex-shrink-0" strokeWidth={isActive ? 2.2 : 1.8} />
<span className="truncate">{p.displayName}</span>

View File

@@ -1,4 +1,5 @@
import { useState } from 'react';
import { useTranslation } from 'react-i18next';
import { Trash2, RefreshCw, GitBranch, Loader2, ServerCrash, ShieldAlert, ExternalLink, BookOpen, Download, BarChart3 } from 'lucide-react';
import { usePlugins } from '../../../contexts/PluginsContext';
import type { Plugin } from '../../../contexts/PluginsContext';
@@ -32,7 +33,7 @@ function ToggleSwitch({ checked, onChange, ariaLabel }: { checked: boolean; onCh
}
/* ─── Server Dot ────────────────────────────────────────────────────────── */
function ServerDot({ running }: { running: boolean }) {
function ServerDot({ running, t }: { running: boolean; t: any }) {
if (!running) return null;
return (
<span className="relative flex items-center gap-1.5">
@@ -41,7 +42,7 @@ function ServerDot({ running }: { running: boolean }) {
<span className="relative inline-flex h-1.5 w-1.5 rounded-full bg-emerald-500" />
</span>
<span className="font-mono text-[10px] uppercase tracking-wide text-emerald-600 dark:text-emerald-400">
running
{t('pluginSettings.runningStatus')}
</span>
</span>
);
@@ -71,6 +72,7 @@ function PluginCard({
onCancelUninstall,
updateError,
}: PluginCardProps) {
const { t } = useTranslation('settings');
const accentColor = plugin.enabled
? 'bg-emerald-500'
: 'bg-muted-foreground/20';
@@ -108,7 +110,7 @@ function PluginCard({
<span className="rounded bg-muted px-1.5 py-0.5 text-[10px] text-muted-foreground">
{plugin.slot}
</span>
<ServerDot running={!!plugin.serverRunning} />
<ServerDot running={!!plugin.serverRunning} t={t} />
</div>
{plugin.description && (
<p className="mt-1 text-sm leading-snug text-muted-foreground">
@@ -143,8 +145,8 @@ function PluginCard({
<button
onClick={onUpdate}
disabled={updating || !plugin.repoUrl}
title={plugin.repoUrl ? 'Pull latest from git' : 'No git remote — update not available'}
aria-label={`Update ${plugin.displayName}`}
title={plugin.repoUrl ? t('pluginSettings.pullLatest') : t('pluginSettings.noGitRemote')}
aria-label={t('pluginSettings.pullLatest')}
className="rounded p-1.5 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground disabled:opacity-40"
>
{updating ? (
@@ -156,18 +158,17 @@ function PluginCard({
<button
onClick={onUninstall}
title={confirmingUninstall ? 'Click again to confirm' : 'Uninstall plugin'}
aria-label={`Uninstall ${plugin.displayName}`}
className={`rounded p-1.5 transition-colors ${
confirmingUninstall
? 'bg-red-50 text-red-500 hover:bg-red-100 dark:bg-red-900/20 dark:hover:bg-red-900/30'
: 'text-muted-foreground hover:bg-muted hover:text-red-500'
}`}
title={confirmingUninstall ? t('pluginSettings.confirmUninstall') : t('pluginSettings.uninstallPlugin')}
aria-label={t('pluginSettings.uninstallPlugin')}
className={`rounded p-1.5 transition-colors ${confirmingUninstall
? 'bg-red-50 text-red-500 hover:bg-red-100 dark:bg-red-900/20 dark:hover:bg-red-900/30'
: 'text-muted-foreground hover:bg-muted hover:text-red-500'
}`}
>
<Trash2 className="h-3.5 w-3.5" />
</button>
<ToggleSwitch checked={plugin.enabled} onChange={onToggle} ariaLabel={`${plugin.enabled ? 'Disable' : 'Enable'} ${plugin.displayName}`} />
<ToggleSwitch checked={plugin.enabled} onChange={onToggle} ariaLabel={`${plugin.enabled ? t('pluginSettings.disable') : t('pluginSettings.enable')} ${plugin.displayName}`} />
</div>
</div>
@@ -175,20 +176,20 @@ function PluginCard({
{confirmingUninstall && (
<div className="mt-3 flex items-center justify-between gap-3 rounded border border-red-200 bg-red-50 px-3 py-2 dark:border-red-800/50 dark:bg-red-950/30">
<span className="text-sm text-red-600 dark:text-red-400">
Remove <span className="font-semibold">{plugin.displayName}</span>? This cannot be undone.
{t('pluginSettings.confirmUninstallMessage', { name: plugin.displayName })}
</span>
<div className="flex gap-1.5">
<button
onClick={onCancelUninstall}
className="rounded border border-border px-2.5 py-1 text-sm text-muted-foreground transition-colors hover:bg-muted hover:text-foreground"
>
Cancel
{t('pluginSettings.cancel')}
</button>
<button
onClick={onUninstall}
className="rounded border border-red-300 px-2.5 py-1 text-sm font-medium text-red-600 transition-colors hover:bg-red-100 dark:border-red-700 dark:text-red-400 dark:hover:bg-red-900/30"
>
Remove
{t('pluginSettings.remove')}
</button>
</div>
</div>
@@ -208,6 +209,8 @@ function PluginCard({
/* ─── Starter Plugin Card ───────────────────────────────────────────────── */
function StarterPluginCard({ onInstall, installing }: { onInstall: () => void; installing: boolean }) {
const { t } = useTranslation('settings');
return (
<div className="relative flex overflow-hidden rounded-lg border border-dashed border-border bg-card transition-all duration-200 hover:border-blue-400 dark:hover:border-blue-500">
<div className="w-[3px] flex-shrink-0 bg-blue-500/30" />
@@ -220,17 +223,17 @@ function StarterPluginCard({ onInstall, installing }: { onInstall: () => void; i
<div className="min-w-0">
<div className="flex flex-wrap items-center gap-2">
<span className="text-sm font-semibold leading-none text-foreground">
Project Stats
{t('pluginSettings.starterPlugin.name')}
</span>
<span className="rounded bg-blue-50 px-1.5 py-0.5 text-[10px] font-medium text-blue-600 dark:bg-blue-950/50 dark:text-blue-400">
starter
{t('pluginSettings.starterPlugin.badge')}
</span>
<span className="rounded bg-muted px-1.5 py-0.5 text-[10px] text-muted-foreground">
tab
{t('pluginSettings.tab')}
</span>
</div>
<p className="mt-1 text-sm leading-snug text-muted-foreground">
File counts, lines of code, file-type breakdown, and recent activity for your project.
{t('pluginSettings.starterPlugin.description')}
</p>
<a
href={STARTER_PLUGIN_URL}
@@ -253,7 +256,7 @@ function StarterPluginCard({ onInstall, installing }: { onInstall: () => void; i
) : (
<Download className="h-3.5 w-3.5" />
)}
{installing ? 'Installing' : 'Install'}
{installing ? t('pluginSettings.installing') : t('pluginSettings.starterPlugin.install')}
</button>
</div>
</div>
@@ -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 */}
<div>
<h3 className="mb-1 text-base font-semibold text-foreground">
Plugins
{t('pluginSettings.title')}
</h3>
<p className="text-sm text-muted-foreground">
Extend the interface with custom plugins. Install from{' '}
<code className="rounded bg-muted px-1.5 py-0.5 text-xs font-semibold">
git
</code>{' '}
or drop a folder in{' '}
<code className="rounded bg-muted px-1.5 py-0.5 text-xs font-semibold">
~/.claude-code-ui/plugins/
</code>
{t('pluginSettings.description')}
</p>
</div>
@@ -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 ? (
<Loader2 className="h-4 w-4 animate-spin" />
) : (
'Install'
t('pluginSettings.installButton')
)}
</button>
</div>
@@ -381,7 +378,7 @@ export default function PluginSettingsTab() {
<p className="-mt-4 flex items-start gap-1.5 text-xs leading-snug text-muted-foreground/50">
<ShieldAlert className="mt-px h-3 w-3 flex-shrink-0" />
<span>
Only install plugins whose source code you have reviewed or from authors you trust.
{t('pluginSettings.securityWarning')}
</span>
</p>
@@ -394,26 +391,35 @@ export default function PluginSettingsTab() {
{loading ? (
<div className="flex items-center justify-center gap-2 py-10 text-sm text-muted-foreground">
<Loader2 className="h-4 w-4 animate-spin" />
Scanning plugins
{t('pluginSettings.scanningPlugins')}
</div>
) : plugins.length === 0 ? (
<p className="py-8 text-center text-sm text-muted-foreground">No plugins installed</p>
<p className="py-8 text-center text-sm text-muted-foreground">{t('pluginSettings.noPluginsInstalled')}</p>
) : (
<div className="space-y-2">
{plugins.map((plugin, index) => (
<PluginCard
key={plugin.name}
plugin={plugin}
index={index}
onToggle={(enabled) => 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 (
<PluginCard
key={plugin.name}
plugin={plugin}
index={index}
onToggle={(enabled) => 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}
/>
);
})}
</div>
)}
@@ -422,7 +428,7 @@ export default function PluginSettingsTab() {
<div className="flex min-w-0 items-center gap-2">
<BookOpen className="h-3.5 w-3.5 flex-shrink-0 text-muted-foreground/40" />
<span className="text-xs text-muted-foreground/60">
Build your own plugin
{t('pluginSettings.buildYourOwn')}
</span>
</div>
<div className="flex flex-shrink-0 items-center gap-3">
@@ -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 <ExternalLink className="h-2.5 w-2.5" />
{t('pluginSettings.starter')} <ExternalLink className="h-2.5 w-2.5" />
</a>
<span className="text-muted-foreground/20">·</span>
<a
@@ -441,7 +447,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"
>
Docs <ExternalLink className="h-2.5 w-2.5" />
{t('pluginSettings.docs')} <ExternalLink className="h-2.5 w-2.5" />
</a>
</div>
</div>

View File

@@ -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"
}
}
}

View File

@@ -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": "実行中"
}
}
}

View File

@@ -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": "실행 중"
}
}
}

View File

@@ -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": "запущен"
}
}
}

View File

@@ -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": "运行中"
}
}
}