From 7cefde6a1ddc4eab1dee7b76f9926ac529684a09 Mon Sep 17 00:00:00 2001
From: Dawnlck
Date: Mon, 17 Nov 2025 14:55:32 +0800
Subject: [PATCH 1/7] Add i18n support with English and Chinese resources
---
src/components/MainContent.jsx | 34 +++++++-------
src/components/MobileNav.jsx | 19 +++++---
src/components/Sidebar.jsx | 44 +++++++++---------
src/i18n/en.json | 19 ++++++++
src/i18n/index.jsx | 85 ++++++++++++++++++++++++++++++++++
src/i18n/zh.json | 19 ++++++++
src/main.jsx | 5 +-
7 files changed, 181 insertions(+), 44 deletions(-)
create mode 100644 src/i18n/en.json
create mode 100644 src/i18n/index.jsx
create mode 100644 src/i18n/zh.json
diff --git a/src/components/MainContent.jsx b/src/components/MainContent.jsx
index 58a8749e8..8a8167c8f 100644
--- a/src/components/MainContent.jsx
+++ b/src/components/MainContent.jsx
@@ -27,6 +27,7 @@ import Tooltip from './Tooltip';
import { useTaskMaster } from '../contexts/TaskMasterContext';
import { useTasksSettings } from '../contexts/TasksSettingsContext';
import { api } from '../utils/api';
+import { useTranslation } from '../i18n';
function MainContent({
selectedProject,
@@ -71,10 +72,11 @@ function MainContent({
const [selectedPRD, setSelectedPRD] = useState(null);
const [existingPRDs, setExistingPRDs] = useState([]);
const [prdNotification, setPRDNotification] = useState(null);
-
+
// TaskMaster context
const { tasks, currentProject, refreshTasks, setCurrentProject } = useTaskMaster();
const { tasksEnabled, isTaskMasterInstalled, isTaskMasterReady } = useTasksSettings();
+ const { t } = useTranslation();
// Only show tasks tab if TaskMaster is installed and enabled
const shouldShowTasksTab = tasksEnabled && isTaskMasterInstalled;
@@ -331,7 +333,7 @@ function MainContent({
) : activeTab === 'chat' && !selectedSession ? (
- New Session
+ {t('sidebar.newSession')}
{selectedProject.displayName}
@@ -340,10 +342,10 @@ function MainContent({
) : (
- {activeTab === 'files' ? 'Project Files' :
- activeTab === 'git' ? 'Source Control' :
- (activeTab === 'tasks' && shouldShowTasksTab) ? 'TaskMaster' :
- 'Project'}
+ {activeTab === 'files' ? t('navigation.projectFiles') :
+ activeTab === 'git' ? t('navigation.sourceControl') :
+ (activeTab === 'tasks' && shouldShowTasksTab) ? t('navigation.tasks') :
+ t('navigation.project')}
{selectedProject.displayName}
@@ -357,7 +359,7 @@ function MainContent({
{/* Modern Tab Navigation - Right Side */}
-
+
setActiveTab('chat')}
className={`relative px-2 sm:px-3 py-1.5 text-xs sm:text-sm font-medium rounded-md ${
@@ -370,11 +372,11 @@ function MainContent({
- Chat
+ {t('navigation.chat')}
-
+
setActiveTab('shell')}
className={`relative px-2 sm:px-3 py-1.5 text-xs sm:text-sm font-medium rounded-md transition-all duration-200 ${
@@ -387,11 +389,11 @@ function MainContent({
- Shell
+ {t('navigation.shell')}
-
+
setActiveTab('files')}
className={`relative px-2 sm:px-3 py-1.5 text-xs sm:text-sm font-medium rounded-md transition-all duration-200 ${
@@ -404,11 +406,11 @@ function MainContent({
- Files
+ {t('navigation.files')}
-
+
setActiveTab('git')}
className={`relative px-2 sm:px-3 py-1.5 text-xs sm:text-sm font-medium rounded-md transition-all duration-200 ${
@@ -421,12 +423,12 @@ function MainContent({
- Source Control
+ {t('navigation.sourceControl')}
{shouldShowTasksTab && (
-
+
setActiveTab('tasks')}
className={`relative px-2 sm:px-3 py-1.5 text-xs sm:text-sm font-medium rounded-md transition-all duration-200 ${
@@ -439,7 +441,7 @@ function MainContent({
- Tasks
+ {t('navigation.tasks')}
diff --git a/src/components/MobileNav.jsx b/src/components/MobileNav.jsx
index 9940b421b..d56bb0b9a 100644
--- a/src/components/MobileNav.jsx
+++ b/src/components/MobileNav.jsx
@@ -1,35 +1,42 @@
import React from 'react';
import { MessageSquare, Folder, Terminal, GitBranch, Globe, CheckSquare } from 'lucide-react';
import { useTasksSettings } from '../contexts/TasksSettingsContext';
+import { useTranslation } from '../i18n';
function MobileNav({ activeTab, setActiveTab, isInputFocused }) {
const { tasksEnabled } = useTasksSettings();
+ const { t } = useTranslation();
const navItems = [
{
id: 'chat',
icon: MessageSquare,
- onClick: () => setActiveTab('chat')
+ onClick: () => setActiveTab('chat'),
+ label: t('navigation.chat')
},
{
id: 'shell',
icon: Terminal,
- onClick: () => setActiveTab('shell')
+ onClick: () => setActiveTab('shell'),
+ label: t('navigation.shell')
},
{
id: 'files',
icon: Folder,
- onClick: () => setActiveTab('files')
+ onClick: () => setActiveTab('files'),
+ label: t('navigation.files')
},
{
id: 'git',
icon: GitBranch,
- onClick: () => setActiveTab('git')
+ onClick: () => setActiveTab('git'),
+ label: t('navigation.sourceControl')
},
// Conditionally add tasks tab if enabled
...(tasksEnabled ? [{
id: 'tasks',
icon: CheckSquare,
- onClick: () => setActiveTab('tasks')
+ onClick: () => setActiveTab('tasks'),
+ label: t('navigation.tasks')
}] : [])
];
@@ -57,7 +64,7 @@ function MobileNav({ activeTab, setActiveTab, isInputFocused }) {
? 'text-blue-600 dark:text-blue-400'
: 'text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white'
}`}
- aria-label={item.id}
+ aria-label={item.label || item.id}
>
{isActive && (
diff --git a/src/components/Sidebar.jsx b/src/components/Sidebar.jsx
index 8a160ccd9..843369203 100644
--- a/src/components/Sidebar.jsx
+++ b/src/components/Sidebar.jsx
@@ -14,6 +14,7 @@ import ProjectCreationWizard from './ProjectCreationWizard';
import { api } from '../utils/api';
import { useTaskMaster } from '../contexts/TaskMasterContext';
import { useTasksSettings } from '../contexts/TasksSettingsContext';
+import { useTranslation } from '../i18n';
// Move formatTimeAgo outside component to avoid recreation on every render
const formatTimeAgo = (dateString, currentTime) => {
@@ -80,6 +81,7 @@ function Sidebar({
// TaskMaster context
const { setCurrentProject, mcpServerStatus } = useTaskMaster();
const { tasksEnabled } = useTasksSettings();
+ const { t } = useTranslation();
// Starred projects state - persisted in localStorage
@@ -1021,7 +1023,7 @@ function Sidebar({
const isActive = diffInMinutes < 10;
// Get session display values
- const sessionName = isCursorSession ? (session.name || 'Untitled Session') : (session.summary || 'New Session');
+ const sessionName = isCursorSession ? (session.name || 'Untitled Session') : (session.summary || t('sidebar.newSession'));
const sessionTime = isCursorSession ? session.createdAt : session.lastActivity;
const messageCount = session.messageCount || 0;
@@ -1213,7 +1215,7 @@ function Sidebar({
onClick={(e) => {
e.stopPropagation();
setEditingSession(session.id);
- setEditingSessionName(session.summary || 'New Session');
+ setEditingSessionName(session.summary || t('sidebar.newSession'));
}}
title="Manually edit session name"
>
@@ -1252,12 +1254,12 @@ function Sidebar({
{loadingSessions[project.name] ? (
<>
- Loading...
+ {t('common.loading')}
>
) : (
<>
- Show more sessions
+ {t('sidebar.showMoreSessions')}
>
)}
@@ -1267,25 +1269,25 @@ function Sidebar({
{
- handleProjectSelect(project);
- onNewSession(project);
- }}
- >
-
- New Session
-
-
-
- onNewSession(project)}
+ onClick={() => {
+ handleProjectSelect(project);
+ onNewSession(project);
+ }}
>
- New Session
-
+ {t('sidebar.newSession')}
+
+
+
+
onNewSession(project)}
+ >
+
+ {t('sidebar.newSession')}
+
)}
diff --git a/src/i18n/en.json b/src/i18n/en.json
new file mode 100644
index 000000000..85f3d47cd
--- /dev/null
+++ b/src/i18n/en.json
@@ -0,0 +1,19 @@
+{
+ "navigation": {
+ "project": "Project",
+ "projectFiles": "Project Files",
+ "sourceControl": "Source Control",
+ "chat": "Chat",
+ "shell": "Shell",
+ "files": "Files",
+ "git": "Source Control",
+ "tasks": "TaskMaster"
+ },
+ "sidebar": {
+ "newSession": "New Session",
+ "showMoreSessions": "Show more sessions"
+ },
+ "common": {
+ "loading": "Loading..."
+ }
+}
diff --git a/src/i18n/index.jsx b/src/i18n/index.jsx
new file mode 100644
index 000000000..a266e9c6b
--- /dev/null
+++ b/src/i18n/index.jsx
@@ -0,0 +1,85 @@
+import React, { createContext, useCallback, useContext, useEffect, useMemo, useState } from 'react';
+import en from './en.json';
+import zh from './zh.json';
+
+const resources = {
+ en,
+ zh
+};
+
+const I18nContext = createContext({
+ t: (key) => key,
+ i18n: {
+ language: 'en',
+ changeLanguage: () => {}
+ }
+});
+
+const resolveKeyPath = (key, language) => {
+ const parts = key.split('.');
+ let value = resources[language];
+
+ for (const part of parts) {
+ if (value && typeof value === 'object' && part in value) {
+ value = value[part];
+ } else {
+ return undefined;
+ }
+ }
+
+ return typeof value === 'string' ? value : undefined;
+};
+
+export function I18nProvider({ children, defaultLanguage = 'en' }) {
+ const [language, setLanguage] = useState(defaultLanguage);
+
+ useEffect(() => {
+ const savedLanguage = localStorage.getItem('claudecodeui-language');
+ if (savedLanguage && resources[savedLanguage]) {
+ setLanguage(savedLanguage);
+ return;
+ }
+
+ const browserLanguage = navigator.language?.toLowerCase().startsWith('zh') ? 'zh' : 'en';
+ if (resources[browserLanguage]) {
+ setLanguage(browserLanguage);
+ }
+ }, []);
+
+ useEffect(() => {
+ localStorage.setItem('claudecodeui-language', language);
+ }, [language]);
+
+ const t = useCallback(
+ (key, options = {}) => {
+ const { defaultValue } = options;
+ const translated = resolveKeyPath(key, language) ?? resolveKeyPath(key, 'en');
+ return translated ?? defaultValue ?? key;
+ },
+ [language]
+ );
+
+ const value = useMemo(() => ({
+ t,
+ i18n: {
+ language,
+ changeLanguage: setLanguage
+ }
+ }), [t, language]);
+
+ return (
+
+ {children}
+
+ );
+}
+
+export function useTranslation() {
+ const context = useContext(I18nContext);
+ return {
+ t: context.t,
+ i18n: context.i18n
+ };
+}
+
+export { resources };
diff --git a/src/i18n/zh.json b/src/i18n/zh.json
new file mode 100644
index 000000000..d3649455f
--- /dev/null
+++ b/src/i18n/zh.json
@@ -0,0 +1,19 @@
+{
+ "navigation": {
+ "project": "项目",
+ "projectFiles": "项目文件",
+ "sourceControl": "源代码管理",
+ "chat": "聊天",
+ "shell": "终端",
+ "files": "文件",
+ "git": "源代码管理",
+ "tasks": "任务管理"
+ },
+ "sidebar": {
+ "newSession": "新会话",
+ "showMoreSessions": "显示更多会话"
+ },
+ "common": {
+ "loading": "加载中..."
+ }
+}
diff --git a/src/main.jsx b/src/main.jsx
index f80d47a7e..24790a065 100644
--- a/src/main.jsx
+++ b/src/main.jsx
@@ -3,6 +3,7 @@ import ReactDOM from 'react-dom/client'
import App from './App.jsx'
import './index.css'
import 'katex/dist/katex.min.css'
+import { I18nProvider } from './i18n'
// Clean up stale service workers on app load to prevent caching issues after builds
if ('serviceWorker' in navigator) {
@@ -17,6 +18,8 @@ if ('serviceWorker' in navigator) {
ReactDOM.createRoot(document.getElementById('root')).render(
-
+
+
+
,
)
From fae9ba5a166e3317b332ec54fc3ceeab0d87405a Mon Sep 17 00:00:00 2001
From: tata
Date: Mon, 17 Nov 2025 16:22:37 +0800
Subject: [PATCH 2/7] Add Chinese README and improve documentation formatting
Add Chinese translation of README (README.zh-CN.md) with link in main README. Clean up formatting throughout main README: remove extra blank lines, fix list numbering, standardize italic formatting for optional features, and improve code block consistency.
---
README.md | 69 ++++++----
README.zh-CN.md | 334 ++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 376 insertions(+), 27 deletions(-)
create mode 100644 README.zh-CN.md
diff --git a/README.md b/README.md
index 1f81fd0d3..3eed1bf99 100644
--- a/README.md
+++ b/README.md
@@ -3,6 +3,7 @@
Claude Code UI
+[中文文档 README.zh-CN](./README.zh-CN.md)
A desktop and mobile UI for [Claude Code](https://docs.anthropic.com/en/docs/claude-code), and [Cursor CLI](https://docs.cursor.com/en/cli/overview). You can use it locally or remotely to view your active projects and sessions in Claude Code or Cursor and make changes to them from everywhere (mobile or desktop). This gives you a proper interface that works everywhere. Supports models including **Claude Sonnet 4**, **Opus 4.1**, and **GPT-5**
@@ -35,22 +36,19 @@ A desktop and mobile UI for [Claude Code](https://docs.anthropic.com/en/docs/cla
-
-
## Features
-- **Responsive Design** - Works seamlessly across desktop, tablet, and mobile so you can also use Claude Code from mobile
+- **Responsive Design** - Works seamlessly across desktop, tablet, and mobile so you can also use Claude Code from mobile
- **Interactive Chat Interface** - Built-in chat interface for seamless communication with Claude Code or Cursor
- **Integrated Shell Terminal** - Direct access to Claude Code or Cursor CLI through built-in shell functionality
- **File Explorer** - Interactive file tree with syntax highlighting and live editing
-- **Git Explorer** - View, stage and commit your changes. You can also switch branches
+- **Git Explorer** - View, stage and commit your changes. You can also switch branches
- **Session Management** - Resume conversations, manage multiple sessions, and track history
-- **TaskMaster AI Integration** *(Optional)* - Advanced project management with AI-powered task planning, PRD parsing, and workflow automation
+- **TaskMaster AI Integration** _(Optional)_ - Advanced project management with AI-powered task planning, PRD parsing, and workflow automation
- **Model Compatibility** - Works with Claude Sonnet 4, Opus 4.1, and GPT-5
-
## Quick Start
### Prerequisites
@@ -70,6 +68,7 @@ npx @siteboon/claude-code-ui
The server will start and be accessible at `http://localhost:3001` (or your configured PORT).
**To restart**: Simply run the same `npx` command again after stopping the server
+
### Global Installation (For Regular Use)
For frequent use, install globally once:
@@ -84,7 +83,6 @@ Then start with a simple command:
claude-code-ui
```
-
**To restart**: Stop with Ctrl+C and run `claude-code-ui` again.
### CLI Commands
@@ -107,13 +105,14 @@ cloudcli version
```
**The `cloudcli status` command shows you:**
+
- Installation directory location
- Database location (where credentials are stored)
- Current configuration (PORT, DATABASE_PATH, etc.)
- Claude projects folder location
- Configuration file location
-```
+````
### Run as Background Service (Recommended for Production)
@@ -123,7 +122,7 @@ For production use, run Claude Code UI as a background service using PM2 (Proces
```bash
npm install -g pm2
-```
+````
#### Start as Background Service
@@ -135,7 +134,6 @@ pm2 start claude-code-ui --name "claude-code-ui"
pm2 start cloudcli --name "claude-code-ui"
```
-
#### Auto-Start on System Boot
To make Claude Code UI start automatically when your system boots:
@@ -148,32 +146,36 @@ pm2 startup
pm2 save
```
-
### Local Development Installation
1. **Clone the repository:**
+
```bash
git clone https://github.com/siteboon/claudecodeui.git
cd claudecodeui
```
2. **Install dependencies:**
+
```bash
npm install
```
3. **Configure environment:**
+
```bash
cp .env.example .env
# Edit .env with your preferred settings
```
4. **Start the application:**
+
```bash
# Development mode (with hot reload)
npm run dev
```
+
The application will start at the port you specified in your .env
5. **Open your browser:**
@@ -188,50 +190,54 @@ The application will start at the port you specified in your .env
To use Claude Code's full functionality, you'll need to manually enable tools:
1. **Open Tools Settings** - Click the gear icon in the sidebar
-3. **Enable Selectively** - Turn on only the tools you need
-4. **Apply Settings** - Your preferences are saved locally
+2. **Enable Selectively** - Turn on only the tools you need
+3. **Apply Settings** - Your preferences are saved locally

-*Tools Settings interface - enable only what you need*
+_Tools Settings interface - enable only what you need_
**Recommended approach**: Start with basic tools enabled and add more as needed. You can always adjust these settings later.
-## TaskMaster AI Integration *(Optional)*
+## TaskMaster AI Integration _(Optional)_
Claude Code UI supports **[TaskMaster AI](https://github.com/eyaltoledano/claude-task-master)** (aka claude-task-master) integration for advanced project management and AI-powered task planning.
It provides
+
- AI-powered task generation from PRDs (Product Requirements Documents)
-- Smart task breakdown and dependency management
+- Smart task breakdown and dependency management
- Visual task boards and progress tracking
**Setup & Documentation**: Visit the [TaskMaster AI GitHub repository](https://github.com/eyaltoledano/claude-task-master) for installation instructions, configuration guides, and usage examples.
After installing it you should be able to enable it from the Settings
-
## Usage Guide
### Core Features
#### Project Management
+
The UI automatically discovers Claude Code projects from `~/.claude/projects/` and provides:
+
- **Visual Project Browser** - All available projects with metadata and session counts
- **Project Actions** - Rename, delete, and organize projects
- **Smart Navigation** - Quick access to recent projects and sessions
-- **MCP support** - Add your own MCP servers through the UI
+- **MCP support** - Add your own MCP servers through the UI
#### Chat Interface
-- **Use responsive chat or Claude Code/Cursor CLI** - You can either use the adapted chat interface or use the shell button to connect to your selected CLI.
+
+- **Use responsive chat or Claude Code/Cursor CLI** - You can either use the adapted chat interface or use the shell button to connect to your selected CLI.
- **Real-time Communication** - Stream responses from Claude with WebSocket connection
- **Session Management** - Resume previous conversations or start fresh sessions
- **Message History** - Complete conversation history with timestamps and metadata
- **Multi-format Support** - Text, code blocks, and file references
#### File Explorer & Editor
+
- **Interactive File Tree** - Browse project structure with expand/collapse navigation
- **Live File Editing** - Read, modify, and save files directly in the interface
- **Syntax Highlighting** - Support for multiple programming languages
@@ -239,19 +245,21 @@ The UI automatically discovers Claude Code projects from `~/.claude/projects/` a
#### Git Explorer
+#### TaskMaster AI Integration _(Optional)_
-#### TaskMaster AI Integration *(Optional)*
- **Visual Task Board** - Kanban-style interface for managing development tasks
- **PRD Parser** - Create Product Requirements Documents and parse them into structured tasks
- **Progress Tracking** - Real-time status updates and completion tracking
#### Session Management
+
- **Session Persistence** - All conversations automatically saved
- **Session Organization** - Group sessions by project and timestamp
- **Session Actions** - Rename, delete, and export conversation history
- **Cross-device Sync** - Access sessions from any device
### Mobile App
+
- **Responsive Design** - Optimized for all screen sizes
- **Touch-friendly Interface** - Swipe gestures and touch navigation
- **Mobile Navigation** - Bottom tab bar for easy thumb navigation
@@ -270,6 +278,7 @@ The UI automatically discovers Claude Code projects from `~/.claude/projects/` a
```
### Backend (Node.js + Express)
+
- **Express Server** - RESTful API with static file serving
- **WebSocket Server** - Communication for chats and project refresh
- **CLI Integration (Claude Code / Cursor)** - Process spawning and management
@@ -277,24 +286,23 @@ The UI automatically discovers Claude Code projects from `~/.claude/projects/` a
- **File System API** - Exposing file browser for projects
### Frontend (React + Vite)
+
- **React 18** - Modern component architecture with hooks
- **CodeMirror** - Advanced code editor with syntax highlighting
-
-
-
-
### Contributing
We welcome contributions! Please follow these guidelines:
#### Getting Started
+
1. **Fork** the repository
2. **Clone** your fork: `git clone
`
3. **Install** dependencies: `npm install`
4. **Create** a feature branch: `git checkout -b feature/amazing-feature`
#### Development Process
+
1. **Make your changes** following the existing code style
2. **Test thoroughly** - ensure all features work correctly
3. **Run quality checks**: `npm run lint && npm run format`
@@ -306,6 +314,7 @@ We welcome contributions! Please follow these guidelines:
- Test results if applicable
#### What to Contribute
+
- **Bug fixes** - Help us improve stability
- **New features** - Enhance functionality (discuss in issues first)
- **Documentation** - Improve guides and API docs
@@ -316,23 +325,25 @@ We welcome contributions! Please follow these guidelines:
### Common Issues & Solutions
-
#### "No Claude projects found"
+
**Problem**: The UI shows no projects or empty project list
**Solutions**:
+
- Ensure [Claude CLI](https://docs.anthropic.com/en/docs/claude-code) is properly installed
- Run `claude` command in at least one project directory to initialize
- Verify `~/.claude/projects/` directory exists and has proper permissions
#### File Explorer Issues
+
**Problem**: Files not loading, permission errors, empty directories
**Solutions**:
+
- Check project directory permissions (`ls -la` in terminal)
- Verify the project path exists and is accessible
- Review server console logs for detailed error messages
- Ensure you're not trying to access system directories outside project scope
-
## License
GNU General Public License v3.0 - see [LICENSE](LICENSE) file for details.
@@ -342,22 +353,26 @@ This project is open source and free to use, modify, and distribute under the GP
## Acknowledgments
### Built With
+
- **[Claude Code](https://docs.anthropic.com/en/docs/claude-code)** - Anthropic's official CLI
- **[React](https://react.dev/)** - User interface library
- **[Vite](https://vitejs.dev/)** - Fast build tool and dev server
- **[Tailwind CSS](https://tailwindcss.com/)** - Utility-first CSS framework
- **[CodeMirror](https://codemirror.net/)** - Advanced code editor
-- **[TaskMaster AI](https://github.com/eyaltoledano/claude-task-master)** *(Optional)* - AI-powered project management and task planning
+- **[TaskMaster AI](https://github.com/eyaltoledano/claude-task-master)** _(Optional)_ - AI-powered project management and task planning
## Support & Community
### Stay Updated
+
- **Star** this repository to show support
- **Watch** for updates and new releases
- **Follow** the project for announcements
### Sponsors
+
- [Siteboon - AI powered website builder](https://siteboon.ai)
+
---
diff --git a/README.zh-CN.md b/README.zh-CN.md
new file mode 100644
index 000000000..49c28070d
--- /dev/null
+++ b/README.zh-CN.md
@@ -0,0 +1,334 @@
+
+
+
Claude Code UI
+
+
+一个用于 [Claude Code](https://docs.anthropic.com/en/docs/claude-code) 和 [Cursor CLI](https://docs.cursor.com/en/cli/overview) 的桌面与移动端 Web 界面。你可以在本地或远程使用它,方便地查看 Claude Code / Cursor 中的活动项目与会话,并在任意设备(手机或电脑)上进行修改。支持 **Claude Sonnet 4**、**Opus 4.1**、**GPT-5** 等多种模型。
+
+---
+
+## 目录
+
+- [功能特性](#功能特性)
+- [快速开始](#快速开始)
+- [安全与工具配置](#安全与工具配置)
+- [TaskMaster AI 集成(可选)](#taskmaster-ai-集成可选)
+- [使用指南](#使用指南)
+- [移动端体验](#移动端体验)
+- [架构概览](#架构概览)
+- [贡献指南](#贡献指南)
+- [故障排查](#故障排查)
+- [许可证](#许可证)
+- [致谢](#致谢)
+- [社区与支持](#社区与支持)
+
+---
+
+## 功能特性
+
+- **自适应布局**:在桌面、平板和手机上都能正常工作,便于你在移动端使用 Claude Code
+- **交互式聊天界面**:内置聊天界面,可与 Claude Code 或 Cursor 无缝交互
+- **集成 Shell 终端**:通过内置终端直接访问 Claude Code / Cursor CLI
+- **文件浏览器**:交互式文件树,支持语法高亮和在线编辑
+- **Git 浏览器**:查看、暂存并提交修改,可切换分支
+- **会话管理**:恢复对话、管理多会话并查看历史记录
+- **TaskMaster AI 集成(可选)**:提供 AI 驱动的项目管理、PRD 解析与任务规划
+- **模型兼容性**:支持 Claude Sonnet 4、Opus 4.1 和 GPT-5 等模型
+
+---
+
+## 快速开始
+
+### 环境要求
+
+- [Node.js](https://nodejs.org/) v20 或更高版本
+- 已安装并配置好的 [Claude Code CLI](https://docs.anthropic.com/en/docs/claude-code) 和/或
+- 已安装并配置好的 [Cursor CLI](https://docs.cursor.com/en/cli/overview)
+
+### 一键运行(推荐)
+
+无需安装,直接运行:
+
+```bash
+npx @siteboon/claude-code-ui
+```
+
+服务启动后默认可通过 `http://localhost:3001`(或你配置的 PORT)访问。
+
+**重启方式**:停止服务后再次运行相同的 `npx` 命令即可。
+
+### 全局安装(适合经常使用)
+
+```bash
+npm install -g @siteboon/claude-code-ui
+```
+
+安装完成后:
+
+```bash
+claude-code-ui
+```
+
+**重启方式**:使用 `Ctrl+C` 停止服务,然后再次运行 `claude-code-ui`。
+
+### CLI 命令
+
+全局安装后,你可以使用 `claude-code-ui` 和 `cloudcli` 两组命令:
+
+```bash
+# 启动服务(默认命令)
+claude-code-ui
+cloudcli start
+
+# 查看配置与数据路径
+cloudcli status
+
+# 查看帮助
+cloudcli help
+
+# 查看版本
+cloudcli version
+```
+
+`cloudcli status` 会显示:
+
+- 安装目录位置
+- 数据库存储位置(凭据保存位置)
+- 当前配置(PORT、DATABASE_PATH 等)
+- Claude 项目目录位置
+- 配置文件路径
+
+### 作为后台服务运行(生产环境推荐)
+
+使用 PM2 将 Claude Code UI 以后台服务方式运行:
+
+```bash
+npm install -g pm2
+```
+
+启动服务:
+
+```bash
+pm2 start claude-code-ui --name "claude-code-ui"
+# 或使用别名
+pm2 start cloudcli --name "claude-code-ui"
+```
+
+开机自启:
+
+```bash
+pm2 startup
+pm2 save
+```
+
+### 本地开发
+
+1. 克隆仓库:
+
+```bash
+git clone https://github.com/siteboon/claudecodeui.git
+cd claudecodeui
+```
+
+2. 安装依赖:
+
+```bash
+npm install
+```
+
+3. 配置环境变量:
+
+```bash
+cp .env.example .env
+# 根据需要编辑 .env
+```
+
+4. 启动开发服务器:
+
+```bash
+npm run dev
+```
+
+应用将运行在 `.env` 中设置的端口上(默认 `3001`)。
+
+5. 浏览器访问:
+
+- 开发环境:`http://localhost:3001`
+
+---
+
+## 安全与工具配置
+
+> **重要提示**:所有 Claude Code 工具默认处于禁用状态,以避免潜在危险操作被自动执行。
+
+启用工具步骤:
+
+1. 打开工具设置(侧边栏齿轮图标)
+2. 根据需要选择性启用工具
+3. 应用设置(偏好会保存在本地)
+
+建议:先只启用基础工具,后续按需逐步开启更多能力。
+
+---
+
+## TaskMaster AI 集成(可选)
+
+Claude Code UI 支持与 **[TaskMaster AI](https://github.com/eyaltoledano/claude-task-master)** 集成,用于更高级的项目管理与任务规划:
+
+- 从 PRD 自动生成任务
+- 智能拆解任务与依赖管理
+- 看板视图与进度追踪
+
+安装与配置方法请参考其 GitHub 仓库文档,安装完成后可在本项目设置中启用。
+
+---
+
+## 使用指南
+
+### 项目管理
+
+UI 会自动从 `~/.claude/projects/` 读取 Claude Code 项目,并提供:
+
+- 可视化项目浏览
+- 项目重命名、删除与整理
+- 最近项目与会话快捷访问
+- MCP 服务器管理(在 UI 中添加你自己的 MCP)
+
+### 聊天界面
+
+- 可在自适应聊天界面与 Claude Code / Cursor CLI 间切换
+- WebSocket 实时流式响应
+- 支持会话恢复与多会话管理
+- 完整消息历史与元数据
+- 支持文本、代码块与文件引用
+
+### 文件浏览与编辑
+
+- 交互式文件树
+- 在线编辑并保存
+- 多语言语法高亮
+- 基本文件操作(新建、重命名、删除)
+
+### Git 浏览
+
+- 查看变更
+- 暂存与提交
+- 分支切换等(具体能力取决于配置)
+
+### 会话管理
+
+- 自动保存所有会话
+- 按项目与时间组织
+- 重命名、删除、导出会话
+- 跨设备访问(通过统一服务端)
+
+---
+
+## 移动端体验
+
+- 自适应移动端布局
+- 触控优化(滑动、点击区域)
+- 底部导航栏,便于单手操作
+- 可添加到主屏幕,以 PWA 方式运行
+
+---
+
+## 架构概览
+
+整体架构示意:
+
+```text
+┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
+│ 前端 │ │ 后端 │ │ Claude CLI │
+│ (React/Vite) │◄──►│ (Express/WS) │◄──►│ 集成 │
+└─────────────────┘ └─────────────────┘ └─────────────────┘
+```
+
+### 后端(Node.js + Express)
+
+- Express 服务与 REST API
+- WebSocket 服务(聊天与项目刷新)
+- 与 Claude Code / Cursor 的 CLI 进程管理
+- 会话持久化(JSONL 等)
+- 文件系统 API(提供项目文件浏览与编辑)
+
+### 前端(React + Vite)
+
+- 使用 React 18 组件化架构
+- 基于 CodeMirror 的代码编辑器
+- Tailwind CSS 构建界面样式
+
+---
+
+## 贡献指南
+
+非常欢迎社区贡献!简单流程:
+
+1. Fork 仓库
+2. 克隆到本地:`git clone
`
+3. 安装依赖:`npm install`
+4. 新建分支:`git checkout -b feature/my-feature`
+5. 按现有代码风格进行开发
+6. 运行校验:`npm run lint && npm run format`
+7. 提交并推送:`git push origin feature/my-feature`
+8. 提交 Pull Request,附上变更说明、必要截图与测试结果
+
+---
+
+## 故障排查
+
+### “No Claude projects found”
+
+可能原因:没有检测到 Claude 项目。
+
+排查建议:
+
+- 确认已正确安装 [Claude CLI](https://docs.anthropic.com/en/docs/claude-code)
+- 在至少一个项目目录中运行一次 `claude` 命令
+- 检查 `~/.claude/projects/` 目录是否存在且有权限
+
+### 文件浏览器问题
+
+如果出现文件无法加载、权限错误或目录为空:
+
+- 检查项目目录权限(终端运行 `ls -la`)
+- 确认项目路径存在且可访问
+- 查看服务器日志以获取详细错误信息
+- 确保没有尝试访问项目目录外的系统路径
+
+---
+
+## 许可证
+
+本项目使用 **GNU General Public License v3.0**。
+
+详情请参见仓库中的 [LICENSE](LICENSE) 文件。
+
+你可以在 GPL v3 协议下自由使用、修改和分发本项目。
+
+---
+
+## 致谢
+
+本项目基于以下关键技术构建:
+
+- **[Claude Code](https://docs.anthropic.com/en/docs/claude-code)**
+- **[React](https://react.dev/)**
+- **[Vite](https://vitejs.dev/)**
+- **[Tailwind CSS](https://tailwindcss.com/)**
+- **[CodeMirror](https://codemirror.net/)**
+- **[TaskMaster AI](https://github.com/eyaltoledano/claude-task-master)**(可选)
+
+---
+
+## 社区与支持
+
+- 欢迎 Star 本仓库支持项目
+- 关注更新与新版本发布
+- 如有问题或建议,可通过 Issue 反馈
+
+---
+
+
+ 为 Claude Code 社区用心打造。
+
From 68e4b0fd1a4da929f6f5db178d3dd63e80a76a49 Mon Sep 17 00:00:00 2001
From: tata
Date: Tue, 18 Nov 2025 08:36:51 +0800
Subject: [PATCH 3/7] feat: Add i18n translations to ChatInterface,
CommandMenu, ProjectCreationWizard, QuickSettingsPanel and Settings
components
---
src/components/ChatInterface.jsx | 8 +-
src/components/CommandMenu.jsx | 18 +-
src/components/ProjectCreationWizard.jsx | 71 +--
src/components/QuickSettingsPanel.jsx | 44 +-
src/components/Settings.jsx | 528 ++++++++++++-----------
src/components/Sidebar.jsx | 26 +-
src/i18n/en.json | 236 +++++++++-
src/i18n/index.jsx | 20 +-
src/i18n/zh.json | 236 +++++++++-
9 files changed, 850 insertions(+), 337 deletions(-)
diff --git a/src/components/ChatInterface.jsx b/src/components/ChatInterface.jsx
index a16e8698d..ce6edb5db 100644
--- a/src/components/ChatInterface.jsx
+++ b/src/components/ChatInterface.jsx
@@ -27,6 +27,7 @@ import ClaudeLogo from './ClaudeLogo.jsx';
import CursorLogo from './CursorLogo.jsx';
import NextTaskBanner from './NextTaskBanner.jsx';
import { useTasksSettings } from '../contexts/TasksSettingsContext';
+import { useTranslation } from '../i18n';
import ClaudeStatus from './ClaudeStatus';
import TokenUsagePie from './TokenUsagePie';
@@ -1528,7 +1529,7 @@ const MessageComponent = memo(({ message, index, prevMessage, createDiff, onFile
{showThinking && message.reasoning && (
- 💭 Thinking...
+ 💭 {t('chat.thinking')}
@@ -1642,6 +1643,7 @@ const ImageAttachment = ({ file, onRemove, uploadProgress, error }) => {
// This ensures uninterrupted chat experience by pausing sidebar refreshes during conversations.
function ChatInterface({ selectedProject, selectedSession, ws, sendMessage, messages, onFileOpen, onInputFocusChange, onSessionActive, onSessionInactive, onSessionProcessing, onSessionNotProcessing, processingSessions, onReplaceTemporarySession, onNavigateToSession, onShowSettings, autoExpandTools, showRawParameters, showThinking, autoScrollToBottom, sendByCtrlEnter, externalMessageUpdate, onTaskClick, onShowAllTasks }) {
const { tasksEnabled } = useTasksSettings();
+ const { t } = useTranslation();
const [input, setInput] = useState(() => {
if (typeof window !== 'undefined' && selectedProject) {
return safeLocalStorage.getItem(`draft_input_${selectedProject.name}`) || '';
@@ -4419,7 +4421,7 @@ function ChatInterface({ selectedProject, selectedSession, ws, sendMessage, mess
●
●
●
-
Thinking...
+
{t('chat.thinking')}
@@ -4699,7 +4701,7 @@ function ChatInterface({ selectedProject, selectedSession, ws, sendMessage, mess
type="button"
onClick={open}
className="absolute left-2 top-1/2 transform -translate-y-1/2 p-2 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-lg transition-colors"
- title="Attach images"
+ title={t('chat.attachImages')}
>
diff --git a/src/components/CommandMenu.jsx b/src/components/CommandMenu.jsx
index 4420aed56..c86f6367f 100644
--- a/src/components/CommandMenu.jsx
+++ b/src/components/CommandMenu.jsx
@@ -1,4 +1,5 @@
import React, { useEffect, useRef } from 'react';
+import { useTranslation } from '../i18n';
/**
* CommandMenu - Autocomplete dropdown for slash commands
@@ -12,6 +13,7 @@ import React, { useEffect, useRef } from 'react';
* @param {Array} frequentCommands - Array of frequently used command objects
*/
const CommandMenu = ({ commands = [], selectedIndex = -1, onSelect, onClose, position = { top: 0, left: 0 }, isOpen = false, frequentCommands = [] }) => {
+ const { t } = useTranslation();
const menuRef = useRef(null);
const selectedItemRef = useRef(null);
@@ -103,7 +105,7 @@ const CommandMenu = ({ commands = [], selectedIndex = -1, onSelect, onClose, pos
textAlign: 'center'
}}
>
- No commands available
+ {t('commands.noCommands', { defaultValue: 'No commands available' })}
);
}
@@ -133,11 +135,11 @@ const CommandMenu = ({ commands = [], selectedIndex = -1, onSelect, onClose, pos
const orderedNamespaces = namespaceOrder.filter(ns => groupedCommands[ns]);
const namespaceLabels = {
- frequent: '⭐ Frequently Used',
- builtin: 'Built-in Commands',
- project: 'Project Commands',
- user: 'User Commands',
- other: 'Other Commands'
+ frequent: `⭐ ${t('commands.frequentlyUsed', { defaultValue: 'Frequently Used' })}`,
+ builtin: t('commands.builtinCommands', { defaultValue: 'Built-in Commands' }),
+ project: t('commands.projectCommands', { defaultValue: 'Project Commands' }),
+ user: t('commands.userCommands', { defaultValue: 'User Commands' }),
+ other: t('commands.otherCommands', { defaultValue: 'Other Commands' })
};
// Calculate global index for each command
@@ -277,7 +279,9 @@ const CommandMenu = ({ commands = [], selectedIndex = -1, onSelect, onClose, pos
textOverflow: 'ellipsis'
}}
>
- {command.description}
+ {command.namespace === 'builtin' && command.name.startsWith('/')
+ ? t(`commands.${command.name}`, { defaultValue: command.description })
+ : command.description}
)}
diff --git a/src/components/ProjectCreationWizard.jsx b/src/components/ProjectCreationWizard.jsx
index 38564dc08..6b253749f 100644
--- a/src/components/ProjectCreationWizard.jsx
+++ b/src/components/ProjectCreationWizard.jsx
@@ -3,8 +3,11 @@ import { X, FolderPlus, GitBranch, Key, ChevronRight, ChevronLeft, Check, Loader
import { Button } from './ui/button';
import { Input } from './ui/input';
import { api } from '../utils/api';
+import { useTranslation } from '../i18n';
const ProjectCreationWizard = ({ onClose, onProjectCreated }) => {
+ const { t } = useTranslation();
+
// Wizard state
const [step, setStep] = useState(1); // 1: Choose type, 2: Configure, 3: Confirm
const [workspaceType, setWorkspaceType] = useState(null); // 'existing' or 'new'
@@ -88,13 +91,13 @@ const ProjectCreationWizard = ({ onClose, onProjectCreated }) => {
if (step === 1) {
if (!workspaceType) {
- setError('Please select whether you have an existing workspace or want to create a new one');
+ setError(t('projectWizard.selectTypeError'));
return;
}
setStep(2);
} else if (step === 2) {
if (!workspacePath.trim()) {
- setError('Please provide a workspace path');
+ setError(t('projectWizard.pathRequiredError'));
return;
}
@@ -165,7 +168,7 @@ const ProjectCreationWizard = ({ onClose, onProjectCreated }) => {
- Create New Project
+ {t('projectWizard.title')}
{
{s < step ? : s}
- {s === 1 ? 'Type' : s === 2 ? 'Configure' : 'Confirm'}
+ {s === 1 ? t('projectWizard.steps.type') : s === 2 ? t('projectWizard.steps.configure') : t('projectWizard.steps.confirm')}
{s < 3 && (
@@ -227,7 +230,7 @@ const ProjectCreationWizard = ({ onClose, onProjectCreated }) => {
- Do you already have a workspace, or would you like to create a new one?
+ {t('projectWizard.chooseType')}
{/* Existing Workspace */}
@@ -245,10 +248,10 @@ const ProjectCreationWizard = ({ onClose, onProjectCreated }) => {
- Existing Workspace
+ {t('projectWizard.existingWorkspace')}
- I already have a workspace on my server and just need to add it to the project list
+ {t('projectWizard.existingWorkspaceDesc')}
@@ -269,10 +272,10 @@ const ProjectCreationWizard = ({ onClose, onProjectCreated }) => {
- New Workspace
+ {t('projectWizard.newWorkspace')}
- Create a new workspace, optionally clone from a GitHub repository
+ {t('projectWizard.newWorkspaceDesc')}
@@ -288,14 +291,14 @@ const ProjectCreationWizard = ({ onClose, onProjectCreated }) => {
{/* Workspace Path */}
- {workspaceType === 'existing' ? 'Workspace Path' : 'Where should the workspace be created?'}
+ {t('projectWizard.workspacePath')}
setWorkspacePath(e.target.value)}
- placeholder={workspaceType === 'existing' ? '/path/to/existing/workspace' : '/path/to/new/workspace'}
+ placeholder={t('projectWizard.workspacePathPlaceholder')}
className="w-full"
/>
{showPathDropdown && pathSuggestions.length > 0 && (
@@ -325,13 +328,13 @@ const ProjectCreationWizard = ({ onClose, onProjectCreated }) => {
<>
{tokenMode === 'stored' ? (
- Select Token
+ {t('projectWizard.selectToken')}
{
) : tokenMode === 'new' ? (
- GitHub Token
+ {t('projectWizard.githubToken')}
setNewGithubToken(e.target.value)}
- placeholder="ghp_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
+ placeholder={t('projectWizard.newTokenPlaceholder')}
className="w-full"
/>
@@ -472,17 +475,17 @@ const ProjectCreationWizard = ({ onClose, onProjectCreated }) => {
- Review Your Configuration
+ {t('projectWizard.confirmDetails')}
- Workspace Type:
+ {t('projectWizard.workspaceType')}
- {workspaceType === 'existing' ? 'Existing Workspace' : 'New Workspace'}
+ {workspaceType === 'existing' ? t('projectWizard.existingWorkspace') : t('projectWizard.newWorkspace')}
-
Path:
+
{t('projectWizard.path')}
{workspacePath}
@@ -490,19 +493,19 @@ const ProjectCreationWizard = ({ onClose, onProjectCreated }) => {
{workspaceType === 'new' && githubUrl && (
<>
- Clone From:
+ {t('projectWizard.repository')}
{githubUrl}
- Authentication:
+ {t('projectWizard.token')}
{tokenMode === 'stored' && selectedGithubToken
- ? `Using stored token: ${availableTokens.find(t => t.id.toString() === selectedGithubToken)?.credential_name || 'Unknown'}`
+ ? `${t('projectWizard.stored')}: ${availableTokens.find(t => t.id.toString() === selectedGithubToken)?.credential_name || 'Unknown'}`
: tokenMode === 'new' && newGithubToken
- ? 'Using provided token'
- : 'No authentication'}
+ ? t('projectWizard.new')
+ : t('projectWizard.none')}
>
@@ -531,11 +534,11 @@ const ProjectCreationWizard = ({ onClose, onProjectCreated }) => {
disabled={isCreating}
>
{step === 1 ? (
- 'Cancel'
+ t('common.cancel')
) : (
<>
- Back
+ {t('projectWizard.back')}
>
)}
@@ -546,17 +549,17 @@ const ProjectCreationWizard = ({ onClose, onProjectCreated }) => {
>
{isCreating ? (
<>
-
- Creating...
+
+ {t('projectWizard.creating')}
>
) : step === 3 ? (
<>
- Create Project
+ {t('projectWizard.createProject')}
>
) : (
<>
- Next
+ {t('projectWizard.next')}
>
)}
diff --git a/src/components/QuickSettingsPanel.jsx b/src/components/QuickSettingsPanel.jsx
index 17dce0582..2daa58425 100644
--- a/src/components/QuickSettingsPanel.jsx
+++ b/src/components/QuickSettingsPanel.jsx
@@ -12,10 +12,12 @@ import {
Brain,
Sparkles,
FileText,
- Languages
+ Languages,
+ Globe
} from 'lucide-react';
import DarkModeToggle from './DarkModeToggle';
import { useTheme } from '../contexts/ThemeContext';
+import { useTranslation } from '../i18n';
const QuickSettingsPanel = ({
isOpen,
@@ -37,6 +39,7 @@ const QuickSettingsPanel = ({
return localStorage.getItem('whisperMode') || 'default';
});
const { isDarkMode } = useTheme();
+ const { t, i18n } = useTranslation();
useEffect(() => {
setLocalIsOpen(isOpen);
@@ -80,7 +83,7 @@ const QuickSettingsPanel = ({
- Quick Settings
+ {t('quickSettings.title')}
@@ -88,25 +91,40 @@ const QuickSettingsPanel = ({
{/* Appearance Settings */}
-
Appearance
+
{t('quickSettings.appearance')}
{isDarkMode ? : }
- Dark Mode
+ {t('quickSettings.darkMode')}
+
+
+
+
+ {t('quickSettings.language')}
+
+ i18n.changeLanguage(e.target.value)}
+ className="px-2 py-1 text-sm rounded border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 text-gray-900 dark:text-white focus:ring-2 focus:ring-blue-500 dark:focus:ring-blue-400 focus:border-transparent"
+ >
+ English
+ 中文
+
+
{/* Tool Display Settings */}
-
Tool Display
+
{t('quickSettings.toolDisplay')}
- Auto-expand tools
+ {t('quickSettings.autoExpandTools')}
- Show raw parameters
+ {t('quickSettings.showRawParameters')}
- Show thinking
+ {t('quickSettings.showThinking')}
{/* View Options */}
diff --git a/src/components/Settings.jsx b/src/components/Settings.jsx
index 0bae64ecb..96a3d514f 100644
--- a/src/components/Settings.jsx
+++ b/src/components/Settings.jsx
@@ -2,9 +2,10 @@ import { useState, useEffect } from 'react';
import { Button } from './ui/button';
import { Input } from './ui/input';
import { Badge } from './ui/badge';
-import { X, Plus, Settings as SettingsIcon, Shield, AlertTriangle, Moon, Sun, Server, Edit3, Trash2, Globe, Terminal, Zap, FolderOpen, LogIn, Key } from 'lucide-react';
+import { X, Plus, Settings as SettingsIcon, Shield, AlertTriangle, Moon, Sun, Server, Edit3, Trash2, Globe, Terminal, Zap, FolderOpen, LogIn, Key, Languages } from 'lucide-react';
import { useTheme } from '../contexts/ThemeContext';
import { useTasksSettings } from '../contexts/TasksSettingsContext';
+import { useTranslation } from '../i18n';
import ClaudeLogo from './ClaudeLogo';
import CursorLogo from './CursorLogo';
import CredentialsSettings from './CredentialsSettings';
@@ -12,6 +13,7 @@ import LoginModal from './LoginModal';
function Settings({ isOpen, onClose, projects = [], initialTab = 'tools' }) {
const { isDarkMode, toggleDarkMode } = useTheme();
+ const { t, i18n } = useTranslation();
const {
tasksEnabled,
setTasksEnabled,
@@ -600,7 +602,7 @@ function Settings({ isOpen, onClose, projects = [], initialTab = 'tools' }) {
};
const handleMcpDelete = async (serverId, scope) => {
- if (confirm('Are you sure you want to delete this MCP server?')) {
+ if (confirm(t('common.deleteConfirm'))) {
try {
await deleteMcpServer(serverId, scope);
setSaveStatus('success');
@@ -677,7 +679,7 @@ function Settings({ isOpen, onClose, projects = [], initialTab = 'tools' }) {
- Settings
+ {t('settings.title')}
- Tools
+ {t('settings.tabs.tools')}
setActiveTab('appearance')}
@@ -712,17 +714,17 @@ function Settings({ isOpen, onClose, projects = [], initialTab = 'tools' }) {
: 'border-transparent text-muted-foreground hover:text-foreground'
}`}
>
- Appearance
+ {t('settings.tabs.appearance')}
setActiveTab('tasks')}
className={`px-4 py-3 text-sm font-medium border-b-2 transition-colors ${
- activeTab === 'tasks'
+ activeTab === 'mcpServers'
? 'border-blue-600 text-blue-600 dark:text-blue-400'
: 'border-transparent text-muted-foreground hover:text-foreground'
}`}
>
- Tasks
+ {t('settings.tabs.mcpServers')}
setActiveTab('api')}
@@ -733,7 +735,7 @@ function Settings({ isOpen, onClose, projects = [], initialTab = 'tools' }) {
}`}
>
- API & Tokens
+ {t('settings.tabs.api')}
@@ -743,222 +745,242 @@ function Settings({ isOpen, onClose, projects = [], initialTab = 'tools' }) {
{/* Appearance Tab */}
{activeTab === 'appearance' && (
- {activeTab === 'appearance' && (
-
- {/* Theme Settings */}
-
-
-
-
-
- Dark Mode
-
-
- Toggle between light and dark themes
-
-
-
- Toggle dark mode
-
- {isDarkMode ? (
-
- ) : (
-
- )}
-
-
-
-
-
+ {/* Theme Settings */}
+
+
+
+
+
+ {t('settings.appearance.darkMode')}
+
+
+ {t('settings.appearance.darkModeDesc')}
+
+
+
+ Toggle dark mode
+
+ {isDarkMode ? (
+
+ ) : (
+
+ )}
+
+
+
+
+
- {/* Project Sorting */}
-
-
-
-
-
- Project Sorting
-
-
- How projects are ordered in the sidebar
-
-
-
setProjectSortOrder(e.target.value)}
- className="text-sm bg-gray-50 dark:bg-gray-800 border border-gray-300 dark:border-gray-600 text-gray-900 dark:text-gray-100 rounded-lg focus:ring-blue-500 focus:border-blue-500 p-2 w-32"
- >
- Alphabetical
- Recent Activity
-
-
-
-
+ {/* Language Settings */}
+
+
+
+
+
+
+ {t('settings.appearance.language')}
+
+
+ {t('settings.appearance.languageDesc')}
+
+
+
i18n.changeLanguage(e.target.value)}
+ className="text-sm bg-gray-50 dark:bg-gray-800 border border-gray-300 dark:border-gray-600 text-gray-900 dark:text-gray-100 rounded-lg focus:ring-blue-500 focus:border-blue-500 p-2 w-32"
+ >
+ English
+ 中文
+
+
+
+
- {/* Code Editor Settings */}
-
-
Code Editor
+ {/* Project Sorting */}
+
+
+
+
+
+ {t('settings.appearance.projectSorting')}
+
+
+ {t('settings.appearance.projectSortingDesc')}
+
+
+
setProjectSortOrder(e.target.value)}
+ className="text-sm bg-gray-50 dark:bg-gray-800 border border-gray-300 dark:border-gray-600 text-gray-900 dark:text-gray-100 rounded-lg focus:ring-blue-500 focus:border-blue-500 p-2 w-32"
+ >
+ {t('settings.appearance.alphabetical')}
+ {t('settings.appearance.recentActivity')}
+
+
+
+
- {/* Editor Theme */}
-
-
-
-
- Editor Theme
-
-
- Default theme for the code editor
-
-
-
setCodeEditorTheme(codeEditorTheme === 'dark' ? 'light' : 'dark')}
- className="relative inline-flex h-8 w-14 items-center rounded-full bg-gray-200 dark:bg-gray-700 transition-colors duration-200 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 dark:focus:ring-offset-gray-900"
- role="switch"
- aria-checked={codeEditorTheme === 'dark'}
- aria-label="Toggle editor theme"
- >
- Toggle editor theme
-
- {codeEditorTheme === 'dark' ? (
-
- ) : (
-
- )}
-
-
-
-
+ {/* Code Editor Settings */}
+
+
{t('settings.codeEditor.title')}
- {/* Word Wrap */}
-
-
-
-
- Word Wrap
-
-
- Enable word wrapping by default in the editor
-
-
-
setCodeEditorWordWrap(!codeEditorWordWrap)}
- className="relative inline-flex h-8 w-14 items-center rounded-full bg-gray-200 dark:bg-gray-700 transition-colors duration-200 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 dark:focus:ring-offset-gray-900"
- role="switch"
- aria-checked={codeEditorWordWrap}
- aria-label="Toggle word wrap"
- >
- Toggle word wrap
-
-
-
-
+ {/* Editor Theme */}
+
+
+
+
+ {t('settings.codeEditor.theme')}
+
+
+ {t('settings.codeEditor.themeDesc')}
+
+
+
setCodeEditorTheme(codeEditorTheme === 'dark' ? 'light' : 'dark')}
+ className="relative inline-flex h-8 w-14 items-center rounded-full bg-gray-200 dark:bg-gray-700 transition-colors duration-200 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 dark:focus:ring-offset-gray-900"
+ role="switch"
+ aria-checked={codeEditorTheme === 'dark'}
+ aria-label="Toggle editor theme"
+ >
+ Toggle editor theme
+
+ {codeEditorTheme === 'dark' ? (
+
+ ) : (
+
+ )}
+
+
+
+
- {/* Show Minimap */}
-
-
-
-
- Show Minimap
-
-
- Display a minimap for easier navigation in diff view
-
-
-
setCodeEditorShowMinimap(!codeEditorShowMinimap)}
- className="relative inline-flex h-8 w-14 items-center rounded-full bg-gray-200 dark:bg-gray-700 transition-colors duration-200 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 dark:focus:ring-offset-gray-900"
- role="switch"
- aria-checked={codeEditorShowMinimap}
- aria-label="Toggle minimap"
- >
- Toggle minimap
-
-
-
-
+ {/* Word Wrap */}
+
+
+
+
+ {t('settings.codeEditor.wordWrap')}
+
+
+ {t('settings.codeEditor.wordWrapDesc')}
+
+
+
setCodeEditorWordWrap(!codeEditorWordWrap)}
+ className="relative inline-flex h-8 w-14 items-center rounded-full bg-gray-200 dark:bg-gray-700 transition-colors duration-200 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 dark:focus:ring-offset-gray-900"
+ role="switch"
+ aria-checked={codeEditorWordWrap}
+ aria-label="Toggle word wrap"
+ >
+ Toggle word wrap
+
+
+
+
- {/* Show Line Numbers */}
-
-
-
-
- Show Line Numbers
-
-
- Display line numbers in the editor
-
-
-
setCodeEditorLineNumbers(!codeEditorLineNumbers)}
- className="relative inline-flex h-8 w-14 items-center rounded-full bg-gray-200 dark:bg-gray-700 transition-colors duration-200 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 dark:focus:ring-offset-gray-900"
- role="switch"
- aria-checked={codeEditorLineNumbers}
- aria-label="Toggle line numbers"
- >
- Toggle line numbers
-
-
-
-
+ {/* Show Minimap */}
+
+
+
+
+ {t('settings.codeEditor.showMinimap')}
+
+
+ {t('settings.codeEditor.showMinimapDesc')}
+
+
+
setCodeEditorShowMinimap(!codeEditorShowMinimap)}
+ className="relative inline-flex h-8 w-14 items-center rounded-full bg-gray-200 dark:bg-gray-700 transition-colors duration-200 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 dark:focus:ring-offset-gray-900"
+ role="switch"
+ aria-checked={codeEditorShowMinimap}
+ aria-label="Toggle minimap"
+ >
+ Toggle minimap
+
+
+
+
- {/* Font Size */}
-
-
-
-
- Font Size
-
-
- Editor font size in pixels
-
-
-
setCodeEditorFontSize(e.target.value)}
- className="text-sm bg-gray-50 dark:bg-gray-800 border border-gray-300 dark:border-gray-600 text-gray-900 dark:text-gray-100 rounded-lg focus:ring-blue-500 focus:border-blue-500 p-2 w-24"
- >
- 10px
- 11px
- 12px
- 13px
- 14px
- 15px
- 16px
- 18px
- 20px
-
-
-
-
-
-)}
+ {/* Show Line Numbers */}
+
+
+
+
+ {t('settings.codeEditor.showLineNumbers')}
+
+
+ {t('settings.codeEditor.showLineNumbersDesc')}
+
+
+
setCodeEditorLineNumbers(!codeEditorLineNumbers)}
+ className="relative inline-flex h-8 w-14 items-center rounded-full bg-gray-200 dark:bg-gray-700 transition-colors duration-200 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 dark:focus:ring-offset-gray-900"
+ role="switch"
+ aria-checked={codeEditorLineNumbers}
+ aria-label="Toggle line numbers"
+ >
+ Toggle line numbers
+
+
+
+
+ {/* Font Size */}
+
+
+
+
+ {t('settings.codeEditor.fontSize')}
+
+
+ {t('settings.codeEditor.fontSizeDesc')}
+
+
+
setCodeEditorFontSize(e.target.value)}
+ className="text-sm bg-gray-50 dark:bg-gray-800 border border-gray-300 dark:border-gray-600 text-gray-900 dark:text-gray-100 rounded-lg focus:ring-blue-500 focus:border-blue-500 p-2 w-24"
+ >
+ 10px
+ 11px
+ 12px
+ 13px
+ 14px
+ 15px
+ 16px
+ 18px
+ 20px
+
+
+
+
)}
@@ -1007,7 +1029,7 @@ function Settings({ isOpen, onClose, projects = [], initialTab = 'tools' }) {
- Permission Settings
+ {t('settings.tools.permissionSettings')}
@@ -1020,10 +1042,10 @@ function Settings({ isOpen, onClose, projects = [], initialTab = 'tools' }) {
/>
- Skip permission prompts (use with caution)
+ {t('settings.tools.skipPermissions')}
- Equivalent to --dangerously-skip-permissions flag
+ {t('settings.tools.skipPermissionsDesc')}
@@ -1035,7 +1057,7 @@ function Settings({ isOpen, onClose, projects = [], initialTab = 'tools' }) {
- Authentication
+ {t('settings.tools.authentication')}
@@ -1054,7 +1076,7 @@ function Settings({ isOpen, onClose, projects = [], initialTab = 'tools' }) {
size="sm"
>
- Login
+ {t('settings.tools.login')}
@@ -1065,11 +1087,11 @@ function Settings({ isOpen, onClose, projects = [], initialTab = 'tools' }) {
- Allowed Tools
+ {t('settings.tools.allowedTools')}
- Tools that are automatically allowed without prompting for permission
+ {t('settings.tools.allowedToolsDesc')}
@@ -1092,14 +1114,14 @@ function Settings({ isOpen, onClose, projects = [], initialTab = 'tools' }) {
className="h-10 px-4 touch-manipulation"
>
-
Add Tool
+
{t('common.addTool')}
{/* Common tools quick add */}
- Quick add common tools:
+ {t('common.quickAddCommon')}
{commonTools.map(tool => (
@@ -1135,7 +1157,7 @@ function Settings({ isOpen, onClose, projects = [], initialTab = 'tools' }) {
))}
{allowedTools.length === 0 && (
- No allowed tools configured
+ {t('settings.tools.noAllowedTools')}
)}
@@ -1146,11 +1168,11 @@ function Settings({ isOpen, onClose, projects = [], initialTab = 'tools' }) {
- Disallowed Tools
+ {t('settings.tools.disallowedTools')}
- Tools that are automatically blocked without prompting for permission
+ {t('settings.tools.disallowedToolsDesc')}
@@ -1173,7 +1195,7 @@ function Settings({ isOpen, onClose, projects = [], initialTab = 'tools' }) {
className="h-10 px-4 touch-manipulation"
>
-
Add Tool
+
{t('common.addTool')}
@@ -1195,7 +1217,7 @@ function Settings({ isOpen, onClose, projects = [], initialTab = 'tools' }) {
))}
{disallowedTools.length === 0 && (
- No disallowed tools configured
+ {t('settings.tools.noDisallowedTools')}
)}
@@ -1741,14 +1763,14 @@ function Settings({ isOpen, onClose, projects = [], initialTab = 'tools' }) {
- Cancel
+ {t('common.cancel')}
- {mcpLoading ? 'Saving...' : (editingMcpServer ? 'Update Server' : 'Add Server')}
+ {mcpLoading ? t('common.saving') : (editingMcpServer ? t('settings.mcpServers.updateServer') : t('settings.mcpServers.addServer'))}
@@ -1767,7 +1789,7 @@ function Settings({ isOpen, onClose, projects = [], initialTab = 'tools' }) {
- Cursor Permission Settings
+ {t('cursor.permissionSettings')}
@@ -1780,10 +1802,10 @@ function Settings({ isOpen, onClose, projects = [], initialTab = 'tools' }) {
/>
- Skip permission prompts (use with caution)
+ {t('cursor.skipPermissions')}
- Equivalent to -f flag in Cursor CLI
+ {t('cursor.skipPermissionsDesc')}
@@ -1795,17 +1817,17 @@ function Settings({ isOpen, onClose, projects = [], initialTab = 'tools' }) {
- Authentication
+ {t('cursor.authentication')}
- Cursor CLI Login
+ {t('cursor.cursorLogin')}
- Sign in to your Cursor account to enable AI features
+ {t('cursor.cursorLoginDesc')}
- Login
+ {t('cursor.login')}
@@ -1825,18 +1847,18 @@ function Settings({ isOpen, onClose, projects = [], initialTab = 'tools' }) {
- Allowed Shell Commands
+ {t('cursor.allowedCommands')}
- Shell commands that are automatically allowed without prompting for permission
+ {t('cursor.allowedCommandsDesc')}
setNewCursorCommand(e.target.value)}
- placeholder='e.g., "Shell(ls)" or "Shell(git status)"'
+ placeholder={t('cursor.commandPlaceholder')}
onKeyPress={(e) => {
if (e.key === 'Enter') {
if (newCursorCommand && !cursorAllowedCommands.includes(newCursorCommand)) {
@@ -1860,14 +1882,14 @@ function Settings({ isOpen, onClose, projects = [], initialTab = 'tools' }) {
className="h-10 px-4 touch-manipulation"
>
-
Add Command
+
{t('common.addCommand')}
{/* Common commands quick add */}
- Quick add common commands:
+ {t('common.quickAddCommonCommands')}
{commonCursorCommands.map(cmd => (
@@ -1907,7 +1929,7 @@ function Settings({ isOpen, onClose, projects = [], initialTab = 'tools' }) {
))}
{cursorAllowedCommands.length === 0 && (
- No allowed shell commands configured
+ {t('cursor.noAllowedCommands')}
)}
@@ -1918,18 +1940,18 @@ function Settings({ isOpen, onClose, projects = [], initialTab = 'tools' }) {
- Disallowed Shell Commands
+ {t('cursor.disallowedCommands')}
- Shell commands that should always be denied
+ {t('cursor.disallowedCommandsDesc')}
setNewCursorDisallowedCommand(e.target.value)}
- placeholder='e.g., "Shell(rm -rf)" or "Shell(sudo)"'
+ placeholder={t('cursor.disallowedCommandPlaceholder')}
onKeyPress={(e) => {
if (e.key === 'Enter') {
if (newCursorDisallowedCommand && !cursorDisallowedCommands.includes(newCursorDisallowedCommand)) {
@@ -1953,7 +1975,7 @@ function Settings({ isOpen, onClose, projects = [], initialTab = 'tools' }) {
className="h-10 px-4 touch-manipulation"
>
-
Add Command
+
{t('common.addCommand')}
@@ -1975,7 +1997,7 @@ function Settings({ isOpen, onClose, projects = [], initialTab = 'tools' }) {
))}
{cursorDisallowedCommands.length === 0 && (
- No disallowed shell commands configured
+ {t('cursor.noDisallowedCommands')}
)}
@@ -2187,7 +2209,7 @@ function Settings({ isOpen, onClose, projects = [], initialTab = 'tools' }) {
disabled={isSaving}
className="flex-1 sm:flex-none h-10 touch-manipulation"
>
- Cancel
+ {t('common.cancel')}
- Saving...
+ {t('common.saving')}
) : (
- 'Save Settings'
+ t('settings.save')
)}
diff --git a/src/components/Sidebar.jsx b/src/components/Sidebar.jsx
index 843369203..328112ba3 100644
--- a/src/components/Sidebar.jsx
+++ b/src/components/Sidebar.jsx
@@ -543,7 +543,7 @@ function Sidebar({
Claude Code UI
-
Projects
+
{t('sidebar.title')}
) : (
@@ -553,7 +553,7 @@ function Sidebar({
Claude Code UI
-
Projects
+
{t('sidebar.title')}
)}
@@ -590,7 +590,7 @@ function Sidebar({
setSearchFilter(e.target.value)}
className="pl-9 h-9 text-sm bg-muted/50 border-0 focus:bg-background focus:ring-1 focus:ring-primary/20"
@@ -613,10 +613,10 @@ function Sidebar({
size="sm"
className="flex-1 h-8 text-xs bg-primary hover:bg-primary/90 transition-all duration-200"
onClick={() => setShowNewProject(true)}
- title="Create new project (Ctrl+N)"
+ title={t('sidebar.createNewProject')}
>
- New Project
+ {t('sidebar.newProject')}
- Loading projects...
+ {t('common.loading')}
- Fetching your Claude projects and sessions
+ {t('sidebar.noProjectsDesc')}
) : projects.length === 0 ? (
@@ -658,9 +658,9 @@ function Sidebar({
-
No projects found
+
{t('sidebar.noProjects')}
- Run Claude CLI in a project directory to get started
+ {t('sidebar.noProjectsDesc')}
) : filteredProjects.length === 0 ? (
@@ -668,9 +668,9 @@ function Sidebar({
-
No matching projects
+
{t('sidebar.noSearchResults')}
- Try adjusting your search term
+ {t('sidebar.noSearchResultsDesc')}
) : (
@@ -1356,7 +1356,7 @@ function Sidebar({
- Settings
+ {t('sidebar.settings')}
@@ -1367,7 +1367,7 @@ function Sidebar({
onClick={onShowSettings}
>
- Settings
+ {t('sidebar.settings')}
diff --git a/src/i18n/en.json b/src/i18n/en.json
index 85f3d47cd..a5963dc04 100644
--- a/src/i18n/en.json
+++ b/src/i18n/en.json
@@ -10,10 +10,242 @@
"tasks": "TaskMaster"
},
"sidebar": {
+ "title": "Projects",
"newSession": "New Session",
- "showMoreSessions": "Show more sessions"
+ "showMoreSessions": "Show more sessions",
+ "settings": "Settings",
+ "noProjects": "No projects yet",
+ "noProjectsDesc": "Run Claude CLI in a project directory to get started",
+ "noSearchResults": "No projects found",
+ "noSearchResultsDesc": "Try adjusting your search terms",
+ "searchPlaceholder": "Search projects...",
+ "newProject": "New Project",
+ "starred": "Starred",
+ "refresh": "Refresh",
+ "sessions": "sessions",
+ "session": "session",
+ "createNewProject": "Create new project (Ctrl+N)"
+ },
+ "settings": {
+ "title": "Settings",
+ "tabs": {
+ "tools": "Tools",
+ "appearance": "Appearance",
+ "tasks": "Tasks",
+ "mcpServers": "MCP Servers",
+ "api": "API & Tokens"
+ },
+ "appearance": {
+ "darkMode": "Dark Mode",
+ "darkModeDesc": "Toggle between light and dark themes",
+ "language": "Language",
+ "languageDesc": "Select your preferred language",
+ "projectSorting": "Project Sorting",
+ "projectSortingDesc": "How projects are ordered in the sidebar",
+ "alphabetical": "Alphabetical",
+ "recentActivity": "Recent Activity"
+ },
+ "codeEditor": {
+ "title": "Code Editor",
+ "theme": "Editor Theme",
+ "themeDesc": "Default theme for the code editor",
+ "wordWrap": "Word Wrap",
+ "wordWrapDesc": "Enable word wrapping by default in the editor",
+ "showMinimap": "Show Minimap",
+ "showMinimapDesc": "Display a minimap for easier navigation in diff view",
+ "showLineNumbers": "Show Line Numbers",
+ "showLineNumbersDesc": "Display line numbers in the editor",
+ "fontSize": "Font Size",
+ "fontSizeDesc": "Editor font size in pixels"
+ },
+ "tools": {
+ "permissionSettings": "Permission Settings",
+ "skipPermissions": "Skip permission prompts (use with caution)",
+ "skipPermissionsDesc": "Equivalent to --dangerously-skip-permissions flag",
+ "authentication": "Authentication",
+ "login": "Login",
+ "allowedTools": "Allowed Tools",
+ "allowedToolsDesc": "Tools that are automatically allowed without prompting for permission",
+ "disallowedTools": "Disallowed Tools",
+ "disallowedToolsDesc": "Tools that are always blocked",
+ "noAllowedTools": "No allowed tools configured",
+ "noDisallowedTools": "No disallowed tools configured"
+ },
+ "cursor": {
+ "permissionSettings": "Cursor Permission Settings",
+ "skipPermissions": "Skip permission prompts (use with caution)",
+ "skipPermissionsDesc": "Equivalent to -f flag in Cursor CLI",
+ "authentication": "Authentication",
+ "login": "Login",
+ "cursorLogin": "Cursor CLI Login",
+ "cursorLoginDesc": "Sign in to your Cursor account to enable AI features",
+ "allowedCommands": "Allowed Shell Commands",
+ "allowedCommandsDesc": "Shell commands that are automatically allowed without prompting for permission",
+ "disallowedCommands": "Disallowed Shell Commands",
+ "disallowedCommandsDesc": "Shell commands that are always blocked",
+ "noAllowedCommands": "No allowed shell commands configured",
+ "noDisallowedCommands": "No disallowed shell commands configured",
+ "commandPlaceholder": "e.g., \"Shell(ls)\" or \"Shell(git status)\"",
+ "disallowedCommandPlaceholder": "e.g., \"Shell(rm -rf)\" or \"Shell(sudo)\""
+ },
+ "auth": {
+ "notLoggedIn": "Not logged in",
+ "loggedInAs": "Logged in as",
+ "loginDesc": "Authenticate with your provider to access projects"
+ },
+ "mcpServers": {
+ "title": "MCP Servers",
+ "description": "Model Context Protocol servers provide additional tools and context",
+ "add": "Add MCP Server",
+ "addServer": "Add Server",
+ "updateServer": "Update Server",
+ "edit": "Edit",
+ "delete": "Delete",
+ "test": "Test Connection",
+ "discoverTools": "Discover Tools",
+ "name": "Name",
+ "type": "Type",
+ "scope": "Scope",
+ "command": "Command",
+ "args": "Arguments",
+ "user": "User",
+ "local": "Local",
+ "status": "Status",
+ "connected": "Connected",
+ "disconnected": "Disconnected",
+ "noServers": "No MCP servers configured"
+ },
+ "tasks": {
+ "title": "TaskMaster",
+ "enable": "Enable TaskMaster",
+ "enableDesc": "Enable AI-powered task management and tracking",
+ "notInstalled": "TaskMaster is not installed",
+ "installing": "Installing...",
+ "install": "Install TaskMaster"
+ },
+ "api": {
+ "title": "API Keys & Tokens",
+ "description": "Manage your API keys and authentication tokens",
+ "createKey": "Create API Key",
+ "keyName": "Key Name",
+ "apiKey": "API Key",
+ "created": "Created",
+ "copyKey": "Copy Key",
+ "copied": "Copied!",
+ "deleteKey": "Delete Key"
+ },
+ "save": "Save Settings",
+ "saving": "Saving...",
+ "saved": "Settings saved successfully!",
+ "error": "Failed to save settings"
+ },
+ "quickSettings": {
+ "title": "Quick Settings",
+ "appearance": "Appearance",
+ "darkMode": "Dark Mode",
+ "language": "Language",
+ "toolDisplay": "Tool Display",
+ "autoExpandTools": "Auto-expand tools",
+ "showRawParameters": "Show raw parameters",
+ "showThinking": "Show thinking",
+ "viewOptions": "View Options",
+ "autoScrollToBottom": "Auto-scroll to bottom",
+ "inputSettings": "Input Settings",
+ "sendByCtrlEnter": "Send by Ctrl+Enter",
+ "sendByCtrlEnterDesc": "When enabled, pressing Ctrl+Enter will send the message instead of just Enter. This is useful for IME users to avoid accidental sends."
+ },
+ "chat": {
+ "thinking": "Thinking...",
+ "attachImages": "Attach images",
+ "sendMessage": "Send message",
+ "typeMessage": "Type a message...",
+ "stopGenerating": "Stop generating"
+ },
+ "commands": {
+ "/help": "Show help documentation for Claude Code",
+ "/clear": "Clear the conversation history",
+ "/model": "Switch or view the current AI model",
+ "/cost": "Display token usage and cost information",
+ "/memory": "Open CLAUDE.md memory file for editing",
+ "/config": "Open settings and configuration",
+ "/status": "Show system status and version information",
+ "/rewind": "Rewind the conversation to a previous state",
+ "frequentlyUsed": "Frequently Used",
+ "builtinCommands": "Built-in Commands",
+ "projectCommands": "Project Commands",
+ "userCommands": "User Commands",
+ "otherCommands": "Other Commands",
+ "noCommands": "No commands available"
+ },
+ "projectWizard": {
+ "title": "Create New Project",
+ "steps": {
+ "type": "Type",
+ "configure": "Configure",
+ "confirm": "Confirm"
+ },
+ "chooseType": "Do you already have a workspace, or would you like to create a new one?",
+ "existingWorkspace": "Existing Workspace",
+ "existingWorkspaceDesc": "I already have a workspace on my server and just need to add it to the project list",
+ "newWorkspace": "New Workspace",
+ "newWorkspaceDesc": "Create a new workspace, optionally clone from a GitHub repository",
+ "workspacePath": "Workspace Path",
+ "workspacePathPlaceholder": "Enter workspace path (e.g., ~/projects/my-app)",
+ "githubUrl": "GitHub Repository URL (Optional)",
+ "githubUrlPlaceholder": "https://github.com/username/repo",
+ "githubToken": "GitHub Token",
+ "useStoredToken": "Use stored token",
+ "enterNewToken": "Enter new token",
+ "noToken": "No token (public repos only)",
+ "selectToken": "Select a token",
+ "newTokenPlaceholder": "ghp_...",
+ "confirmDetails": "Please confirm the project details:",
+ "workspaceType": "Workspace Type:",
+ "path": "Path:",
+ "repository": "Repository:",
+ "token": "Token:",
+ "none": "None",
+ "stored": "Stored",
+ "new": "New",
+ "back": "Back",
+ "next": "Next",
+ "createProject": "Create Project",
+ "creating": "Creating...",
+ "selectTypeError": "Please select whether you have an existing workspace or want to create a new one",
+ "pathRequiredError": "Please provide a workspace path"
+ },
+ "mainContent": {
+ "selectProject": "Select a project to start chatting",
+ "noSessions": "No sessions yet",
+ "newSession": "Start a new session",
+ "deleteSession": "Delete session",
+ "renameSession": "Rename session",
+ "loadMore": "Load more"
},
"common": {
- "loading": "Loading..."
+ "loading": "Loading...",
+ "error": "Error",
+ "success": "Success",
+ "cancel": "Cancel",
+ "confirm": "Confirm",
+ "delete": "Delete",
+ "edit": "Edit",
+ "save": "Save",
+ "add": "Add",
+ "remove": "Remove",
+ "search": "Search",
+ "close": "Close",
+ "ok": "OK",
+ "yes": "Yes",
+ "no": "No",
+ "copy": "Copy",
+ "paste": "Paste",
+ "refresh": "Refresh",
+ "addTool": "Add Tool",
+ "addCommand": "Add Command",
+ "saving": "Saving...",
+ "quickAddCommon": "Quick add common tools:",
+ "quickAddCommonCommands": "Quick add common commands:",
+ "deleteConfirm": "Are you sure you want to delete this MCP server?"
}
}
diff --git a/src/i18n/index.jsx b/src/i18n/index.jsx
index a266e9c6b..6ce00c0d5 100644
--- a/src/i18n/index.jsx
+++ b/src/i18n/index.jsx
@@ -31,22 +31,22 @@ const resolveKeyPath = (key, language) => {
};
export function I18nProvider({ children, defaultLanguage = 'en' }) {
- const [language, setLanguage] = useState(defaultLanguage);
-
- useEffect(() => {
+ // Initialize language from localStorage first, to avoid overwriting saved preference
+ const [language, setLanguage] = useState(() => {
const savedLanguage = localStorage.getItem('claudecodeui-language');
+ console.log('[i18n] Initializing with saved language:', savedLanguage);
if (savedLanguage && resources[savedLanguage]) {
- setLanguage(savedLanguage);
- return;
+ return savedLanguage;
}
-
+
const browserLanguage = navigator.language?.toLowerCase().startsWith('zh') ? 'zh' : 'en';
- if (resources[browserLanguage]) {
- setLanguage(browserLanguage);
- }
- }, []);
+ console.log('[i18n] No saved language, using browser language:', browserLanguage);
+ return resources[browserLanguage] ? browserLanguage : defaultLanguage;
+ });
+ // Save language to localStorage whenever it changes
useEffect(() => {
+ console.log('[i18n] Saving language to localStorage:', language);
localStorage.setItem('claudecodeui-language', language);
}, [language]);
diff --git a/src/i18n/zh.json b/src/i18n/zh.json
index d3649455f..d6f0abdee 100644
--- a/src/i18n/zh.json
+++ b/src/i18n/zh.json
@@ -10,10 +10,242 @@
"tasks": "任务管理"
},
"sidebar": {
+ "title": "项目",
"newSession": "新会话",
- "showMoreSessions": "显示更多会话"
+ "showMoreSessions": "显示更多会话",
+ "settings": "设置",
+ "noProjects": "暂无项目",
+ "noProjectsDesc": "在项目目录中运行 Claude CLI 以开始使用",
+ "noSearchResults": "未找到项目",
+ "noSearchResultsDesc": "尝试调整搜索条件",
+ "searchPlaceholder": "搜索项目...",
+ "newProject": "新建项目",
+ "starred": "已收藏",
+ "refresh": "刷新",
+ "sessions": "会话",
+ "session": "会话",
+ "createNewProject": "创建新项目 (Ctrl+N)"
+ },
+ "settings": {
+ "title": "设置",
+ "tabs": {
+ "tools": "工具",
+ "appearance": "外观",
+ "tasks": "任务",
+ "mcpServers": "MCP 服务器",
+ "api": "API 与令牌"
+ },
+ "appearance": {
+ "darkMode": "深色模式",
+ "darkModeDesc": "在浅色和深色主题之间切换",
+ "language": "语言",
+ "languageDesc": "选择您偏好的语言",
+ "projectSorting": "项目排序",
+ "projectSortingDesc": "侧边栏中项目的排序方式",
+ "alphabetical": "按字母顺序",
+ "recentActivity": "按最近活动"
+ },
+ "codeEditor": {
+ "title": "代码编辑器",
+ "theme": "编辑器主题",
+ "themeDesc": "代码编辑器的默认主题",
+ "wordWrap": "自动换行",
+ "wordWrapDesc": "在编辑器中默认启用自动换行",
+ "showMinimap": "显示缩略图",
+ "showMinimapDesc": "在差异视图中显示缩略图以便于导航",
+ "showLineNumbers": "显示行号",
+ "showLineNumbersDesc": "在编辑器中显示行号",
+ "fontSize": "字体大小",
+ "fontSizeDesc": "编辑器字体大小(像素)"
+ },
+ "tools": {
+ "permissionSettings": "权限设置",
+ "skipPermissions": "跳过权限提示(谨慎使用)",
+ "skipPermissionsDesc": "等同于 --dangerously-skip-permissions 标志",
+ "authentication": "身份验证",
+ "login": "登录",
+ "allowedTools": "允许的工具",
+ "allowedToolsDesc": "无需提示即可自动允许的工具",
+ "disallowedTools": "禁止的工具",
+ "disallowedToolsDesc": "始终被阻止的工具",
+ "noAllowedTools": "未配置允许的工具",
+ "noDisallowedTools": "未配置禁止的工具"
+ },
+ "cursor": {
+ "permissionSettings": "Cursor 权限设置",
+ "skipPermissions": "跳过权限提示(谨慎使用)",
+ "skipPermissionsDesc": "等同于 Cursor CLI 中的 -f 标志",
+ "authentication": "身份验证",
+ "login": "登录",
+ "cursorLogin": "Cursor CLI 登录",
+ "cursorLoginDesc": "登录您的 Cursor 账户以启用 AI 功能",
+ "allowedCommands": "允许的 Shell 命令",
+ "allowedCommandsDesc": "无需提示即可自动允许的 Shell 命令",
+ "disallowedCommands": "禁止的 Shell 命令",
+ "disallowedCommandsDesc": "始终被阻止的 Shell 命令",
+ "noAllowedCommands": "未配置允许的 Shell 命令",
+ "noDisallowedCommands": "未配置禁止的 Shell 命令",
+ "commandPlaceholder": "例如:\"Shell(ls)\" 或 \"Shell(git status)\"",
+ "disallowedCommandPlaceholder": "例如:\"Shell(rm -rf)\" 或 \"Shell(sudo)\""
+ },
+ "auth": {
+ "notLoggedIn": "未登录",
+ "loggedInAs": "已登录为",
+ "loginDesc": "使用您的提供商进行身份验证以访问项目"
+ },
+ "mcpServers": {
+ "title": "MCP 服务器",
+ "description": "模型上下文协议服务器提供额外的工具和上下文",
+ "add": "添加 MCP 服务器",
+ "addServer": "添加服务器",
+ "updateServer": "更新服务器",
+ "edit": "编辑",
+ "delete": "删除",
+ "test": "测试连接",
+ "discoverTools": "发现工具",
+ "name": "名称",
+ "type": "类型",
+ "scope": "范围",
+ "command": "命令",
+ "args": "参数",
+ "user": "用户",
+ "local": "本地",
+ "status": "状态",
+ "connected": "已连接",
+ "disconnected": "未连接",
+ "noServers": "未配置 MCP 服务器"
+ },
+ "tasks": {
+ "title": "任务管理器",
+ "enable": "启用任务管理器",
+ "enableDesc": "启用 AI 驱动的任务管理和跟踪",
+ "notInstalled": "未安装任务管理器",
+ "installing": "安装中...",
+ "install": "安装任务管理器"
+ },
+ "api": {
+ "title": "API 密钥与令牌",
+ "description": "管理您的 API 密钥和身份验证令牌",
+ "createKey": "创建 API 密钥",
+ "keyName": "密钥名称",
+ "apiKey": "API 密钥",
+ "created": "创建时间",
+ "copyKey": "复制密钥",
+ "copied": "已复制!",
+ "deleteKey": "删除密钥"
+ },
+ "save": "保存设置",
+ "saving": "保存中...",
+ "saved": "设置保存成功!",
+ "error": "保存设置失败"
+ },
+ "quickSettings": {
+ "title": "快速设置",
+ "appearance": "外观",
+ "darkMode": "深色模式",
+ "language": "语言",
+ "toolDisplay": "工具显示",
+ "autoExpandTools": "自动展开工具",
+ "showRawParameters": "显示原始参数",
+ "showThinking": "显示思考过程",
+ "viewOptions": "查看选项",
+ "autoScrollToBottom": "自动滚动到底部",
+ "inputSettings": "输入设置",
+ "sendByCtrlEnter": "通过 Ctrl+Enter 发送",
+ "sendByCtrlEnterDesc": "启用后,按 Ctrl+Enter 发送消息,而不是仅按 Enter。这对输入法用户很有用,可以避免意外发送。"
+ },
+ "chat": {
+ "thinking": "思考中...",
+ "attachImages": "附加图片",
+ "sendMessage": "发送消息",
+ "typeMessage": "输入消息...",
+ "stopGenerating": "停止生成"
+ },
+ "commands": {
+ "/help": "显示 Claude Code 帮助文档",
+ "/clear": "清除对话历史",
+ "/model": "切换或查看当前 AI 模型",
+ "/cost": "显示令牌使用和成本信息",
+ "/memory": "打开 CLAUDE.md 记忆文件进行编辑",
+ "/config": "打开设置和配置",
+ "/status": "显示系统状态和版本信息",
+ "/rewind": "将对话回退到之前的状态",
+ "frequentlyUsed": "常用命令",
+ "builtinCommands": "内置命令",
+ "projectCommands": "项目命令",
+ "userCommands": "用户命令",
+ "otherCommands": "其他命令",
+ "noCommands": "无可用命令"
+ },
+ "projectWizard": {
+ "title": "创建新项目",
+ "steps": {
+ "type": "类型",
+ "configure": "配置",
+ "confirm": "确认"
+ },
+ "chooseType": "您已经有工作区,还是想创建一个新的?",
+ "existingWorkspace": "现有工作区",
+ "existingWorkspaceDesc": "我已经在服务器上有一个工作区,只需将其添加到项目列表中",
+ "newWorkspace": "新建工作区",
+ "newWorkspaceDesc": "创建新工作区,可选择从 GitHub 仓库克隆",
+ "workspacePath": "工作区路径",
+ "workspacePathPlaceholder": "输入工作区路径(例如:~/projects/my-app)",
+ "githubUrl": "GitHub 仓库 URL(可选)",
+ "githubUrlPlaceholder": "https://github.com/username/repo",
+ "githubToken": "GitHub 令牌",
+ "useStoredToken": "使用已保存的令牌",
+ "enterNewToken": "输入新令牌",
+ "noToken": "无令牌(仅限公共仓库)",
+ "selectToken": "选择令牌",
+ "newTokenPlaceholder": "ghp_...",
+ "confirmDetails": "请确认项目详情:",
+ "workspaceType": "工作区类型:",
+ "path": "路径:",
+ "repository": "仓库:",
+ "token": "令牌:",
+ "none": "无",
+ "stored": "已保存",
+ "new": "新建",
+ "back": "返回",
+ "next": "下一步",
+ "createProject": "创建项目",
+ "creating": "创建中...",
+ "selectTypeError": "请选择您是否有现有工作区或想创建新工作区",
+ "pathRequiredError": "请提供工作区路径"
+ },
+ "mainContent": {
+ "selectProject": "选择一个项目开始聊天",
+ "noSessions": "暂无会话",
+ "newSession": "开始新会话",
+ "deleteSession": "删除会话",
+ "renameSession": "重命名会话",
+ "loadMore": "加载更多"
},
"common": {
- "loading": "加载中..."
+ "loading": "加载中...",
+ "error": "错误",
+ "success": "成功",
+ "cancel": "取消",
+ "confirm": "确认",
+ "delete": "删除",
+ "edit": "编辑",
+ "save": "保存",
+ "add": "添加",
+ "remove": "移除",
+ "search": "搜索",
+ "close": "关闭",
+ "ok": "确定",
+ "yes": "是",
+ "no": "否",
+ "copy": "复制",
+ "paste": "粘贴",
+ "refresh": "刷新",
+ "addTool": "添加工具",
+ "addCommand": "添加命令",
+ "saving": "保存中...",
+ "quickAddCommon": "快速添加常用工具:",
+ "quickAddCommonCommands": "快速添加常用命令:",
+ "deleteConfirm": "确定要删除此 MCP 服务器吗?"
}
}
From 7398f6cb97ec270d9fb7d32cc6e05e5429913393 Mon Sep 17 00:00:00 2001
From: tata
Date: Tue, 18 Nov 2025 09:25:57 +0800
Subject: [PATCH 4/7] feat: Add i18n translations to GitSettings, LoginForm,
MainContent, NextTaskBanner and ChatInterface components
---
src/components/ChatInterface.jsx | 12 +-
src/components/GitSettings.jsx | 22 ++--
src/components/LoginForm.jsx | 20 +--
src/components/MainContent.jsx | 6 +-
src/components/NextTaskBanner.jsx | 102 ++++++++--------
src/components/Onboarding.jsx | 86 ++++++-------
src/components/Settings.jsx | 35 +++---
src/components/SetupForm.jsx | 28 +++--
src/components/Shell.jsx | 6 +-
src/i18n/en.json | 197 ++++++++++++++++++++++++++++--
src/i18n/zh.json | 197 ++++++++++++++++++++++++++++--
11 files changed, 543 insertions(+), 168 deletions(-)
diff --git a/src/components/ChatInterface.jsx b/src/components/ChatInterface.jsx
index 09b484b20..4742846be 100644
--- a/src/components/ChatInterface.jsx
+++ b/src/components/ChatInterface.jsx
@@ -4195,16 +4195,16 @@ function ChatInterface({ selectedProject, selectedSession, ws, sendMessage, mess
-
Loading session messages...
+
{t('chatInterface.loadingMessages')}
) : chatMessages.length === 0 ? (
{!selectedSession && !currentSessionId && (
-
Choose Your AI Assistant
+
{t('chatInterface.chooseAssistant')}
- Select a provider to start a new conversation
+ {t('chatInterface.selectProvider')}
@@ -4296,10 +4296,10 @@ function ChatInterface({ selectedProject, selectedSession, ws, sendMessage, mess
{provider === 'claude'
- ? 'Ready to use Claude AI. Start typing your message below.'
+ ? t('chatInterface.readyClaude')
: provider === 'cursor'
- ? `Ready to use Cursor with ${cursorModel}. Start typing your message below.`
- : 'Select a provider above to begin'
+ ? t('chatInterface.readyCursor', { model: cursorModel })
+ : t('chatInterface.selectProviderPrompt')
}
diff --git a/src/components/GitSettings.jsx b/src/components/GitSettings.jsx
index 91b0902a6..7b0242cda 100644
--- a/src/components/GitSettings.jsx
+++ b/src/components/GitSettings.jsx
@@ -3,8 +3,10 @@ import { Button } from './ui/button';
import { Input } from './ui/input';
import { GitBranch, Check } from 'lucide-react';
import { authenticatedFetch } from '../utils/api';
+import { useTranslation } from '../i18n';
function GitSettings() {
+ const { t } = useTranslation();
const [gitName, setGitName] = useState('');
const [gitEmail, setGitEmail] = useState('');
const [gitConfigLoading, setGitConfigLoading] = useState(false);
@@ -61,47 +63,47 @@ function GitSettings() {
-
Git Configuration
+ {t('gitSettings.title')}
- Configure your git identity for commits. These settings will be applied globally via git config --global
+ {t('gitSettings.description')} git config --global
@@ -110,13 +112,13 @@ function GitSettings() {
onClick={saveGitConfig}
disabled={gitConfigSaving || !gitName || !gitEmail}
>
- {gitConfigSaving ? 'Saving...' : 'Save Configuration'}
+ {gitConfigSaving ? t('common.saving') : t('gitSettings.saveConfig')}
{saveStatus === 'success' && (
- Saved successfully
+ {t('gitSettings.savedSuccess')}
)}
diff --git a/src/components/LoginForm.jsx b/src/components/LoginForm.jsx
index f2a490a13..147010b0f 100644
--- a/src/components/LoginForm.jsx
+++ b/src/components/LoginForm.jsx
@@ -1,8 +1,10 @@
import React, { useState } from 'react';
import { useAuth } from '../contexts/AuthContext';
import { MessageSquare } from 'lucide-react';
+import { useTranslation } from '../i18n';
const LoginForm = () => {
+ const { t } = useTranslation();
const [username, setUsername] = useState('');
const [password, setPassword] = useState('');
const [isLoading, setIsLoading] = useState(false);
@@ -15,7 +17,7 @@ const LoginForm = () => {
setError('');
if (!username || !password) {
- setError('Please enter both username and password');
+ setError(t('loginForm.errorBothRequired'));
return;
}
@@ -41,9 +43,9 @@ const LoginForm = () => {
-
Welcome Back
+
{t('loginForm.welcomeBack')}
- Sign in to your Claude Code UI account
+ {t('loginForm.signInDesc')}
@@ -51,7 +53,7 @@ const LoginForm = () => {
diff --git a/src/components/NextTaskBanner.jsx b/src/components/NextTaskBanner.jsx
index 2a55cb8cf..2abadf1ac 100644
--- a/src/components/NextTaskBanner.jsx
+++ b/src/components/NextTaskBanner.jsx
@@ -5,8 +5,10 @@ import { useTaskMaster } from '../contexts/TaskMasterContext';
import { api } from '../utils/api';
import Shell from './Shell';
import TaskDetail from './TaskDetail';
+import { useTranslation } from '../i18n';
const NextTaskBanner = ({ onShowAllTasks, onStartTask, className = '' }) => {
+ const { t } = useTranslation();
const { nextTask, tasks, currentProject, isLoadingTasks, projectTaskMaster, refreshTasks, refreshProjects } = useTaskMaster();
const [showDetails, setShowDetails] = useState(false);
const [showTaskOptions, setShowTaskOptions] = useState(false);
@@ -68,7 +70,7 @@ const NextTaskBanner = ({ onShowAllTasks, onStartTask, className = '' }) => {
- TaskMaster AI is not configured
+ {t('taskBanner.notConfigured')}
@@ -80,7 +82,7 @@ const NextTaskBanner = ({ onShowAllTasks, onStartTask, className = '' }) => {
className="text-xs px-2 py-1 bg-blue-600 hover:bg-blue-700 text-white rounded transition-colors flex items-center gap-1"
>
- Initialize TaskMaster AI
+ {t('taskBanner.initializeButton')}
@@ -90,14 +92,14 @@ const NextTaskBanner = ({ onShowAllTasks, onStartTask, className = '' }) => {
{!projectTaskMaster?.hasTaskmaster && (
- 🎯 What is TaskMaster?
+ 🎯 {t('taskBanner.whatIsTaskMaster')}
-
• AI-Powered Task Management: Break complex projects into manageable subtasks
-
• PRD Templates: Generate tasks from Product Requirements Documents
-
• Dependency Tracking: Understand task relationships and execution order
-
• Progress Visualization: Kanban boards and detailed task analytics
-
• CLI Integration: Use taskmaster commands for advanced workflows
+
• {t('taskBanner.aiPowered')} {t('taskBanner.aiPoweredDesc')}
+
• {t('taskBanner.prdTemplates')} {t('taskBanner.prdTemplatesDesc')}
+
• {t('taskBanner.dependencyTracking')} {t('taskBanner.dependencyTrackingDesc')}
+
• {t('taskBanner.progressVisualization')} {t('taskBanner.progressVisualizationDesc')}
+
• {t('taskBanner.cliIntegration')} {t('taskBanner.cliIntegrationDesc')}
)}
@@ -108,12 +110,12 @@ const NextTaskBanner = ({ onShowAllTasks, onStartTask, className = '' }) => {
onClick={() => setShowCLI(true)}
>
- Initialize TaskMaster
+ {t('taskBanner.initializeTaskMaster')}
) : (
<>
- Add more tasks: Create additional tasks manually or generate them from a PRD template
+ {t('taskBanner.addMoreTasks')} {t('taskBanner.addMoreTasksDesc')}
{
disabled={isLoading}
>
- Create a new task manually
+ {t('taskBanner.createManualTask')}
{
disabled={isLoading}
>
- {isLoading ? 'Parsing...' : 'Generate tasks from PRD template'}
+ {isLoading ? t('taskBanner.parsing') : t('taskBanner.generateFromPRD')}
>
)}
@@ -151,19 +153,19 @@ const NextTaskBanner = ({ onShowAllTasks, onStartTask, className = '' }) => {
- Task {nextTask.id}
+ {t('taskBanner.task')} {nextTask.id}
{nextTask.priority === 'high' && (
-
+
)}
{nextTask.priority === 'medium' && (
-
+
)}
{nextTask.priority === 'low' && (
-
+
)}
@@ -179,12 +181,12 @@ const NextTaskBanner = ({ onShowAllTasks, onStartTask, className = '' }) => {
className="text-xs px-3 py-1.5 bg-blue-600 hover:bg-blue-700 text-white rounded-md font-medium transition-colors shadow-sm flex items-center gap-1"
>
- Start Task
+ {t('taskBanner.startTask')}
setShowTaskDetail(true)}
className="text-xs px-2 py-1.5 border border-slate-300 dark:border-slate-600 hover:bg-slate-100 dark:hover:bg-slate-800 text-slate-600 dark:text-slate-300 rounded-md transition-colors flex items-center gap-1"
- title="View task details"
+ title={t('taskBanner.viewDetails')}
>
@@ -192,7 +194,7 @@ const NextTaskBanner = ({ onShowAllTasks, onStartTask, className = '' }) => {
@@ -216,7 +218,7 @@ const NextTaskBanner = ({ onShowAllTasks, onStartTask, className = '' }) => {
- {completedTasks === totalTasks ? "All done! 🎉" : "No pending tasks"}
+ {completedTasks === totalTasks ? t('taskBanner.allDone') : t('taskBanner.noPendingTasks')}
@@ -227,7 +229,7 @@ const NextTaskBanner = ({ onShowAllTasks, onStartTask, className = '' }) => {
onClick={onShowAllTasks}
className="text-xs px-2 py-1 bg-purple-600 hover:bg-purple-700 text-white rounded transition-colors"
>
- Review
+ {t('taskBanner.review')}
@@ -277,8 +279,8 @@ const NextTaskBanner = ({ onShowAllTasks, onStartTask, className = '' }) => {
-
TaskMaster Setup
-
Interactive CLI for {currentProject?.displayName}
+
{t('taskBanner.taskmasterSetup')}
+
{t('taskBanner.interactiveCLI')} {currentProject?.displayName}
{
- TaskMaster initialization will start automatically
+ {t('taskBanner.initWillStart')}
setShowCLI(false)}
className="px-4 py-2 text-sm font-medium text-gray-700 dark:text-gray-300 bg-white dark:bg-gray-700 border border-gray-300 dark:border-gray-600 rounded-md hover:bg-gray-50 dark:hover:bg-gray-600 transition-colors"
>
- Close
+ {t('taskBanner.close')}
@@ -336,6 +338,7 @@ const NextTaskBanner = ({ onShowAllTasks, onStartTask, className = '' }) => {
// Simple Create Task Modal Component
const CreateTaskModal = ({ currentProject, onClose, onTaskCreated }) => {
+ const { t } = useTranslation();
const [formData, setFormData] = useState({
title: '',
description: '',
@@ -376,7 +379,7 @@ const CreateTaskModal = ({ currentProject, onClose, onTaskCreated }) => {
-
Create New Task
+ {t('taskBanner.createNewTask')}
{
checked={formData.useAI}
onChange={(e) => setFormData(prev => ({ ...prev, useAI: e.target.checked }))}
/>
- Use AI to generate task details
+ {t('taskBanner.useAI')}
{formData.useAI ? (
- Task Description (AI will generate details)
+ {t('taskBanner.taskDescAI')}
@@ -415,28 +418,28 @@ const CreateTaskModal = ({ currentProject, onClose, onTaskCreated }) => {
<>
- Task Title
+ {t('taskBanner.taskTitle')}
setFormData(prev => ({ ...prev, title: e.target.value }))}
className="w-full p-2 border border-gray-300 dark:border-gray-600 rounded focus:ring-2 focus:ring-blue-500 focus:border-blue-500 dark:bg-gray-700 dark:text-white"
- placeholder="Enter task title..."
+ placeholder={t('taskBanner.enterTaskTitle')}
required
/>
- Description
+ {t('taskBanner.description')}
@@ -445,16 +448,16 @@ const CreateTaskModal = ({ currentProject, onClose, onTaskCreated }) => {
- Priority
+ {t('taskBanner.priority')}
setFormData(prev => ({ ...prev, priority: e.target.value }))}
className="w-full p-2 border border-gray-300 dark:border-gray-600 rounded focus:ring-2 focus:ring-blue-500 focus:border-blue-500 dark:bg-gray-700 dark:text-white"
>
- Low
- Medium
- High
+ {t('taskBanner.low')}
+ {t('taskBanner.medium')}
+ {t('taskBanner.high')}
@@ -465,14 +468,14 @@ const CreateTaskModal = ({ currentProject, onClose, onTaskCreated }) => {
className="flex-1 px-4 py-2 border border-gray-300 dark:border-gray-600 rounded hover:bg-gray-50 dark:hover:bg-gray-700 text-gray-700 dark:text-gray-300"
disabled={isSubmitting}
>
- Cancel
+ {t('taskBanner.cancel')}
- {isSubmitting ? 'Creating...' : 'Create Task'}
+ {isSubmitting ? t('taskBanner.creating') : t('taskBanner.createTask')}
@@ -483,6 +486,7 @@ const CreateTaskModal = ({ currentProject, onClose, onTaskCreated }) => {
// Template Selector Modal Component
const TemplateSelector = ({ currentProject, onClose, onTemplateApplied }) => {
+ const { t } = useTranslation();
const [templates, setTemplates] = useState([]);
const [selectedTemplate, setSelectedTemplate] = useState(null);
const [customizations, setCustomizations] = useState({});
@@ -570,7 +574,7 @@ const TemplateSelector = ({ currentProject, onClose, onTemplateApplied }) => {
-
Loading templates...
+
{t('taskBanner.loadingTemplates')}
@@ -582,9 +586,9 @@ const TemplateSelector = ({ currentProject, onClose, onTemplateApplied }) => {
- {step === 'select' ? 'Select PRD Template' :
- step === 'customize' ? 'Customize Template' :
- 'Generating Tasks'}
+ {step === 'select' ? t('taskBanner.selectPRDTemplate') :
+ step === 'customize' ? t('taskBanner.customizeTemplate') :
+ t('taskBanner.generatingTasks')}
{
- File Name
+ {t('taskBanner.fileName')}
{
{Object.keys(customizations).length > 0 && (
- Customize Template
+ {t('taskBanner.customizeTemplateLabel')}
{Object.entries(customizations).map(([key, value]) => (
@@ -661,14 +665,14 @@ const TemplateSelector = ({ currentProject, onClose, onTemplateApplied }) => {
onClick={() => setStep('select')}
className="flex-1 px-4 py-2 border border-gray-300 dark:border-gray-600 rounded hover:bg-gray-50 dark:hover:bg-gray-700 text-gray-700 dark:text-gray-300"
>
- Back
+ {t('taskBanner.back')}
- {isApplying ? 'Applying...' : 'Apply & Generate Tasks'}
+ {isApplying ? t('taskBanner.applying') : t('taskBanner.applyGenerate')}
@@ -680,10 +684,10 @@ const TemplateSelector = ({ currentProject, onClose, onTemplateApplied }) => {
- Template Applied Successfully!
+ {t('taskBanner.templateApplied')}
- Your PRD has been created and tasks are being generated...
+ {t('taskBanner.prdCreated')}
)}
diff --git a/src/components/Onboarding.jsx b/src/components/Onboarding.jsx
index 33d61e5c6..72ebf3e68 100644
--- a/src/components/Onboarding.jsx
+++ b/src/components/Onboarding.jsx
@@ -5,8 +5,10 @@ import CursorLogo from './CursorLogo';
import LoginModal from './LoginModal';
import { authenticatedFetch } from '../utils/api';
import { useAuth } from '../contexts/AuthContext';
+import { useTranslation } from '../i18n';
const Onboarding = ({ onComplete }) => {
+ const { t } = useTranslation();
const [currentStep, setCurrentStep] = useState(0);
const [gitName, setGitName] = useState('');
const [gitEmail, setGitEmail] = useState('');
@@ -90,7 +92,7 @@ const Onboarding = ({ onComplete }) => {
authenticated: false,
email: null,
loading: false,
- error: 'Failed to check authentication status'
+ error: t('onboarding.errors.authCheckFailed')
});
}
} catch (error) {
@@ -120,7 +122,7 @@ const Onboarding = ({ onComplete }) => {
authenticated: false,
email: null,
loading: false,
- error: 'Failed to check authentication status'
+ error: t('onboarding.errors.authCheckFailed')
});
}
} catch (error) {
@@ -160,14 +162,14 @@ const Onboarding = ({ onComplete }) => {
// Step 0: Git config validation and submission
if (currentStep === 0) {
if (!gitName.trim() || !gitEmail.trim()) {
- setError('Both git name and email are required');
+ setError(t('onboarding.errors.nameEmailRequired'));
return;
}
// Validate email format
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(gitEmail)) {
- setError('Please enter a valid email address');
+ setError(t('onboarding.errors.invalidEmail'));
return;
}
@@ -182,7 +184,7 @@ const Onboarding = ({ onComplete }) => {
if (!response.ok) {
const data = await response.json();
- throw new Error(data.error || 'Failed to save git configuration');
+ throw new Error(data.error || t('onboarding.errors.saveFailed'));
}
setCurrentStep(currentStep + 1);
@@ -215,7 +217,7 @@ const Onboarding = ({ onComplete }) => {
if (!response.ok) {
const data = await response.json();
- throw new Error(data.error || 'Failed to complete onboarding');
+ throw new Error(data.error || t('onboarding.errors.completeFailed'));
}
// Call the onComplete callback
@@ -231,20 +233,20 @@ const Onboarding = ({ onComplete }) => {
const steps = [
{
- title: 'Git Configuration',
- description: 'Set up your git identity for commits',
+ title: t('onboarding.steps.gitConfig'),
+ description: t('onboarding.steps.gitConfigDesc'),
icon: GitBranch,
required: true
},
{
- title: 'Claude Code CLI',
- description: 'Connect your Claude Code account',
+ title: t('onboarding.steps.claudeCli'),
+ description: t('onboarding.steps.claudeCliDesc'),
icon: () => ,
required: false
},
{
- title: 'Cursor CLI',
- description: 'Connect your Cursor account',
+ title: t('onboarding.steps.cursorCli'),
+ description: t('onboarding.steps.cursorCliDesc'),
icon: () => ,
required: false
}
@@ -259,9 +261,9 @@ const Onboarding = ({ onComplete }) => {
- Git Configuration
+ {t('onboarding.gitStep.title')}
- Configure your git identity to ensure proper attribution for your commits
+ {t('onboarding.gitStep.description')}
@@ -269,7 +271,7 @@ const Onboarding = ({ onComplete }) => {
@@ -316,9 +318,9 @@ const Onboarding = ({ onComplete }) => {
- Claude Code CLI
+ {t('onboarding.claudeStep.title')}
- Connect your Claude account to enable AI-powered coding features
+ {t('onboarding.claudeStep.description')}
@@ -331,8 +333,8 @@ const Onboarding = ({ onComplete }) => {
claudeAuthStatus.authenticated ? 'bg-green-500' : 'bg-gray-300'
}`} />
- {claudeAuthStatus.loading ? 'Checking...' :
- claudeAuthStatus.authenticated ? 'Connected' : 'Not Connected'}
+ {claudeAuthStatus.loading ? t('onboarding.claudeStep.checkingAuth') :
+ claudeAuthStatus.authenticated ? t('onboarding.claudeStep.authenticated') : t('onboarding.claudeStep.notAuthenticated')}
{claudeAuthStatus.authenticated && (
@@ -342,21 +344,21 @@ const Onboarding = ({ onComplete }) => {
{claudeAuthStatus.authenticated && claudeAuthStatus.email && (
- Signed in as: {claudeAuthStatus.email}
+ {t('settings.auth.asUser')}: {claudeAuthStatus.email}
)}
{!claudeAuthStatus.authenticated && (
<>
- Click the button below to authenticate with Claude Code CLI. A terminal will open with authentication instructions.
+ {t('settings.auth.signInClaude')}
- Login to Claude Code
+ {t('onboarding.claudeStep.loginButton')}
Or manually run: claude auth login
@@ -372,7 +374,7 @@ const Onboarding = ({ onComplete }) => {
-
This step is optional. You can skip and configure it later in Settings.
+
{t('onboarding.claudeStep.skipButton')}
);
@@ -384,9 +386,9 @@ const Onboarding = ({ onComplete }) => {
- Cursor CLI
+ {t('onboarding.cursorStep.title')}
- Connect your Cursor account to enable AI-powered features
+ {t('onboarding.cursorStep.description')}
@@ -399,8 +401,8 @@ const Onboarding = ({ onComplete }) => {
cursorAuthStatus.authenticated ? 'bg-green-500' : 'bg-gray-300'
}`} />
- {cursorAuthStatus.loading ? 'Checking...' :
- cursorAuthStatus.authenticated ? 'Connected' : 'Not Connected'}
+ {cursorAuthStatus.loading ? t('onboarding.cursorStep.checkingAuth') :
+ cursorAuthStatus.authenticated ? t('onboarding.cursorStep.authenticated') : t('onboarding.cursorStep.notAuthenticated')}
{cursorAuthStatus.authenticated && (
@@ -410,21 +412,21 @@ const Onboarding = ({ onComplete }) => {
{cursorAuthStatus.authenticated && cursorAuthStatus.email && (
- Signed in as: {cursorAuthStatus.email}
+ {t('settings.auth.asUser')}: {cursorAuthStatus.email}
)}
{!cursorAuthStatus.authenticated && (
<>
- Click the button below to authenticate with Cursor CLI. A terminal will open with authentication instructions.
+ {t('settings.auth.signInCursor')}
- Login to Cursor
+ {t('onboarding.cursorStep.loginButton')}
Or manually run: cursor auth login
@@ -440,7 +442,7 @@ const Onboarding = ({ onComplete }) => {
-
This step is optional. You can skip and configure it later in Settings.
+
{t('onboarding.cursorStep.skipButton')}
);
@@ -492,7 +494,7 @@ const Onboarding = ({ onComplete }) => {
{step.title}
{step.required && (
- Required
+ {t('onboarding.gitStep.required')}
)}
@@ -525,7 +527,7 @@ const Onboarding = ({ onComplete }) => {
className="flex items-center gap-2 px-4 py-2 text-sm font-medium text-muted-foreground hover:text-foreground disabled:opacity-50 disabled:cursor-not-allowed transition-colors duration-200"
>
- Previous
+ {t('onboarding.navigation.back')}
@@ -538,11 +540,11 @@ const Onboarding = ({ onComplete }) => {
{isSubmitting ? (
<>
- Saving...
+ {t('common.saving')}
>
) : (
<>
- Next
+ {t('onboarding.navigation.next')}
>
)}
@@ -556,12 +558,12 @@ const Onboarding = ({ onComplete }) => {
{isSubmitting ? (
<>
- Completing...
+ {t('common.saving')}
>
) : (
<>
- Complete Setup
+ {t('onboarding.cursorStep.finishButton')}
>
)}
diff --git a/src/components/Settings.jsx b/src/components/Settings.jsx
index 90471fc08..081ae5f5b 100644
--- a/src/components/Settings.jsx
+++ b/src/components/Settings.jsx
@@ -5,7 +5,7 @@ import { Badge } from './ui/badge';
import { useTheme } from '../contexts/ThemeContext';
import { useTasksSettings } from '../contexts/TasksSettingsContext';
import { useTranslation } from '../i18n';
-import { X, Plus, Settings as SettingsIcon, Shield, AlertTriangle, Moon, Sun, Server, Edit3, Trash2, Globe, Terminal, Zap, FolderOpen, LogIn, Key,Languages, GitBranch, Check } from 'lucide-react';
+import { X, Plus, Settings as SettingsIcon, Shield, AlertTriangle, Moon, Sun, Server, Edit3, Trash2, Globe, Terminal, Zap, FolderOpen, LogIn, Key, GitBranch, Check } from 'lucide-react';
import ClaudeLogo from './ClaudeLogo';
import CursorLogo from './CursorLogo';
import CredentialsSettings from './CredentialsSettings';
@@ -755,7 +755,7 @@ function Settings({ isOpen, onClose, projects = [], initialTab = 'tools' }) {
}`}
>
- Git
+ {t('settings.tabs.git')}
setActiveTab('api')}
@@ -776,7 +776,7 @@ function Settings({ isOpen, onClose, projects = [], initialTab = 'tools' }) {
: 'border-transparent text-muted-foreground hover:text-foreground'
}`}
>
- Tasks
+ {t('settings.tabs.tasks')}
@@ -828,7 +828,6 @@ function Settings({ isOpen, onClose, projects = [], initialTab = 'tools' }) {
-
{t('settings.appearance.language')}
@@ -1108,22 +1107,22 @@ function Settings({ isOpen, onClose, projects = [], initialTab = 'tools' }) {
{claudeAuthStatus.loading ? (
- Checking authentication...
+ {t('settings.auth.checkingAuth')}
) : claudeAuthStatus.authenticated ? (
- ✓ Logged in
+ ✓ {t('settings.auth.loggedIn')}
{claudeAuthStatus.email && (
- as {claudeAuthStatus.email}
+ {t('settings.auth.asUser')} {claudeAuthStatus.email}
)}
) : (
- Not authenticated
+ {t('settings.auth.notAuthenticated')}
)}
@@ -1135,8 +1134,8 @@ function Settings({ isOpen, onClose, projects = [], initialTab = 'tools' }) {
{claudeAuthStatus.authenticated
- ? 'Re-authenticate or switch accounts'
- : 'Sign in to your Claude account to enable AI features'}
+ ? t('settings.auth.reAuthenticate')
+ : t('settings.auth.signInClaude')}
{cursorAuthStatus.loading ? (
- Checking authentication...
+ {t('settings.auth.checkingAuth')}
) : cursorAuthStatus.authenticated ? (
- ✓ Logged in
+ ✓ {t('settings.auth.loggedIn')}
{cursorAuthStatus.email && (
- as {cursorAuthStatus.email}
+ {t('settings.auth.asUser')} {cursorAuthStatus.email}
)}
) : (
- Not authenticated
+ {t('settings.auth.notAuthenticated')}
)}
@@ -1921,8 +1920,8 @@ function Settings({ isOpen, onClose, projects = [], initialTab = 'tools' }) {
{cursorAuthStatus.authenticated
- ? 'Re-authenticate or switch accounts'
- : 'Sign in to your Cursor account to enable AI features'}
+ ? t('settings.auth.reAuthenticate')
+ : t('settings.auth.signInCursor')}
- Settings saved successfully!
+ {t('settings.saveSuccess')}
)}
{saveStatus === 'error' && (
@@ -2147,7 +2146,7 @@ function Settings({ isOpen, onClose, projects = [], initialTab = 'tools' }) {
- Failed to save settings
+ {t('settings.saveError')}
)}
diff --git a/src/components/SetupForm.jsx b/src/components/SetupForm.jsx
index f1aa497e5..c1ad41f43 100644
--- a/src/components/SetupForm.jsx
+++ b/src/components/SetupForm.jsx
@@ -1,8 +1,10 @@
import React, { useState } from 'react';
import { useAuth } from '../contexts/AuthContext';
import ClaudeLogo from './ClaudeLogo';
+import { useTranslation } from '../i18n';
const SetupForm = () => {
+ const { t } = useTranslation();
const [username, setUsername] = useState('');
const [password, setPassword] = useState('');
const [confirmPassword, setConfirmPassword] = useState('');
@@ -16,17 +18,17 @@ const SetupForm = () => {
setError('');
if (password !== confirmPassword) {
- setError('Passwords do not match');
+ setError(t('setupForm.errorPasswordMismatch'));
return;
}
if (username.length < 3) {
- setError('Username must be at least 3 characters long');
+ setError(t('setupForm.errorUsernameLength'));
return;
}
if (password.length < 6) {
- setError('Password must be at least 6 characters long');
+ setError(t('setupForm.errorPasswordLength'));
return;
}
@@ -50,9 +52,9 @@ const SetupForm = () => {
- Welcome to Claude Code UI
+ {t('setupForm.welcomeTitle')}
- Set up your account to get started
+ {t('setupForm.setupDesc')}
@@ -60,7 +62,7 @@ const SetupForm = () => {
@@ -540,11 +540,11 @@ const Onboarding = ({ onComplete }) => {
{isSubmitting ? (
<>
- {t('common.saving')}
+ {translate('common.saving')}
>
) : (
<>
- {t('onboarding.navigation.next')}
+ {translate('onboarding.navigation.next')}
>
)}
@@ -558,12 +558,12 @@ const Onboarding = ({ onComplete }) => {
{isSubmitting ? (
<>
- {t('common.saving')}
+ {translate('common.saving')}
>
) : (
<>
- {t('onboarding.cursorStep.finishButton')}
+ {translate('onboarding.cursorStep.finishButton')}
>
)}
diff --git a/src/components/ProjectCreationWizard.jsx b/src/components/ProjectCreationWizard.jsx
index 6b253749f..2483b6c23 100644
--- a/src/components/ProjectCreationWizard.jsx
+++ b/src/components/ProjectCreationWizard.jsx
@@ -6,7 +6,7 @@ import { api } from '../utils/api';
import { useTranslation } from '../i18n';
const ProjectCreationWizard = ({ onClose, onProjectCreated }) => {
- const { t } = useTranslation();
+ const { translate } = useTranslation();
// Wizard state
const [step, setStep] = useState(1); // 1: Choose type, 2: Configure, 3: Confirm
@@ -168,7 +168,7 @@ const ProjectCreationWizard = ({ onClose, onProjectCreated }) => {
- {t('projectWizard.title')}
+ {translate('projectWizard.title')}
{
{s < step ? : s}
- {s === 1 ? t('projectWizard.steps.type') : s === 2 ? t('projectWizard.steps.configure') : t('projectWizard.steps.confirm')}
+ {s === 1 ? translate('projectWizard.steps.type') : s === 2 ? translate('projectWizard.steps.configure') : translate('projectWizard.steps.confirm')}
{s < 3 && (
@@ -230,7 +230,7 @@ const ProjectCreationWizard = ({ onClose, onProjectCreated }) => {
- {t('projectWizard.chooseType')}
+ {translate('projectWizard.chooseType')}
{/* Existing Workspace */}
@@ -248,10 +248,10 @@ const ProjectCreationWizard = ({ onClose, onProjectCreated }) => {
- {t('projectWizard.existingWorkspace')}
+ {translate('projectWizard.existingWorkspace')}
- {t('projectWizard.existingWorkspaceDesc')}
+ {translate('projectWizard.existingWorkspaceDesc')}
@@ -272,10 +272,10 @@ const ProjectCreationWizard = ({ onClose, onProjectCreated }) => {
- {t('projectWizard.newWorkspace')}
+ {translate('projectWizard.newWorkspace')}
- {t('projectWizard.newWorkspaceDesc')}
+ {translate('projectWizard.newWorkspaceDesc')}
@@ -291,14 +291,14 @@ const ProjectCreationWizard = ({ onClose, onProjectCreated }) => {
{/* Workspace Path */}
@@ -328,17 +328,17 @@ const ProjectCreationWizard = ({ onClose, onProjectCreated }) => {
<>
@@ -349,10 +349,10 @@ const ProjectCreationWizard = ({ onClose, onProjectCreated }) => {
- GitHub Authentication (Optional)
+ {translate('projectWizard.githubAuthTitle')}
- Only required for private repositories. Public repos can be cloned without authentication.
+ {translate('projectWizard.githubAuthDesc')}
@@ -360,7 +360,7 @@ const ProjectCreationWizard = ({ onClose, onProjectCreated }) => {
{loadingTokens ? (
- Loading stored tokens...
+ {translate('projectWizard.loadingTokens')}
) : availableTokens.length > 0 ? (
<>
@@ -374,7 +374,7 @@ const ProjectCreationWizard = ({ onClose, onProjectCreated }) => {
: 'bg-gray-200 dark:bg-gray-700 text-gray-700 dark:text-gray-300'
}`}
>
- {t('projectWizard.useStoredToken')}
+ {translate('projectWizard.useStoredToken')}
setTokenMode('new')}
@@ -384,7 +384,7 @@ const ProjectCreationWizard = ({ onClose, onProjectCreated }) => {
: 'bg-gray-200 dark:bg-gray-700 text-gray-700 dark:text-gray-300'
}`}
>
- {t('projectWizard.enterNewToken')}
+ {translate('projectWizard.enterNewToken')}
{
@@ -398,21 +398,21 @@ const ProjectCreationWizard = ({ onClose, onProjectCreated }) => {
: 'bg-gray-200 dark:bg-gray-700 text-gray-700 dark:text-gray-300'
}`}
>
- {t('projectWizard.noToken')}
+ {translate('projectWizard.noToken')}
{tokenMode === 'stored' ? (
- {t('projectWizard.selectToken')}
+ {translate('projectWizard.selectToken')}
setSelectedGithubToken(e.target.value)}
className="w-full px-3 py-2 bg-white dark:bg-gray-800 border border-gray-300 dark:border-gray-600 rounded-lg text-sm"
>
- -- Select a token --
+ {translate('projectWizard.selectTokenPlaceholder')}
{availableTokens.map((token) => (
{token.credential_name}
@@ -423,17 +423,17 @@ const ProjectCreationWizard = ({ onClose, onProjectCreated }) => {
) : tokenMode === 'new' ? (
) : null}
@@ -442,23 +442,23 @@ const ProjectCreationWizard = ({ onClose, onProjectCreated }) => {
- 💡 Public repositories don't require authentication. You can skip providing a token if cloning a public repo.
+ 💡 {translate('projectWizard.publicRepoTip')} {translate('projectWizard.publicRepoTipDesc')}
@@ -475,17 +475,17 @@ const ProjectCreationWizard = ({ onClose, onProjectCreated }) => {
- {t('projectWizard.confirmDetails')}
+ {translate('projectWizard.confirmDetails')}
- {t('projectWizard.workspaceType')}
+ {translate('projectWizard.workspaceType')}
- {workspaceType === 'existing' ? t('projectWizard.existingWorkspace') : t('projectWizard.newWorkspace')}
+ {workspaceType === 'existing' ? translate('projectWizard.existingWorkspace') : translate('projectWizard.newWorkspace')}
-
{t('projectWizard.path')}
+
{translate('projectWizard.path')}
{workspacePath}
@@ -493,19 +493,19 @@ const ProjectCreationWizard = ({ onClose, onProjectCreated }) => {
{workspaceType === 'new' && githubUrl && (
<>
- {t('projectWizard.repository')}
+ {translate('projectWizard.repository')}
{githubUrl}
- {t('projectWizard.token')}
+ {translate('projectWizard.token')}
{tokenMode === 'stored' && selectedGithubToken
- ? `${t('projectWizard.stored')}: ${availableTokens.find(t => t.id.toString() === selectedGithubToken)?.credential_name || 'Unknown'}`
+ ? `${translate('projectWizard.stored')}: ${availableTokens.find(t => t.id.toString() === selectedGithubToken)?.credential_name || 'Unknown'}`
: tokenMode === 'new' && newGithubToken
- ? t('projectWizard.new')
- : t('projectWizard.none')}
+ ? translate('projectWizard.new')
+ : translate('projectWizard.none')}
>
@@ -516,10 +516,10 @@ const ProjectCreationWizard = ({ onClose, onProjectCreated }) => {
{workspaceType === 'existing'
- ? 'The workspace will be added to your project list and will be available for Claude/Cursor sessions.'
+ ? translate('projectWizard.confirmExisting')
: githubUrl
- ? 'A new workspace will be created and the repository will be cloned from GitHub.'
- : 'An empty workspace directory will be created at the specified path.'}
+ ? translate('projectWizard.confirmNewWithRepo')
+ : translate('projectWizard.confirmNewEmpty')}
@@ -534,11 +534,11 @@ const ProjectCreationWizard = ({ onClose, onProjectCreated }) => {
disabled={isCreating}
>
{step === 1 ? (
- t('common.cancel')
+ translate('common.cancel')
) : (
<>
- {t('projectWizard.back')}
+ {translate('projectWizard.back')}
>
)}
@@ -550,16 +550,16 @@ const ProjectCreationWizard = ({ onClose, onProjectCreated }) => {
{isCreating ? (
<>
- {t('projectWizard.creating')}
+ {translate('projectWizard.creating')}
>
) : step === 3 ? (
<>
- {t('projectWizard.createProject')}
+ {translate('projectWizard.createProject')}
>
) : (
<>
- {t('projectWizard.next')}
+ {translate('projectWizard.next')}
>
)}
diff --git a/src/components/QuickSettingsPanel.jsx b/src/components/QuickSettingsPanel.jsx
index 2daa58425..befa2d309 100644
--- a/src/components/QuickSettingsPanel.jsx
+++ b/src/components/QuickSettingsPanel.jsx
@@ -39,7 +39,7 @@ const QuickSettingsPanel = ({
return localStorage.getItem('whisperMode') || 'default';
});
const { isDarkMode } = useTheme();
- const { t, i18n } = useTranslation();
+ const { translate, i18n } = useTranslation();
useEffect(() => {
setLocalIsOpen(isOpen);
@@ -83,7 +83,7 @@ const QuickSettingsPanel = ({
- {t('quickSettings.title')}
+ {translate('quickSettings.title')}
@@ -91,12 +91,12 @@ const QuickSettingsPanel = ({
{/* Appearance Settings */}
-
{t('quickSettings.appearance')}
+
{translate('quickSettings.appearance')}
{isDarkMode ? : }
- {t('quickSettings.darkMode')}
+ {translate('quickSettings.darkMode')}
@@ -104,7 +104,7 @@ const QuickSettingsPanel = ({
- {t('quickSettings.language')}
+ {translate('quickSettings.language')}
- {t('quickSettings.toolDisplay')}
+ {translate('quickSettings.toolDisplay')}
- {t('quickSettings.autoExpandTools')}
+ {translate('quickSettings.autoExpandTools')}
- {t('quickSettings.showRawParameters')}
+ {translate('quickSettings.showRawParameters')}
- {t('quickSettings.showThinking')}
+ {translate('quickSettings.showThinking')}
{/* View Options */}
diff --git a/src/components/Settings.jsx b/src/components/Settings.jsx
index 081ae5f5b..0308b21e3 100644
--- a/src/components/Settings.jsx
+++ b/src/components/Settings.jsx
@@ -16,7 +16,7 @@ import { authenticatedFetch } from '../utils/api';
function Settings({ isOpen, onClose, projects = [], initialTab = 'tools' }) {
const { isDarkMode, toggleDarkMode } = useTheme();
- const { t, i18n } = useTranslation();
+ const { translate, i18n } = useTranslation();
const {
tasksEnabled,
setTasksEnabled,
@@ -709,7 +709,7 @@ function Settings({ isOpen, onClose, projects = [], initialTab = 'tools' }) {
- {t('settings.title')}
+ {translate('settings.title')}
- {t('settings.tabs.tools')}
+ {translate('settings.tabs.tools')}
setActiveTab('appearance')}
@@ -744,7 +744,7 @@ function Settings({ isOpen, onClose, projects = [], initialTab = 'tools' }) {
: 'border-transparent text-muted-foreground hover:text-foreground'
}`}
>
- {t('settings.tabs.appearance')}
+ {translate('settings.tabs.appearance')}
setActiveTab('git')}
@@ -755,7 +755,7 @@ function Settings({ isOpen, onClose, projects = [], initialTab = 'tools' }) {
}`}
>
- {t('settings.tabs.git')}
+ {translate('settings.tabs.git')}
setActiveTab('api')}
@@ -766,7 +766,7 @@ function Settings({ isOpen, onClose, projects = [], initialTab = 'tools' }) {
}`}
>
- {t('settings.tabs.api')}
+ {translate('settings.tabs.api')}
setActiveTab('tasks')}
@@ -776,7 +776,7 @@ function Settings({ isOpen, onClose, projects = [], initialTab = 'tools' }) {
: 'border-transparent text-muted-foreground hover:text-foreground'
}`}
>
- {t('settings.tabs.tasks')}
+ {translate('settings.tabs.tasks')}
@@ -792,10 +792,10 @@ function Settings({ isOpen, onClose, projects = [], initialTab = 'tools' }) {
- {t('settings.appearance.darkMode')}
+ {translate('settings.appearance.darkMode')}
- {t('settings.appearance.darkModeDesc')}
+ {translate('settings.appearance.darkModeDesc')}
- {t('settings.appearance.language')}
+ {translate('settings.appearance.language')}
- {t('settings.appearance.languageDesc')}
+ {translate('settings.appearance.languageDesc')}
- {t('settings.appearance.projectSorting')}
+ {translate('settings.appearance.projectSorting')}
- {t('settings.appearance.projectSortingDesc')}
+ {translate('settings.appearance.projectSortingDesc')}
setProjectSortOrder(e.target.value)}
className="text-sm bg-gray-50 dark:bg-gray-800 border border-gray-300 dark:border-gray-600 text-gray-900 dark:text-gray-100 rounded-lg focus:ring-blue-500 focus:border-blue-500 p-2 w-32"
>
- {t('settings.appearance.alphabetical')}
- {t('settings.appearance.recentActivity')}
+ {translate('settings.appearance.alphabetical')}
+ {translate('settings.appearance.recentActivity')}
@@ -872,17 +872,17 @@ function Settings({ isOpen, onClose, projects = [], initialTab = 'tools' }) {
{/* Code Editor Settings */}
-
{t('settings.codeEditor.title')}
+
{translate('settings.codeEditor.title')}
{/* Editor Theme */}
- {t('settings.codeEditor.theme')}
+ {translate('settings.codeEditor.theme')}
- {t('settings.codeEditor.themeDesc')}
+ {translate('settings.codeEditor.themeDesc')}
- {t('settings.codeEditor.wordWrap')}
+ {translate('settings.codeEditor.wordWrap')}
- {t('settings.codeEditor.wordWrapDesc')}
+ {translate('settings.codeEditor.wordWrapDesc')}
- {t('settings.codeEditor.showMinimap')}
+ {translate('settings.codeEditor.showMinimap')}
- {t('settings.codeEditor.showMinimapDesc')}
+ {translate('settings.codeEditor.showMinimapDesc')}
- {t('settings.codeEditor.showLineNumbers')}
+ {translate('settings.codeEditor.showLineNumbers')}
- {t('settings.codeEditor.showLineNumbersDesc')}
+ {translate('settings.codeEditor.showLineNumbersDesc')}
- {t('settings.codeEditor.fontSize')}
+ {translate('settings.codeEditor.fontSize')}
- {t('settings.codeEditor.fontSizeDesc')}
+ {translate('settings.codeEditor.fontSizeDesc')}
- {t('settings.tools.permissionSettings')}
+ {translate('settings.tools.permissionSettings')}
@@ -1085,10 +1085,10 @@ function Settings({ isOpen, onClose, projects = [], initialTab = 'tools' }) {
/>
- {t('settings.tools.skipPermissions')}
+ {translate('settings.tools.skipPermissions')}
- {t('settings.tools.skipPermissionsDesc')}
+ {translate('settings.tools.skipPermissionsDesc')}
@@ -1099,7 +1099,7 @@ function Settings({ isOpen, onClose, projects = [], initialTab = 'tools' }) {
- {t('settings.tools.authentication')}
+ {translate('settings.tools.authentication')}
@@ -1107,22 +1107,22 @@ function Settings({ isOpen, onClose, projects = [], initialTab = 'tools' }) {
{claudeAuthStatus.loading ? (
- {t('settings.auth.checkingAuth')}
+ {translate('settings.auth.checkingAuth')}
) : claudeAuthStatus.authenticated ? (
- ✓ {t('settings.auth.loggedIn')}
+ ✓ {translate('settings.auth.loggedIn')}
{claudeAuthStatus.email && (
- {t('settings.auth.asUser')} {claudeAuthStatus.email}
+ {translate('settings.auth.asUser')} {claudeAuthStatus.email}
)}
) : (
- {t('settings.auth.notAuthenticated')}
+ {translate('settings.auth.notAuthenticated')}
)}
@@ -1134,8 +1134,8 @@ function Settings({ isOpen, onClose, projects = [], initialTab = 'tools' }) {
{claudeAuthStatus.authenticated
- ? t('settings.auth.reAuthenticate')
- : t('settings.auth.signInClaude')}
+ ? translate('settings.auth.reAuthenticate')
+ : translate('settings.auth.signInClaude')}
- {t('settings.tools.allowedTools')}
+ {translate('settings.tools.allowedTools')}
- {t('settings.tools.allowedToolsDesc')}
+ {translate('settings.tools.allowedToolsDesc')}
@@ -1183,14 +1183,14 @@ function Settings({ isOpen, onClose, projects = [], initialTab = 'tools' }) {
className="h-10 px-4 touch-manipulation"
>
-
{t('common.addTool')}
+
{translate('common.addTool')}
{/* Common tools quick add */}
- {t('common.quickAddCommon')}
+ {translate('common.quickAddCommon')}
{commonTools.map(tool => (
@@ -1226,7 +1226,7 @@ function Settings({ isOpen, onClose, projects = [], initialTab = 'tools' }) {
))}
{allowedTools.length === 0 && (
- {t('settings.tools.noAllowedTools')}
+ {translate('settings.tools.noAllowedTools')}
)}
@@ -1237,11 +1237,11 @@ function Settings({ isOpen, onClose, projects = [], initialTab = 'tools' }) {
- {t('settings.tools.disallowedTools')}
+ {translate('settings.tools.disallowedTools')}
- {t('settings.tools.disallowedToolsDesc')}
+ {translate('settings.tools.disallowedToolsDesc')}
@@ -1264,7 +1264,7 @@ function Settings({ isOpen, onClose, projects = [], initialTab = 'tools' }) {
className="h-10 px-4 touch-manipulation"
>
-
{t('common.addTool')}
+
{translate('common.addTool')}
@@ -1286,7 +1286,7 @@ function Settings({ isOpen, onClose, projects = [], initialTab = 'tools' }) {
))}
{disallowedTools.length === 0 && (
- {t('settings.tools.noDisallowedTools')}
+ {translate('settings.tools.noDisallowedTools')}
)}
@@ -1832,14 +1832,14 @@ function Settings({ isOpen, onClose, projects = [], initialTab = 'tools' }) {
- {t('common.cancel')}
+ {translate('common.cancel')}
- {mcpLoading ? t('common.saving') : (editingMcpServer ? t('settings.mcpServers.updateServer') : t('settings.mcpServers.addServer'))}
+ {mcpLoading ? translate('common.saving') : (editingMcpServer ? translate('settings.mcpServers.updateServer') : translate('settings.mcpServers.addServer'))}
@@ -1858,7 +1858,7 @@ function Settings({ isOpen, onClose, projects = [], initialTab = 'tools' }) {
- {t('cursor.permissionSettings')}
+ {translate('cursor.permissionSettings')}
@@ -1871,10 +1871,10 @@ function Settings({ isOpen, onClose, projects = [], initialTab = 'tools' }) {
/>
- {t('cursor.skipPermissions')}
+ {translate('cursor.skipPermissions')}
- {t('cursor.skipPermissionsDesc')}
+ {translate('cursor.skipPermissionsDesc')}
@@ -1885,7 +1885,7 @@ function Settings({ isOpen, onClose, projects = [], initialTab = 'tools' }) {
- {t('cursor.authentication')}
+ {translate('cursor.authentication')}
@@ -1893,22 +1893,22 @@ function Settings({ isOpen, onClose, projects = [], initialTab = 'tools' }) {
{cursorAuthStatus.loading ? (
- {t('settings.auth.checkingAuth')}
+ {translate('settings.auth.checkingAuth')}
) : cursorAuthStatus.authenticated ? (
- ✓ {t('settings.auth.loggedIn')}
+ ✓ {translate('settings.auth.loggedIn')}
{cursorAuthStatus.email && (
- {t('settings.auth.asUser')} {cursorAuthStatus.email}
+ {translate('settings.auth.asUser')} {cursorAuthStatus.email}
)}
) : (
- {t('settings.auth.notAuthenticated')}
+ {translate('settings.auth.notAuthenticated')}
)}
@@ -1920,8 +1920,8 @@ function Settings({ isOpen, onClose, projects = [], initialTab = 'tools' }) {
{cursorAuthStatus.authenticated
- ? t('settings.auth.reAuthenticate')
- : t('settings.auth.signInCursor')}
+ ? translate('settings.auth.reAuthenticate')
+ : translate('settings.auth.signInCursor')}
- {t('cursor.allowedCommands')}
+ {translate('cursor.allowedCommands')}
- {t('cursor.allowedCommandsDesc')}
+ {translate('cursor.allowedCommandsDesc')}
setNewCursorCommand(e.target.value)}
- placeholder={t('cursor.commandPlaceholder')}
+ placeholder={translate('cursor.commandPlaceholder')}
onKeyPress={(e) => {
if (e.key === 'Enter') {
if (newCursorCommand && !cursorAllowedCommands.includes(newCursorCommand)) {
@@ -1977,14 +1977,14 @@ function Settings({ isOpen, onClose, projects = [], initialTab = 'tools' }) {
className="h-10 px-4 touch-manipulation"
>
-
{t('common.addCommand')}
+
{translate('common.addCommand')}
{/* Common commands quick add */}
- {t('common.quickAddCommonCommands')}
+ {translate('common.quickAddCommonCommands')}
{commonCursorCommands.map(cmd => (
@@ -2024,7 +2024,7 @@ function Settings({ isOpen, onClose, projects = [], initialTab = 'tools' }) {
))}
{cursorAllowedCommands.length === 0 && (
- {t('cursor.noAllowedCommands')}
+ {translate('cursor.noAllowedCommands')}
)}
@@ -2035,18 +2035,18 @@ function Settings({ isOpen, onClose, projects = [], initialTab = 'tools' }) {
- {t('cursor.disallowedCommands')}
+ {translate('cursor.disallowedCommands')}
- {t('cursor.disallowedCommandsDesc')}
+ {translate('cursor.disallowedCommandsDesc')}
setNewCursorDisallowedCommand(e.target.value)}
- placeholder={t('cursor.disallowedCommandPlaceholder')}
+ placeholder={translate('cursor.disallowedCommandPlaceholder')}
onKeyPress={(e) => {
if (e.key === 'Enter') {
if (newCursorDisallowedCommand && !cursorDisallowedCommands.includes(newCursorDisallowedCommand)) {
@@ -2070,7 +2070,7 @@ function Settings({ isOpen, onClose, projects = [], initialTab = 'tools' }) {
className="h-10 px-4 touch-manipulation"
>
-
{t('common.addCommand')}
+
{translate('common.addCommand')}
@@ -2092,7 +2092,7 @@ function Settings({ isOpen, onClose, projects = [], initialTab = 'tools' }) {
))}
{cursorDisallowedCommands.length === 0 && (
- {t('cursor.noDisallowedCommands')}
+ {translate('cursor.noDisallowedCommands')}
)}
@@ -2138,7 +2138,7 @@ function Settings({ isOpen, onClose, projects = [], initialTab = 'tools' }) {
- {t('settings.saveSuccess')}
+ {translate('settings.saveSuccess')}
)}
{saveStatus === 'error' && (
@@ -2146,7 +2146,7 @@ function Settings({ isOpen, onClose, projects = [], initialTab = 'tools' }) {
- {t('settings.saveError')}
+ {translate('settings.saveError')}
)}
@@ -2157,7 +2157,7 @@ function Settings({ isOpen, onClose, projects = [], initialTab = 'tools' }) {
disabled={isSaving}
className="flex-1 sm:flex-none h-10 touch-manipulation"
>
- {t('common.cancel')}
+ {translate('common.cancel')}
- {t('common.saving')}
+ {translate('common.saving')}
) : (
- t('settings.save')
+ translate('settings.save')
)}
diff --git a/src/components/SetupForm.jsx b/src/components/SetupForm.jsx
index c1ad41f43..e4e7b0780 100644
--- a/src/components/SetupForm.jsx
+++ b/src/components/SetupForm.jsx
@@ -4,7 +4,7 @@ import ClaudeLogo from './ClaudeLogo';
import { useTranslation } from '../i18n';
const SetupForm = () => {
- const { t } = useTranslation();
+ const { translate } = useTranslation();
const [username, setUsername] = useState('');
const [password, setPassword] = useState('');
const [confirmPassword, setConfirmPassword] = useState('');
@@ -52,9 +52,9 @@ const SetupForm = () => {
- {t('setupForm.welcomeTitle')}
+ {translate('setupForm.welcomeTitle')}
- {t('setupForm.setupDesc')}
+ {translate('setupForm.setupDesc')}
@@ -62,7 +62,7 @@ const SetupForm = () => {
- {t('setupForm.username')}
+ {translate('setupForm.username')}
{
value={username}
onChange={(e) => setUsername(e.target.value)}
className="w-full px-3 py-2 border border-border rounded-md bg-background text-foreground focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"
- placeholder={t('setupForm.enterUsername')}
+ placeholder={translate('setupForm.enterUsername')}
required
disabled={isLoading}
/>
@@ -78,7 +78,7 @@ const SetupForm = () => {
- {t('setupForm.password')}
+ {translate('setupForm.password')}
{
value={password}
onChange={(e) => setPassword(e.target.value)}
className="w-full px-3 py-2 border border-border rounded-md bg-background text-foreground focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"
- placeholder={t('setupForm.enterPassword')}
+ placeholder={translate('setupForm.enterPassword')}
required
disabled={isLoading}
/>
@@ -94,7 +94,7 @@ const SetupForm = () => {
diff --git a/src/components/Shell.jsx b/src/components/Shell.jsx
index 87c9b6adb..b828b2e99 100644
--- a/src/components/Shell.jsx
+++ b/src/components/Shell.jsx
@@ -26,7 +26,7 @@ if (typeof document !== 'undefined') {
}
function Shell({ selectedProject, selectedSession, initialCommand, isPlainShell = false, onProcessComplete, minimal = false, autoConnect = false }) {
- const { t } = useTranslation();
+ const { translate } = useTranslation();
const terminalRef = useRef(null);
const terminal = useRef(null);
const fitAddon = useRef(null);
@@ -376,8 +376,8 @@ function Shell({ selectedProject, selectedSession, initialCommand, isPlainShell
-
{t('shell.selectProject')}
-
{t('shell.selectProjectDesc')}
+
{translate('shell.selectProject')}
+
{translate('shell.selectProjectDesc')}
);
diff --git a/src/components/Sidebar.jsx b/src/components/Sidebar.jsx
index 328112ba3..e772ce8da 100644
--- a/src/components/Sidebar.jsx
+++ b/src/components/Sidebar.jsx
@@ -81,7 +81,7 @@ function Sidebar({
// TaskMaster context
const { setCurrentProject, mcpServerStatus } = useTaskMaster();
const { tasksEnabled } = useTasksSettings();
- const { t } = useTranslation();
+ const { translate } = useTranslation();
// Starred projects state - persisted in localStorage
@@ -543,7 +543,7 @@ function Sidebar({
Claude Code UI
-
{t('sidebar.title')}
+
{translate('sidebar.title')}
) : (
@@ -553,7 +553,7 @@ function Sidebar({
Claude Code UI
-
{t('sidebar.title')}
+
{translate('sidebar.title')}
)}
@@ -590,7 +590,7 @@ function Sidebar({
setSearchFilter(e.target.value)}
className="pl-9 h-9 text-sm bg-muted/50 border-0 focus:bg-background focus:ring-1 focus:ring-primary/20"
@@ -613,10 +613,10 @@ function Sidebar({
size="sm"
className="flex-1 h-8 text-xs bg-primary hover:bg-primary/90 transition-all duration-200"
onClick={() => setShowNewProject(true)}
- title={t('sidebar.createNewProject')}
+ title={translate('sidebar.createNewProject')}
>
- {t('sidebar.newProject')}
+ {translate('sidebar.newProject')}
- {t('common.loading')}
+ {translate('common.loading')}
- {t('sidebar.noProjectsDesc')}
+ {translate('sidebar.noProjectsDesc')}
) : projects.length === 0 ? (
@@ -658,9 +658,9 @@ function Sidebar({
- {t('sidebar.noProjects')}
+ {translate('sidebar.noProjects')}
- {t('sidebar.noProjectsDesc')}
+ {translate('sidebar.noProjectsDesc')}
) : filteredProjects.length === 0 ? (
@@ -668,9 +668,9 @@ function Sidebar({
- {t('sidebar.noSearchResults')}
+ {translate('sidebar.noSearchResults')}
- {t('sidebar.noSearchResultsDesc')}
+ {translate('sidebar.noSearchResultsDesc')}
) : (
@@ -1023,7 +1023,7 @@ function Sidebar({
const isActive = diffInMinutes < 10;
// Get session display values
- const sessionName = isCursorSession ? (session.name || 'Untitled Session') : (session.summary || t('sidebar.newSession'));
+ const sessionName = isCursorSession ? (session.name || 'Untitled Session') : (session.summary || translate('sidebar.newSession'));
const sessionTime = isCursorSession ? session.createdAt : session.lastActivity;
const messageCount = session.messageCount || 0;
@@ -1215,7 +1215,7 @@ function Sidebar({
onClick={(e) => {
e.stopPropagation();
setEditingSession(session.id);
- setEditingSessionName(session.summary || t('sidebar.newSession'));
+ setEditingSessionName(session.summary || translate('sidebar.newSession'));
}}
title="Manually edit session name"
>
@@ -1254,12 +1254,12 @@ function Sidebar({
{loadingSessions[project.name] ? (
<>
- {t('common.loading')}
+ {translate('common.loading')}
>
) : (
<>
- {t('sidebar.showMoreSessions')}
+ {translate('sidebar.showMoreSessions')}
>
)}
@@ -1275,7 +1275,7 @@ function Sidebar({
}}
>
- {t('sidebar.newSession')}
+ {translate('sidebar.newSession')}
@@ -1286,7 +1286,7 @@ function Sidebar({
onClick={() => onNewSession(project)}
>
- {t('sidebar.newSession')}
+ {translate('sidebar.newSession')}
)}
@@ -1356,7 +1356,7 @@ function Sidebar({
- {t('sidebar.settings')}
+ {translate('sidebar.settings')}
@@ -1367,7 +1367,7 @@ function Sidebar({
onClick={onShowSettings}
>
- {t('sidebar.settings')}
+ {translate('sidebar.settings')}
diff --git a/src/i18n/en.json b/src/i18n/en.json
index fcfa37a84..6c63ac7d9 100644
--- a/src/i18n/en.json
+++ b/src/i18n/en.json
@@ -223,7 +223,23 @@
"createProject": "Create Project",
"creating": "Creating...",
"selectTypeError": "Please select whether you have an existing workspace or want to create a new one",
- "pathRequiredError": "Please provide a workspace path"
+ "pathRequiredError": "Please provide a workspace path",
+ "pathHelpExisting": "Full path to your existing workspace directory",
+ "pathHelpNew": "Full path where the new workspace will be created",
+ "githubUrlHelp": "Leave empty to create an empty workspace, or provide a GitHub URL to clone",
+ "githubAuthTitle": "GitHub Authentication (Optional)",
+ "githubAuthDesc": "Only required for private repositories. Public repos can be cloned without authentication.",
+ "loadingTokens": "Loading stored tokens...",
+ "selectTokenPlaceholder": "-- Select a token --",
+ "tokenOnlyForOperation": "This token will be used only for this operation",
+ "publicRepoTip": "Public repositories",
+ "publicRepoTipDesc": "don't require authentication. You can skip providing a token if cloning a public repo.",
+ "githubTokenOptional": "GitHub Token (Optional for Public Repos)",
+ "tokenPlaceholderPublic": "ghp_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx (leave empty for public repos)",
+ "noStoredTokens": "No stored tokens available. You can add tokens in Settings → API Keys for easier reuse.",
+ "confirmExisting": "The workspace will be added to your project list and will be available for Claude/Cursor sessions.",
+ "confirmNewWithRepo": "A new workspace will be created and the repository will be cloned from GitHub.",
+ "confirmNewEmpty": "An empty workspace directory will be created at the specified path."
},
"mainContent": {
"selectProject": "Select a project to start chatting",
diff --git a/src/i18n/index.jsx b/src/i18n/index.jsx
index 6ce00c0d5..2701f733e 100644
--- a/src/i18n/index.jsx
+++ b/src/i18n/index.jsx
@@ -77,6 +77,7 @@ export function I18nProvider({ children, defaultLanguage = 'en' }) {
export function useTranslation() {
const context = useContext(I18nContext);
return {
+ translate: context.t,
t: context.t,
i18n: context.i18n
};
diff --git a/src/i18n/zh.json b/src/i18n/zh.json
index ba89428c8..518f7ed50 100644
--- a/src/i18n/zh.json
+++ b/src/i18n/zh.json
@@ -223,7 +223,23 @@
"createProject": "创建项目",
"creating": "创建中...",
"selectTypeError": "请选择您是否有现有工作区或想创建新工作区",
- "pathRequiredError": "请提供工作区路径"
+ "pathRequiredError": "请提供工作区路径",
+ "pathHelpExisting": "现有工作区目录的完整路径",
+ "pathHelpNew": "将创建新工作区的完整路径",
+ "githubUrlHelp": "留空以创建空工作区,或提供 GitHub URL 以克隆",
+ "githubAuthTitle": "GitHub 认证(可选)",
+ "githubAuthDesc": "仅私有仓库需要。公共仓库可以无需认证克隆。",
+ "loadingTokens": "加载已保存的令牌中...",
+ "selectTokenPlaceholder": "-- 选择一个令牌 --",
+ "tokenOnlyForOperation": "此令牌仅用于本次操作",
+ "publicRepoTip": "公共仓库",
+ "publicRepoTipDesc": "不需要认证。如果克隆公共仓库,您可以跳过提供令牌。",
+ "githubTokenOptional": "GitHub 令牌(公共仓库可选)",
+ "tokenPlaceholderPublic": "ghp_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx(公共仓库可留空)",
+ "noStoredTokens": "没有已保存的令牌。您可以在设置 → API 密钥中添加令牌以便重复使用。",
+ "confirmExisting": "工作区将添加到您的项目列表中,并可用于 Claude/Cursor 会话。",
+ "confirmNewWithRepo": "将创建新工作区并从 GitHub 克隆仓库。",
+ "confirmNewEmpty": "将在指定路径创建空工作区目录。"
},
"mainContent": {
"selectProject": "选择一个项目开始聊天",
From 0e78bbb25d90f7644d27a055ac41392b56295e7d Mon Sep 17 00:00:00 2001
From: tata
Date: Wed, 19 Nov 2025 09:36:33 +0800
Subject: [PATCH 7/7] refactor: replace `t` with `translate` in i18n hook usage
across components
---
src/components/LoginForm.jsx | 2 +-
src/components/MobileNav.jsx | 2 +-
src/components/Onboarding.jsx | 4 ++--
src/components/ProjectCreationWizard.jsx | 4 ++--
src/components/Settings.jsx | 2 +-
src/components/SetupForm.jsx | 6 +++---
6 files changed, 10 insertions(+), 10 deletions(-)
diff --git a/src/components/LoginForm.jsx b/src/components/LoginForm.jsx
index 2df2f50d8..24b04619b 100644
--- a/src/components/LoginForm.jsx
+++ b/src/components/LoginForm.jsx
@@ -17,7 +17,7 @@ const LoginForm = () => {
setError('');
if (!username || !password) {
- setError(t('loginForm.errorBothRequired'));
+ setError(translate('loginForm.errorBothRequired'));
return;
}
diff --git a/src/components/MobileNav.jsx b/src/components/MobileNav.jsx
index cb101acdb..74d4a8b13 100644
--- a/src/components/MobileNav.jsx
+++ b/src/components/MobileNav.jsx
@@ -5,7 +5,7 @@ import { useTranslation } from '../i18n';
function MobileNav({ activeTab, setActiveTab, isInputFocused }) {
const { tasksEnabled } = useTasksSettings();
- const { t } = useTranslation();
+ const { translate } = useTranslation();
const navItems = [
{
id: 'chat',
diff --git a/src/components/Onboarding.jsx b/src/components/Onboarding.jsx
index f6dd16e65..77e17bb50 100644
--- a/src/components/Onboarding.jsx
+++ b/src/components/Onboarding.jsx
@@ -162,14 +162,14 @@ const Onboarding = ({ onComplete }) => {
// Step 0: Git config validation and submission
if (currentStep === 0) {
if (!gitName.trim() || !gitEmail.trim()) {
- setError(t('onboarding.errors.nameEmailRequired'));
+ setError(translate('onboarding.errors.nameEmailRequired'));
return;
}
// Validate email format
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(gitEmail)) {
- setError(t('onboarding.errors.invalidEmail'));
+ setError(translate('onboarding.errors.invalidEmail'));
return;
}
diff --git a/src/components/ProjectCreationWizard.jsx b/src/components/ProjectCreationWizard.jsx
index 2483b6c23..883c6bbf4 100644
--- a/src/components/ProjectCreationWizard.jsx
+++ b/src/components/ProjectCreationWizard.jsx
@@ -91,13 +91,13 @@ const ProjectCreationWizard = ({ onClose, onProjectCreated }) => {
if (step === 1) {
if (!workspaceType) {
- setError(t('projectWizard.selectTypeError'));
+ setError(translate('projectWizard.selectTypeError'));
return;
}
setStep(2);
} else if (step === 2) {
if (!workspacePath.trim()) {
- setError(t('projectWizard.pathRequiredError'));
+ setError(translate('projectWizard.pathRequiredError'));
return;
}
diff --git a/src/components/Settings.jsx b/src/components/Settings.jsx
index 0308b21e3..34a14874f 100644
--- a/src/components/Settings.jsx
+++ b/src/components/Settings.jsx
@@ -632,7 +632,7 @@ function Settings({ isOpen, onClose, projects = [], initialTab = 'tools' }) {
};
const handleMcpDelete = async (serverId, scope) => {
- if (confirm(t('common.deleteConfirm'))) {
+ if (confirm(translate('common.deleteConfirm'))) {
try {
await deleteMcpServer(serverId, scope);
setSaveStatus('success');
diff --git a/src/components/SetupForm.jsx b/src/components/SetupForm.jsx
index e4e7b0780..50772c08a 100644
--- a/src/components/SetupForm.jsx
+++ b/src/components/SetupForm.jsx
@@ -18,17 +18,17 @@ const SetupForm = () => {
setError('');
if (password !== confirmPassword) {
- setError(t('setupForm.errorPasswordMismatch'));
+ setError(translate('setupForm.errorPasswordMismatch'));
return;
}
if (username.length < 3) {
- setError(t('setupForm.errorUsernameLength'));
+ setError(translate('setupForm.errorUsernameLength'));
return;
}
if (password.length < 6) {
- setError(t('setupForm.errorPasswordLength'));
+ setError(translate('setupForm.errorPasswordLength'));
return;
}