refactor: 完成全站国际化改造,统一多语言支持

此提交完成了全项目的国际化适配:
1. 新增多语言翻译文件与基础配置
2. 替换所有硬编码文本为i18n调用
3. 优化语言切换与文档语言同步逻辑
4. 重构部分业务逻辑以支持动态翻译
5. 移除过时代码与硬编码配置
This commit is contained in:
2026-05-15 10:41:14 +08:00
parent ac612cb32c
commit f2c7f5e4f1
53 changed files with 2179 additions and 767 deletions

View File

@@ -10,10 +10,6 @@ export default async function DrawResultByNoPage(props: PageProps) {
return (
<div className="flex flex-col gap-4">
<div>
<h1 className="text-lg font-semibold tracking-tight"></h1>
<p className="text-xs text-muted-foreground"> · 23 </p>
</div>
<DrawResultDetailScreen drawNo={drawNo} />
</div>
);

View File

@@ -3,13 +3,8 @@ import { DrawResultsListScreen } from "@/features/results/draw-results-list-scre
export default function DrawResultsHistoryPage() {
return (
<div className="flex flex-col gap-4">
<div>
<h1 className="text-lg font-semibold tracking-tight"></h1>
<p className="text-xs text-muted-foreground">
GMT §4.6
</p>
</div>
<DrawResultsListScreen />
</div>
);
}

View File

@@ -6,7 +6,7 @@ import { EntryGate } from "@/features/player/entry-gate";
function EntryFallback(): ReactNode {
return (
<div className="flex min-h-dvh flex-col items-center justify-center bg-gradient-to-b from-red-800 to-red-950 px-4 text-sm text-white/90">
<p></p>
<p>Loading...</p>
</div>
);
}

View File

@@ -3,8 +3,10 @@
import { useEffect } from "react";
import Link from "next/link";
import { AlertTriangle, Home, RotateCcw } from "lucide-react";
import { useTranslation } from "react-i18next";
import { ERROR_COLORS } from "@/stores/error-store";
import "@/i18n";
/**
* Next.js 错误边界组件
@@ -18,6 +20,8 @@ export default function ErrorBoundary({
error: Error & { digest?: string };
reset: () => void;
}): React.ReactElement {
const { t } = useTranslation("player");
useEffect(() => {
// 记录错误日志
console.error("[ErrorBoundary] Application error:", error);
@@ -52,26 +56,28 @@ export default function ErrorBoundary({
{/* 错误标题 */}
<h1 className="mb-2 text-2xl font-bold text-foreground">
{isServerError ? "服务器暂时不可用" : "应用发生错误"}
{isServerError ? t("serverError.unavailable") : t("serverError.appTitle")}
</h1>
{/* 错误消息 */}
<p className="mb-2 text-base text-muted-foreground">
{isServerError
? "服务器暂时不可用,请稍后重试"
: "抱歉,应用遇到了问题,请尝试刷新页面"}
? t("serverError.serverMessage")
: t("serverError.appMessage")}
</p>
{/* 技术详情(开发模式) */}
{process.env.NODE_ENV === "development" && (
<div className="mb-6 w-full rounded-lg bg-muted p-3 text-left">
<p className="mb-1 text-xs font-medium text-muted-foreground"></p>
<p className="mb-1 text-xs font-medium text-muted-foreground">
{t("serverError.details")}
</p>
<code className="block max-h-32 overflow-auto whitespace-pre-wrap break-words text-xs text-destructive">
{error.message}
</code>
{error.digest && (
<p className="mt-2 text-xs text-muted-foreground">
ID: {error.digest}
{t("serverError.errorId", { id: error.digest })}
</p>
)}
</div>
@@ -85,7 +91,7 @@ export default function ErrorBoundary({
style={{ borderColor: isServerError ? ERROR_COLORS.error : ERROR_COLORS.warning, color: isServerError ? ERROR_COLORS.error : ERROR_COLORS.warning }}
>
<RotateCcw className="size-4" />
{t("actions.retry")}
</button>
<Link
@@ -96,7 +102,7 @@ export default function ErrorBoundary({
}}
>
<Home className="size-4" />
{t("serverError.home")}
</Link>
</div>
@@ -106,21 +112,21 @@ export default function ErrorBoundary({
onClick={() => window.location.reload()}
className="hover:text-foreground hover:underline"
>
{t("serverError.refreshPage")}
</button>
<span>|</span>
<Link
href="/hall"
className="hover:text-foreground hover:underline"
>
{t("serverError.hall")}
</Link>
<span>|</span>
<Link
href="/wallet"
className="hover:text-foreground hover:underline"
>
{t("serverError.wallet")}
</Link>
</div>
</div>

View File

@@ -2,6 +2,7 @@ import type { Metadata } from "next";
import { Geist, Geist_Mono } from "next/font/google";
import { Providers } from "@/components/providers";
import { DEFAULT_LANGUAGE } from "@/i18n";
import "./globals.css";
const geistSans = Geist({
@@ -26,7 +27,7 @@ export default function RootLayout({
}>) {
return (
<html
lang="en"
lang={DEFAULT_LANGUAGE}
suppressHydrationWarning
className={`${geistSans.variable} ${geistMono.variable} h-full antialiased`}
>

View File

@@ -2,8 +2,10 @@
import Link from "next/link";
import { FileQuestion, Home } from "lucide-react";
import { useTranslation } from "react-i18next";
import { ERROR_COLORS } from "@/stores/error-store";
import "@/i18n";
/**
* 全局 404 页面
@@ -13,6 +15,8 @@ import { ERROR_COLORS } from "@/stores/error-store";
* 使用中性颜色 #d9d9d9 作为背景/插图
*/
export default function NotFoundPage(): React.ReactElement {
const { t } = useTranslation("player");
return (
<div
className="flex min-h-screen flex-col items-center justify-center p-4"
@@ -42,12 +46,12 @@ export default function NotFoundPage(): React.ReactElement {
{/* 标题 */}
<h1 className="mb-3 text-2xl font-bold text-gray-800">
{t("notFound.title")}
</h1>
{/* 描述 */}
<p className="mb-8 text-base text-gray-500">
访
{t("notFound.description")}
</p>
{/* 返回首页按钮 */}
@@ -57,21 +61,21 @@ export default function NotFoundPage(): React.ReactElement {
style={{ backgroundColor: "#333" }}
>
<Home className="size-5" />
{t("notFound.home")}
</Link>
{/* 帮助链接 */}
<div className="mt-8 flex gap-4 text-sm text-gray-400">
<Link href="/hall" className="hover:text-gray-600 hover:underline">
{t("notFound.hall")}
</Link>
<span>|</span>
<Link href="/results" className="hover:text-gray-600 hover:underline">
{t("notFound.results")}
</Link>
<span>|</span>
<Link href="/wallet" className="hover:text-gray-600 hover:underline">
{t("notFound.wallet")}
</Link>
</div>
</div>