fix(i18n): overhaul i18n with [locale] routing, SSR translations, and hreflang SEO

Rewrites the i18n system introduced in PR #94 to use Next.js App Router
[locale] dynamic segments with SSR-rendered translations and proper
middleware locale routing.

- Add middleware locale detection: / rewrites to /zh-CN/ internally,
  /en and /ja pass through, /zh-CN/... redirects to bare path
- Move all 7 pages under app/[locale]/ with SSR translation injection
- Fix server→client serialization: pre-evaluate function-valued
  translations (makeSerializable) to eliminate hydration flash
- Fix language switch key flash: use hard navigation with localStorage-
  only persistence, avoiding React state update before page reload
- Add <link rel="alternate" hreflang> tags for multilingual SEO
- Fix Supabase setAll overwriting locale rewrite response
- Trim locales from 22 to 3 (zh-CN/en/ja), delete 19 incomplete files
- LLM-translate 240 firstact game preset JSONs (en + ja, landscape +
  portrait) and story titles via gemini-3.5-flash
- Delete 11 one-off migration scripts and outdated i18n docs
- Add useLocalePath hook and navigation utilities

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
yuanzonghao
2026-06-18 23:16:17 +08:00
parent 941b54c3f8
commit 0a7076d5b9
301 changed files with 2447 additions and 4358 deletions
+225
View File
@@ -0,0 +1,225 @@
import type { Metadata } from "next";
import Link from "next/link";
import { localePath } from "@/lib/i18n/navigation";
import { isValidLocale } from "@/lib/i18n/utils";
import { DEFAULT_LOCALE, type Locale } from "@/lib/i18n/config";
export const metadata: Metadata = {
title: "服务条款 — InfiPlot",
description: "InfiPlot 服务条款:使用 InfiPlot 服务前请阅读本条款。",
};
export default async function TermsPage({
params,
}: {
params: Promise<{ locale: string }>;
}) {
const { locale: rawLocale } = await params;
const locale: Locale = isValidLocale(rawLocale) ? rawLocale : DEFAULT_LOCALE;
const lp = (path: string) => localePath(path, locale);
return (
<main className="mx-auto w-full max-w-3xl px-6 md:px-16 py-16 md:py-24">
<Link
href={lp("/")}
className="inline-flex items-center gap-2 text-clay-500 hover:text-ember-500 transition-colors text-sm mb-12"
>
<i className="fa-solid fa-arrow-left text-xs" />
<span></span>
</Link>
<h1 className="font-serif text-3xl md:text-4xl text-clay-900 mb-4">
</h1>
<p className="text-sm text-clay-500 mb-12">
2026 6 14 &nbsp;|&nbsp; 2026 6 14
</p>
<div className="hairline-full w-full mb-12" />
<div className="space-y-10 text-clay-800 text-[15px] leading-[1.85]">
<section>
<h2 className="font-serif text-xl text-clay-900 mb-3">
</h2>
<p>
InfiPlot"我们""本服务" AI
</p>
<p className="mt-3">
使使
</p>
</section>
<section>
<h2 className="font-serif text-xl text-clay-900 mb-3">
</h2>
<p>
GoogleGitHub
</p>
</section>
<section>
<h2 className="font-serif text-xl text-clay-900 mb-3">
</h2>
<p>使</p>
<ul className="list-disc pl-6 space-y-1 mt-3">
<li></li>
<li>使 API</li>
<li></li>
<li></li>
</ul>
</section>
<section>
<h2 className="font-serif text-xl text-clay-900 mb-3">
AI
</h2>
<p>
AI
AI AI
</p>
<p className="mt-3">
AI
使
AI
</p>
</section>
<section>
<h2 className="font-serif text-xl text-clay-900 mb-3">
</h2>
<p>
InfiPlot {" "}
<a
href="https://www.gnu.org/licenses/agpl-3.0.html"
target="_blank"
rel="noopener noreferrer"
className="text-ember-500 hover:text-ember-400 transition-colors underline decoration-clay-900/20 underline-offset-2"
>
AGPL-3.0
</a>{" "}
</p>
<p className="mt-3">
</p>
<p className="mt-3">
CLA GitHub {" "}
<a
href="https://github.com/zonghaoyuan/infiplot/blob/staging/CLA.md"
target="_blank"
rel="noopener noreferrer"
className="text-ember-500 hover:text-ember-400 transition-colors underline decoration-clay-900/20 underline-offset-2"
>
CLA.md
</a>
<a
href="https://github.com/zonghaoyuan/infiplot/blob/staging/CLA.zh.md"
target="_blank"
rel="noopener noreferrer"
className="text-ember-500 hover:text-ember-400 transition-colors underline decoration-clay-900/20 underline-offset-2"
>
</a>
</p>
</section>
<section>
<h2 className="font-serif text-xl text-clay-900 mb-3">
</h2>
<p>
AI {" "}
<Link
href={lp("/privacy")}
className="text-ember-500 hover:text-ember-400 transition-colors underline decoration-clay-900/20 underline-offset-2"
>
</Link>
</p>
</section>
<section>
<h2 className="font-serif text-xl text-clay-900 mb-3">
</h2>
<p>
使
</p>
<p className="mt-3">
</p>
</section>
<section>
<h2 className="font-serif text-xl text-clay-900 mb-3">
</h2>
<p>
"现状""可用"
</p>
</section>
<section>
<h2 className="font-serif text-xl text-clay-900 mb-3">
</h2>
<p>
InfiPlot
使使
</p>
</section>
<section>
<h2 className="font-serif text-xl text-clay-900 mb-3">
</h2>
<p>
</p>
<ul className="list-disc pl-6 space-y-1 mt-3">
<li></li>
<li></li>
<li></li>
</ul>
</section>
<section>
<h2 className="font-serif text-xl text-clay-900 mb-3">
</h2>
<p>
使
</p>
</section>
<section>
<h2 className="font-serif text-xl text-clay-900 mb-3"></h2>
<p>
</p>
<p className="mt-3">
{" "}
<a
href="mailto:hi@infiplot.com"
className="text-ember-500 hover:text-ember-400 transition-colors"
>
hi@infiplot.com
</a>
</p>
</section>
</div>
<div className="hairline-full w-full mt-16 mb-8" />
<footer className="text-center text-[10px] smallcaps text-clay-500 pb-10">
<span>© 2026 InfiPlot. All rights reserved.</span>
</footer>
</main>
);
}