Forráskód Böngészése

fix: 清理 usage-doc / big-screen i18n 硬编码 + 修复 Next.js params Promise 报错

YangQing-Lin 2 hónapja
szülő
commit
4dad830cd2

+ 2 - 0
messages/en/bigScreen.json

@@ -1,4 +1,6 @@
 {
+  "pageTitle": "Realtime big screen - Claude Code Hub",
+  "pageDescription": "Claude Code Hub realtime monitoring big screen",
   "title": "CLAUDE CODE HUB",
   "subtitle": "REALTIME DATA MONITOR",
   "metrics": {

+ 31 - 2
messages/en/usage.json

@@ -19,7 +19,7 @@
   },
 
   "codeExamples": {
-    "label": "Code example - ${language}",
+    "label": "Code example - {language}",
     "description": "Click the code block to copy to clipboard"
   },
 
@@ -206,7 +206,7 @@
         "unix": {
           "temporary": "Temporary setting (current session):",
           "permanent": "Permanent setting:",
-          "permanentNote": "Add to your shell configuration file (${shellConfig}):"
+          "permanentNote": "Add to your shell configuration file ({shellConfig}):"
         }
       },
 
@@ -698,6 +698,35 @@
     "linux": "Linux"
   },
 
+  "placeholders": {
+    "windowsUserName": "your-username",
+    "shellConfig": {
+      "linux": "~/.bashrc",
+      "macos": "~/.zshrc"
+    },
+    "codexVsCodeConfigFiles": "config.toml and auth.json"
+  },
+
+  "snippets": {
+    "comments": {
+      "updateHomebrew": "# Update Homebrew",
+      "installNodeJs": "# Install Node.js",
+      "usingChocolatey": "# Using Chocolatey",
+      "orUsingScoop": "# Or using Scoop",
+      "addNodeSourceRepo": "# Add NodeSource repository",
+      "ubuntuDebian": "# Ubuntu/Debian",
+      "centosRhelFedora": "# CentOS/RHEL/Fedora",
+      "addToPathIfMissing": "# Add to PATH (if missing)",
+      "checkEnvVar": "# Check environment variable",
+      "testNetworkConnection": "# Test network connection"
+    }
+  },
+
+  "layout": {
+    "headerTitle": "Usage Docs",
+    "loginConsole": "Log in to console"
+  },
+
   "ui": {
     "mainContent": "Documentation content",
     "main": "main",

+ 2 - 0
messages/ja/bigScreen.json

@@ -1,4 +1,6 @@
 {
+  "pageTitle": "リアルタイム大画面 - Claude Code Hub",
+  "pageDescription": "Claude Code Hub のリアルタイム監視ダッシュボード",
   "title": "CLAUDE CODE HUB",
   "subtitle": "リアルタイムデータモニター",
   "metrics": {

+ 31 - 2
messages/ja/usage.json

@@ -19,7 +19,7 @@
   },
 
   "codeExamples": {
-    "label": "コード例 - ${language}",
+    "label": "コード例 - {language}",
     "description": "コードブロックをクリックしてクリップボードにコピー"
   },
 
@@ -206,7 +206,7 @@
         "unix": {
           "temporary": "一時設定 (現在のセッション):",
           "permanent": "永続設定:",
-          "permanentNote": "シェル設定ファイル (${shellConfig}) に追加:"
+          "permanentNote": "シェル設定ファイル ({shellConfig}) に追加:"
         }
       },
 
@@ -698,6 +698,35 @@
     "linux": "Linux"
   },
 
