汉化
This commit is contained in:
@@ -5,6 +5,7 @@ import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { Toast, ToastContainer } from "@/components/ui/toast";
|
||||
import { useTranslation } from "@/hooks/useTranslation";
|
||||
import { api, type Agent } from "@/lib/api";
|
||||
import { cn } from "@/lib/utils";
|
||||
import MDEditor from "@uiw/react-md-editor";
|
||||
@@ -43,6 +44,7 @@ export const CreateAgent: React.FC<CreateAgentProps> = ({
|
||||
onAgentCreated,
|
||||
className,
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const [name, setName] = useState(agent?.name || "");
|
||||
const [selectedIcon, setSelectedIcon] = useState<AgentIconName>((agent?.icon as AgentIconName) || "bot");
|
||||
const [systemPrompt, setSystemPrompt] = useState(agent?.system_prompt || "");
|
||||
@@ -92,9 +94,9 @@ export const CreateAgent: React.FC<CreateAgentProps> = ({
|
||||
onAgentCreated();
|
||||
} catch (err) {
|
||||
console.error("Failed to save agent:", err);
|
||||
setError(isEditMode ? "Failed to update agent" : "Failed to create agent");
|
||||
setError(isEditMode ? t('agents.updateFailed') : t('agents.createFailed'));
|
||||
setToast({
|
||||
message: isEditMode ? "Failed to update agent" : "Failed to create agent",
|
||||
message: isEditMode ? t('agents.updateFailed') : t('agents.createFailed'),
|
||||
type: "error"
|
||||
});
|
||||
} finally {
|
||||
@@ -108,7 +110,7 @@ export const CreateAgent: React.FC<CreateAgentProps> = ({
|
||||
systemPrompt !== (agent?.system_prompt || "") ||
|
||||
defaultTask !== (agent?.default_task || "") ||
|
||||
model !== (agent?.model || "sonnet")) &&
|
||||
!confirm("You have unsaved changes. Are you sure you want to leave?")) {
|
||||
!confirm(t('messages.unsavedChanges'))) {
|
||||
return;
|
||||
}
|
||||
onBack();
|
||||
@@ -135,10 +137,10 @@ export const CreateAgent: React.FC<CreateAgentProps> = ({
|
||||
</Button>
|
||||
<div>
|
||||
<h2 className="text-lg font-semibold">
|
||||
{isEditMode ? "Edit CC Agent" : "Create CC Agent"}
|
||||
{isEditMode ? t('agents.editAgent') : t('agents.createAgent')}
|
||||
</h2>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
{isEditMode ? "Update your Claude Code agent" : "Create a new Claude Code agent"}
|
||||
{isEditMode ? t('agents.updateAgentDescription') : t('agents.createAgentDescription')}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -153,7 +155,7 @@ export const CreateAgent: React.FC<CreateAgentProps> = ({
|
||||
) : (
|
||||
<Save className="mr-2 h-4 w-4" />
|
||||
)}
|
||||
{saving ? "Saving..." : "Save"}
|
||||
{saving ? t('app.loading') : t('app.save')}
|
||||
</Button>
|
||||
</motion.div>
|
||||
|
||||
|
85
src/components/LanguageSwitcher.tsx
Normal file
85
src/components/LanguageSwitcher.tsx
Normal file
@@ -0,0 +1,85 @@
|
||||
import React from 'react';
|
||||
import { Globe, Check } from 'lucide-react';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
} from '@/components/ui/dropdown-menu';
|
||||
import { useTranslation } from '@/hooks/useTranslation';
|
||||
import { api } from '@/lib/api';
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
interface LanguageSwitcherProps {
|
||||
className?: string;
|
||||
showText?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* 语言切换组件
|
||||
*
|
||||
* @example
|
||||
* <LanguageSwitcher />
|
||||
* <LanguageSwitcher showText={true} className="ml-2" />
|
||||
*/
|
||||
export const LanguageSwitcher: React.FC<LanguageSwitcherProps> = ({
|
||||
className,
|
||||
showText = false
|
||||
}) => {
|
||||
const { changeLanguage, currentLanguage, supportedLanguages } = useTranslation();
|
||||
|
||||
const handleLanguageChange = async (languageCode: string) => {
|
||||
try {
|
||||
// 映射前端语言代码到后端格式
|
||||
const backendLocale = languageCode === 'zh' ? 'zh-CN' : 'en-US';
|
||||
|
||||
// 同步到后端
|
||||
await api.setLanguage(backendLocale);
|
||||
|
||||
// 更新前端
|
||||
changeLanguage(languageCode);
|
||||
} catch (error) {
|
||||
console.error('Failed to change language:', error);
|
||||
// 即使后端同步失败,也要尝试更新前端
|
||||
changeLanguage(languageCode);
|
||||
}
|
||||
};
|
||||
|
||||
const getCurrentLanguageDisplay = () => {
|
||||
const currentLang = supportedLanguages.find(lang => lang.code === currentLanguage);
|
||||
return currentLang?.nativeName || currentLanguage.toUpperCase();
|
||||
};
|
||||
|
||||
return (
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className={cn("gap-2", className)}
|
||||
>
|
||||
<Globe className="h-4 w-4" />
|
||||
{showText && <span className="hidden sm:inline">{getCurrentLanguageDisplay()}</span>}
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end" className="w-48">
|
||||
{supportedLanguages.map((language) => (
|
||||
<DropdownMenuItem
|
||||
key={language.code}
|
||||
onClick={() => handleLanguageChange(language.code)}
|
||||
className="flex items-center justify-between cursor-pointer"
|
||||
>
|
||||
<div className="flex flex-col">
|
||||
<span className="font-medium">{language.nativeName}</span>
|
||||
<span className="text-xs text-muted-foreground">{language.name}</span>
|
||||
</div>
|
||||
{currentLanguage === language.code && (
|
||||
<Check className="h-4 w-4 text-primary" />
|
||||
)}
|
||||
</DropdownMenuItem>
|
||||
))}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
);
|
||||
};
|
@@ -3,6 +3,8 @@ import { motion } from "framer-motion";
|
||||
import { Circle, FileText, Settings, ExternalLink, BarChart3, Network, Info, Bot } from "lucide-react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Popover } from "@/components/ui/popover";
|
||||
import { LanguageSwitcher } from "@/components/LanguageSwitcher";
|
||||
import { useTranslation } from "@/hooks/useTranslation";
|
||||
import { api, type ClaudeVersionStatus } from "@/lib/api";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
@@ -57,6 +59,7 @@ export const Topbar: React.FC<TopbarProps> = ({
|
||||
onAgentsClick,
|
||||
className,
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const [versionStatus, setVersionStatus] = useState<ClaudeVersionStatus | null>(null);
|
||||
const [checking, setChecking] = useState(true);
|
||||
|
||||
@@ -130,7 +133,7 @@ export const Topbar: React.FC<TopbarProps> = ({
|
||||
trigger={statusContent}
|
||||
content={
|
||||
<div className="space-y-3 max-w-xs">
|
||||
<p className="text-sm font-medium">Claude Code not found</p>
|
||||
<p className="text-sm font-medium">{t('messages.claudeCodeNotFound')}</p>
|
||||
<div className="rounded-md bg-muted p-3">
|
||||
<pre className="text-xs font-mono whitespace-pre-wrap">
|
||||
{versionStatus.output}
|
||||
@@ -142,7 +145,7 @@ export const Topbar: React.FC<TopbarProps> = ({
|
||||
className="w-full"
|
||||
onClick={onSettingsClick}
|
||||
>
|
||||
Select Claude Installation
|
||||
{t('messages.selectClaudeInstallation')}
|
||||
</Button>
|
||||
<a
|
||||
href="https://www.anthropic.com/claude-code"
|
||||
@@ -150,7 +153,7 @@ export const Topbar: React.FC<TopbarProps> = ({
|
||||
rel="noopener noreferrer"
|
||||
className="flex items-center space-x-1 text-xs text-primary hover:underline"
|
||||
>
|
||||
<span>Install Claude Code</span>
|
||||
<span>{t('messages.installClaudeCode')}</span>
|
||||
<ExternalLink className="h-3 w-3" />
|
||||
</a>
|
||||
</div>
|
||||
@@ -186,7 +189,7 @@ export const Topbar: React.FC<TopbarProps> = ({
|
||||
className="text-xs"
|
||||
>
|
||||
<Bot className="mr-2 h-3 w-3" />
|
||||
Agents
|
||||
{t('navigation.agents')}
|
||||
</Button>
|
||||
)}
|
||||
|
||||
@@ -197,7 +200,7 @@ export const Topbar: React.FC<TopbarProps> = ({
|
||||
className="text-xs"
|
||||
>
|
||||
<BarChart3 className="mr-2 h-3 w-3" />
|
||||
Usage Dashboard
|
||||
{t('navigation.usage')}
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
@@ -217,7 +220,7 @@ export const Topbar: React.FC<TopbarProps> = ({
|
||||
className="text-xs"
|
||||
>
|
||||
<Network className="mr-2 h-3 w-3" />
|
||||
MCP
|
||||
{t('navigation.mcp')}
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
@@ -227,15 +230,18 @@ export const Topbar: React.FC<TopbarProps> = ({
|
||||
className="text-xs"
|
||||
>
|
||||
<Settings className="mr-2 h-3 w-3" />
|
||||
Settings
|
||||
{t('navigation.settings')}
|
||||
</Button>
|
||||
|
||||
{/* Language Switcher */}
|
||||
<LanguageSwitcher />
|
||||
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={onInfoClick}
|
||||
className="h-8 w-8"
|
||||
title="About"
|
||||
title={t('navigation.about')}
|
||||
>
|
||||
<Info className="h-4 w-4" />
|
||||
</Button>
|
||||
|
Reference in New Issue
Block a user