diff --git a/AGENTS.md b/AGENTS.md
index 8687583..54dff50 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -39,6 +39,7 @@
- 背景 → `bg-card` / `bg-muted/50` / `bg-accent`
- 边框 → `border-border`
- hover → `hover:text-foreground` / `hover:bg-accent`
+- **国际化**: 所有用户可见文本使用 `useT()` hook,翻译 key 位于 `src/i18n/locales/`。新增页面先加翻译 key 再写 UI。中文默认,英文同步维护。
## 关键架构决策
@@ -110,3 +111,7 @@ npm run test:e2e
---
> 此文件仅在架构/约定变更时更新。进度追踪见 `docs/progress.md`。
+
+---
+
+> 此文件仅在架构/约定变更时更新。进度追踪见 `docs/progress.md`。
diff --git a/docs/progress.md b/docs/progress.md
index bb8bfdb..d9bbd68 100644
--- a/docs/progress.md
+++ b/docs/progress.md
@@ -1,21 +1,22 @@
# 项目进度追踪
-## 当前阶段:Phase 1-3 全部完成
+## 当前阶段:P0 — 全局优化
-### Phase 1 — 全部完成 ✅
-1. **场景化提示词模板** — 5 预置场景 + system prompt + 建议问题
-2. **多模型一键切换** — 模型下拉选择,上下文保留
-3. **对话历史管理** — 侧边栏 + 搜索 + 加载 + 删除
+### Phase 1-3 — 全部完成 ✅
-### Phase 2 — 全部完成 ✅
-4. **参数调节面板** — temperature/top-p/max-tokens 滑块 + 后端透传
-5. **提示词工坊完善** — 保存到提示词库 + JWT + CSS 变量主题
-6. **代码沙盒** — `/sandbox/code` 编辑器 + iframe 预览 + 4 模板 + AI 代码块入口
+### P0 — 全局优化(当前阶段)
+| 子项 | 说明 | 状态 |
+|------|------|------|
+| **P0a** | i18n 基础设施 + 翻译文件 + LanguageProvider + useT hook | ✅ 完成 |
+| **P0b** | UI 统一 — Design Token + 共享组件库 | ⏳ 待开始 |
+| **P0c** | 逐页翻译 + 语言切换 | ⏳ 待开始 |
+| **P0d** | 会员定价页重构(用量可视化) | ⏳ 待开始 |
+| **P0e** | 模型选择器升级(卡片式 + 能力标签) | ⏳ 待开始 |
+| **P0f** | 会话分享链接 | ⏳ 待开始 |
-### Phase 3 — 全部完成 ✅
-7. **学情分析看板** — `GET /learning/analytics` + `/learning/analytics` 页面(知识领域覆盖、掌握度、薄弱分析、推荐)
-8. **分阶段学习路径** — `GET /learning/path` + `/learning/path` 页面(4 阶段:认识大模型→提示词工程→微调→Agent)
-9. **对比模式完善** — 颜色修复、参数面板、model ID 同步
+### 参考来源
+- opencode 源码克隆到 `/tmp/opencode-source/`(21 packages, 147MB)
+- 关键参考模块: `packages/plugin`(hooks + tool 系统)、`packages/ui/i18n/`(20+ 语言)、`packages/llm`(provider 接口)
### 新页面
- `/learning/analytics` — 学情分析看板(对话分析→知识图谱→薄弱环节→推荐)
diff --git a/frontend/src/app/layout-client.tsx b/frontend/src/app/layout-client.tsx
new file mode 100644
index 0000000..9122f1a
--- /dev/null
+++ b/frontend/src/app/layout-client.tsx
@@ -0,0 +1,24 @@
+'use client'
+
+import { I18nProvider } from '@/i18n'
+import { ThemeProvider } from '@/components/providers/theme-provider'
+import { AuthProvider } from '@/lib/auth-context'
+import { Toaster } from '@/components/ui/sonner'
+import { Header } from '@/components/layout/header'
+import { Footer } from '@/components/layout/footer'
+import type { ReactNode } from 'react'
+
+export function RootLayoutClient({ children }: { children: ReactNode }) {
+ return (
+
+
+
+
+ {children}
+
+
+
+
+
+ )
+}
diff --git a/frontend/src/app/layout.tsx b/frontend/src/app/layout.tsx
index 91bb4d8..5ee5097 100644
--- a/frontend/src/app/layout.tsx
+++ b/frontend/src/app/layout.tsx
@@ -1,10 +1,6 @@
import type { Metadata } from 'next';
import './globals.css';
-import { Header } from '@/components/layout/header';
-import { Footer } from '@/components/layout/footer';
-import { ThemeProvider } from '@/components/providers/theme-provider';
-import { AuthProvider } from '@/lib/auth-context';
-import { Toaster } from '@/components/ui/sonner';
+import { RootLayoutClient } from './layout-client';
export const metadata: Metadata = {
title: {
@@ -32,14 +28,7 @@ export default function RootLayout({ children }: { children: React.ReactNode })
return (
-
-
-
- {children}
-
-
-
-
+ {children}
);
diff --git a/frontend/src/components/layout/header.tsx b/frontend/src/components/layout/header.tsx
index 1c05b34..1fea0da 100644
--- a/frontend/src/components/layout/header.tsx
+++ b/frontend/src/components/layout/header.tsx
@@ -6,9 +6,10 @@ import { usePathname } from 'next/navigation';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { ThemeToggle } from '@/components/ui/theme-toggle';
-import { Search, Menu, X, Bell } from 'lucide-react';
+import { Search, Menu, X, Bell, Languages } from 'lucide-react';
import { apiFetch } from '@/lib/auth';
import { useAuth } from '@/lib/auth-context';
+import { useLang } from '@/i18n';
const navItems = [
{ href: '/', label: '首页' },
@@ -60,6 +61,7 @@ export function Header() {
const [mobileOpen, setMobileOpen] = useState(false);
const [scrolled, setScrolled] = useState(false);
const { isLoggedIn, logout } = useAuth();
+ const { lang, setLang } = useLang();
function isActive(href: string) {
if (href === '/') return pathname === '/';
@@ -114,6 +116,10 @@ export function Header() {
/>
+
@@ -145,6 +151,10 @@ export function Header() {
+