feat: 语言国际化重构

This commit is contained in:
DEV_DSW
2026-04-21 16:52:45 +08:00
parent 0c068e9f4d
commit 3349d41881
76 changed files with 4440 additions and 3232 deletions

View File

@@ -1,17 +1,17 @@
import { memo, useEffect, useRef, useState } from 'react';
import type { ChatHistoryBucket } from './types';
import {
ChevronDown,
LoaderCircle,
Plus,
MoreHorizontal,
PanelLeftClose,
PanelLeftOpen,
PencilLine,
Plus,
Trash2,
} from 'lucide-react';
import type { ChatHistoryBucket } from './types';
import blueLogo from '../../assets/images/login/blue_logo.png';
import { useI18n } from '../../i18n';
type ChatHistoryPanelProps = {
buckets: ChatHistoryBucket[];
@@ -40,6 +40,7 @@ function ChatHistoryPanel({
onRenameConversation,
onDeleteConversation,
}: ChatHistoryPanelProps) {
const { t } = useI18n();
const panelRef = useRef<HTMLElement | null>(null);
const [collapsedBuckets, setCollapsedBuckets] = useState<Record<string, boolean>>({});
const [menuState, setMenuState] = useState<MenuState>(null);
@@ -104,6 +105,9 @@ function ChatHistoryPanel({
}, [menuState]);
const panelWidthClass = isCompact ? 'md:w-[70px] lg:w-[70px]' : 'md:w-[240px] lg:w-[252px]';
const toggleSidebarLabel = isCompact
? t('conversation.historyPanel.expandSidebar')
: t('conversation.historyPanel.collapseSidebar');
return (
<aside
@@ -113,31 +117,33 @@ function ChatHistoryPanel({
panelWidthClass,
)}
>
<div className="flex h-full min-h-0 flex-col bg-[#fbfcfe] px-3 py-3 dark:border-[#2a2a2d] dark:bg-[#1b1b1d]">
<div className="flex h-full min-h-0 flex-col bg-[#fbfcfe] px-3 py-3 dark:border-[#2a2a2d] dark:bg-[#1b1b1d]">
<div className={cx('flex items-center justify-between gap-3', isCompact && 'flex-col items-center')}>
{!isCompact ? (
<div className={cx('flex min-w-0 items-center gap-3', isCompact && 'flex-col gap-2')}>
<div className="flex h-12 w-12 flex-none items-center justify-center overflow-hidden rounded-2xl border border-white bg-white shadow-[0_6px_16px_rgba(15,23,42,0.08)]">
<img className="h-full w-full object-cover" src={blueLogo} alt="YINIAN" />
</div>
<div className="truncate text-[20px] font-semibold whitespace-nowrap tracking-[0.06em] text-[#111827] dark:text-gray-50">
<div className="truncate whitespace-nowrap text-[20px] font-semibold tracking-[0.06em] text-[#111827] dark:text-gray-50">
YINIAN
</div>
</div>
) : null}
) : null}
<button
type="button"
className={cx('inline-flex h-12 w-12 items-center justify-center text-[#64748b] transition-colors border hover:text-[#111827] rounded-lg dark:border-[#2a2a2d] dark:text-gray-300 dark:hover:border-[#3a3a3f] dark:hover:text-gray-100', isCompact ? 'border-[#e3eaf3]' : 'border-transparent')}
title={isCompact ? '展开侧栏' : '收起侧栏'}
className={cx(
'inline-flex h-12 w-12 items-center justify-center rounded-lg border text-[#64748b] transition-colors hover:text-[#111827] dark:border-[#2a2a2d] dark:text-gray-300 dark:hover:border-[#3a3a3f] dark:hover:text-gray-100',
isCompact ? 'border-[#e3eaf3]' : 'border-transparent',
)}
title={toggleSidebarLabel}
aria-label={toggleSidebarLabel}
onClick={() => {
setMenuState(null);
setIsCompact((current) => !current);
}}
>
{isCompact ? <PanelLeftOpen /> : <PanelLeftClose />}
{isCompact ? <PanelLeftOpen /> : <PanelLeftClose />}
</button>
</div>
@@ -147,18 +153,19 @@ function ChatHistoryPanel({
'mt-4 inline-flex h-12 items-center justify-center gap-2 rounded-lg border border-[#e3eaf3] bg-white px-4 text-[15px] font-medium text-[#111827] shadow-[0_4px_14px_rgba(15,23,42,0.05)] transition-all hover:-translate-y-px hover:border-[#d5e3f4] hover:shadow-[0_10px_24px_rgba(15,23,42,0.08)] dark:border-[#2a2a2d] dark:bg-[#202024] dark:text-gray-100 dark:hover:border-[#3a3a3f]',
isCompact && 'px-0',
)}
title="新对话"
title={t('conversation.newConversation')}
aria-label={t('conversation.newConversation')}
onClick={onNewChat}
>
<Plus className="h-5 w-5 flex-none" />
{!isCompact ? <span className='whitespace-nowrap'></span> : null}
{!isCompact ? <span className="whitespace-nowrap">{t('conversation.newConversation')}</span> : null}
</button>
<div className="min-h-0 flex-1 overflow-y-auto pt-4">
{loading ? (
<div className="flex flex-col items-center justify-center gap-3 rounded-[18px] border border-dashed border-[#dbe7f4] bg-white px-4 py-8 text-sm text-[#94a3b8] shadow-[0_4px_14px_rgba(15,23,42,0.04)] dark:border-[#2a2a2d] dark:bg-[#202024] dark:text-gray-400">
<LoaderCircle className="h-5 w-5 animate-spin text-[#8bb7ff]" />
...
{t('conversation.historyPanel.loading')}
</div>
) : null}
@@ -168,30 +175,31 @@ function ChatHistoryPanel({
return (
<section key={bucket.key} className="mb-4 last:mb-0">
{bucket.sessions.length ? (<button
type="button"
className="flex w-full items-center justify-between rounded-lg px-4 py-4 text-left transition-colors border border-[#e5edf7] dark:border-[#2f3136] dark:bg-[#202024]"
aria-expanded={!isCollapsed}
onClick={() => {
setMenuState(null);
setCollapsedBuckets((current) => ({
...current,
[bucket.key]: !current[bucket.key],
}));
}}
>
<div className="flex items-center gap-2">
<span className="text-[14px] font-medium text-[#94a3b8] dark:text-gray-400">{bucket.label}</span>
<span className="text-[11px] text-[#c2cad6] dark:text-gray-600">{bucket.sessions.length}</span>
</div>
<ChevronDown
className={cx(
'h-4 w-4 text-[#b2bccb] transition-transform duration-200 dark:text-gray-500',
isCollapsed && '-rotate-90',
)}
/>
</button>) : null}
{bucket.sessions.length ? (
<button
type="button"
className="flex w-full items-center justify-between rounded-lg border border-[#e5edf7] px-4 py-4 text-left transition-colors dark:border-[#2f3136] dark:bg-[#202024]"
aria-expanded={!isCollapsed}
onClick={() => {
setMenuState(null);
setCollapsedBuckets((current) => ({
...current,
[bucket.key]: !current[bucket.key],
}));
}}
>
<div className="flex items-center gap-2">
<span className="text-[14px] font-medium text-[#94a3b8] dark:text-gray-400">{bucket.label}</span>
<span className="text-[11px] text-[#c2cad6] dark:text-gray-600">{bucket.sessions.length}</span>
</div>
<ChevronDown
className={cx(
'h-4 w-4 text-[#b2bccb] transition-transform duration-200 dark:text-gray-500',
isCollapsed && '-rotate-90',
)}
/>
</button>
) : null}
{!isCollapsed ? (
<ul className="mt-2 space-y-2">
@@ -233,15 +241,14 @@ function ChatHistoryPanel({
? 'opacity-100'
: 'pointer-events-none opacity-0 group-hover:pointer-events-auto group-hover:opacity-100',
)}
title="更多操作"
title={t('conversation.historyPanel.moreActions')}
aria-label={t('conversation.historyPanel.moreActions')}
onClick={(event) => {
event.stopPropagation();
setMenuState((current) =>
current?.conversationId === session.conversationId
? null
: {
conversationId: session.conversationId,
},
: { conversationId: session.conversationId },
);
}}
>
@@ -264,7 +271,7 @@ function ChatHistoryPanel({
}}
>
<PencilLine className="h-4 w-4 text-[#94a3b8]" />
{t('conversation.historyPanel.rename')}
</button>
<button
type="button"
@@ -277,7 +284,7 @@ function ChatHistoryPanel({
}}
>
<Trash2 className="h-4 w-4" />
{t('conversation.historyPanel.delete')}
</button>
</div>
) : null}