+  "placeholders": {
+    "windowsUserName": "your-username",
+    "shellConfig": {
+      "linux": "~/.bashrc",
+      "macos": "~/.zshrc"
+    },
+    "codexVsCodeConfigFiles": "config.toml と auth.json"
+  },
+
+  "snippets": {
+    "comments": {
+      "updateHomebrew": "# Homebrew を更新",
+      "installNodeJs": "# Node.js をインストール",
+      "usingChocolatey": "# Chocolatey を使用",
+      "orUsingScoop": "# または Scoop を使用",
+      "addNodeSourceRepo": "# NodeSource リポジトリを追加",
+      "ubuntuDebian": "# Ubuntu/Debian",
+      "centosRhelFedora": "# CentOS/RHEL/Fedora",
+      "addToPathIfMissing": "# PATH に追加(未設定の場合)",
+      "checkEnvVar": "# 環境変数を確認",
+      "testNetworkConnection": "# ネットワーク接続を確認"
+    }
+  },
+
+  "layout": {
+    "headerTitle": "利用ドキュメント",
+    "loginConsole": "コンソールにログイン"
+  },
+
   "ui": {
     "mainContent": "ドキュメントコンテンツ",
     "main": "main",

+ 2 - 0
messages/ru/bigScreen.json

@@ -1,4 +1,6 @@
 {
+  "pageTitle": "Панель реального времени - Claude Code Hub",
+  "pageDescription": "Большой экран мониторинга в реальном времени Claude Code Hub",
   "title": "CLAUDE CODE HUB",
   "subtitle": "МОНИТОР ДАННЫХ В РЕАЛЬНОМ ВРЕМЕНИ",
   "metrics": {

+ 31 - 2
messages/ru/usage.json

@@ -19,7 +19,7 @@
   },
 
   "codeExamples": {
-    "label": "Пример кода - ${language}",
+    "label": "Пример кода - {language}",
     "description": "Нажмите на блок кода для копирования в буфер обмена"
   },
 
@@ -206,7 +206,7 @@
         "unix": {
           "temporary": "Временная настройка (текущий сеанс):",
           "permanent": "Постоянная настройка:",
-          "permanentNote": "Добавьте в файл конфигурации оболочки (${shellConfig}):"
+          "permanentNote": "Добавьте в файл конфигурации оболочки ({shellConfig}):"
         }
       },
 
@@ -698,6 +698,35 @@
     "linux": "Linux"
   },
 
+  "placeholders": {
+    "windowsUserName": "your-username",
+    "shellConfig": {
+      "linux": "~/.bashrc",
+      "macos": "~/.zshrc"
+    },
+    "codexVsCodeConfigFiles": "config.toml и auth.json"
+  },
+
+  "snippets": {
+    "comments": {
+      "updateHomebrew": "# Обновить Homebrew",
+      "installNodeJs": "# Установить Node.js",
+      "usingChocolatey": "# Используя Chocolatey",
+      "orUsingScoop": "# Или через Scoop",
+      "addNodeSourceRepo": "# Добавить репозиторий NodeSource",
+      "ubuntuDebian": "# Ubuntu/Debian",
+      "centosRhelFedora": "# CentOS/RHEL/Fedora",
+      "addToPathIfMissing": "# Добавить в PATH (если отсутствует)",
+      "checkEnvVar": "# Проверить переменную окружения",
+      "testNetworkConnection": "# Проверить сетевое подключение"
+    }
+  },
+
+  "layout": {
+    "headerTitle": "Документация",
+    "loginConsole": "Войти в консоль"
+  },
+
   "ui": {
     "mainContent": "Содержимое документации",
     "main": "main",

+ 2 - 0
messages/zh-CN/bigScreen.json

@@ -1,4 +1,6 @@
 {
+  "pageTitle": "实时数据大屏 - Claude Code Hub",
+  "pageDescription": "Claude Code Hub 实时监控数据大屏",
   "title": "CLAUDE CODE HUB",
   "subtitle": "实时数据监控中台",
   "metrics": {

+ 31 - 2
messages/zh-CN/usage.json

@@ -19,7 +19,7 @@
   },
 
   "codeExamples": {
-    "label": "代码示例 - ${language}",
+    "label": "代码示例 - {language}",
     "description": "点击代码块可复制到剪贴板"
   },
 
@@ -202,7 +202,7 @@
         "unix": {
           "temporary": "临时设置(当前会话):",
           "permanent": "永久设置:",
-          "permanentNote": "添加到您的 shell 配置文件(${shellConfig}):"
+          "permanentNote": "添加到您的 shell 配置文件({shellConfig}):"
         }
       },
 
@@ -694,6 +694,35 @@
     "linux": "Linux"
   },
 
