mirror of
https://github.com/siteboon/claudecodeui.git
synced 2026-01-23 18:07:34 +00:00
Merge pull request #327 from NobitaYuan/feat/add-i18n
update: Add translations for more components
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
<div align="center">
|
||||
<img src="public/logo.svg" alt="Claude Code UI" width="64" height="64">
|
||||
<h1>Cloud CLI (aka Claude Code UI)</h1>
|
||||
<h1>Cloud CLI (又名 Claude Code UI)</h1>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
46
src/App.jsx
46
src/App.jsx
@@ -36,7 +36,7 @@ import ProtectedRoute from './components/ProtectedRoute';
|
||||
import { useVersionCheck } from './hooks/useVersionCheck';
|
||||
import useLocalStorage from './hooks/useLocalStorage';
|
||||
import { api, authenticatedFetch } from './utils/api';
|
||||
import { I18nextProvider } from 'react-i18next';
|
||||
import { I18nextProvider, useTranslation } from 'react-i18next';
|
||||
import i18n from './i18n/config.js';
|
||||
|
||||
|
||||
@@ -44,6 +44,7 @@ import i18n from './i18n/config.js';
|
||||
function AppContent() {
|
||||
const navigate = useNavigate();
|
||||
const { sessionId } = useParams();
|
||||
const { t } = useTranslation('common');
|
||||
|
||||
const { updateAvailable, latestVersion, currentVersion, releaseInfo } = useVersionCheck('siteboon', 'claudecodeui');
|
||||
const [showVersionModal, setShowVersionModal] = useState(false);
|
||||
@@ -579,6 +580,7 @@ function AppContent() {
|
||||
|
||||
// Version Upgrade Modal Component
|
||||
const VersionUpgradeModal = () => {
|
||||
const { t } = useTranslation('common');
|
||||
const [isUpdating, setIsUpdating] = useState(false);
|
||||
const [updateOutput, setUpdateOutput] = useState('');
|
||||
const [updateError, setUpdateError] = useState('');
|
||||
@@ -639,7 +641,7 @@ function AppContent() {
|
||||
<button
|
||||
className="fixed inset-0 bg-black/50 backdrop-blur-sm"
|
||||
onClick={() => setShowVersionModal(false)}
|
||||
aria-label="Close version upgrade modal"
|
||||
aria-label={t('versionUpdate.ariaLabels.closeModal')}
|
||||
/>
|
||||
|
||||
{/* Modal */}
|
||||
@@ -653,9 +655,9 @@ function AppContent() {
|
||||
</svg>
|
||||
</div>
|
||||
<div>
|
||||
<h2 className="text-lg font-semibold text-gray-900 dark:text-white">Update Available</h2>
|
||||
<h2 className="text-lg font-semibold text-gray-900 dark:text-white">{t('versionUpdate.title')}</h2>
|
||||
<p className="text-sm text-gray-500 dark:text-gray-400">
|
||||
{releaseInfo?.title || 'A new version is ready'}
|
||||
{releaseInfo?.title || t('versionUpdate.newVersionReady')}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -672,11 +674,11 @@ function AppContent() {
|
||||
{/* Version Info */}
|
||||
<div className="space-y-3">
|
||||
<div className="flex justify-between items-center p-3 bg-gray-50 dark:bg-gray-700/50 rounded-lg">
|
||||
<span className="text-sm font-medium text-gray-700 dark:text-gray-300">Current Version</span>
|
||||
<span className="text-sm font-medium text-gray-700 dark:text-gray-300">{t('versionUpdate.currentVersion')}</span>
|
||||
<span className="text-sm text-gray-900 dark:text-white font-mono">{currentVersion}</span>
|
||||
</div>
|
||||
<div className="flex justify-between items-center p-3 bg-blue-50 dark:bg-blue-900/20 rounded-lg border border-blue-200 dark:border-blue-700">
|
||||
<span className="text-sm font-medium text-blue-700 dark:text-blue-300">Latest Version</span>
|
||||
<span className="text-sm font-medium text-blue-700 dark:text-blue-300">{t('versionUpdate.latestVersion')}</span>
|
||||
<span className="text-sm text-blue-900 dark:text-blue-100 font-mono">{latestVersion}</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -685,7 +687,7 @@ function AppContent() {
|
||||
{releaseInfo?.body && (
|
||||
<div className="space-y-3">
|
||||
<div className="flex items-center justify-between">
|
||||
<h3 className="text-sm font-medium text-gray-900 dark:text-white">What's New:</h3>
|
||||
<h3 className="text-sm font-medium text-gray-900 dark:text-white">{t('versionUpdate.whatsNew')}</h3>
|
||||
{releaseInfo?.htmlUrl && (
|
||||
<a
|
||||
href={releaseInfo.htmlUrl}
|
||||
@@ -693,7 +695,7 @@ function AppContent() {
|
||||
rel="noopener noreferrer"
|
||||
className="text-xs text-blue-600 dark:text-blue-400 hover:text-blue-700 dark:hover:text-blue-300 hover:underline flex items-center gap-1"
|
||||
>
|
||||
View full release
|
||||
{t('versionUpdate.viewFullRelease')}
|
||||
<svg className="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" />
|
||||
</svg>
|
||||
@@ -711,7 +713,7 @@ function AppContent() {
|
||||
{/* Update Output */}
|
||||
{updateOutput && (
|
||||
<div className="space-y-2">
|
||||
<h3 className="text-sm font-medium text-gray-900 dark:text-white">Update Progress:</h3>
|
||||
<h3 className="text-sm font-medium text-gray-900 dark:text-white">{t('versionUpdate.updateProgress')}</h3>
|
||||
<div className="bg-gray-900 dark:bg-gray-950 rounded-lg p-4 border border-gray-700 max-h-48 overflow-y-auto">
|
||||
<pre className="text-xs text-green-400 font-mono whitespace-pre-wrap">{updateOutput}</pre>
|
||||
</div>
|
||||
@@ -721,14 +723,14 @@ function AppContent() {
|
||||
{/* Upgrade Instructions */}
|
||||
{!isUpdating && !updateOutput && (
|
||||
<div className="space-y-3">
|
||||
<h3 className="text-sm font-medium text-gray-900 dark:text-white">Manual upgrade:</h3>
|
||||
<h3 className="text-sm font-medium text-gray-900 dark:text-white">{t('versionUpdate.manualUpgrade')}</h3>
|
||||
<div className="bg-gray-100 dark:bg-gray-800 rounded-lg p-3 border">
|
||||
<code className="text-sm text-gray-800 dark:text-gray-200 font-mono">
|
||||
git checkout main && git pull && npm install
|
||||
</code>
|
||||
</div>
|
||||
<p className="text-xs text-gray-600 dark:text-gray-400">
|
||||
Or click "Update Now" to run the update automatically.
|
||||
{t('versionUpdate.manualUpgradeHint')}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
@@ -739,7 +741,7 @@ function AppContent() {
|
||||
onClick={() => setShowVersionModal(false)}
|
||||
className="flex-1 px-4 py-2 text-sm font-medium text-gray-700 dark:text-gray-300 bg-gray-100 dark:bg-gray-700 hover:bg-gray-200 dark:hover:bg-gray-600 rounded-md transition-colors"
|
||||
>
|
||||
{updateOutput ? 'Close' : 'Later'}
|
||||
{updateOutput ? t('versionUpdate.buttons.close') : t('versionUpdate.buttons.later')}
|
||||
</button>
|
||||
{!updateOutput && (
|
||||
<>
|
||||
@@ -749,7 +751,7 @@ function AppContent() {
|
||||
}}
|
||||
className="flex-1 px-4 py-2 text-sm font-medium text-gray-700 dark:text-gray-300 bg-gray-100 dark:bg-gray-700 hover:bg-gray-200 dark:hover:bg-gray-600 rounded-md transition-colors"
|
||||
>
|
||||
Copy Command
|
||||
{t('versionUpdate.buttons.copyCommand')}
|
||||
</button>
|
||||
<button
|
||||
onClick={handleUpdateNow}
|
||||
@@ -759,10 +761,10 @@ function AppContent() {
|
||||
{isUpdating ? (
|
||||
<>
|
||||
<div className="w-4 h-4 border-2 border-white border-t-transparent rounded-full animate-spin" />
|
||||
Updating...
|
||||
{t('versionUpdate.buttons.updating')}
|
||||
</>
|
||||
) : (
|
||||
'Update Now'
|
||||
t('versionUpdate.buttons.updateNow')
|
||||
)}
|
||||
</button>
|
||||
</>
|
||||
@@ -813,8 +815,8 @@ function AppContent() {
|
||||
<button
|
||||
onClick={() => setSidebarVisible(true)}
|
||||
className="p-2 hover:bg-accent rounded-md transition-colors duration-200 group"
|
||||
aria-label="Show sidebar"
|
||||
title="Show sidebar"
|
||||
aria-label={t('versionUpdate.ariaLabels.showSidebar')}
|
||||
title={t('versionUpdate.ariaLabels.showSidebar')}
|
||||
>
|
||||
<svg
|
||||
className="w-5 h-5 text-foreground group-hover:scale-110 transition-transform"
|
||||
@@ -830,8 +832,8 @@ function AppContent() {
|
||||
<button
|
||||
onClick={() => setShowSettings(true)}
|
||||
className="p-2 hover:bg-accent rounded-md transition-colors duration-200"
|
||||
aria-label="Settings"
|
||||
title="Settings"
|
||||
aria-label={t('versionUpdate.ariaLabels.settings')}
|
||||
title={t('versionUpdate.ariaLabels.settings')}
|
||||
>
|
||||
<SettingsIcon className="w-5 h-5 text-muted-foreground hover:text-foreground transition-colors" />
|
||||
</button>
|
||||
@@ -841,8 +843,8 @@ function AppContent() {
|
||||
<button
|
||||
onClick={() => setShowVersionModal(true)}
|
||||
className="relative p-2 hover:bg-accent rounded-md transition-colors duration-200"
|
||||
aria-label="Update available"
|
||||
title="Update available"
|
||||
aria-label={t('versionUpdate.ariaLabels.updateAvailable')}
|
||||
title={t('versionUpdate.ariaLabels.updateAvailable')}
|
||||
>
|
||||
<Sparkles className="w-5 h-5 text-blue-500" />
|
||||
<span className="absolute top-1 right-1 w-2 h-2 bg-blue-500 rounded-full animate-pulse" />
|
||||
@@ -870,7 +872,7 @@ function AppContent() {
|
||||
e.stopPropagation();
|
||||
setSidebarOpen(false);
|
||||
}}
|
||||
aria-label="Close sidebar"
|
||||
aria-label={t('versionUpdate.ariaLabels.closeSidebar')}
|
||||
/>
|
||||
<div
|
||||
className={`relative w-[85vw] max-w-sm sm:w-80 h-full bg-card border-r border-border transform transition-transform duration-150 ease-out ${
|
||||
|
||||
@@ -4789,16 +4789,16 @@ function ChatInterface({ selectedProject, selectedSession, ws, sendMessage, mess
|
||||
<div className="text-center text-gray-500 dark:text-gray-400 mt-8">
|
||||
<div className="flex items-center justify-center space-x-2">
|
||||
<div className="animate-spin rounded-full h-4 w-4 border-b-2 border-gray-400"></div>
|
||||
<p>Loading session messages...</p>
|
||||
<p>{t('session.loading.sessionMessages')}</p>
|
||||
</div>
|
||||
</div>
|
||||
) : chatMessages.length === 0 ? (
|
||||
<div className="flex items-center justify-center h-full">
|
||||
{!selectedSession && !currentSessionId && (
|
||||
<div className="text-center px-6 sm:px-4 py-8">
|
||||
<h2 className="text-2xl font-bold text-gray-900 dark:text-white mb-3">Choose Your AI Assistant</h2>
|
||||
<h2 className="text-2xl font-bold text-gray-900 dark:text-white mb-3">{t('providerSelection.title')}</h2>
|
||||
<p className="text-gray-600 dark:text-gray-400 mb-8">
|
||||
Select a provider to start a new conversation
|
||||
{t('providerSelection.description')}
|
||||
</p>
|
||||
|
||||
<div className="flex flex-col sm:flex-row gap-4 justify-center items-center mb-8">
|
||||
@@ -4820,7 +4820,7 @@ function ChatInterface({ selectedProject, selectedSession, ws, sendMessage, mess
|
||||
<ClaudeLogo className="w-10 h-10" />
|
||||
<div>
|
||||
<p className="font-semibold text-gray-900 dark:text-white">Claude</p>
|
||||
<p className="text-xs text-gray-500 dark:text-gray-400">by Anthropic</p>
|
||||
<p className="text-xs text-gray-500 dark:text-gray-400">{t('providerSelection.providerInfo.anthropic')}</p>
|
||||
</div>
|
||||
</div>
|
||||
{provider === 'claude' && (
|
||||
@@ -4852,7 +4852,7 @@ function ChatInterface({ selectedProject, selectedSession, ws, sendMessage, mess
|
||||
<CursorLogo className="w-10 h-10" />
|
||||
<div>
|
||||
<p className="font-semibold text-gray-900 dark:text-white">Cursor</p>
|
||||
<p className="text-xs text-gray-500 dark:text-gray-400">AI Code Editor</p>
|
||||
<p className="text-xs text-gray-500 dark:text-gray-400">{t('providerSelection.providerInfo.cursorEditor')}</p>
|
||||
</div>
|
||||
</div>
|
||||
{provider === 'cursor' && (
|
||||
@@ -4884,7 +4884,7 @@ function ChatInterface({ selectedProject, selectedSession, ws, sendMessage, mess
|
||||
<CodexLogo className="w-10 h-10" />
|
||||
<div>
|
||||
<p className="font-semibold text-gray-900 dark:text-white">Codex</p>
|
||||
<p className="text-xs text-gray-500 dark:text-gray-400">by OpenAI</p>
|
||||
<p className="text-xs text-gray-500 dark:text-gray-400">{t('providerSelection.providerInfo.openai')}</p>
|
||||
</div>
|
||||
</div>
|
||||
{provider === 'codex' && (
|
||||
@@ -4902,7 +4902,7 @@ function ChatInterface({ selectedProject, selectedSession, ws, sendMessage, mess
|
||||
{/* Model Selection - Always reserve space to prevent jumping */}
|
||||
<div className={`mb-6 transition-opacity duration-200 ${provider ? 'opacity-100' : 'opacity-0 pointer-events-none'}`}>
|
||||
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
||||
Select Model
|
||||
{t('providerSelection.selectModel')}
|
||||
</label>
|
||||
{provider === 'claude' ? (
|
||||
<select
|
||||
@@ -4952,12 +4952,12 @@ function ChatInterface({ selectedProject, selectedSession, ws, sendMessage, mess
|
||||
|
||||
<p className="text-sm text-gray-500 dark:text-gray-400">
|
||||
{provider === 'claude'
|
||||
? `Ready to use Claude with ${claudeModel}. Start typing your message below.`
|
||||
? t('providerSelection.readyPrompt.claude', { model: claudeModel })
|
||||
: provider === 'cursor'
|
||||
? `Ready to use Cursor with ${cursorModel}. Start typing your message below.`
|
||||
? t('providerSelection.readyPrompt.cursor', { model: cursorModel })
|
||||
: provider === 'codex'
|
||||
? `Ready to use Codex with ${codexModel}. Start typing your message below.`
|
||||
: 'Select a provider above to begin'
|
||||
? t('providerSelection.readyPrompt.codex', { model: codexModel })
|
||||
: t('providerSelection.readyPrompt.default')
|
||||
}
|
||||
</p>
|
||||
|
||||
@@ -4974,9 +4974,9 @@ function ChatInterface({ selectedProject, selectedSession, ws, sendMessage, mess
|
||||
)}
|
||||
{selectedSession && (
|
||||
<div className="text-center text-gray-500 dark:text-gray-400 px-6 sm:px-4">
|
||||
<p className="font-bold text-lg sm:text-xl mb-3">Continue your conversation</p>
|
||||
<p className="font-bold text-lg sm:text-xl mb-3">{t('session.continue.title')}</p>
|
||||
<p className="text-sm sm:text-base leading-relaxed">
|
||||
Ask questions about your code, request changes, or get help with development tasks
|
||||
{t('session.continue.description')}
|
||||
</p>
|
||||
|
||||
{/* Show NextTaskBanner for existing sessions too, only if TaskMaster is installed */}
|
||||
@@ -4998,7 +4998,7 @@ function ChatInterface({ selectedProject, selectedSession, ws, sendMessage, mess
|
||||
<div className="text-center text-gray-500 dark:text-gray-400 py-3">
|
||||
<div className="flex items-center justify-center space-x-2">
|
||||
<div className="animate-spin rounded-full h-4 w-4 border-b-2 border-gray-400"></div>
|
||||
<p className="text-sm">Loading older messages...</p>
|
||||
<p className="text-sm">{t('session.loading.olderMessages')}</p>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
@@ -5008,8 +5008,8 @@ function ChatInterface({ selectedProject, selectedSession, ws, sendMessage, mess
|
||||
<div className="text-center text-gray-500 dark:text-gray-400 text-sm py-2 border-b border-gray-200 dark:border-gray-700">
|
||||
{totalMessages > 0 && (
|
||||
<span>
|
||||
Showing {sessionMessages.length} of {totalMessages} messages •
|
||||
<span className="text-xs">Scroll up to load more</span>
|
||||
{t('session.messages.showingOf', { shown: sessionMessages.length, total: totalMessages })} •
|
||||
<span className="text-xs">{t('session.messages.scrollToLoad')}</span>
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
@@ -5018,12 +5018,12 @@ function ChatInterface({ selectedProject, selectedSession, ws, sendMessage, mess
|
||||
{/* Legacy message count indicator (for non-paginated view) */}
|
||||
{!hasMoreMessages && chatMessages.length > visibleMessageCount && (
|
||||
<div className="text-center text-gray-500 dark:text-gray-400 text-sm py-2 border-b border-gray-200 dark:border-gray-700">
|
||||
Showing last {visibleMessageCount} messages ({chatMessages.length} total) •
|
||||
<button
|
||||
{t('session.messages.showingLast', { count: visibleMessageCount, total: chatMessages.length })} •
|
||||
<button
|
||||
className="ml-1 text-blue-600 hover:text-blue-700 underline"
|
||||
onClick={loadEarlierMessages}
|
||||
>
|
||||
Load earlier messages
|
||||
{t('session.messages.loadEarlier')}
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -12,8 +12,10 @@ import { unifiedMergeView, getChunks } from '@codemirror/merge';
|
||||
import { showMinimap } from '@replit/codemirror-minimap';
|
||||
import { X, Save, Download, Maximize2, Minimize2 } from 'lucide-react';
|
||||
import { api } from '../utils/api';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
function CodeEditor({ file, onClose, projectPath, isSidebar = false, isExpanded = false, onToggleExpand = null }) {
|
||||
const { t } = useTranslation('codeEditor');
|
||||
const [content, setContent] = useState('');
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [saving, setSaving] = useState(false);
|
||||
@@ -125,13 +127,13 @@ function CodeEditor({ file, onClose, projectPath, isSidebar = false, isExpanded
|
||||
toolbarHTML += '<div style="display: flex; align-items: center; gap: 8px;">';
|
||||
if (hasDiff) {
|
||||
toolbarHTML += `
|
||||
<span style="font-weight: 500;">${chunkCount > 0 ? `${currentIndex + 1}/${chunkCount}` : '0'} changes</span>
|
||||
<button class="cm-diff-nav-btn cm-diff-nav-prev" title="Previous change" ${chunkCount === 0 ? 'disabled' : ''}>
|
||||
<span style="font-weight: 500;">${chunkCount > 0 ? `${currentIndex + 1}/${chunkCount}` : '0'} ${t('toolbar.changes')}</span>
|
||||
<button class="cm-diff-nav-btn cm-diff-nav-prev" title="${t('toolbar.previousChange')}" ${chunkCount === 0 ? 'disabled' : ''}>
|
||||
<svg width="16" height="16" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 15l7-7 7 7" />
|
||||
</svg>
|
||||
</button>
|
||||
<button class="cm-diff-nav-btn cm-diff-nav-next" title="Next change" ${chunkCount === 0 ? 'disabled' : ''}>
|
||||
<button class="cm-diff-nav-btn cm-diff-nav-next" title="${t('toolbar.nextChange')}" ${chunkCount === 0 ? 'disabled' : ''}>
|
||||
<svg width="16" height="16" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
|
||||
</svg>
|
||||
@@ -146,7 +148,7 @@ function CodeEditor({ file, onClose, projectPath, isSidebar = false, isExpanded
|
||||
// Show/hide diff button (only if there's diff info)
|
||||
if (file.diffInfo) {
|
||||
toolbarHTML += `
|
||||
<button class="cm-toolbar-btn cm-toggle-diff-btn" title="${showDiff ? 'Hide diff highlighting' : 'Show diff highlighting'}">
|
||||
<button class="cm-toolbar-btn cm-toggle-diff-btn" title="${showDiff ? t('toolbar.hideDiff') : t('toolbar.showDiff')}">
|
||||
<svg width="16" height="16" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
${showDiff ?
|
||||
'<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13.875 18.825A10.05 10.05 0 0112 19c-4.478 0-8.268-2.943-9.543-7a9.97 9.97 0 011.563-3.029m5.858.908a3 3 0 114.243 4.243M9.878 9.878l4.242 4.242M9.88 9.88l-3.29-3.29m7.532 7.532l3.29 3.29M3 3l3.59 3.59m0 0A9.953 9.953 0 0112 5c4.478 0 8.268 2.943 9.543 7a10.025 10.025 0 01-4.132 5.411m0 0L21 21" />' :
|
||||
@@ -159,7 +161,7 @@ function CodeEditor({ file, onClose, projectPath, isSidebar = false, isExpanded
|
||||
|
||||
// Settings button
|
||||
toolbarHTML += `
|
||||
<button class="cm-toolbar-btn cm-settings-btn" title="Editor Settings">
|
||||
<button class="cm-toolbar-btn cm-settings-btn" title="${t('toolbar.settings')}">
|
||||
<svg width="16" height="16" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z" /><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
|
||||
</svg>
|
||||
@@ -169,7 +171,7 @@ function CodeEditor({ file, onClose, projectPath, isSidebar = false, isExpanded
|
||||
// Expand button (only in sidebar mode)
|
||||
if (isSidebar && onToggleExpand) {
|
||||
toolbarHTML += `
|
||||
<button class="cm-toolbar-btn cm-expand-btn" title="${isExpanded ? 'Collapse editor' : 'Expand editor to full width'}">
|
||||
<button class="cm-toolbar-btn cm-expand-btn" title="${isExpanded ? t('toolbar.collapse') : t('toolbar.expand')}">
|
||||
<svg width="16" height="16" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
${isExpanded ?
|
||||
'<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 9V4.5M9 9H4.5M9 9L3.75 3.75M9 15v4.5M9 15H4.5M9 15l-5.25 5.25M15 9h4.5M15 9V4.5M15 9l5.25-5.25M15 15h4.5M15 15v4.5m0-4.5l5.25 5.25" />' :
|
||||
@@ -463,7 +465,7 @@ function CodeEditor({ file, onClose, projectPath, isSidebar = false, isExpanded
|
||||
<div className="w-full h-full flex items-center justify-center bg-background">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="animate-spin rounded-full h-6 w-6 border-b-2 border-blue-600"></div>
|
||||
<span className="text-gray-900 dark:text-white">Loading {file.name}...</span>
|
||||
<span className="text-gray-900 dark:text-white">{t('loading', { fileName: file.name })}</span>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
@@ -471,7 +473,7 @@ function CodeEditor({ file, onClose, projectPath, isSidebar = false, isExpanded
|
||||
<div className="code-editor-loading w-full h-full md:rounded-lg md:w-auto md:h-auto p-8 flex items-center justify-center">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="animate-spin rounded-full h-6 w-6 border-b-2 border-blue-600"></div>
|
||||
<span className="text-gray-900 dark:text-white">Loading {file.name}...</span>
|
||||
<span className="text-gray-900 dark:text-white">{t('loading', { fileName: file.name })}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -574,7 +576,7 @@ function CodeEditor({ file, onClose, projectPath, isSidebar = false, isExpanded
|
||||
<h3 className="font-medium text-gray-900 dark:text-white truncate">{file.name}</h3>
|
||||
{file.diffInfo && (
|
||||
<span className="text-xs bg-blue-100 dark:bg-blue-900 text-blue-600 dark:text-blue-300 px-2 py-1 rounded whitespace-nowrap">
|
||||
Showing changes
|
||||
{t('header.showingChanges')}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
@@ -586,7 +588,7 @@ function CodeEditor({ file, onClose, projectPath, isSidebar = false, isExpanded
|
||||
<button
|
||||
onClick={handleDownload}
|
||||
className="p-2 md:p-2 text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white rounded-md hover:bg-gray-100 dark:hover:bg-gray-800 min-w-[44px] min-h-[44px] md:min-w-0 md:min-h-0 flex items-center justify-center"
|
||||
title="Download file"
|
||||
title={t('actions.download')}
|
||||
>
|
||||
<Download className="w-5 h-5 md:w-4 md:h-4" />
|
||||
</button>
|
||||
@@ -605,12 +607,12 @@ function CodeEditor({ file, onClose, projectPath, isSidebar = false, isExpanded
|
||||
<svg className="w-5 h-5 md:w-4 md:h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 13l4 4L19 7" />
|
||||
</svg>
|
||||
<span className="hidden sm:inline">Saved!</span>
|
||||
<span className="hidden sm:inline">{t('actions.saved')}</span>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Save className="w-5 h-5 md:w-4 md:h-4" />
|
||||
<span className="hidden sm:inline">{saving ? 'Saving...' : 'Save'}</span>
|
||||
<span className="hidden sm:inline">{saving ? t('actions.saving') : t('actions.save')}</span>
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
@@ -619,7 +621,7 @@ function CodeEditor({ file, onClose, projectPath, isSidebar = false, isExpanded
|
||||
<button
|
||||
onClick={toggleFullscreen}
|
||||
className="hidden md:flex p-2 text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white rounded-md hover:bg-gray-100 dark:hover:bg-gray-800 items-center justify-center"
|
||||
title={isFullscreen ? 'Exit fullscreen' : 'Fullscreen'}
|
||||
title={isFullscreen ? t('actions.exitFullscreen') : t('actions.fullscreen')}
|
||||
>
|
||||
{isFullscreen ? <Minimize2 className="w-4 h-4" /> : <Maximize2 className="w-4 h-4" />}
|
||||
</button>
|
||||
@@ -628,7 +630,7 @@ function CodeEditor({ file, onClose, projectPath, isSidebar = false, isExpanded
|
||||
<button
|
||||
onClick={onClose}
|
||||
className="p-2 md:p-2 text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white rounded-md hover:bg-gray-100 dark:hover:bg-gray-800 min-w-[44px] min-h-[44px] md:min-w-0 md:min-h-0 flex items-center justify-center"
|
||||
title="Close"
|
||||
title={t('actions.close')}
|
||||
>
|
||||
<X className="w-6 h-6 md:w-4 md:h-4" />
|
||||
</button>
|
||||
@@ -686,12 +688,12 @@ function CodeEditor({ file, onClose, projectPath, isSidebar = false, isExpanded
|
||||
{/* Footer */}
|
||||
<div className="flex items-center justify-between p-3 border-t border-border bg-muted flex-shrink-0">
|
||||
<div className="flex items-center gap-4 text-sm text-gray-600 dark:text-gray-400">
|
||||
<span>Lines: {content.split('\n').length}</span>
|
||||
<span>Characters: {content.length}</span>
|
||||
<span>{t('footer.lines')} {content.split('\n').length}</span>
|
||||
<span>{t('footer.characters')} {content.length}</span>
|
||||
</div>
|
||||
|
||||
<div className="text-sm text-gray-500 dark:text-gray-400">
|
||||
Press Ctrl+S to save • Esc to close
|
||||
{t('footer.shortcuts')}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -233,8 +233,8 @@ const QuickSettingsPanel = ({
|
||||
isDragging ? 'cursor-grabbing' : 'cursor-pointer'
|
||||
} touch-none`}
|
||||
style={{ ...getPositionStyle(), touchAction: 'none', WebkitTouchCallout: 'none', WebkitUserSelect: 'none' }}
|
||||
aria-label={isDragging ? 'Dragging handle' : localIsOpen ? 'Close settings panel' : 'Open settings panel'}
|
||||
title={isDragging ? 'Dragging...' : 'Click to toggle, drag to move'}
|
||||
aria-label={isDragging ? t('quickSettings.dragHandle.dragging') : localIsOpen ? t('quickSettings.dragHandle.closePanel') : t('quickSettings.dragHandle.openPanel')}
|
||||
title={isDragging ? t('quickSettings.dragHandle.draggingStatus') : t('quickSettings.dragHandle.toggleAndMove')}
|
||||
>
|
||||
{isDragging ? (
|
||||
<GripVertical className="h-5 w-5 text-blue-500 dark:text-blue-400" />
|
||||
@@ -383,10 +383,10 @@ const QuickSettingsPanel = ({
|
||||
<div className="ml-3 flex-1">
|
||||
<span className="flex items-center gap-2 text-sm font-medium text-gray-900 dark:text-white">
|
||||
<Mic className="h-4 w-4 text-gray-600 dark:text-gray-400" />
|
||||
Default Mode
|
||||
{t('quickSettings.whisper.modes.default')}
|
||||
</span>
|
||||
<p className="text-xs text-gray-500 dark:text-gray-400 mt-1">
|
||||
Direct transcription of your speech
|
||||
{t('quickSettings.whisper.modes.defaultDescription')}
|
||||
</p>
|
||||
</div>
|
||||
</label>
|
||||
@@ -407,10 +407,10 @@ const QuickSettingsPanel = ({
|
||||
<div className="ml-3 flex-1">
|
||||
<span className="flex items-center gap-2 text-sm font-medium text-gray-900 dark:text-white">
|
||||
<Sparkles className="h-4 w-4 text-gray-600 dark:text-gray-400" />
|
||||
Prompt Enhancement
|
||||
{t('quickSettings.whisper.modes.prompt')}
|
||||
</span>
|
||||
<p className="text-xs text-gray-500 dark:text-gray-400 mt-1">
|
||||
Transform rough ideas into clear, detailed AI prompts
|
||||
{t('quickSettings.whisper.modes.promptDescription')}
|
||||
</p>
|
||||
</div>
|
||||
</label>
|
||||
@@ -431,10 +431,10 @@ const QuickSettingsPanel = ({
|
||||
<div className="ml-3 flex-1">
|
||||
<span className="flex items-center gap-2 text-sm font-medium text-gray-900 dark:text-white">
|
||||
<FileText className="h-4 w-4 text-gray-600 dark:text-gray-400" />
|
||||
Vibe Mode
|
||||
{t('quickSettings.whisper.modes.vibe')}
|
||||
</span>
|
||||
<p className="text-xs text-gray-500 dark:text-gray-400 mt-1">
|
||||
Format ideas as clear agent instructions with details
|
||||
{t('quickSettings.whisper.modes.vibeDescription')}
|
||||
</p>
|
||||
</div>
|
||||
</label>
|
||||
|
||||
@@ -4,6 +4,7 @@ import { FitAddon } from '@xterm/addon-fit';
|
||||
import { WebglAddon } from '@xterm/addon-webgl';
|
||||
import { WebLinksAddon } from '@xterm/addon-web-links';
|
||||
import '@xterm/xterm/css/xterm.css';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
const xtermStyles = `
|
||||
.xterm .xterm-screen {
|
||||
@@ -25,6 +26,7 @@ if (typeof document !== 'undefined') {
|
||||
}
|
||||
|
||||
function Shell({ selectedProject, selectedSession, initialCommand, isPlainShell = false, onProcessComplete, minimal = false, autoConnect = false }) {
|
||||
const { t } = useTranslation('chat');
|
||||
const terminalRef = useRef(null);
|
||||
const terminal = useRef(null);
|
||||
const fitAddon = useRef(null);
|
||||
@@ -373,8 +375,8 @@ function Shell({ selectedProject, selectedSession, initialCommand, isPlainShell
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M8 9l3 3-3 3m5 0h3M5 20h14a2 2 0 002-2V6a2 2 0 00-2-2H5a2 2 0 00-2 2v14a2 2 0 002 2z" />
|
||||
</svg>
|
||||
</div>
|
||||
<h3 className="text-lg font-semibold mb-2">Select a Project</h3>
|
||||
<p>Choose a project to open an interactive shell in that directory</p>
|
||||
<h3 className="text-lg font-semibold mb-2">{t('shell.selectProject.title')}</h3>
|
||||
<p>{t('shell.selectProject.description')}</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@@ -400,13 +402,13 @@ function Shell({ selectedProject, selectedSession, initialCommand, isPlainShell
|
||||
</span>
|
||||
)}
|
||||
{!selectedSession && (
|
||||
<span className="text-xs text-gray-400">(New Session)</span>
|
||||
<span className="text-xs text-gray-400">{t('shell.status.newSession')}</span>
|
||||
)}
|
||||
{!isInitialized && (
|
||||
<span className="text-xs text-yellow-400">(Initializing...)</span>
|
||||
<span className="text-xs text-yellow-400">{t('shell.status.initializing')}</span>
|
||||
)}
|
||||
{isRestarting && (
|
||||
<span className="text-xs text-blue-400">(Restarting...)</span>
|
||||
<span className="text-xs text-blue-400">{t('shell.status.restarting')}</span>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex items-center space-x-3">
|
||||
@@ -414,12 +416,12 @@ function Shell({ selectedProject, selectedSession, initialCommand, isPlainShell
|
||||
<button
|
||||
onClick={disconnectFromShell}
|
||||
className="px-3 py-1 text-xs bg-red-600 text-white rounded hover:bg-red-700 flex items-center space-x-1"
|
||||
title="Disconnect from shell"
|
||||
title={t('shell.actions.disconnectTitle')}
|
||||
>
|
||||
<svg className="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
|
||||
</svg>
|
||||
<span>Disconnect</span>
|
||||
<span>{t('shell.actions.disconnect')}</span>
|
||||
</button>
|
||||
)}
|
||||
|
||||
@@ -427,12 +429,12 @@ function Shell({ selectedProject, selectedSession, initialCommand, isPlainShell
|
||||
onClick={restartShell}
|
||||
disabled={isRestarting || isConnected}
|
||||
className="text-xs text-gray-400 hover:text-white disabled:opacity-50 disabled:cursor-not-allowed flex items-center space-x-1"
|
||||
title="Restart Shell (disconnect first)"
|
||||
title={t('shell.actions.restartTitle')}
|
||||
>
|
||||
<svg className="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" />
|
||||
</svg>
|
||||
<span>Restart</span>
|
||||
<span>{t('shell.actions.restart')}</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -443,7 +445,7 @@ function Shell({ selectedProject, selectedSession, initialCommand, isPlainShell
|
||||
|
||||
{!isInitialized && (
|
||||
<div className="absolute inset-0 flex items-center justify-center bg-gray-900 bg-opacity-90">
|
||||
<div className="text-white">Loading terminal...</div>
|
||||
<div className="text-white">{t('shell.loading')}</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -453,19 +455,19 @@ function Shell({ selectedProject, selectedSession, initialCommand, isPlainShell
|
||||
<button
|
||||
onClick={connectToShell}
|
||||
className="px-6 py-3 bg-green-600 text-white rounded-lg hover:bg-green-700 transition-colors flex items-center justify-center space-x-2 text-base font-medium w-full sm:w-auto"
|
||||
title="Connect to shell"
|
||||
title={t('shell.actions.connectTitle')}
|
||||
>
|
||||
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M13 10V3L4 14h7v7l9-11h-7z" />
|
||||
</svg>
|
||||
<span>Continue in Shell</span>
|
||||
<span>{t('shell.actions.connect')}</span>
|
||||
</button>
|
||||
<p className="text-gray-400 text-sm mt-3 px-2">
|
||||
{isPlainShell ?
|
||||
`Run ${initialCommand || 'command'} in ${selectedProject.displayName}` :
|
||||
t('shell.runCommand', { command: initialCommand || t('shell.defaultCommand'), projectName: selectedProject.displayName }) :
|
||||
selectedSession ?
|
||||
`Resume session: ${sessionDisplayNameLong}...` :
|
||||
'Start a new Claude session'
|
||||
t('shell.resumeSession', { displayName: sessionDisplayNameLong }) :
|
||||
t('shell.startSession')
|
||||
}
|
||||
</p>
|
||||
</div>
|
||||
@@ -477,12 +479,12 @@ function Shell({ selectedProject, selectedSession, initialCommand, isPlainShell
|
||||
<div className="text-center max-w-sm w-full">
|
||||
<div className="flex items-center justify-center space-x-3 text-yellow-400">
|
||||
<div className="w-6 h-6 animate-spin rounded-full border-2 border-yellow-400 border-t-transparent"></div>
|
||||
<span className="text-base font-medium">Connecting to shell...</span>
|
||||
<span className="text-base font-medium">{t('shell.connecting')}</span>
|
||||
</div>
|
||||
<p className="text-gray-400 text-sm mt-3 px-2">
|
||||
{isPlainShell ?
|
||||
`Running ${initialCommand || 'command'} in ${selectedProject.displayName}` :
|
||||
`Starting Claude CLI in ${selectedProject.displayName}`
|
||||
t('shell.runCommand', { command: initialCommand || t('shell.defaultCommand'), projectName: selectedProject.displayName }) :
|
||||
t('shell.startCli', { projectName: selectedProject.displayName })
|
||||
}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -795,7 +795,7 @@ function Sidebar({
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{t('projects.fetchingProjects')}
|
||||
</p>
|
||||
<h3 className="text-base font-medium text-foreground mb-2 md:mb-1">Loading projects...</h3>
|
||||
<h3 className="text-base font-medium text-foreground mb-2 md:mb-1">{t('projects.loadingProjects')}</h3>
|
||||
{loadingProgress && loadingProgress.total > 0 ? (
|
||||
<div className="space-y-2">
|
||||
<div className="w-full bg-muted rounded-full h-2 overflow-hidden">
|
||||
@@ -805,7 +805,7 @@ function Sidebar({
|
||||
/>
|
||||
</div>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{loadingProgress.current}/{loadingProgress.total} projects
|
||||
{loadingProgress.current}/{loadingProgress.total} {t('projects.projects')}
|
||||
</p>
|
||||
{loadingProgress.currentProject && (
|
||||
<p className="text-xs text-muted-foreground/70 truncate max-w-[200px] mx-auto" title={loadingProgress.currentProject}>
|
||||
@@ -815,7 +815,7 @@ function Sidebar({
|
||||
</div>
|
||||
) : (
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Fetching your Claude projects and sessions
|
||||
{t('projects.fetchingProjects')}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -19,12 +19,14 @@ import enSettings from './locales/en/settings.json';
|
||||
import enAuth from './locales/en/auth.json';
|
||||
import enSidebar from './locales/en/sidebar.json';
|
||||
import enChat from './locales/en/chat.json';
|
||||
import enCodeEditor from './locales/en/codeEditor.json';
|
||||
|
||||
import zhCommon from './locales/zh-CN/common.json';
|
||||
import zhSettings from './locales/zh-CN/settings.json';
|
||||
import zhAuth from './locales/zh-CN/auth.json';
|
||||
import zhSidebar from './locales/zh-CN/sidebar.json';
|
||||
import zhChat from './locales/zh-CN/chat.json';
|
||||
import zhCodeEditor from './locales/zh-CN/codeEditor.json';
|
||||
|
||||
// Import supported languages configuration
|
||||
import { languages } from './languages.js';
|
||||
@@ -56,6 +58,7 @@ i18n
|
||||
auth: enAuth,
|
||||
sidebar: enSidebar,
|
||||
chat: enChat,
|
||||
codeEditor: enCodeEditor,
|
||||
},
|
||||
'zh-CN': {
|
||||
common: zhCommon,
|
||||
@@ -63,6 +66,7 @@ i18n
|
||||
auth: zhAuth,
|
||||
sidebar: zhSidebar,
|
||||
chat: zhChat,
|
||||
codeEditor: zhCodeEditor,
|
||||
},
|
||||
},
|
||||
|
||||
@@ -76,7 +80,7 @@ i18n
|
||||
debug: import.meta.env.DEV,
|
||||
|
||||
// Namespaces - load only what's needed
|
||||
ns: ['common', 'settings', 'auth', 'sidebar', 'chat'],
|
||||
ns: ['common', 'settings', 'auth', 'sidebar', 'chat', 'codeEditor'],
|
||||
defaultNS: 'common',
|
||||
|
||||
// Key separator for nested keys (default: '.')
|
||||
|
||||
@@ -143,5 +143,63 @@
|
||||
}
|
||||
},
|
||||
"buttonTitle": "Thinking mode: {{mode}}"
|
||||
},
|
||||
"providerSelection": {
|
||||
"title": "Choose Your AI Assistant",
|
||||
"description": "Select a provider to start a new conversation",
|
||||
"selectModel": "Select Model",
|
||||
"providerInfo": {
|
||||
"anthropic": "by Anthropic",
|
||||
"openai": "by OpenAI",
|
||||
"cursorEditor": "AI Code Editor"
|
||||
},
|
||||
"readyPrompt": {
|
||||
"claude": "Ready to use Claude with {{model}}. Start typing your message below.",
|
||||
"cursor": "Ready to use Cursor with {{model}}. Start typing your message below.",
|
||||
"codex": "Ready to use Codex with {{model}}. Start typing your message below.",
|
||||
"default": "Select a provider above to begin"
|
||||
}
|
||||
},
|
||||
"session": {
|
||||
"continue": {
|
||||
"title": "Continue your conversation",
|
||||
"description": "Ask questions about your code, request changes, or get help with development tasks"
|
||||
},
|
||||
"loading": {
|
||||
"olderMessages": "Loading older messages...",
|
||||
"sessionMessages": "Loading session messages..."
|
||||
},
|
||||
"messages": {
|
||||
"showingOf": "Showing {{shown}} of {{total}} messages",
|
||||
"scrollToLoad": "Scroll up to load more",
|
||||
"showingLast": "Showing last {{count}} messages ({{total}} total)",
|
||||
"loadEarlier": "Load earlier messages"
|
||||
}
|
||||
},
|
||||
"shell": {
|
||||
"selectProject": {
|
||||
"title": "Select a Project",
|
||||
"description": "Choose a project to open an interactive shell in that directory"
|
||||
},
|
||||
"status": {
|
||||
"newSession": "New Session",
|
||||
"initializing": "Initializing...",
|
||||
"restarting": "Restarting..."
|
||||
},
|
||||
"actions": {
|
||||
"disconnect": "Disconnect",
|
||||
"disconnectTitle": "Disconnect from shell",
|
||||
"restart": "Restart",
|
||||
"restartTitle": "Restart Shell (disconnect first)",
|
||||
"connect": "Continue in Shell",
|
||||
"connectTitle": "Connect to shell"
|
||||
},
|
||||
"loading": "Loading terminal...",
|
||||
"connecting": "Connecting to shell...",
|
||||
"startSession": "Start a new Claude session",
|
||||
"resumeSession": "Resume session: {{displayName}}...",
|
||||
"runCommand": "Run {{command}} in {{projectName}}",
|
||||
"startCli": "Starting Claude CLI in {{projectName}}",
|
||||
"defaultCommand": "command"
|
||||
}
|
||||
}
|
||||
|
||||
30
src/i18n/locales/en/codeEditor.json
Normal file
30
src/i18n/locales/en/codeEditor.json
Normal file
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"toolbar": {
|
||||
"changes": "changes",
|
||||
"previousChange": "Previous change",
|
||||
"nextChange": "Next change",
|
||||
"hideDiff": "Hide diff highlighting",
|
||||
"showDiff": "Show diff highlighting",
|
||||
"settings": "Editor Settings",
|
||||
"collapse": "Collapse editor",
|
||||
"expand": "Expand editor to full width"
|
||||
},
|
||||
"loading": "Loading {{fileName}}...",
|
||||
"header": {
|
||||
"showingChanges": "Showing changes"
|
||||
},
|
||||
"actions": {
|
||||
"download": "Download file",
|
||||
"save": "Save",
|
||||
"saving": "Saving...",
|
||||
"saved": "Saved!",
|
||||
"exitFullscreen": "Exit fullscreen",
|
||||
"fullscreen": "Fullscreen",
|
||||
"close": "Close"
|
||||
},
|
||||
"footer": {
|
||||
"lines": "Lines:",
|
||||
"characters": "Characters:",
|
||||
"shortcuts": "Press Ctrl+S to save • Esc to close"
|
||||
}
|
||||
}
|
||||
@@ -186,5 +186,33 @@
|
||||
"providePath": "Please provide a workspace path",
|
||||
"failedToCreate": "Failed to create workspace"
|
||||
}
|
||||
},
|
||||
"versionUpdate": {
|
||||
"title": "Update Available",
|
||||
"newVersionReady": "A new version is ready",
|
||||
"currentVersion": "Current Version",
|
||||
"latestVersion": "Latest Version",
|
||||
"whatsNew": "What's New:",
|
||||
"viewFullRelease": "View full release",
|
||||
"updateProgress": "Update Progress:",
|
||||
"manualUpgrade": "Manual upgrade:",
|
||||
"manualUpgradeHint": "Or click \"Update Now\" to run the update automatically.",
|
||||
"updateCompleted": "Update completed successfully!",
|
||||
"restartServer": "Please restart the server to apply changes.",
|
||||
"updateFailed": "Update failed",
|
||||
"buttons": {
|
||||
"close": "Close",
|
||||
"later": "Later",
|
||||
"copyCommand": "Copy Command",
|
||||
"updateNow": "Update Now",
|
||||
"updating": "Updating..."
|
||||
},
|
||||
"ariaLabels": {
|
||||
"closeModal": "Close version upgrade modal",
|
||||
"showSidebar": "Show sidebar",
|
||||
"settings": "Settings",
|
||||
"updateAvailable": "Update available",
|
||||
"closeSidebar": "Close sidebar"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,7 +64,24 @@
|
||||
"showThinking": "Show thinking",
|
||||
"autoScrollToBottom": "Auto-scroll to bottom",
|
||||
"sendByCtrlEnter": "Send by Ctrl+Enter",
|
||||
"sendByCtrlEnterDescription": "When enabled, pressing Ctrl+Enter will send the message instead of just Enter. This is useful for IME users to avoid accidental sends."
|
||||
"sendByCtrlEnterDescription": "When enabled, pressing Ctrl+Enter will send the message instead of just Enter. This is useful for IME users to avoid accidental sends.",
|
||||
"dragHandle": {
|
||||
"dragging": "Dragging handle",
|
||||
"closePanel": "Close settings panel",
|
||||
"openPanel": "Open settings panel",
|
||||
"draggingStatus": "Dragging...",
|
||||
"toggleAndMove": "Click to toggle, drag to move"
|
||||
},
|
||||
"whisper": {
|
||||
"modes": {
|
||||
"default": "Default Mode",
|
||||
"defaultDescription": "Direct transcription of your speech",
|
||||
"prompt": "Prompt Enhancement",
|
||||
"promptDescription": "Transform rough ideas into clear, detailed AI prompts",
|
||||
"vibe": "Vibe Mode",
|
||||
"vibeDescription": "Format ideas as clear agent instructions with details"
|
||||
}
|
||||
}
|
||||
},
|
||||
"mainTabs": {
|
||||
"agents": "Agents",
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
"newSession": "New Session",
|
||||
"codexSession": "Codex Session",
|
||||
"fetchingProjects": "Fetching your Claude projects and sessions",
|
||||
"projects": "projects",
|
||||
"noMatchingProjects": "No matching projects",
|
||||
"tryDifferentSearch": "Try adjusting your search term",
|
||||
"runClaudeCli": "Run Claude CLI in a project directory to get started"
|
||||
|
||||
@@ -143,5 +143,63 @@
|
||||
}
|
||||
},
|
||||
"buttonTitle": "思考模式:{{mode}}"
|
||||
},
|
||||
"providerSelection": {
|
||||
"title": "选择您的 AI 助手",
|
||||
"description": "选择一个供应商以开始新对话",
|
||||
"selectModel": "选择模型",
|
||||
"providerInfo": {
|
||||
"anthropic": "Anthropic",
|
||||
"openai": "OpenAI",
|
||||
"cursorEditor": "AI 代码编辑器"
|
||||
},
|
||||
"readyPrompt": {
|
||||
"claude": "已准备好使用 Claude {{model}}。在下方输入您的消息。",
|
||||
"cursor": "已准备好使用 Cursor {{model}}。在下方输入您的消息。",
|
||||
"codex": "已准备好使用 Codex {{model}}。在下方输入您的消息。",
|
||||
"default": "请在上方选择一个供应商以开始"
|
||||
}
|
||||
},
|
||||
"session": {
|
||||
"continue": {
|
||||
"title": "继续您的对话",
|
||||
"description": "询问有关代码的问题、请求更改或获取开发任务的帮助"
|
||||
},
|
||||
"loading": {
|
||||
"olderMessages": "正在加载更早的消息...",
|
||||
"sessionMessages": "正在加载会话消息..."
|
||||
},
|
||||
"messages": {
|
||||
"showingOf": "显示 {{shown}} / {{total}} 条消息",
|
||||
"scrollToLoad": "向上滚动以加载更多",
|
||||
"showingLast": "显示最近 {{count}} 条消息(共 {{total}} 条)",
|
||||
"loadEarlier": "加载更早的消息"
|
||||
}
|
||||
},
|
||||
"shell": {
|
||||
"selectProject": {
|
||||
"title": "选择项目",
|
||||
"description": "选择一个项目以在该目录中打开交互式 Shell"
|
||||
},
|
||||
"status": {
|
||||
"newSession": "新会话",
|
||||
"initializing": "初始化中...",
|
||||
"restarting": "重启中..."
|
||||
},
|
||||
"actions": {
|
||||
"disconnect": "断开连接",
|
||||
"disconnectTitle": "断开 Shell 连接",
|
||||
"restart": "重启",
|
||||
"restartTitle": "重启 Shell(请先断开连接)",
|
||||
"connect": "在 Shell 中继续",
|
||||
"connectTitle": "连接到 Shell"
|
||||
},
|
||||
"loading": "正在加载终端...",
|
||||
"connecting": "正在连接到 Shell...",
|
||||
"startSession": "启动新的 Claude 会话",
|
||||
"resumeSession": "恢复会话:{{displayName}}...",
|
||||
"runCommand": "在 {{projectName}} 中运行 {{command}}",
|
||||
"startCli": "在 {{projectName}} 中启动 Claude CLI",
|
||||
"defaultCommand": "命令"
|
||||
}
|
||||
}
|
||||
|
||||
30
src/i18n/locales/zh-CN/codeEditor.json
Normal file
30
src/i18n/locales/zh-CN/codeEditor.json
Normal file
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"toolbar": {
|
||||
"changes": "个更改",
|
||||
"previousChange": "上一个更改",
|
||||
"nextChange": "下一个更改",
|
||||
"hideDiff": "隐藏差异高亮",
|
||||
"showDiff": "显示差异高亮",
|
||||
"settings": "编辑器设置",
|
||||
"collapse": "折叠编辑器",
|
||||
"expand": "展开编辑器到全宽"
|
||||
},
|
||||
"loading": "正在加载 {{fileName}}...",
|
||||
"header": {
|
||||
"showingChanges": "显示更改"
|
||||
},
|
||||
"actions": {
|
||||
"download": "下载文件",
|
||||
"save": "保存",
|
||||
"saving": "保存中...",
|
||||
"saved": "已保存!",
|
||||
"exitFullscreen": "退出全屏",
|
||||
"fullscreen": "全屏",
|
||||
"close": "关闭"
|
||||
},
|
||||
"footer": {
|
||||
"lines": "行数:",
|
||||
"characters": "字符数:",
|
||||
"shortcuts": "按 Ctrl+S 保存 • Esc 关闭"
|
||||
}
|
||||
}
|
||||
@@ -186,5 +186,33 @@
|
||||
"providePath": "请提供工作区路径",
|
||||
"failedToCreate": "创建工作区失败"
|
||||
}
|
||||
},
|
||||
"versionUpdate": {
|
||||
"title": "有可用更新",
|
||||
"newVersionReady": "新版本已准备就绪",
|
||||
"currentVersion": "当前版本",
|
||||
"latestVersion": "最新版本",
|
||||
"whatsNew": "新内容:",
|
||||
"viewFullRelease": "查看完整发布",
|
||||
"updateProgress": "更新进度:",
|
||||
"manualUpgrade": "手动升级:",
|
||||
"manualUpgradeHint": "或点击'立即更新'以自动运行更新。",
|
||||
"updateCompleted": "更新成功完成!",
|
||||
"restartServer": "请重启服务器以应用更改。",
|
||||
"updateFailed": "更新失败",
|
||||
"buttons": {
|
||||
"close": "关闭",
|
||||
"later": "稍后",
|
||||
"copyCommand": "复制命令",
|
||||
"updateNow": "立即更新",
|
||||
"updating": "更新中..."
|
||||
},
|
||||
"ariaLabels": {
|
||||
"closeModal": "关闭版本升级模态框",
|
||||
"showSidebar": "显示侧边栏",
|
||||
"settings": "设置",
|
||||
"updateAvailable": "有可用更新",
|
||||
"closeSidebar": "关闭侧边栏"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,7 +64,24 @@
|
||||
"showThinking": "显示思考过程",
|
||||
"autoScrollToBottom": "自动滚动到底部",
|
||||
"sendByCtrlEnter": "使用 Ctrl+Enter 发送",
|
||||
"sendByCtrlEnterDescription": "启用后,按 Ctrl+Enter 发送消息,而不是仅按 Enter。这对于使用输入法的用户可以避免意外发送。"
|
||||
"sendByCtrlEnterDescription": "启用后,按 Ctrl+Enter 发送消息,而不是仅按 Enter。这对于使用输入法的用户可以避免意外发送。",
|
||||
"dragHandle": {
|
||||
"dragging": "正在拖拽手柄",
|
||||
"closePanel": "关闭设置面板",
|
||||
"openPanel": "打开设置面板",
|
||||
"draggingStatus": "正在拖拽...",
|
||||
"toggleAndMove": "点击切换,拖拽移动"
|
||||
},
|
||||
"whisper": {
|
||||
"modes": {
|
||||
"default": "默认模式",
|
||||
"defaultDescription": "直接转录您的语音",
|
||||
"prompt": "提示词增强",
|
||||
"promptDescription": "将粗略的想法转化为清晰、详细的 AI 提示词",
|
||||
"vibe": "Vibe 模式",
|
||||
"vibeDescription": "将想法格式化为带有详细说明的清晰智能体指令"
|
||||
}
|
||||
}
|
||||
},
|
||||
"mainTabs": {
|
||||
"agents": "智能体",
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
"newSession": "新会话",
|
||||
"codexSession": "Codex 会话",
|
||||
"fetchingProjects": "正在获取您的 Claude 项目和会话",
|
||||
"projects": "项目",
|
||||
"noMatchingProjects": "未找到匹配的项目",
|
||||
"tryDifferentSearch": "尝试调整您的搜索词",
|
||||
"runClaudeCli": "在项目目录中运行 Claude CLI 以开始使用"
|
||||
|
||||
Reference in New Issue
Block a user