+  "placeholders": {
+    "windowsUserName": "你的用户名",
+    "shellConfig": {
+      "linux": "~/.bashrc",
+      "macos": "~/.zshrc"
+    },
+    "codexVsCodeConfigFiles": "config.toml 和 auth.json"
+  },
+
+  "snippets": {
+    "comments": {
+      "updateHomebrew": "# 更新 Homebrew",
+      "installNodeJs": "# 安装 Node.js",
+      "usingChocolatey": "# 使用 Chocolatey",
+      "orUsingScoop": "# 或使用 Scoop",
+      "addNodeSourceRepo": "# 添加 NodeSource 仓库",
+      "ubuntuDebian": "# Ubuntu/Debian",
+      "centosRhelFedora": "# CentOS/RHEL/Fedora",
+      "addToPathIfMissing": "# 添加到 PATH(如果不在)",
+      "checkEnvVar": "# 检查环境变量",
+      "testNetworkConnection": "# 测试网络连接"
+    }
+  },
+
+  "layout": {
+    "headerTitle": "使用文档",
+    "loginConsole": "登录控制台"
+  },
+
   "ui": {
     "mainContent": "文档内容",
     "main": "main",

+ 2 - 0
messages/zh-TW/bigScreen.json

@@ -1,4 +1,6 @@
 {
+  "pageTitle": "即時資料大屏 - Claude Code Hub",
+  "pageDescription": "Claude Code Hub 即時監控資料大屏",
   "title": "CLAUDE CODE HUB",
   "subtitle": "即時資料監控中台",
   "metrics": {

+ 31 - 2
messages/zh-TW/usage.json

@@ -19,7 +19,7 @@
   },
 
   "codeExamples": {
-    "label": "代碼示例 - ${language}",
+    "label": "代碼示例 - {language}",
     "description": "點擊代碼塊可複製到剪貼板"
   },
 
@@ -202,7 +202,7 @@
         "unix": {
           "temporary": "臨時設置(當前會話):",
           "permanent": "永久設置:",
-          "permanentNote": "添加到您的 shell 配置文件(${shellConfig}):"
+          "permanentNote": "添加到您的 shell 配置文件({shellConfig}):"
         }
       },
 
@@ -694,6 +694,35 @@
     "linux": "Linux"
   },
 
+  "placeholders": {
+    "windowsUserName": "你的用戶名",
+    "shellConfig": {
+      "linux": "~/.bashrc",
+      "macos": "~/.zshrc"
+    },
+    "codexVsCodeConfigFiles": "config.toml 和 auth.json"
+  },
+
+  "snippets": {
+    "comments": {
+      "updateHomebrew": "# 更新 Homebrew",
+      "installNodeJs": "# 安裝 Node.js",
+      "usingChocolatey": "# 使用 Chocolatey",
+      "orUsingScoop": "# 或使用 Scoop",
+      "addNodeSourceRepo": "# 添加 NodeSource 倉庫",
+      "ubuntuDebian": "# Ubuntu/Debian",
+      "centosRhelFedora": "# CentOS/RHEL/Fedora",
+      "addToPathIfMissing": "# 添加到 PATH(如果不在)",
+      "checkEnvVar": "# 檢查環境變數",
+      "testNetworkConnection": "# 測試網路連線"
+    }
+  },
+
+  "layout": {
+    "headerTitle": "使用文檔",
+    "loginConsole": "登入控制台"
+  },
+
   "ui": {
     "mainContent": "文檔內容",
     "main": "main",

+ 15 - 4
src/app/[locale]/internal/dashboard/big-screen/layout.tsx

@@ -1,9 +1,20 @@
 import type { Metadata } from "next";
+import { getTranslations } from "next-intl/server";
 
-export const metadata: Metadata = {
-  title: "实时数据大屏 - Claude Code Hub",
-  description: "Claude Code Hub 实时监控数据大屏",
-};
+type BigScreenParams = { locale: string };
+
+export async function generateMetadata({
+  params,
+}: {
+  params: Promise<BigScreenParams> | BigScreenParams;
+}): Promise<Metadata> {
+  const { locale } = await params;
+  const t = await getTranslations({ locale, namespace: "bigScreen" });
+  return {
+    title: t("pageTitle"),
+    description: t("pageDescription"),
+  };
+}
 
 export default function BigScreenLayout({ children }: { children: React.ReactNode }) {
   // 全屏布局,移除所有导航栏、侧边栏等元素

+ 31 - 8
src/app/[locale]/usage-doc/layout.tsx

@@ -1,21 +1,44 @@
 import { Book, LogIn } from "lucide-react";
 import type { Metadata } from "next";
+import { getTranslations } from "next-intl/server";
+import { cache } from "react";
 import { Link } from "@/i18n/routing";
 import { getSession } from "@/lib/auth";
 import { DashboardHeader } from "../dashboard/_components/dashboard-header";
 
-export const metadata: Metadata = {
-  title: "使用文档 - Claude Code Hub",
-  description: "Claude Code Hub API 代理服务使用文档和指南",
-};
+type UsageDocParams = { locale: string };
+
+const getUsageTranslations = cache((locale: string) =>
+  getTranslations({ locale, namespace: "usage" })
+);
+
+export async function generateMetadata({
+  params,
+}: {
+  params: Promise<UsageDocParams> | UsageDocParams;
+}): Promise<Metadata> {
+  const { locale } = await params;
+  const t = await getUsageTranslations(locale);
+  return {
+    title: t("pageTitle"),
+    description: t("pageDescription"),
+  };
+}
 
 /**
  * 文档页面布局
  * 提供文档页面的容器、样式和共用头部
  * 支持未登录访问:未登录时显示简化版头部,已登录时显示完整的 DashboardHeader
  */
-export default async function UsageDocLayout({ children }: { children: React.ReactNode }) {
-  const session = await getSession();
+export default async function UsageDocLayout({
+  children,
+  params,
+}: {
+  children: React.ReactNode;
+  params: Promise<UsageDocParams> | UsageDocParams;
+}) {
+  const { locale } = await params;
+  const [session, t] = await Promise.all([getSession(), getUsageTranslations(locale)]);
 
   return (
     <div className="min-h-screen bg-background">
@@ -27,14 +50,14 @@ export default async function UsageDocLayout({ children }: { children: React.Rea
           <div className="container flex h-14 items-center justify-between px-6">
             <div className="flex items-center gap-2">
               <Book className="h-5 w-5 text-orange-500" />
-              <span className="font-semibold">使用文档</span>
+              <span className="font-semibold">{t("layout.headerTitle")}</span>
             </div>
             <Link
               href="/login?from=/usage-doc"
               className="inline-flex items-center gap-2 rounded-md bg-orange-500 px-4 py-2 text-sm font-medium text-white transition-colors hover:bg-orange-600"
             >
               <LogIn className="h-4 w-4" />
-              登录控制台
+              {t("layout.loginConsole")}
             </Link>
           </div>
         </header>

+ 39 - 36
src/app/[locale]/usage-doc/page.tsx

@@ -82,7 +82,7 @@ function getCLIConfigs(t: (key: string) => string): Record<string, CLIConfig> {
         configFile: "config.json",
         configPath: {
           macos: "~/.claude",
-          windows: "C:\\Users\\你的用户名\\.claude",
+          windows: `C:\\Users\\${t("placeholders.windowsUserName")}\\.claude`,
           linux: "~/.claude",
         },
       },
@@ -95,10 +95,10 @@ function getCLIConfigs(t: (key: string) => string): Record<string, CLIConfig> {
       requiresNodeJs: true,
       vsCodeExtension: {
         name: "Codex – OpenAI's coding agent",
-        configFile: "config.toml 和 auth.json",
+        configFile: t("placeholders.codexVsCodeConfigFiles"),
         configPath: {
           macos: "~/.codex",
-          windows: "C:\\Users\\你的用户名\\.codex",
+          windows: `C:\\Users\\${t("placeholders.windowsUserName")}\\.codex`,
           linux: "~/.codex",
         },
       },
@@ -150,9 +150,9 @@ export function UsageDocContent({ origin }: UsageDocContentProps) {
           <h4 className={headingClasses.h4}>{t("claudeCode.environmentSetup.macos.homebrew")}</h4>
           <CodeBlock
             language="bash"
-            code={`# 更新 Homebrew
+            code={`${t("snippets.comments.updateHomebrew")}
 brew update
-# 安装 Node.js
+${t("snippets.comments.installNodeJs")}
 brew install node`}
           />
           <h4 className={headingClasses.h4}>{t("claudeCode.environmentSetup.macos.official")}</h4>
@@ -197,10 +197,10 @@ brew install node`}
           </h4>
           <CodeBlock
             language="powershell"
-            code={`# 使用 Chocolatey
+            code={`${t("snippets.comments.usingChocolatey")}
 choco install nodejs
 
-# 或使用 Scoop
+${t("snippets.comments.orUsingScoop")}
 scoop install nodejs`}
           />
           <blockquote className="space-y-1 rounded-lg border-l-2 border-primary/50 bg-muted/40 px-4 py-3">
@@ -218,9 +218,9 @@ scoop install nodejs`}
           <h4 className={headingClasses.h4}>{t("claudeCode.environmentSetup.linux.official")}</h4>
           <CodeBlock
             language="bash"
-            code={`# 添加 NodeSource 仓库
+            code={`${t("snippets.comments.addNodeSourceRepo")}
 curl -fsSL https://deb.nodesource.com/setup_lts.x | sudo -E bash -
-# 安装 Node.js
+${t("snippets.comments.installNodeJs")}
 sudo apt-get install -y nodejs`}
           />
           <h4 className={headingClasses.h4}>
@@ -228,11 +228,11 @@ sudo apt-get install -y nodejs`}
           </h4>
           <CodeBlock
             language="bash"
-            code={`# Ubuntu/Debian
+            code={`${t("snippets.comments.ubuntuDebian")}
 sudo apt update
 sudo apt install nodejs npm
 
-# CentOS/RHEL/Fedora
+${t("snippets.comments.centosRhelFedora")}
 sudo dnf install nodejs npm`}
           />
         </div>
@@ -308,14 +308,9 @@ npm --version`}
                 </p>
                 <CodeBlock
                   language="bash"
-                  code={`# 安装稳定版(默认)
-curl -fsSL https://claude.ai/install.sh | bash
-
-# 安装最新版
-curl -fsSL https://claude.ai/install.sh | bash -s latest
-
-# 安装指定版本
-curl -fsSL https://claude.ai/install.sh | bash -s 1.0.58`}
+                  code={(
+                    t.raw("claudeCode.installation.nativeInstall.macos.curls") as string[]
+                  ).join("\n")}
                 />
               </div>
             </div>
@@ -454,15 +449,22 @@ curl -fsSL https://claude.ai/install.sh | bash -s 1.0.58`}
    * 渲染 Claude Code 配置
    */
   const renderClaudeCodeConfiguration = (os: OS) => {
+    const windowsUserName = t("placeholders.windowsUserName");
     const configPath =
       os === "windows"
-        ? "C:\\Users\\你的用户名\\.claude\\settings.json"
+        ? `C:\\Users\\${windowsUserName}\\.claude\\settings.json`
         : "~/.claude/settings.json";
+    const shellConfigFile =
+      os === "linux"
+        ? t("placeholders.shellConfig.linux")
+        : os === "macos"
+          ? t("placeholders.shellConfig.macos")
+          : "";
     const shellConfig =
       os === "linux"
-        ? "~/.bashrc 或 ~/.zshrc"
+        ? t("placeholders.shellConfig.linux")
         : os === "macos"
-          ? "~/.zshrc 或 ~/.bash_profile"
+          ? t("placeholders.shellConfig.macos")
           : "";
 
     return (
@@ -546,9 +548,9 @@ export ANTHROPIC_AUTH_TOKEN="your-api-key-here"`}
               </p>
               <CodeBlock
                 language="bash"
-                code={`echo 'export ANTHROPIC_BASE_URL="${resolvedOrigin}"' >> ${shellConfig.split(" ")[0]}
-echo 'export ANTHROPIC_AUTH_TOKEN="your-api-key-here"' >> ${shellConfig.split(" ")[0]}
-source ${shellConfig.split(" ")[0]}`}
+                code={`echo 'export ANTHROPIC_BASE_URL="${resolvedOrigin}"' >> ${shellConfigFile}
+echo 'export ANTHROPIC_AUTH_TOKEN="your-api-key-here"' >> ${shellConfigFile}
+source ${shellConfigFile}`}
               />
             </>
           )}
@@ -623,12 +625,13 @@ sk_xxxxxxxxxxxxxxxxxx`}
    * 渲染 Codex 配置
    */
   const renderCodexConfiguration = (os: OS) => {
-    const configPath = os === "windows" ? "C:\\Users\\你的用户名\\.codex" : "~/.codex";
-    const shellConfig =
+    const windowsUserName = t("placeholders.windowsUserName");
+    const configPath = os === "windows" ? `C:\\Users\\${windowsUserName}\\.codex` : "~/.codex";
+    const shellConfigFile =
       os === "linux"
-        ? "~/.bashrc 或 ~/.zshrc"
+        ? t("placeholders.shellConfig.linux")
         : os === "macos"
-          ? "~/.zshrc 或 ~/.bash_profile"
+          ? t("placeholders.shellConfig.macos")
           : "";
 
     return (
@@ -742,8 +745,8 @@ network_access = true`}
                     <p>{t("codex.configuration.envVars.unix.instruction")}</p>
                     <CodeBlock
                       language="bash"
-                      code={`echo 'export CCH_API_KEY="your-api-key-here"' >> ${shellConfig.split(" ")[0]}
-source ${shellConfig.split(" ")[0]}`}
+                      code={`echo 'export CCH_API_KEY="your-api-key-here"' >> ${shellConfigFile}
+source ${shellConfigFile}`}
                     />
                   </>
                 )}
@@ -1475,7 +1478,7 @@ ${cli.cliName}`}
               code={`# ${t(cmdNotFoundUnixKey)}
 npm config get prefix
 
-# 添加到 PATH(如果不在)
+${t("snippets.comments.addToPathIfMissing")}
 echo 'export PATH="$HOME/.npm-global/bin:$PATH"' >> ~/.${os === "macos" ? "zshrc" : "bashrc"}
 source ~/.${os === "macos" ? "zshrc" : "bashrc"}`}
             />
@@ -1497,19 +1500,19 @@ source ~/.${os === "macos" ? "zshrc" : "bashrc"}`}
             ) : os === "windows" ? (
               <CodeBlock
                 language="powershell"
-                code={`# 检查环境变量
+                code={`${t("snippets.comments.checkEnvVar")}
 echo $env:${envKeyName}
 
-# 测试网络连接
+${t("snippets.comments.testNetworkConnection")}
 Test-NetConnection -ComputerName ${resolvedOrigin.replace("https://", "").replace("http://", "")} -Port 443`}
               />
             ) : (
               <CodeBlock
                 language="bash"
-                code={`# 检查环境变量
+                code={`${t("snippets.comments.checkEnvVar")}
 echo $${envKeyName}
 
-# 测试网络连接
+${t("snippets.comments.testNetworkConnection")}
 curl -I ${resolvedOrigin}`}
               />
             )}

+ 18 - 0
tests/unit/i18n/big-screen-metadata-keys.test.ts

@@ -0,0 +1,18 @@
+import { describe, expect, test } from "vitest";
+
+import enBigScreen from "../../../messages/en/bigScreen.json";
+import jaBigScreen from "../../../messages/ja/bigScreen.json";
+import ruBigScreen from "../../../messages/ru/bigScreen.json";
+import zhCNBigScreen from "../../../messages/zh-CN/bigScreen.json";
+import zhTWBigScreen from "../../../messages/zh-TW/bigScreen.json";
+
+describe("messages/<locale>/bigScreen metadata keys", () => {
+  test("provides pageTitle/pageDescription", () => {
+    const all = [enBigScreen, jaBigScreen, ruBigScreen, zhCNBigScreen, zhTWBigScreen];
+
+    for (const bigScreen of all) {
+      expect(bigScreen).toHaveProperty("pageTitle");
+      expect(bigScreen).toHaveProperty("pageDescription");
+    }
+  });
+});

+ 101 - 0
tests/unit/nextjs/async-params-layouts.test.tsx

@@ -0,0 +1,101 @@
+import type { ReactNode } from "react";
+import { beforeEach, describe, expect, test, vi } from "vitest";
+
+const authMocks = vi.hoisted(() => ({
+  getSession: vi.fn(async () => null),
+}));
+
+vi.mock("@/lib/auth", () => authMocks);
+
+vi.mock("@/i18n/routing", () => ({
+  Link: ({
+    href,
+    children,
+    ...rest
+  }: {
+    href: string;
+    children: ReactNode;
+    className?: string;
+  }) => (
+    <a href={href} {...rest}>
+      {children}
+    </a>
+  ),
+}));
+
+const intlServerMocks = vi.hoisted(() => ({
+  getTranslations: vi.fn(async ({ locale, namespace }: { locale: string; namespace: string }) => {
+    return (key: string) => `${namespace}.${key}.${locale}`;
+  }),
+}));
+
+vi.mock("next-intl/server", () => intlServerMocks);
+
+function makeAsyncParams(locale: string) {
+  const promise = Promise.resolve({ locale });
+
+  Object.defineProperty(promise, "locale", {
+    get() {
+      throw new Error("sync access to params.locale is not allowed");
+    },
+  });
+
+  return promise as Promise<{ locale: string }> & { locale: string };
+}
+
+describe("Next.js async params compatibility", () => {
+  beforeEach(() => {
+    authMocks.getSession.mockReset();
+    authMocks.getSession.mockResolvedValue(null);
+    intlServerMocks.getTranslations.mockClear();
+  });
+
+  test("usage-doc generateMetadata awaits params before reading locale", async () => {
+    const { generateMetadata } = await import("@/app/[locale]/usage-doc/layout");
+
+    const metadata = await generateMetadata({
+      params: makeAsyncParams("en") as unknown as Promise<{ locale: string }>,
+    });
+
+    expect(metadata).toEqual({
+      title: "usage.pageTitle.en",
+      description: "usage.pageDescription.en",
+    });
+  });
+
+  test("UsageDocLayout awaits params before reading locale (session/no-session branches)", async () => {
+    const UsageDocLayoutModule = await import("@/app/[locale]/usage-doc/layout");
+
+    authMocks.getSession.mockResolvedValueOnce(null);
+    const noSession = await UsageDocLayoutModule.default({
+      children: <div />,
+      params: makeAsyncParams("en") as unknown as Promise<{ locale: string }>,
+    });
+    expect(noSession).toBeTruthy();
+
+    authMocks.getSession.mockResolvedValueOnce({} as never);
+    const hasSession = await UsageDocLayoutModule.default({
+      children: <div />,
+      params: makeAsyncParams("en") as unknown as Promise<{ locale: string }>,
+    });
+    expect(hasSession).toBeTruthy();
+  });
+
+  test("big-screen generateMetadata awaits params before reading locale", async () => {
+    const BigScreenLayoutModule = await import(
+      "@/app/[locale]/internal/dashboard/big-screen/layout"
+    );
+
+    const metadata = await BigScreenLayoutModule.generateMetadata({
+      params: makeAsyncParams("en") as unknown as Promise<{ locale: string }>,
+    });
+
+    expect(metadata).toEqual({
+      title: "bigScreen.pageTitle.en",
+      description: "bigScreen.pageDescription.en",
+    });
+
+    const element = BigScreenLayoutModule.default({ children: <div /> });
+    expect(element).toBeTruthy();
+  });
+});

+ 15 - 1
tests/unit/settings/providers/model-multi-select-custom-models-ui.test.tsx

@@ -8,7 +8,11 @@ import { createRoot } from "react-dom/client";
 import { NextIntlClientProvider } from "next-intl";
 import { beforeEach, describe, expect, test, vi } from "vitest";
 import { ModelMultiSelect } from "@/app/[locale]/settings/providers/_components/model-multi-select";
-import { loadMessages as loadTestMessages } from "../prices/test-messages";
+import commonMessages from "../../../../messages/en/common.json";
+import errorsMessages from "../../../../messages/en/errors.json";
+import formsMessages from "../../../../messages/en/forms.json";
+import settingsMessages from "../../../../messages/en/settings";
+import uiMessages from "../../../../messages/en/ui.json";
 
 const modelPricesActionMocks = vi.hoisted(() => ({
   getAvailableModelsByProviderType: vi.fn(async () => ["remote-model-1"]),
@@ -21,6 +25,16 @@ const providersActionMocks = vi.hoisted(() => ({
 }));
 vi.mock("@/actions/providers", () => providersActionMocks);
 
+function loadMessages() {
+  return {
+    common: commonMessages,
+    errors: errorsMessages,
+    ui: uiMessages,
+    forms: formsMessages,
+    settings: settingsMessages,
+  };
+}
+
 function render(node: ReactNode) {
   const container = document.createElement("div");
   document.body.appendChild(container);

+ 18 - 0
tests/unit/usage-doc/opencode-usage-doc.test.tsx

@@ -132,6 +132,24 @@ describe("UsageDoc - OpenCode 配置教程", () => {
       expect(usageMessages).toHaveProperty("opencode.configuration.title");
       expect(usageMessages).toHaveProperty("opencode.startup.title");
       expect(usageMessages).toHaveProperty("opencode.commonIssues.title");
+
+      expect(usageMessages).toHaveProperty("layout.headerTitle");
+      expect(usageMessages).toHaveProperty("layout.loginConsole");
+
+      expect(usageMessages).toHaveProperty("placeholders.windowsUserName");
+      expect(usageMessages).toHaveProperty("placeholders.shellConfig.linux");
+      expect(usageMessages).toHaveProperty("placeholders.shellConfig.macos");
+      expect(usageMessages).toHaveProperty("placeholders.codexVsCodeConfigFiles");
+
+      expect(usageMessages).toHaveProperty("claudeCode.installation.nativeInstall.macos.curls");
+
+      expect(usageMessages).toHaveProperty("snippets.comments.updateHomebrew");
+      expect(usageMessages).toHaveProperty("snippets.comments.installNodeJs");
+      expect(usageMessages).toHaveProperty("snippets.comments.ubuntuDebian");
+      expect(usageMessages).toHaveProperty("snippets.comments.centosRhelFedora");
+      expect(usageMessages).toHaveProperty("snippets.comments.addToPathIfMissing");
+      expect(usageMessages).toHaveProperty("snippets.comments.checkEnvVar");
+      expect(usageMessages).toHaveProperty("snippets.comments.testNetworkConnection");
     }
   });
 });

+ 14 - 0
tests/unit/usage-doc/usage-doc-page.test.tsx

@@ -80,6 +80,20 @@ describe("UsageDocPage - 目录/快速链接交互", () => {
     Reflect.deleteProperty(document, "cookie");
   });
 
+  test("ru 语言不应显示中文占位符与代码块注释", async () => {
+    const { unmount } = await renderWithIntl("ru", <UsageDocPage />);
+
+    const text = document.body.textContent || "";
+
+    expect(text).not.toContain("你的用户名");
+    expect(text).not.toContain("检查环境变量");
+    expect(text).not.toContain("添加到 PATH");
+
+    expect(text).toContain("C:\\Users\\your-username");
+
+    await unmount();
+  });
+
   test("目录项点击后应触发平滑滚动", async () => {
     const scrollToMock = vi.fn();
     Object.defineProperty(window, "scrollTo", {