From aaada4f942f105981a37fb1c78f93eb24579aee3 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 6 May 2026 13:58:47 +0000 Subject: [PATCH 1/2] =?UTF-8?q?refactor:=20i18n=20URL=20=E6=AE=B5=E5=8C=96?= =?UTF-8?q?=EF=BC=88/zh/...=E3=80=81/en/...=EF=BC=89=E8=AE=A9=20docs=20?= =?UTF-8?q?=E5=85=A8=E9=87=8F=20SSG=EF=BC=8C=E7=A0=8D=20Vercel=20Fluid=20C?= =?UTF-8?q?PU?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 主因:next-intl 原方案用 cookie + RSC 切语言(i18n/request.ts 调 cookies()), 让全站 RSC 树都被钉成 dynamic,318 篇 docs 每次访问现 SSR 一次,30 天 Vercel Fluid Active CPU 月用量逼近 4h(Hobby 上限)。 改造: 1. next-intl 切到 URL routing - i18n/routing.ts (defineRouting) localePrefix: always - i18n/navigation.ts (createNavigation 出 Link/router/redirect) - i18n/request.ts 从 requestLocale 读 locale,不再 await cookies() 2. 路由重排 - 17 个 user-facing page + admin 全部移到 app/[locale]/... - app/api/* / app/sitemap.ts / app/robots.ts 保留在根 - app/layout.tsx 极简化(不读 cookies;只剩 html/body + 全局 metadata + theme inline script + structured data + analytics) - app/[locale]/layout.tsx 调 setRequestLocale + 包所有 provider (NextIntlClientProvider / ThemeProvider / AuthProvider / fumadocs RootProvider) 3. fumadocs i18n - lib/source.ts defineI18n: zh/en, dot parser, fallbackLanguage zh - mdx 内容从 app/docs 拆到 content/docs(路由文件 / 内容分离,符合 fumadocs 推荐) - normalize 8 对 conflict(无后缀=英文 + .zh.mdx=中文翻译 swap 成 无后缀=zh、.en.mdx=英文翻译,单一规则) - app/[locale]/docs/[...slug]/page.tsx 用 source.getPage(slug, locale) + setRequestLocale + force-static - SectionIndex 简化(删手写翻译版剪枝,靠 fumadocs i18n 的 pageTree 按 locale 隔离) 4. SEO + 切换 - LocaleToggle 改 next-intl router.replace + locale prefix - sitemap.ts 输出双语 entry + alternates.languages(hreflang 自动) - robots.ts disallow 用 wildcard 匹配两种语言 - canonical / hreflang 在 docs page generateMetadata 重新生成 5. proxy 合并 (Next.js 16 用 proxy.ts 不是 middleware.ts) - 删掉旧 IP geo + Accept-Language + cookie 写入逻辑(next-intl createMiddleware 原生支持) - 老 leetcode 中文 slug 301 redirect 保留,先于 i18n middleware 跑 6. 路径统一 - DOCS_BASE / contributors normalize / api/chat fs.readFile / scripts 全部 app/docs → content/docs 预期: - /[locale]/docs/[...slug] 从 ƒ Dynamic 变 ● SSG,build 时 322 静态页面 预渲染 - Vercel Fluid CPU docs 这块归零(占当前 50%) - 首页 / events / feed 等仍 ƒ(fetchHomepageEvents 等 server fetch 阻挡, 下一轮单独 PR 处理) 验证: - pnpm exec tsc --noEmit: 0 错误 - pnpm build: 通过 - .next/prerender-manifest.json: 322 routes prerendered --- app/{ => [locale]}/admin/community/layout.tsx | 0 app/{ => [locale]}/admin/community/lib.ts | 2 +- app/{ => [locale]}/admin/community/page.tsx | 4 +- app/{ => [locale]}/admin/database/page.tsx | 0 .../admin/events/AdminGuard.tsx | 0 app/{ => [locale]}/admin/events/EventForm.tsx | 6 +- .../admin/events/[id]/edit/page.tsx | 2 +- app/{ => [locale]}/admin/events/layout.tsx | 6 +- app/{ => [locale]}/admin/events/lib.ts | 2 +- app/{ => [locale]}/admin/events/new/page.tsx | 0 app/{ => [locale]}/admin/events/page.tsx | 2 +- app/{ => [locale]}/admin/layout.tsx | 0 app/{ => [locale]}/admin/page.tsx | 0 app/{ => [locale]}/admin/users/lib.ts | 0 app/{ => [locale]}/admin/users/page.tsx | 4 +- app/{ => [locale]}/docs/[...slug]/page.tsx | 138 +++++----- app/[locale]/docs/layout.tsx | 106 ++++++++ app/{ => [locale]}/docs/page.tsx | 35 ++- .../editor/EditorPageClient.tsx | 0 app/{ => [locale]}/editor/page.tsx | 0 .../events/[id]/InterestButton.tsx | 0 app/{ => [locale]}/events/[id]/page.tsx | 0 app/{ => [locale]}/events/page.tsx | 0 app/{ => [locale]}/events/types.ts | 0 .../feed/components/CategoryTabs.tsx | 2 +- .../feed/components/FeedAuthWrapper.tsx | 4 +- .../feed/components/LinkCard.tsx | 4 +- .../feed/components/ReportButton.tsx | 0 app/{ => [locale]}/feed/page.tsx | 10 +- app/{ => [locale]}/feed/submit/layout.tsx | 0 app/{ => [locale]}/feed/submit/page.tsx | 0 app/{ => [locale]}/feed/types.ts | 0 app/[locale]/layout.tsx | 91 +++++++ app/{ => [locale]}/login/page.tsx | 0 app/[locale]/page.tsx | 45 ++++ app/{ => [locale]}/rank/page.tsx | 0 app/{ => [locale]}/settings/SettingsForm.tsx | 0 app/{ => [locale]}/settings/page.tsx | 0 app/{ => [locale]}/share/page.tsx | 0 .../u/[username]/ActivityHeatmap.tsx | 0 .../u/[username]/AdminLinkIfOwnerAdmin.tsx | 0 .../u/[username]/DeveloperToolsIfOwner.tsx | 0 .../u/[username]/EditLinkIfOwner.tsx | 0 .../u/[username]/FollowButton.tsx | 0 .../u/[username]/GithubRepos.tsx | 0 .../u/[username]/ProfileCard.tsx | 0 .../u/[username]/SharesLinkIfOwner.tsx | 0 .../u/[username]/SharesOnProfile.tsx | 2 +- .../u/[username]/edit/EditProfileForm.tsx | 0 app/{ => [locale]}/u/[username]/edit/page.tsx | 0 app/{ => [locale]}/u/[username]/error.tsx | 0 app/{ => [locale]}/u/[username]/page.tsx | 0 .../u/[username]/shares/layout.tsx | 0 .../u/[username]/shares/page.tsx | 4 +- app/api/chat/route.ts | 3 +- app/components/LocaleToggle.tsx | 77 ++---- app/components/docs/SectionIndex.tsx | 187 ++++---------- app/docs/layout.tsx | 231 ----------------- app/layout.tsx | 113 ++++----- app/page.tsx | 28 -- app/robots.ts | 27 +- app/sitemap.ts | 240 +++++++++--------- .../docs/career/events/coffee-chat.en.md | 0 .../docs/career/events/coffee-chat.md | 0 .../docs/career/events/event-takeway.en.md | 0 .../docs/career/events/event-takeway.md | 0 {app => content}/docs/career/index.mdx | 0 .../docs/career/interview-prep/bq.en.md | 0 .../docs/career/interview-prep/bq.md | 0 .../interview-prep/interview-tips.en.mdx | 0 .../career/interview-prep/interview-tips.mdx | 0 .../1004-max-consecutive-ones-iii.en.md | 0 .../leetcode/1004_translated.md | 0 .../121-best-time-to-buy-and-sell-stock.en.md | 0 ...eplace-substring-for-balanced-string.en.md | 0 ...227\347\254\246\344\270\262_translated.md" | 0 ...by-vegan-friendly-price-and-distance.en.md | 0 .../leetcode/142-linked-list-cycle-ii.en.md | 0 ...2\351\223\276\350\241\250II_translated.md" | 0 .../leetcode/146-lru-cache.en.md | 0 ...45-find-kth-bit-in-nth-binary-string.en.md | 0 ...um-deletions-to-make-string-balanced.en.md | 0 ...244\346\254\241\346\225\260_translated.md" | 0 .../1664-ways-to-make-a-fair-array.en.md | 0 ...271\346\241\210\346\225\260_translated.md" | 0 .../leetcode/1825-mk-average.en.md | 0 ...263\345\235\207\345\200\274_translated.md" | 0 ...-on-number-of-points-inside-a-circle.en.md | 0 ...204\346\225\260\347\233\256_translated.md" | 0 .../leetcode/213-house-robber-ii.en.md | 0 ...me-by-concatenating-two-letter-words.en.md | 0 ...77\345\233\236\346\226\207\344\270\262.md" | 0 .../leetcode/219-contains-duplicate-ii.en.md | 0 .../interview-prep/leetcode/219_translated.md | 0 .../leetcode/2241-design-an-atm-machine.zh.md | 0 .../leetcode/2241. Design an ATM Machine.md | 0 .../2270-number-of-ways-to-split-array.zh.md | 0 .../2270. Number of Ways to Split Array.md | 0 .../leetcode/2293-min-max-game.en.md | 0 .../leetcode/2293_translated.md | 0 .../2299-strong-password-checker-ii.en.md | 0 ...0\351\252\214\345\231\250II_translated.md" | 0 ...glish-letter-in-upper-and-lower-case.en.md | 0 ...207\345\255\227\346\257\215_translated.md" | 0 ...-minimum-amount-of-time-to-fill-cups.en.md | 0 ...273\346\227\266\351\225\277_translated.md" | 0 ...341-maximum-number-of-pairs-in-array.en.md | 0 ...221\346\225\260\345\257\271_translated.md" | 0 .../leetcode/2490-circular-sentence.en.md | 0 ...2-find-the-array-concatenation-value.en.md | 0 .../leetcode/2582-pass-the-pillow.en.md | 0 .../2639-find-column-width-of-grid.en.md | 0 ...204\345\256\275\345\272\246_translated.md" | 0 .../leetcode/2679-sum-in-a-matrix.en.md | 0 ...255\347\232\204\345\222\214_translated.md" | 0 ...le-and-non-divisible-sums-difference.en.md | 0 ...14\345\271\266\344\275\234\345\267\256.md" | 0 ...stribute-elements-into-two-arrays-ii.en.md | 0 ...\347\273\204\344\270\255 II_translated.md" | 0 ...imum-length-of-anagram-concatenation.zh.md | 0 ...Minimum Length of Anagram Concatenation.md | 0 .../345-reverse-vowels-of-a-string.en.md | 0 ...263\345\255\227\346\257\215_translated.md" | 0 .../leetcode/42-trapping-rain-water.en.md | 0 .../docs/career/interview-prep/leetcode/42.md | 0 .../leetcode/46-permutations.zh.md | 0 ...6.\345\205\250\346\216\222\345\210\227.md" | 0 .../538-convert-bst-to-greater-sum-tree.en.md | 0 ...257\345\212\240\346\240\221_translated.md" | 0 ...distribute-money-to-maximum-children.en.md | 0 ...204\345\204\277\347\253\245_translated.md" | 0 .../76-minimum-window-substring.en.md | 0 ...226\345\255\220\344\270\262_translated.md" | 0 ...move-duplicates-from-sorted-array-ii.en.md | 0 .../interview-prep/leetcode/80_translated.md | 0 .../leetcode/9021-tut-3-25t1.zh.md | 0 .../leetcode/9021_TUT_3_25T1.md | 0 .../leetcode/93-restore-ip-addresses.zh.md | 0 ...\345\216\237Ip\345\234\260\345\235\200.md" | 0 .../leetcode/994-rotting-oranges.en.md | 0 ...204\346\251\230\345\255\220_translated.md" | 0 ...ing Stars-Inter-Uni Programming Contest.md | 0 ...263\346\227\266\346\234\237_translated.md" | 0 ...207\346\273\244\345\231\250_translated.md" | 0 ...RU \347\274\223\345\255\230_translated.md" | 0 ...347\232\204\347\254\254 K \344\275\215.md" | 0 ...\345\212\253\350\210\215 II_translated.md" | 0 ...236\347\216\257\345\217\245_translated.md" | 0 ...262\350\201\224\345\200\274_translated.md" | 0 ...222\346\236\225\345\244\264_translated.md" | 0 .../brief-alternate-homework-help.en.md | 0 ...232\345\270\256\345\277\231_translated.md" | 0 ...-stars-inter-uni-programming-contest.zh.md | 0 .../career/interview-prep/leetcode/index.mdx | 0 ...021-remove-nth-node-from-end-of-list.en.md | 0 ...252\347\273\223\347\202\271_translated.md" | 0 .../career/interview-prep/pre-interview.en.md | 0 .../career/interview-prep/pre-interview.md | 0 ...ations-to-get-an-offer-as-a-student.en.mdx | 0 ...parations-to-get-an-offer-as-a-student.mdx | 0 .../CommonUsedMarkdown.assets/testpic1.jpg | Bin .../dev-tips/CommonUsedMarkdown.en.md | 0 .../community/dev-tips/CommonUsedMarkdown.md | 0 .../docs/community/dev-tips/Katex/Seb1.en.mdx | 0 .../docs/community/dev-tips/Katex/Seb1.mdx | 0 .../docs/community/dev-tips/Katex/Seb2.en.mdx | 0 .../docs/community/dev-tips/Katex/Seb2.mdx | 0 .../docs/community/dev-tips/Katex/index.mdx | 0 ...dflare-r2-sharex-free-image-hosting.en.mdx | 0 ...loudflare-r2-sharex-free-image-hosting.mdx | 0 .../docs/community/dev-tips/git101.en.mdx | 0 .../docs/community/dev-tips/git101.mdx | 0 .../docs/community/dev-tips/index.mdx | 0 .../docs/community/dev-tips/picturecdn.en.mdx | 0 .../docs/community/dev-tips/picturecdn.mdx | 0 .../community/dev-tips/raspberry-guide.en.md | 0 .../community/dev-tips/raspberry-guide.md | 0 {app => content}/docs/community/index.mdx | 0 .../docs/community/language/pte-intro.md | 0 .../community/life/unsw-student-benefit.md | 0 .../community/mental-health/burnout-guide.mdx | 0 .../docs/community/mental-health/index.mdx | 0 .../docs/community/papers/leworldmodel.en.md | 0 .../docs/community/papers/leworldmodel.md | 0 ...petition-improves-non-reasoning-llms.en.md | 0 ...-repetition-improves-non-reasoning-llms.md | 0 .../docs/community/tools/index.md | 0 .../community/tools/perplexity-comet.en.md | 0 .../docs/community/tools/perplexity-comet.md | 0 .../docs/community/tools/swanlab.en.mdx | 0 .../docs/community/tools/swanlab.mdx | 0 .../introduction_of_multi-agents_system.en.md | 0 .../introduction_of_multi-agents_system.md | 0 .../img-20250920112106486.png | Bin .../img-20250920112106518.png | Bin .../img-20250920112106554.png | Bin .../img-20250920112106588.png | Bin .../docs/learn/ai/MoE/MOE-intro.en.md | 0 .../docs/learn/ai/MoE/MOE-intro.md | 0 .../docs/learn/ai/MoE/moe-update.en.md | 0 .../docs/learn/ai/MoE/moe-update.md | 0 .../code-translation-intro.en.mdx | 0 .../code-translation-intro.mdx | 0 .../ai/agents-todo/agent-ecosystem.en.mdx | 0 .../learn/ai/agents-todo/agent-ecosystem.mdx | 0 .../ai/agents-todo/cs294-194-196/index.en.mdx | 0 .../ai/agents-todo/cs294-194-196/index.mdx | 0 .../calculus-optimization/index.en.mdx | 0 .../calculus-optimization/index.mdx | 0 .../information-theory/index.en.mdx | 0 .../information-theory/index.mdx | 0 .../linear-algebra/index.en.mdx | 0 .../ai-math-basics/linear-algebra/index.mdx | 0 .../linear-algebra/resources/index.en.mdx | 0 .../linear-algebra/resources/index.mdx | 0 .../ai/ai-math-basics/math-foundations.en.mdx | 0 .../ai/ai-math-basics/math-foundations.mdx | 0 .../learn/ai/ai-math-basics/math_books.en.md | 0 .../learn/ai/ai-math-basics/math_books.md | 0 .../numerical-analysis/index.en.mdx | 0 .../numerical-analysis/index.mdx | 0 .../probability-statistics/index.en.mdx | 0 .../probability-statistics/index.mdx | 0 .../resources/index.en.mdx | 0 .../resources/index.mdx | 0 .../compute-platforms-handbook.en.mdx | 0 .../compute-platforms-handbook.mdx | 0 .../model-compuational-resource-demand.en.md | 0 .../model-compuational-resource-demand.md | 0 .../foundation-models/datasets/index.en.mdx | 0 .../ai/foundation-models/datasets/index.mdx | 0 .../deploy-infer/index.en.mdx | 0 .../foundation-models/deploy-infer/index.mdx | 0 .../foundation-models/evaluation/index.en.mdx | 0 .../ai/foundation-models/evaluation/index.mdx | 0 .../foundation-models/finetune/index.en.mdx | 0 .../ai/foundation-models/finetune/index.mdx | 0 .../foundation-models-lifecycle.en.mdx | 0 .../foundation-models-lifecycle.mdx | 0 .../qkv-interview/index.en.mdx | 0 .../foundation-models/qkv-interview/index.mdx | 0 .../rag/context-engineering-intro.en.md | 0 .../rag/context-engineering-intro.md | 0 .../ai/foundation-models/rag/embedding.en.mdx | 0 .../ai/foundation-models/rag/embedding.mdx | 0 .../learn/ai/foundation-models/rag/rag.en.mdx | 0 .../learn/ai/foundation-models/rag/rag.mdx | 0 .../foundation-models/training/index.en.mdx | 0 .../ai/foundation-models/training/index.mdx | 0 .../generative-models-plan.en.mdx | 0 .../generative-models-plan.mdx | 0 {app => content}/docs/learn/ai/index.mdx | 0 .../learn/ai/llm-basics/courses/index.en.mdx | 0 .../learn/ai/llm-basics/courses/index.mdx | 0 .../learn/ai/llm-basics/cuda/index.en.mdx | 0 .../docs/learn/ai/llm-basics/cuda/index.mdx | 0 .../llm-basics/deep-learning/d2l/index.en.mdx | 0 .../ai/llm-basics/deep-learning/d2l/index.mdx | 0 .../ai/llm-basics/deep-learning/index.en.mdx | 0 .../ai/llm-basics/deep-learning/index.mdx | 0 .../deep-learning/misc/index.en.mdx | 0 .../llm-basics/deep-learning/misc/index.mdx | 0 .../llm-basics/deep-learning/nlp/index.en.mdx | 0 .../ai/llm-basics/deep-learning/nlp/index.mdx | 0 .../ai/llm-basics/embeddings/index.en.mdx | 0 .../learn/ai/llm-basics/embeddings/index.mdx | 0 .../embeddings/qwen3-embedding/index.en.mdx | 0 .../embeddings/qwen3-embedding/index.mdx | 0 .../ai/llm-basics/llm-foundations.en.mdx | 0 .../learn/ai/llm-basics/llm-foundations.mdx | 0 .../pytorch/index.assets/word-img-01.png | Bin .../pytorch/index.assets/word-img-02.png | Bin .../learn/ai/llm-basics/pytorch/index.en.mdx | 0 .../learn/ai/llm-basics/pytorch/index.mdx | 0 .../transformer/ai-by-hand/index.en.mdx | 0 .../transformer/ai-by-hand/index.mdx | 0 .../ai/llm-basics/transformer/index.en.mdx | 0 .../learn/ai/llm-basics/transformer/index.mdx | 0 .../methodology/research-methodology.en.mdx | 0 .../ai/methodology/research-methodology.mdx | 0 .../ai/misc-tools/learning-toolkit.en.mdx | 0 .../learn/ai/misc-tools/learning-toolkit.mdx | 0 .../platform-and-datasets.en.mdx | 0 .../platform-and-datasets.mdx | 0 .../ai/multimodal/RQVAE/index.assets/2.png | Bin .../learn/ai/multimodal/RQVAE/index.en.mdx | 0 .../docs/learn/ai/multimodal/RQVAE/index.mdx | 0 ...61521005c5a8213b60bf362f9c25c22f_1440w.png | Bin .../v2-643d5ffb4aa480808bc9c82a55450a80_r.png | Bin ...92a9061e7079089b75c37650943c6f25_1440w.png | Bin ...97e09bd511a1fb0c3240fa717ce235d2_1440w.png | Bin ...dda0855d2d3e00e786956a827b1c5f26_1440w.png | Bin .../v2-df06f2d1471615dae76b1e09488091b5_r.png | Bin .../docs/learn/ai/multimodal/VAE/index.en.mdx | 0 .../docs/learn/ai/multimodal/VAE/index.mdx | 0 .../ai/multimodal/VQVAE/index.assets/1.png | Bin .../learn/ai/multimodal/VQVAE/index.en.mdx | 0 .../docs/learn/ai/multimodal/VQVAE/index.mdx | 0 .../learn/ai/multimodal/courses/index.en.mdx | 0 .../learn/ai/multimodal/courses/index.mdx | 0 .../llava/index.assets/word-img-03.png | Bin .../llava/index.assets/word-img-04.png | Bin .../llava/index.assets/word-img-05.png | Bin .../learn/ai/multimodal/llava/index.en.mdx | 0 .../docs/learn/ai/multimodal/llava/index.mdx | 0 .../learn/ai/multimodal/mllm/index.en.mdx | 0 .../docs/learn/ai/multimodal/mllm/index.mdx | 0 .../ai/multimodal/multimodal-overview.en.mdx | 0 .../ai/multimodal/multimodal-overview.mdx | 0 .../learn/ai/multimodal/qwenvl/index.en.mdx | 0 .../docs/learn/ai/multimodal/qwenvl/index.mdx | 0 .../ai/multimodal/video-mm-todo/index.en.mdx | 0 .../ai/multimodal/video-mm-todo/index.mdx | 0 .../docs/learn/ai/multimodal/vit/index.en.mdx | 0 .../docs/learn/ai/multimodal/vit/index.mdx | 0 .../recommender-roadmap.en.mdx | 0 .../recommender-roadmap.mdx | 0 .../wangshusen_recommend_crossing.en.mdx | 0 .../wangshusen_recommend_crossing.mdx | 0 .../images/3-1-1.png | Bin .../images/3-1-2.png | Bin .../images/3-2-1.png | Bin .../images/3-2-2.png | Bin .../images/3-2-3.png | Bin .../images/3-4-1.png | Bin .../images/3-4-2.png | Bin .../images/3-4-3.png | Bin .../images/3-5-1.png | Bin .../images/3-6-1.png | Bin .../images/3-6-2.png | Bin .../images/3-6-3.png | Bin .../images/3-6-4.png | Bin .../images/4-1-1.png | Bin .../images/4-2-1.png | Bin .../images/4-2-2.png | Bin .../images/4-2-3.png | Bin .../images/4-2-4.png | Bin .../images/4-2-5.png | Bin .../images/4-2-6.png | Bin .../images/4-3-1.png | Bin .../images/4-3-2.png | Bin .../images/4-4-1.png | Bin .../images/4-4-2.png | Bin .../images/4-4-3.png | Bin .../images/4-4-4.png | Bin .../images/4-4-5.png | Bin .../images/4-4-6.png | Bin .../images/4-4-7.png | Bin .../images/5-1-1.png | Bin .../images/5-1-2.png | Bin .../images/5-2-1.png | Bin .../images/5-2-2.png | Bin .../images/5-3-1.png | Bin .../images/6-1-1.png | Bin .../images/6-1-2.png | Bin .../images/6-1-3.png | Bin .../images/6-1-4.png | Bin .../images/6-1-5.png | Bin .../images/6-2-1.png | Bin .../images/6-2-2.png | Bin .../images/6-4-1.png | Bin .../images/6-4-2.png | Bin .../images/6-4-3.png | Bin .../images/6-4-4.png | Bin .../images/6-4-5.png | Bin .../images/6-5-1.png | Bin .../images/7-3-1.png | Bin .../images/7-3-2.png | Bin .../images/7-3-3.png | Bin .../images/7-4-1.png | Bin .../images/7-4-2.png | Bin .../images/7-4-3.png | Bin .../images/7-4-4.png | Bin .../images/7-5-1.png | Bin .../images/7-5-2.png | Bin .../images/7-6-1.png | Bin .../images/7-6-2.png | Bin .../images/7-6-3.png | Bin .../images/7-6-4.png | Bin .../images/7-6-5.png | Bin .../images/8-2-1.png | Bin .../images/8-3-1.png | Bin .../images/8-3-2.png | Bin .../images/8-3-3.png | Bin .../images/8-3-4.png | Bin .../images/8-3-5.png | Bin .../2-10-1.png | Bin .../2-10-2.png | Bin .../2-10-3.png | Bin .../2-10-4.png | Bin .../2-10-5.png | Bin .../2-10-6.png | Bin .../2-10-7.png | Bin .../2-10-8.png | Bin .../2-12-1.png | Bin .../2-12-2.png | Bin .../2-12-3.png | Bin .../2-4-1.png | Bin .../2-5-1.png | Bin .../2-5-2.png | Bin .../2-5-3.png | Bin .../2-5-4.png | Bin .../2-5-5.png | Bin .../2-6-1.png | Bin .../2-6-2.png | Bin .../2-6-3.png | Bin .../2-6-4.png | Bin .../2-6-5.png | Bin .../2-6-6.png | Bin .../2-7-1.png | Bin .../2-7-2.png | Bin .../2-7-3.png | Bin .../2-7-4.png | Bin .../2-8-1.png | Bin .../2-8-2.png | Bin .../2-9-1.png | Bin .../2-9-2.png | Bin .../2-9-3.png | Bin .../2-9-4.png | Bin ...wangshusen_recommend_note_retrieval.en.mdx | 0 .../wangshusen_recommend_note_retrieval.mdx | 0 ...wangshusen_recommend_note_coldstart.en.mdx | 0 .../wangshusen_recommend_note_coldstart.mdx | 0 ...ngshusen_recommend_note_improvement.en.mdx | 0 .../wangshusen_recommend_note_improvement.mdx | 0 .../wangshusen_recommend_note_rank.en.mdx | 0 .../wangshusen_recommend_note_rank.mdx | 0 .../wangshusen_recommend_note_rerank.en.mdx | 0 .../wangshusen_recommend_note_rerank.mdx | 0 .../learn/ai/reinforcement-learning/ppo.md | 0 .../reinforcement-learning-overview.en.mdx | 0 .../reinforcement-learning-overview.mdx | 0 .../cpp-backend/easy-compile/1-cpp-libs.en.md | 0 .../cs/cpp-backend/easy-compile/1-cpp-libs.md | 0 .../cpp-backend/easy-compile/2-base-gcc.en.md | 0 .../cs/cpp-backend/easy-compile/2-base-gcc.md | 0 .../cs/cpp-backend/easy-compile/3-make.en.md | 0 .../cs/cpp-backend/easy-compile/3-make.md | 0 .../cs/cpp-backend/easy-compile/4-cmake.en.md | 0 .../cs/cpp-backend/easy-compile/4-cmake.md | 0 .../cs/cpp-backend/easy-compile/5-vcpkg.en.md | 0 .../cs/cpp-backend/easy-compile/5-vcpkg.md | 0 .../1-handwritten-threadpool.en.md | 0 .../1-handwritten-threadpool.md | 0 .../2-handwritten-mempool1.en.md | 0 .../2-handwritten-mempool1.md | 0 .../cs/cpp-backend/mempool-simple.en.mdx | 0 .../learn/cs/cpp-backend/mempool-simple.mdx | 0 .../array/01-static-array.en.mdx | 0 .../data-structures/array/01-static-array.mdx | 0 .../array/02-dynamic-array.en.mdx | 0 .../array/02-dynamic-array.mdx | 0 .../cs/data-structures/array/index.en.mdx | 0 .../learn/cs/data-structures/array/index.mdx | 0 .../learn/cs/data-structures/index.en.mdx | 0 .../docs/learn/cs/data-structures/index.mdx | 0 .../linked-list/01-singly-linked-list.en.mdx | 0 .../linked-list/01-singly-linked-list.mdx | 0 .../data-structures/linked-list/index.en.mdx | 0 .../cs/data-structures/linked-list/index.mdx | 0 .../frontend/frontend-learning/index.en.mdx | 0 .../cs/frontend/frontend-learning/index.mdx | 0 .../docs/learn/cs/frontend/index.en.mdx | 0 .../docs/learn/cs/frontend/index.mdx | 0 {app => content}/docs/learn/cs/index.en.mdx | 0 {app => content}/docs/learn/cs/index.mdx | 0 {app => content}/docs/learn/index.mdx | 0 {app => content}/docs/projects/ai-town.en.mdx | 0 {app => content}/docs/projects/ai-town.mdx | 0 {app => content}/docs/projects/index.mdx | 0 .../docs/projects/multimodal-rl.en.mdx | 0 .../docs/projects/multimodal-rl.mdx | 0 i18n/navigation.ts | 23 ++ i18n/request.ts | 24 +- i18n/routing.ts | 24 ++ lib/contributors.ts | 4 +- lib/events-fetch.ts | 12 +- lib/github.ts | 5 +- lib/source.ts | 29 +++ proxy.ts | 117 +++------ scripts/backfill-contributors.mjs | 2 +- scripts/check-images.mjs | 2 +- scripts/escape-angles.mjs | 2 +- scripts/generate-leetcode-slug-map.mts | 2 +- scripts/move-doc-images.mjs | 2 +- scripts/uuid.mjs | 2 +- source.config.ts | 13 +- 487 files changed, 779 insertions(+), 859 deletions(-) rename app/{ => [locale]}/admin/community/layout.tsx (100%) rename app/{ => [locale]}/admin/community/lib.ts (96%) rename app/{ => [locale]}/admin/community/page.tsx (98%) rename app/{ => [locale]}/admin/database/page.tsx (100%) rename app/{ => [locale]}/admin/events/AdminGuard.tsx (100%) rename app/{ => [locale]}/admin/events/EventForm.tsx (98%) rename app/{ => [locale]}/admin/events/[id]/edit/page.tsx (98%) rename app/{ => [locale]}/admin/events/layout.tsx (78%) rename app/{ => [locale]}/admin/events/lib.ts (96%) rename app/{ => [locale]}/admin/events/new/page.tsx (100%) rename app/{ => [locale]}/admin/events/page.tsx (99%) rename app/{ => [locale]}/admin/layout.tsx (100%) rename app/{ => [locale]}/admin/page.tsx (100%) rename app/{ => [locale]}/admin/users/lib.ts (100%) rename app/{ => [locale]}/admin/users/page.tsx (99%) rename app/{ => [locale]}/docs/[...slug]/page.tsx (57%) create mode 100644 app/[locale]/docs/layout.tsx rename app/{ => [locale]}/docs/page.tsx (55%) rename app/{ => [locale]}/editor/EditorPageClient.tsx (100%) rename app/{ => [locale]}/editor/page.tsx (100%) rename app/{ => [locale]}/events/[id]/InterestButton.tsx (100%) rename app/{ => [locale]}/events/[id]/page.tsx (100%) rename app/{ => [locale]}/events/page.tsx (100%) rename app/{ => [locale]}/events/types.ts (100%) rename app/{ => [locale]}/feed/components/CategoryTabs.tsx (96%) rename app/{ => [locale]}/feed/components/FeedAuthWrapper.tsx (90%) rename app/{ => [locale]}/feed/components/LinkCard.tsx (97%) rename app/{ => [locale]}/feed/components/ReportButton.tsx (100%) rename app/{ => [locale]}/feed/page.tsx (95%) rename app/{ => [locale]}/feed/submit/layout.tsx (100%) rename app/{ => [locale]}/feed/submit/page.tsx (100%) rename app/{ => [locale]}/feed/types.ts (100%) create mode 100644 app/[locale]/layout.tsx rename app/{ => [locale]}/login/page.tsx (100%) create mode 100644 app/[locale]/page.tsx rename app/{ => [locale]}/rank/page.tsx (100%) rename app/{ => [locale]}/settings/SettingsForm.tsx (100%) rename app/{ => [locale]}/settings/page.tsx (100%) rename app/{ => [locale]}/share/page.tsx (100%) rename app/{ => [locale]}/u/[username]/ActivityHeatmap.tsx (100%) rename app/{ => [locale]}/u/[username]/AdminLinkIfOwnerAdmin.tsx (100%) rename app/{ => [locale]}/u/[username]/DeveloperToolsIfOwner.tsx (100%) rename app/{ => [locale]}/u/[username]/EditLinkIfOwner.tsx (100%) rename app/{ => [locale]}/u/[username]/FollowButton.tsx (100%) rename app/{ => [locale]}/u/[username]/GithubRepos.tsx (100%) rename app/{ => [locale]}/u/[username]/ProfileCard.tsx (100%) rename app/{ => [locale]}/u/[username]/SharesLinkIfOwner.tsx (100%) rename app/{ => [locale]}/u/[username]/SharesOnProfile.tsx (98%) rename app/{ => [locale]}/u/[username]/edit/EditProfileForm.tsx (100%) rename app/{ => [locale]}/u/[username]/edit/page.tsx (100%) rename app/{ => [locale]}/u/[username]/error.tsx (100%) rename app/{ => [locale]}/u/[username]/page.tsx (100%) rename app/{ => [locale]}/u/[username]/shares/layout.tsx (100%) rename app/{ => [locale]}/u/[username]/shares/page.tsx (97%) delete mode 100644 app/docs/layout.tsx delete mode 100644 app/page.tsx rename {app => content}/docs/career/events/coffee-chat.en.md (100%) rename {app => content}/docs/career/events/coffee-chat.md (100%) rename {app => content}/docs/career/events/event-takeway.en.md (100%) rename {app => content}/docs/career/events/event-takeway.md (100%) rename {app => content}/docs/career/index.mdx (100%) rename {app => content}/docs/career/interview-prep/bq.en.md (100%) rename {app => content}/docs/career/interview-prep/bq.md (100%) rename {app => content}/docs/career/interview-prep/interview-tips.en.mdx (100%) rename {app => content}/docs/career/interview-prep/interview-tips.mdx (100%) rename {app => content}/docs/career/interview-prep/leetcode/1004-max-consecutive-ones-iii.en.md (100%) rename {app => content}/docs/career/interview-prep/leetcode/1004_translated.md (100%) rename {app => content}/docs/career/interview-prep/leetcode/121-best-time-to-buy-and-sell-stock.en.md (100%) rename {app => content}/docs/career/interview-prep/leetcode/1234-replace-substring-for-balanced-string.en.md (100%) rename "app/docs/career/interview-prep/leetcode/1234. \346\233\277\346\215\242\345\255\220\344\270\262\345\276\227\345\210\260\345\271\263\350\241\241\345\255\227\347\254\246\344\270\262_translated.md" => "content/docs/career/interview-prep/leetcode/1234. \346\233\277\346\215\242\345\255\220\344\270\262\345\276\227\345\210\260\345\271\263\350\241\241\345\255\227\347\254\246\344\270\262_translated.md" (100%) rename {app => content}/docs/career/interview-prep/leetcode/1333-filter-restaurants-by-vegan-friendly-price-and-distance.en.md (100%) rename {app => content}/docs/career/interview-prep/leetcode/142-linked-list-cycle-ii.en.md (100%) rename "app/docs/career/interview-prep/leetcode/142.\347\216\257\345\275\242\351\223\276\350\241\250II_translated.md" => "content/docs/career/interview-prep/leetcode/142.\347\216\257\345\275\242\351\223\276\350\241\250II_translated.md" (100%) rename {app => content}/docs/career/interview-prep/leetcode/146-lru-cache.en.md (100%) rename {app => content}/docs/career/interview-prep/leetcode/1545-find-kth-bit-in-nth-binary-string.en.md (100%) rename {app => content}/docs/career/interview-prep/leetcode/1653-minimum-deletions-to-make-string-balanced.en.md (100%) rename "app/docs/career/interview-prep/leetcode/1653. \344\275\277\345\255\227\347\254\246\344\270\262\345\271\263\350\241\241\347\232\204\346\234\200\345\260\221\345\210\240\351\231\244\346\254\241\346\225\260_translated.md" => "content/docs/career/interview-prep/leetcode/1653. \344\275\277\345\255\227\347\254\246\344\270\262\345\271\263\350\241\241\347\232\204\346\234\200\345\260\221\345\210\240\351\231\244\346\254\241\346\225\260_translated.md" (100%) rename {app => content}/docs/career/interview-prep/leetcode/1664-ways-to-make-a-fair-array.en.md (100%) rename "app/docs/career/interview-prep/leetcode/1664\347\224\237\346\210\220\345\271\263\350\241\241\346\225\260\347\273\204\347\232\204\346\226\271\346\241\210\346\225\260_translated.md" => "content/docs/career/interview-prep/leetcode/1664\347\224\237\346\210\220\345\271\263\350\241\241\346\225\260\347\273\204\347\232\204\346\226\271\346\241\210\346\225\260_translated.md" (100%) rename {app => content}/docs/career/interview-prep/leetcode/1825-mk-average.en.md (100%) rename "app/docs/career/interview-prep/leetcode/1825\346\261\202\345\207\272 MK \345\271\263\345\235\207\345\200\274_translated.md" => "content/docs/career/interview-prep/leetcode/1825\346\261\202\345\207\272 MK \345\271\263\345\235\207\345\200\274_translated.md" (100%) rename {app => content}/docs/career/interview-prep/leetcode/1828-queries-on-number-of-points-inside-a-circle.en.md (100%) rename "app/docs/career/interview-prep/leetcode/1828\347\273\237\350\256\241\344\270\200\344\270\252\345\234\206\344\270\255\347\202\271\347\232\204\346\225\260\347\233\256_translated.md" => "content/docs/career/interview-prep/leetcode/1828\347\273\237\350\256\241\344\270\200\344\270\252\345\234\206\344\270\255\347\202\271\347\232\204\346\225\260\347\233\256_translated.md" (100%) rename {app => content}/docs/career/interview-prep/leetcode/213-house-robber-ii.en.md (100%) rename {app => content}/docs/career/interview-prep/leetcode/2131-longest-palindrome-by-concatenating-two-letter-words.en.md (100%) rename "app/docs/career/interview-prep/leetcode/2131. \350\277\236\346\216\245\344\270\244\345\255\227\346\257\215\345\215\225\350\257\215\345\276\227\345\210\260\347\232\204\346\234\200\351\225\277\345\233\236\346\226\207\344\270\262.md" => "content/docs/career/interview-prep/leetcode/2131. \350\277\236\346\216\245\344\270\244\345\255\227\346\257\215\345\215\225\350\257\215\345\276\227\345\210\260\347\232\204\346\234\200\351\225\277\345\233\236\346\226\207\344\270\262.md" (100%) rename {app => content}/docs/career/interview-prep/leetcode/219-contains-duplicate-ii.en.md (100%) rename {app => content}/docs/career/interview-prep/leetcode/219_translated.md (100%) rename {app => content}/docs/career/interview-prep/leetcode/2241-design-an-atm-machine.zh.md (100%) rename {app => content}/docs/career/interview-prep/leetcode/2241. Design an ATM Machine.md (100%) rename {app => content}/docs/career/interview-prep/leetcode/2270-number-of-ways-to-split-array.zh.md (100%) rename {app => content}/docs/career/interview-prep/leetcode/2270. Number of Ways to Split Array.md (100%) rename {app => content}/docs/career/interview-prep/leetcode/2293-min-max-game.en.md (100%) rename {app => content}/docs/career/interview-prep/leetcode/2293_translated.md (100%) rename {app => content}/docs/career/interview-prep/leetcode/2299-strong-password-checker-ii.en.md (100%) rename "app/docs/career/interview-prep/leetcode/2299\345\274\272\345\257\206\347\240\201\346\243\200\351\252\214\345\231\250II_translated.md" => "content/docs/career/interview-prep/leetcode/2299\345\274\272\345\257\206\347\240\201\346\243\200\351\252\214\345\231\250II_translated.md" (100%) rename {app => content}/docs/career/interview-prep/leetcode/2309-greatest-english-letter-in-upper-and-lower-case.en.md (100%) rename "app/docs/career/interview-prep/leetcode/2309\345\205\274\345\205\267\345\244\247\345\260\217\345\206\231\347\232\204\346\234\200\345\245\275\350\213\261\346\226\207\345\255\227\346\257\215_translated.md" => "content/docs/career/interview-prep/leetcode/2309\345\205\274\345\205\267\345\244\247\345\260\217\345\206\231\347\232\204\346\234\200\345\245\275\350\213\261\346\226\207\345\255\227\346\257\215_translated.md" (100%) rename {app => content}/docs/career/interview-prep/leetcode/2335-minimum-amount-of-time-to-fill-cups.en.md (100%) rename "app/docs/career/interview-prep/leetcode/2335. \350\243\205\346\273\241\346\235\257\345\255\220\351\234\200\350\246\201\347\232\204\346\234\200\347\237\255\346\200\273\346\227\266\351\225\277_translated.md" => "content/docs/career/interview-prep/leetcode/2335. \350\243\205\346\273\241\346\235\257\345\255\220\351\234\200\350\246\201\347\232\204\346\234\200\347\237\255\346\200\273\346\227\266\351\225\277_translated.md" (100%) rename {app => content}/docs/career/interview-prep/leetcode/2341-maximum-number-of-pairs-in-array.en.md (100%) rename "app/docs/career/interview-prep/leetcode/2341. \346\225\260\347\273\204\350\203\275\345\275\242\346\210\220\345\244\232\345\260\221\346\225\260\345\257\271_translated.md" => "content/docs/career/interview-prep/leetcode/2341. \346\225\260\347\273\204\350\203\275\345\275\242\346\210\220\345\244\232\345\260\221\346\225\260\345\257\271_translated.md" (100%) rename {app => content}/docs/career/interview-prep/leetcode/2490-circular-sentence.en.md (100%) rename {app => content}/docs/career/interview-prep/leetcode/2562-find-the-array-concatenation-value.en.md (100%) rename {app => content}/docs/career/interview-prep/leetcode/2582-pass-the-pillow.en.md (100%) rename {app => content}/docs/career/interview-prep/leetcode/2639-find-column-width-of-grid.en.md (100%) rename "app/docs/career/interview-prep/leetcode/2639. \346\237\245\350\257\242\347\275\221\346\240\274\345\233\276\344\270\255\346\257\217\344\270\200\345\210\227\347\232\204\345\256\275\345\272\246_translated.md" => "content/docs/career/interview-prep/leetcode/2639. \346\237\245\350\257\242\347\275\221\346\240\274\345\233\276\344\270\255\346\257\217\344\270\200\345\210\227\347\232\204\345\256\275\345\272\246_translated.md" (100%) rename {app => content}/docs/career/interview-prep/leetcode/2679-sum-in-a-matrix.en.md (100%) rename "app/docs/career/interview-prep/leetcode/2679.\347\237\251\351\230\265\344\270\255\347\232\204\345\222\214_translated.md" => "content/docs/career/interview-prep/leetcode/2679.\347\237\251\351\230\265\344\270\255\347\232\204\345\222\214_translated.md" (100%) rename {app => content}/docs/career/interview-prep/leetcode/2894-divisible-and-non-divisible-sums-difference.en.md (100%) rename "app/docs/career/interview-prep/leetcode/2894. \345\210\206\347\261\273\346\261\202\345\222\214\345\271\266\344\275\234\345\267\256.md" => "content/docs/career/interview-prep/leetcode/2894. \345\210\206\347\261\273\346\261\202\345\222\214\345\271\266\344\275\234\345\267\256.md" (100%) rename {app => content}/docs/career/interview-prep/leetcode/3072-distribute-elements-into-two-arrays-ii.en.md (100%) rename "app/docs/career/interview-prep/leetcode/3072. \345\260\206\345\205\203\347\264\240\345\210\206\351\205\215\345\210\260\344\270\244\344\270\252\346\225\260\347\273\204\344\270\255 II_translated.md" => "content/docs/career/interview-prep/leetcode/3072. \345\260\206\345\205\203\347\264\240\345\210\206\351\205\215\345\210\260\344\270\244\344\270\252\346\225\260\347\273\204\344\270\255 II_translated.md" (100%) rename {app => content}/docs/career/interview-prep/leetcode/3138-minimum-length-of-anagram-concatenation.zh.md (100%) rename {app => content}/docs/career/interview-prep/leetcode/3138. Minimum Length of Anagram Concatenation.md (100%) rename {app => content}/docs/career/interview-prep/leetcode/345-reverse-vowels-of-a-string.en.md (100%) rename "app/docs/career/interview-prep/leetcode/345. \345\217\215\350\275\254\345\255\227\347\254\246\344\270\262\344\270\255\347\232\204\345\205\203\351\237\263\345\255\227\346\257\215_translated.md" => "content/docs/career/interview-prep/leetcode/345. \345\217\215\350\275\254\345\255\227\347\254\246\344\270\262\344\270\255\347\232\204\345\205\203\351\237\263\345\255\227\346\257\215_translated.md" (100%) rename {app => content}/docs/career/interview-prep/leetcode/42-trapping-rain-water.en.md (100%) rename {app => content}/docs/career/interview-prep/leetcode/42.md (100%) rename {app => content}/docs/career/interview-prep/leetcode/46-permutations.zh.md (100%) rename "app/docs/career/interview-prep/leetcode/46.\345\205\250\346\216\222\345\210\227.md" => "content/docs/career/interview-prep/leetcode/46.\345\205\250\346\216\222\345\210\227.md" (100%) rename {app => content}/docs/career/interview-prep/leetcode/538-convert-bst-to-greater-sum-tree.en.md (100%) rename "app/docs/career/interview-prep/leetcode/538.\346\212\212\344\272\214\345\217\211\346\220\234\347\264\242\346\240\221\350\275\254\346\215\242\344\270\272\347\264\257\345\212\240\346\240\221_translated.md" => "content/docs/career/interview-prep/leetcode/538.\346\212\212\344\272\214\345\217\211\346\220\234\347\264\242\346\240\221\350\275\254\346\215\242\344\270\272\347\264\257\345\212\240\346\240\221_translated.md" (100%) rename {app => content}/docs/career/interview-prep/leetcode/6323-distribute-money-to-maximum-children.en.md (100%) rename "app/docs/career/interview-prep/leetcode/6323. \345\260\206\351\222\261\345\210\206\347\273\231\346\234\200\345\244\232\347\232\204\345\204\277\347\253\245_translated.md" => "content/docs/career/interview-prep/leetcode/6323. \345\260\206\351\222\261\345\210\206\347\273\231\346\234\200\345\244\232\347\232\204\345\204\277\347\253\245_translated.md" (100%) rename {app => content}/docs/career/interview-prep/leetcode/76-minimum-window-substring.en.md (100%) rename "app/docs/career/interview-prep/leetcode/76\346\234\200\345\260\217\350\246\206\347\233\226\345\255\220\344\270\262_translated.md" => "content/docs/career/interview-prep/leetcode/76\346\234\200\345\260\217\350\246\206\347\233\226\345\255\220\344\270\262_translated.md" (100%) rename {app => content}/docs/career/interview-prep/leetcode/80-remove-duplicates-from-sorted-array-ii.en.md (100%) rename {app => content}/docs/career/interview-prep/leetcode/80_translated.md (100%) rename {app => content}/docs/career/interview-prep/leetcode/9021-tut-3-25t1.zh.md (100%) rename {app => content}/docs/career/interview-prep/leetcode/9021_TUT_3_25T1.md (100%) rename {app => content}/docs/career/interview-prep/leetcode/93-restore-ip-addresses.zh.md (100%) rename "app/docs/career/interview-prep/leetcode/93\345\244\215\345\216\237Ip\345\234\260\345\235\200.md" => "content/docs/career/interview-prep/leetcode/93\345\244\215\345\216\237Ip\345\234\260\345\235\200.md" (100%) rename {app => content}/docs/career/interview-prep/leetcode/994-rotting-oranges.en.md (100%) rename "app/docs/career/interview-prep/leetcode/994.\350\205\220\347\203\202\347\232\204\346\251\230\345\255\220_translated.md" => "content/docs/career/interview-prep/leetcode/994.\350\205\220\347\203\202\347\232\204\346\251\230\345\255\220_translated.md" (100%) rename {app => content}/docs/career/interview-prep/leetcode/Counting Stars-Inter-Uni Programming Contest.md (100%) rename "app/docs/career/interview-prep/leetcode/[121]\344\271\260\345\215\226\350\202\241\347\245\250\347\232\204\346\234\200\344\275\263\346\227\266\346\234\237_translated.md" => "content/docs/career/interview-prep/leetcode/[121]\344\271\260\345\215\226\350\202\241\347\245\250\347\232\204\346\234\200\344\275\263\346\227\266\346\234\237_translated.md" (100%) rename "app/docs/career/interview-prep/leetcode/[1333]\351\244\220\345\216\205\350\277\207\346\273\244\345\231\250_translated.md" => "content/docs/career/interview-prep/leetcode/[1333]\351\244\220\345\216\205\350\277\207\346\273\244\345\231\250_translated.md" (100%) rename "app/docs/career/interview-prep/leetcode/[146]LRU \347\274\223\345\255\230_translated.md" => "content/docs/career/interview-prep/leetcode/[146]LRU \347\274\223\345\255\230_translated.md" (100%) rename "app/docs/career/interview-prep/leetcode/[1545]\346\211\276\345\207\272\347\254\254 N \344\270\252\344\272\214\350\277\233\345\210\266\345\255\227\347\254\246\344\270\262\344\270\255\347\232\204\347\254\254 K \344\275\215.md" => "content/docs/career/interview-prep/leetcode/[1545]\346\211\276\345\207\272\347\254\254 N \344\270\252\344\272\214\350\277\233\345\210\266\345\255\227\347\254\246\344\270\262\344\270\255\347\232\204\347\254\254 K \344\275\215.md" (100%) rename "app/docs/career/interview-prep/leetcode/[213]\346\211\223\345\256\266\345\212\253\350\210\215 II_translated.md" => "content/docs/career/interview-prep/leetcode/[213]\346\211\223\345\256\266\345\212\253\350\210\215 II_translated.md" (100%) rename "app/docs/career/interview-prep/leetcode/[2490]\345\233\236\347\216\257\345\217\245_translated.md" => "content/docs/career/interview-prep/leetcode/[2490]\345\233\236\347\216\257\345\217\245_translated.md" (100%) rename "app/docs/career/interview-prep/leetcode/[2562]\346\211\276\345\207\272\346\225\260\347\273\204\347\232\204\344\270\262\350\201\224\345\200\274_translated.md" => "content/docs/career/interview-prep/leetcode/[2562]\346\211\276\345\207\272\346\225\260\347\273\204\347\232\204\344\270\262\350\201\224\345\200\274_translated.md" (100%) rename "app/docs/career/interview-prep/leetcode/[2582]\351\200\222\346\236\225\345\244\264_translated.md" => "content/docs/career/interview-prep/leetcode/[2582]\351\200\222\346\236\225\345\244\264_translated.md" (100%) rename {app => content}/docs/career/interview-prep/leetcode/brief-alternate-homework-help.en.md (100%) rename "app/docs/career/interview-prep/leetcode/brief_alternate \344\275\234\344\270\232\345\270\256\345\277\231_translated.md" => "content/docs/career/interview-prep/leetcode/brief_alternate \344\275\234\344\270\232\345\270\256\345\277\231_translated.md" (100%) rename {app => content}/docs/career/interview-prep/leetcode/counting-stars-inter-uni-programming-contest.zh.md (100%) rename {app => content}/docs/career/interview-prep/leetcode/index.mdx (100%) rename {app => content}/docs/career/interview-prep/leetcode/sword-offer-ii-021-remove-nth-node-from-end-of-list.en.md (100%) rename "app/docs/career/interview-prep/leetcode/\345\211\221\346\214\207 Offer II 021. \345\210\240\351\231\244\351\223\276\350\241\250\347\232\204\345\200\222\346\225\260\347\254\254 n \344\270\252\347\273\223\347\202\271_translated.md" => "content/docs/career/interview-prep/leetcode/\345\211\221\346\214\207 Offer II 021. \345\210\240\351\231\244\351\223\276\350\241\250\347\232\204\345\200\222\346\225\260\347\254\254 n \344\270\252\347\273\223\347\202\271_translated.md" (100%) rename {app => content}/docs/career/interview-prep/pre-interview.en.md (100%) rename {app => content}/docs/career/interview-prep/pre-interview.md (100%) rename {app => content}/docs/career/interview-prep/preparations-to-get-an-offer-as-a-student.en.mdx (100%) rename {app => content}/docs/career/interview-prep/preparations-to-get-an-offer-as-a-student.mdx (100%) rename {app => content}/docs/community/dev-tips/CommonUsedMarkdown.assets/testpic1.jpg (100%) rename {app => content}/docs/community/dev-tips/CommonUsedMarkdown.en.md (100%) rename {app => content}/docs/community/dev-tips/CommonUsedMarkdown.md (100%) rename {app => content}/docs/community/dev-tips/Katex/Seb1.en.mdx (100%) rename {app => content}/docs/community/dev-tips/Katex/Seb1.mdx (100%) rename {app => content}/docs/community/dev-tips/Katex/Seb2.en.mdx (100%) rename {app => content}/docs/community/dev-tips/Katex/Seb2.mdx (100%) rename {app => content}/docs/community/dev-tips/Katex/index.mdx (100%) rename {app => content}/docs/community/dev-tips/cloudflare-r2-sharex-free-image-hosting.en.mdx (100%) rename {app => content}/docs/community/dev-tips/cloudflare-r2-sharex-free-image-hosting.mdx (100%) rename {app => content}/docs/community/dev-tips/git101.en.mdx (100%) rename {app => content}/docs/community/dev-tips/git101.mdx (100%) rename {app => content}/docs/community/dev-tips/index.mdx (100%) rename {app => content}/docs/community/dev-tips/picturecdn.en.mdx (100%) rename {app => content}/docs/community/dev-tips/picturecdn.mdx (100%) rename {app => content}/docs/community/dev-tips/raspberry-guide.en.md (100%) rename {app => content}/docs/community/dev-tips/raspberry-guide.md (100%) rename {app => content}/docs/community/index.mdx (100%) rename {app => content}/docs/community/language/pte-intro.md (100%) rename {app => content}/docs/community/life/unsw-student-benefit.md (100%) rename {app => content}/docs/community/mental-health/burnout-guide.mdx (100%) rename {app => content}/docs/community/mental-health/index.mdx (100%) rename app/docs/community/papers/leworldmodel.md => content/docs/community/papers/leworldmodel.en.md (100%) rename app/docs/community/papers/leworldmodel.zh.md => content/docs/community/papers/leworldmodel.md (100%) rename app/docs/community/papers/prompt-repetition-improves-non-reasoning-llms.md => content/docs/community/papers/prompt-repetition-improves-non-reasoning-llms.en.md (100%) rename app/docs/community/papers/prompt-repetition-improves-non-reasoning-llms.zh.md => content/docs/community/papers/prompt-repetition-improves-non-reasoning-llms.md (100%) rename {app => content}/docs/community/tools/index.md (100%) rename {app => content}/docs/community/tools/perplexity-comet.en.md (100%) rename {app => content}/docs/community/tools/perplexity-comet.md (100%) rename {app => content}/docs/community/tools/swanlab.en.mdx (100%) rename {app => content}/docs/community/tools/swanlab.mdx (100%) rename {app => content}/docs/learn/ai/Introduction-of-Multi-agents-system/introduction_of_multi-agents_system.en.md (100%) rename {app => content}/docs/learn/ai/Introduction-of-Multi-agents-system/introduction_of_multi-agents_system.md (100%) rename {app => content}/docs/learn/ai/MoE/MOE-intro.assets/img-20250920112106486.png (100%) rename {app => content}/docs/learn/ai/MoE/MOE-intro.assets/img-20250920112106518.png (100%) rename {app => content}/docs/learn/ai/MoE/MOE-intro.assets/img-20250920112106554.png (100%) rename {app => content}/docs/learn/ai/MoE/MOE-intro.assets/img-20250920112106588.png (100%) rename {app => content}/docs/learn/ai/MoE/MOE-intro.en.md (100%) rename {app => content}/docs/learn/ai/MoE/MOE-intro.md (100%) rename {app => content}/docs/learn/ai/MoE/moe-update.en.md (100%) rename {app => content}/docs/learn/ai/MoE/moe-update.md (100%) rename {app => content}/docs/learn/ai/Multi-agents-system-on-Code-Translation/code-translation-intro.en.mdx (100%) rename {app => content}/docs/learn/ai/Multi-agents-system-on-Code-Translation/code-translation-intro.mdx (100%) rename {app => content}/docs/learn/ai/agents-todo/agent-ecosystem.en.mdx (100%) rename {app => content}/docs/learn/ai/agents-todo/agent-ecosystem.mdx (100%) rename {app => content}/docs/learn/ai/agents-todo/cs294-194-196/index.en.mdx (100%) rename {app => content}/docs/learn/ai/agents-todo/cs294-194-196/index.mdx (100%) rename {app => content}/docs/learn/ai/ai-math-basics/calculus-optimization/index.en.mdx (100%) rename {app => content}/docs/learn/ai/ai-math-basics/calculus-optimization/index.mdx (100%) rename {app => content}/docs/learn/ai/ai-math-basics/information-theory/index.en.mdx (100%) rename {app => content}/docs/learn/ai/ai-math-basics/information-theory/index.mdx (100%) rename {app => content}/docs/learn/ai/ai-math-basics/linear-algebra/index.en.mdx (100%) rename {app => content}/docs/learn/ai/ai-math-basics/linear-algebra/index.mdx (100%) rename {app => content}/docs/learn/ai/ai-math-basics/linear-algebra/resources/index.en.mdx (100%) rename {app => content}/docs/learn/ai/ai-math-basics/linear-algebra/resources/index.mdx (100%) rename {app => content}/docs/learn/ai/ai-math-basics/math-foundations.en.mdx (100%) rename {app => content}/docs/learn/ai/ai-math-basics/math-foundations.mdx (100%) rename {app => content}/docs/learn/ai/ai-math-basics/math_books.en.md (100%) rename {app => content}/docs/learn/ai/ai-math-basics/math_books.md (100%) rename {app => content}/docs/learn/ai/ai-math-basics/numerical-analysis/index.en.mdx (100%) rename {app => content}/docs/learn/ai/ai-math-basics/numerical-analysis/index.mdx (100%) rename {app => content}/docs/learn/ai/ai-math-basics/probability-statistics/index.en.mdx (100%) rename {app => content}/docs/learn/ai/ai-math-basics/probability-statistics/index.mdx (100%) rename {app => content}/docs/learn/ai/ai-math-basics/probability-statistics/resources/index.en.mdx (100%) rename {app => content}/docs/learn/ai/ai-math-basics/probability-statistics/resources/index.mdx (100%) rename {app => content}/docs/learn/ai/compute-platforms/compute-platforms-handbook.en.mdx (100%) rename {app => content}/docs/learn/ai/compute-platforms/compute-platforms-handbook.mdx (100%) rename {app => content}/docs/learn/ai/compute-platforms/model-compuational-resource-demand.en.md (100%) rename {app => content}/docs/learn/ai/compute-platforms/model-compuational-resource-demand.md (100%) rename {app => content}/docs/learn/ai/foundation-models/datasets/index.en.mdx (100%) rename {app => content}/docs/learn/ai/foundation-models/datasets/index.mdx (100%) rename {app => content}/docs/learn/ai/foundation-models/deploy-infer/index.en.mdx (100%) rename {app => content}/docs/learn/ai/foundation-models/deploy-infer/index.mdx (100%) rename {app => content}/docs/learn/ai/foundation-models/evaluation/index.en.mdx (100%) rename {app => content}/docs/learn/ai/foundation-models/evaluation/index.mdx (100%) rename {app => content}/docs/learn/ai/foundation-models/finetune/index.en.mdx (100%) rename {app => content}/docs/learn/ai/foundation-models/finetune/index.mdx (100%) rename {app => content}/docs/learn/ai/foundation-models/foundation-models-lifecycle.en.mdx (100%) rename {app => content}/docs/learn/ai/foundation-models/foundation-models-lifecycle.mdx (100%) rename {app => content}/docs/learn/ai/foundation-models/qkv-interview/index.en.mdx (100%) rename {app => content}/docs/learn/ai/foundation-models/qkv-interview/index.mdx (100%) rename {app => content}/docs/learn/ai/foundation-models/rag/context-engineering-intro.en.md (100%) rename {app => content}/docs/learn/ai/foundation-models/rag/context-engineering-intro.md (100%) rename {app => content}/docs/learn/ai/foundation-models/rag/embedding.en.mdx (100%) rename {app => content}/docs/learn/ai/foundation-models/rag/embedding.mdx (100%) rename {app => content}/docs/learn/ai/foundation-models/rag/rag.en.mdx (100%) rename {app => content}/docs/learn/ai/foundation-models/rag/rag.mdx (100%) rename {app => content}/docs/learn/ai/foundation-models/training/index.en.mdx (100%) rename {app => content}/docs/learn/ai/foundation-models/training/index.mdx (100%) rename {app => content}/docs/learn/ai/generative-todo/generative-models-plan.en.mdx (100%) rename {app => content}/docs/learn/ai/generative-todo/generative-models-plan.mdx (100%) rename {app => content}/docs/learn/ai/index.mdx (100%) rename {app => content}/docs/learn/ai/llm-basics/courses/index.en.mdx (100%) rename {app => content}/docs/learn/ai/llm-basics/courses/index.mdx (100%) rename {app => content}/docs/learn/ai/llm-basics/cuda/index.en.mdx (100%) rename {app => content}/docs/learn/ai/llm-basics/cuda/index.mdx (100%) rename {app => content}/docs/learn/ai/llm-basics/deep-learning/d2l/index.en.mdx (100%) rename {app => content}/docs/learn/ai/llm-basics/deep-learning/d2l/index.mdx (100%) rename {app => content}/docs/learn/ai/llm-basics/deep-learning/index.en.mdx (100%) rename {app => content}/docs/learn/ai/llm-basics/deep-learning/index.mdx (100%) rename {app => content}/docs/learn/ai/llm-basics/deep-learning/misc/index.en.mdx (100%) rename {app => content}/docs/learn/ai/llm-basics/deep-learning/misc/index.mdx (100%) rename {app => content}/docs/learn/ai/llm-basics/deep-learning/nlp/index.en.mdx (100%) rename {app => content}/docs/learn/ai/llm-basics/deep-learning/nlp/index.mdx (100%) rename {app => content}/docs/learn/ai/llm-basics/embeddings/index.en.mdx (100%) rename {app => content}/docs/learn/ai/llm-basics/embeddings/index.mdx (100%) rename {app => content}/docs/learn/ai/llm-basics/embeddings/qwen3-embedding/index.en.mdx (100%) rename {app => content}/docs/learn/ai/llm-basics/embeddings/qwen3-embedding/index.mdx (100%) rename {app => content}/docs/learn/ai/llm-basics/llm-foundations.en.mdx (100%) rename {app => content}/docs/learn/ai/llm-basics/llm-foundations.mdx (100%) rename {app => content}/docs/learn/ai/llm-basics/pytorch/index.assets/word-img-01.png (100%) rename {app => content}/docs/learn/ai/llm-basics/pytorch/index.assets/word-img-02.png (100%) rename {app => content}/docs/learn/ai/llm-basics/pytorch/index.en.mdx (100%) rename {app => content}/docs/learn/ai/llm-basics/pytorch/index.mdx (100%) rename {app => content}/docs/learn/ai/llm-basics/transformer/ai-by-hand/index.en.mdx (100%) rename {app => content}/docs/learn/ai/llm-basics/transformer/ai-by-hand/index.mdx (100%) rename {app => content}/docs/learn/ai/llm-basics/transformer/index.en.mdx (100%) rename {app => content}/docs/learn/ai/llm-basics/transformer/index.mdx (100%) rename {app => content}/docs/learn/ai/methodology/research-methodology.en.mdx (100%) rename {app => content}/docs/learn/ai/methodology/research-methodology.mdx (100%) rename {app => content}/docs/learn/ai/misc-tools/learning-toolkit.en.mdx (100%) rename {app => content}/docs/learn/ai/misc-tools/learning-toolkit.mdx (100%) rename {app => content}/docs/learn/ai/model-datasets-platforms/platform-and-datasets.en.mdx (100%) rename {app => content}/docs/learn/ai/model-datasets-platforms/platform-and-datasets.mdx (100%) rename {app => content}/docs/learn/ai/multimodal/RQVAE/index.assets/2.png (100%) rename {app => content}/docs/learn/ai/multimodal/RQVAE/index.en.mdx (100%) rename {app => content}/docs/learn/ai/multimodal/RQVAE/index.mdx (100%) rename {app => content}/docs/learn/ai/multimodal/VAE/index.assets/v2-61521005c5a8213b60bf362f9c25c22f_1440w.png (100%) rename {app => content}/docs/learn/ai/multimodal/VAE/index.assets/v2-643d5ffb4aa480808bc9c82a55450a80_r.png (100%) rename {app => content}/docs/learn/ai/multimodal/VAE/index.assets/v2-92a9061e7079089b75c37650943c6f25_1440w.png (100%) rename {app => content}/docs/learn/ai/multimodal/VAE/index.assets/v2-97e09bd511a1fb0c3240fa717ce235d2_1440w.png (100%) rename {app => content}/docs/learn/ai/multimodal/VAE/index.assets/v2-dda0855d2d3e00e786956a827b1c5f26_1440w.png (100%) rename {app => content}/docs/learn/ai/multimodal/VAE/index.assets/v2-df06f2d1471615dae76b1e09488091b5_r.png (100%) rename {app => content}/docs/learn/ai/multimodal/VAE/index.en.mdx (100%) rename {app => content}/docs/learn/ai/multimodal/VAE/index.mdx (100%) rename {app => content}/docs/learn/ai/multimodal/VQVAE/index.assets/1.png (100%) rename {app => content}/docs/learn/ai/multimodal/VQVAE/index.en.mdx (100%) rename {app => content}/docs/learn/ai/multimodal/VQVAE/index.mdx (100%) rename {app => content}/docs/learn/ai/multimodal/courses/index.en.mdx (100%) rename {app => content}/docs/learn/ai/multimodal/courses/index.mdx (100%) rename {app => content}/docs/learn/ai/multimodal/llava/index.assets/word-img-03.png (100%) rename {app => content}/docs/learn/ai/multimodal/llava/index.assets/word-img-04.png (100%) rename {app => content}/docs/learn/ai/multimodal/llava/index.assets/word-img-05.png (100%) rename {app => content}/docs/learn/ai/multimodal/llava/index.en.mdx (100%) rename {app => content}/docs/learn/ai/multimodal/llava/index.mdx (100%) rename {app => content}/docs/learn/ai/multimodal/mllm/index.en.mdx (100%) rename {app => content}/docs/learn/ai/multimodal/mllm/index.mdx (100%) rename {app => content}/docs/learn/ai/multimodal/multimodal-overview.en.mdx (100%) rename {app => content}/docs/learn/ai/multimodal/multimodal-overview.mdx (100%) rename {app => content}/docs/learn/ai/multimodal/qwenvl/index.en.mdx (100%) rename {app => content}/docs/learn/ai/multimodal/qwenvl/index.mdx (100%) rename {app => content}/docs/learn/ai/multimodal/video-mm-todo/index.en.mdx (100%) rename {app => content}/docs/learn/ai/multimodal/video-mm-todo/index.mdx (100%) rename {app => content}/docs/learn/ai/multimodal/vit/index.en.mdx (100%) rename {app => content}/docs/learn/ai/multimodal/vit/index.mdx (100%) rename {app => content}/docs/learn/ai/recommender-systems/recommender-roadmap.en.mdx (100%) rename {app => content}/docs/learn/ai/recommender-systems/recommender-roadmap.mdx (100%) rename {app => content}/docs/learn/ai/recommender-systems/wangshusen_recommend_crossing.en.mdx (100%) rename {app => content}/docs/learn/ai/recommender-systems/wangshusen_recommend_crossing.mdx (100%) rename {app => content}/docs/learn/ai/recommender-systems/wangshusen_recommend_note/images/3-1-1.png (100%) rename {app => content}/docs/learn/ai/recommender-systems/wangshusen_recommend_note/images/3-1-2.png (100%) rename {app => content}/docs/learn/ai/recommender-systems/wangshusen_recommend_note/images/3-2-1.png (100%) rename {app => content}/docs/learn/ai/recommender-systems/wangshusen_recommend_note/images/3-2-2.png (100%) rename {app => content}/docs/learn/ai/recommender-systems/wangshusen_recommend_note/images/3-2-3.png (100%) rename {app => content}/docs/learn/ai/recommender-systems/wangshusen_recommend_note/images/3-4-1.png (100%) rename {app => content}/docs/learn/ai/recommender-systems/wangshusen_recommend_note/images/3-4-2.png (100%) rename {app => content}/docs/learn/ai/recommender-systems/wangshusen_recommend_note/images/3-4-3.png (100%) rename {app => content}/docs/learn/ai/recommender-systems/wangshusen_recommend_note/images/3-5-1.png (100%) rename {app => content}/docs/learn/ai/recommender-systems/wangshusen_recommend_note/images/3-6-1.png (100%) rename {app => content}/docs/learn/ai/recommender-systems/wangshusen_recommend_note/images/3-6-2.png (100%) rename {app => content}/docs/learn/ai/recommender-systems/wangshusen_recommend_note/images/3-6-3.png (100%) rename {app => content}/docs/learn/ai/recommender-systems/wangshusen_recommend_note/images/3-6-4.png (100%) rename {app => content}/docs/learn/ai/recommender-systems/wangshusen_recommend_note/images/4-1-1.png (100%) rename {app => content}/docs/learn/ai/recommender-systems/wangshusen_recommend_note/images/4-2-1.png (100%) rename {app => content}/docs/learn/ai/recommender-systems/wangshusen_recommend_note/images/4-2-2.png (100%) rename {app => content}/docs/learn/ai/recommender-systems/wangshusen_recommend_note/images/4-2-3.png (100%) rename {app => content}/docs/learn/ai/recommender-systems/wangshusen_recommend_note/images/4-2-4.png (100%) rename {app => content}/docs/learn/ai/recommender-systems/wangshusen_recommend_note/images/4-2-5.png (100%) rename {app => content}/docs/learn/ai/recommender-systems/wangshusen_recommend_note/images/4-2-6.png (100%) rename {app => content}/docs/learn/ai/recommender-systems/wangshusen_recommend_note/images/4-3-1.png (100%) rename {app => content}/docs/learn/ai/recommender-systems/wangshusen_recommend_note/images/4-3-2.png (100%) rename {app => content}/docs/learn/ai/recommender-systems/wangshusen_recommend_note/images/4-4-1.png (100%) rename {app => content}/docs/learn/ai/recommender-systems/wangshusen_recommend_note/images/4-4-2.png (100%) rename {app => content}/docs/learn/ai/recommender-systems/wangshusen_recommend_note/images/4-4-3.png (100%) rename {app => content}/docs/learn/ai/recommender-systems/wangshusen_recommend_note/images/4-4-4.png (100%) rename {app => content}/docs/learn/ai/recommender-systems/wangshusen_recommend_note/images/4-4-5.png (100%) rename {app => content}/docs/learn/ai/recommender-systems/wangshusen_recommend_note/images/4-4-6.png (100%) rename {app => content}/docs/learn/ai/recommender-systems/wangshusen_recommend_note/images/4-4-7.png (100%) rename {app => content}/docs/learn/ai/recommender-systems/wangshusen_recommend_note/images/5-1-1.png (100%) rename {app => content}/docs/learn/ai/recommender-systems/wangshusen_recommend_note/images/5-1-2.png (100%) rename {app => content}/docs/learn/ai/recommender-systems/wangshusen_recommend_note/images/5-2-1.png (100%) rename {app => content}/docs/learn/ai/recommender-systems/wangshusen_recommend_note/images/5-2-2.png (100%) rename {app => content}/docs/learn/ai/recommender-systems/wangshusen_recommend_note/images/5-3-1.png (100%) rename {app => content}/docs/learn/ai/recommender-systems/wangshusen_recommend_note/images/6-1-1.png (100%) rename {app => content}/docs/learn/ai/recommender-systems/wangshusen_recommend_note/images/6-1-2.png (100%) rename {app => content}/docs/learn/ai/recommender-systems/wangshusen_recommend_note/images/6-1-3.png (100%) rename {app => content}/docs/learn/ai/recommender-systems/wangshusen_recommend_note/images/6-1-4.png (100%) rename {app => content}/docs/learn/ai/recommender-systems/wangshusen_recommend_note/images/6-1-5.png (100%) rename {app => content}/docs/learn/ai/recommender-systems/wangshusen_recommend_note/images/6-2-1.png (100%) rename {app => content}/docs/learn/ai/recommender-systems/wangshusen_recommend_note/images/6-2-2.png (100%) rename {app => content}/docs/learn/ai/recommender-systems/wangshusen_recommend_note/images/6-4-1.png (100%) rename {app => content}/docs/learn/ai/recommender-systems/wangshusen_recommend_note/images/6-4-2.png (100%) rename {app => content}/docs/learn/ai/recommender-systems/wangshusen_recommend_note/images/6-4-3.png (100%) rename {app => content}/docs/learn/ai/recommender-systems/wangshusen_recommend_note/images/6-4-4.png (100%) rename {app => content}/docs/learn/ai/recommender-systems/wangshusen_recommend_note/images/6-4-5.png (100%) rename {app => content}/docs/learn/ai/recommender-systems/wangshusen_recommend_note/images/6-5-1.png (100%) rename {app => content}/docs/learn/ai/recommender-systems/wangshusen_recommend_note/images/7-3-1.png (100%) rename {app => content}/docs/learn/ai/recommender-systems/wangshusen_recommend_note/images/7-3-2.png (100%) rename {app => content}/docs/learn/ai/recommender-systems/wangshusen_recommend_note/images/7-3-3.png (100%) rename {app => content}/docs/learn/ai/recommender-systems/wangshusen_recommend_note/images/7-4-1.png (100%) rename {app => content}/docs/learn/ai/recommender-systems/wangshusen_recommend_note/images/7-4-2.png (100%) rename {app => content}/docs/learn/ai/recommender-systems/wangshusen_recommend_note/images/7-4-3.png (100%) rename {app => content}/docs/learn/ai/recommender-systems/wangshusen_recommend_note/images/7-4-4.png (100%) rename {app => content}/docs/learn/ai/recommender-systems/wangshusen_recommend_note/images/7-5-1.png (100%) rename {app => content}/docs/learn/ai/recommender-systems/wangshusen_recommend_note/images/7-5-2.png (100%) rename {app => content}/docs/learn/ai/recommender-systems/wangshusen_recommend_note/images/7-6-1.png (100%) rename {app => content}/docs/learn/ai/recommender-systems/wangshusen_recommend_note/images/7-6-2.png (100%) rename {app => content}/docs/learn/ai/recommender-systems/wangshusen_recommend_note/images/7-6-3.png (100%) rename {app => content}/docs/learn/ai/recommender-systems/wangshusen_recommend_note/images/7-6-4.png (100%) rename {app => content}/docs/learn/ai/recommender-systems/wangshusen_recommend_note/images/7-6-5.png (100%) rename {app => content}/docs/learn/ai/recommender-systems/wangshusen_recommend_note/images/8-2-1.png (100%) rename {app => content}/docs/learn/ai/recommender-systems/wangshusen_recommend_note/images/8-3-1.png (100%) rename {app => content}/docs/learn/ai/recommender-systems/wangshusen_recommend_note/images/8-3-2.png (100%) rename {app => content}/docs/learn/ai/recommender-systems/wangshusen_recommend_note/images/8-3-3.png (100%) rename {app => content}/docs/learn/ai/recommender-systems/wangshusen_recommend_note/images/8-3-4.png (100%) rename {app => content}/docs/learn/ai/recommender-systems/wangshusen_recommend_note/images/8-3-5.png (100%) rename {app => content}/docs/learn/ai/recommender-systems/wangshusen_recommend_note/wangshusen_recommend_note_retrieval.assets/2-10-1.png (100%) rename {app => content}/docs/learn/ai/recommender-systems/wangshusen_recommend_note/wangshusen_recommend_note_retrieval.assets/2-10-2.png (100%) rename {app => content}/docs/learn/ai/recommender-systems/wangshusen_recommend_note/wangshusen_recommend_note_retrieval.assets/2-10-3.png (100%) rename {app => content}/docs/learn/ai/recommender-systems/wangshusen_recommend_note/wangshusen_recommend_note_retrieval.assets/2-10-4.png (100%) rename {app => content}/docs/learn/ai/recommender-systems/wangshusen_recommend_note/wangshusen_recommend_note_retrieval.assets/2-10-5.png (100%) rename {app => content}/docs/learn/ai/recommender-systems/wangshusen_recommend_note/wangshusen_recommend_note_retrieval.assets/2-10-6.png (100%) rename {app => content}/docs/learn/ai/recommender-systems/wangshusen_recommend_note/wangshusen_recommend_note_retrieval.assets/2-10-7.png (100%) rename {app => content}/docs/learn/ai/recommender-systems/wangshusen_recommend_note/wangshusen_recommend_note_retrieval.assets/2-10-8.png (100%) rename {app => content}/docs/learn/ai/recommender-systems/wangshusen_recommend_note/wangshusen_recommend_note_retrieval.assets/2-12-1.png (100%) rename {app => content}/docs/learn/ai/recommender-systems/wangshusen_recommend_note/wangshusen_recommend_note_retrieval.assets/2-12-2.png (100%) rename {app => content}/docs/learn/ai/recommender-systems/wangshusen_recommend_note/wangshusen_recommend_note_retrieval.assets/2-12-3.png (100%) rename {app => content}/docs/learn/ai/recommender-systems/wangshusen_recommend_note/wangshusen_recommend_note_retrieval.assets/2-4-1.png (100%) rename {app => content}/docs/learn/ai/recommender-systems/wangshusen_recommend_note/wangshusen_recommend_note_retrieval.assets/2-5-1.png (100%) rename {app => content}/docs/learn/ai/recommender-systems/wangshusen_recommend_note/wangshusen_recommend_note_retrieval.assets/2-5-2.png (100%) rename {app => content}/docs/learn/ai/recommender-systems/wangshusen_recommend_note/wangshusen_recommend_note_retrieval.assets/2-5-3.png (100%) rename {app => content}/docs/learn/ai/recommender-systems/wangshusen_recommend_note/wangshusen_recommend_note_retrieval.assets/2-5-4.png (100%) rename {app => content}/docs/learn/ai/recommender-systems/wangshusen_recommend_note/wangshusen_recommend_note_retrieval.assets/2-5-5.png (100%) rename {app => content}/docs/learn/ai/recommender-systems/wangshusen_recommend_note/wangshusen_recommend_note_retrieval.assets/2-6-1.png (100%) rename {app => content}/docs/learn/ai/recommender-systems/wangshusen_recommend_note/wangshusen_recommend_note_retrieval.assets/2-6-2.png (100%) rename {app => content}/docs/learn/ai/recommender-systems/wangshusen_recommend_note/wangshusen_recommend_note_retrieval.assets/2-6-3.png (100%) rename {app => content}/docs/learn/ai/recommender-systems/wangshusen_recommend_note/wangshusen_recommend_note_retrieval.assets/2-6-4.png (100%) rename {app => content}/docs/learn/ai/recommender-systems/wangshusen_recommend_note/wangshusen_recommend_note_retrieval.assets/2-6-5.png (100%) rename {app => content}/docs/learn/ai/recommender-systems/wangshusen_recommend_note/wangshusen_recommend_note_retrieval.assets/2-6-6.png (100%) rename {app => content}/docs/learn/ai/recommender-systems/wangshusen_recommend_note/wangshusen_recommend_note_retrieval.assets/2-7-1.png (100%) rename {app => content}/docs/learn/ai/recommender-systems/wangshusen_recommend_note/wangshusen_recommend_note_retrieval.assets/2-7-2.png (100%) rename {app => content}/docs/learn/ai/recommender-systems/wangshusen_recommend_note/wangshusen_recommend_note_retrieval.assets/2-7-3.png (100%) rename {app => content}/docs/learn/ai/recommender-systems/wangshusen_recommend_note/wangshusen_recommend_note_retrieval.assets/2-7-4.png (100%) rename {app => content}/docs/learn/ai/recommender-systems/wangshusen_recommend_note/wangshusen_recommend_note_retrieval.assets/2-8-1.png (100%) rename {app => content}/docs/learn/ai/recommender-systems/wangshusen_recommend_note/wangshusen_recommend_note_retrieval.assets/2-8-2.png (100%) rename {app => content}/docs/learn/ai/recommender-systems/wangshusen_recommend_note/wangshusen_recommend_note_retrieval.assets/2-9-1.png (100%) rename {app => content}/docs/learn/ai/recommender-systems/wangshusen_recommend_note/wangshusen_recommend_note_retrieval.assets/2-9-2.png (100%) rename {app => content}/docs/learn/ai/recommender-systems/wangshusen_recommend_note/wangshusen_recommend_note_retrieval.assets/2-9-3.png (100%) rename {app => content}/docs/learn/ai/recommender-systems/wangshusen_recommend_note/wangshusen_recommend_note_retrieval.assets/2-9-4.png (100%) rename {app => content}/docs/learn/ai/recommender-systems/wangshusen_recommend_note/wangshusen_recommend_note_retrieval.en.mdx (100%) rename {app => content}/docs/learn/ai/recommender-systems/wangshusen_recommend_note/wangshusen_recommend_note_retrieval.mdx (100%) rename {app => content}/docs/learn/ai/recommender-systems/wangshusen_recommend_note_coldstart.en.mdx (100%) rename {app => content}/docs/learn/ai/recommender-systems/wangshusen_recommend_note_coldstart.mdx (100%) rename {app => content}/docs/learn/ai/recommender-systems/wangshusen_recommend_note_improvement.en.mdx (100%) rename {app => content}/docs/learn/ai/recommender-systems/wangshusen_recommend_note_improvement.mdx (100%) rename {app => content}/docs/learn/ai/recommender-systems/wangshusen_recommend_note_rank.en.mdx (100%) rename {app => content}/docs/learn/ai/recommender-systems/wangshusen_recommend_note_rank.mdx (100%) rename {app => content}/docs/learn/ai/recommender-systems/wangshusen_recommend_note_rerank.en.mdx (100%) rename {app => content}/docs/learn/ai/recommender-systems/wangshusen_recommend_note_rerank.mdx (100%) rename {app => content}/docs/learn/ai/reinforcement-learning/ppo.md (100%) rename {app => content}/docs/learn/ai/reinforcement-learning/reinforcement-learning-overview.en.mdx (100%) rename {app => content}/docs/learn/ai/reinforcement-learning/reinforcement-learning-overview.mdx (100%) rename {app => content}/docs/learn/cs/cpp-backend/easy-compile/1-cpp-libs.en.md (100%) rename {app => content}/docs/learn/cs/cpp-backend/easy-compile/1-cpp-libs.md (100%) rename {app => content}/docs/learn/cs/cpp-backend/easy-compile/2-base-gcc.en.md (100%) rename {app => content}/docs/learn/cs/cpp-backend/easy-compile/2-base-gcc.md (100%) rename {app => content}/docs/learn/cs/cpp-backend/easy-compile/3-make.en.md (100%) rename {app => content}/docs/learn/cs/cpp-backend/easy-compile/3-make.md (100%) rename {app => content}/docs/learn/cs/cpp-backend/easy-compile/4-cmake.en.md (100%) rename {app => content}/docs/learn/cs/cpp-backend/easy-compile/4-cmake.md (100%) rename {app => content}/docs/learn/cs/cpp-backend/easy-compile/5-vcpkg.en.md (100%) rename {app => content}/docs/learn/cs/cpp-backend/easy-compile/5-vcpkg.md (100%) rename {app => content}/docs/learn/cs/cpp-backend/handwritten-pool-components/1-handwritten-threadpool.en.md (100%) rename {app => content}/docs/learn/cs/cpp-backend/handwritten-pool-components/1-handwritten-threadpool.md (100%) rename {app => content}/docs/learn/cs/cpp-backend/handwritten-pool-components/2-handwritten-mempool1.en.md (100%) rename {app => content}/docs/learn/cs/cpp-backend/handwritten-pool-components/2-handwritten-mempool1.md (100%) rename {app => content}/docs/learn/cs/cpp-backend/mempool-simple.en.mdx (100%) rename {app => content}/docs/learn/cs/cpp-backend/mempool-simple.mdx (100%) rename app/docs/learn/cs/data-structures/array/01-static-array.mdx => content/docs/learn/cs/data-structures/array/01-static-array.en.mdx (100%) rename app/docs/learn/cs/data-structures/array/01-static-array.zh.mdx => content/docs/learn/cs/data-structures/array/01-static-array.mdx (100%) rename app/docs/learn/cs/data-structures/array/02-dynamic-array.mdx => content/docs/learn/cs/data-structures/array/02-dynamic-array.en.mdx (100%) rename app/docs/learn/cs/data-structures/array/02-dynamic-array.zh.mdx => content/docs/learn/cs/data-structures/array/02-dynamic-array.mdx (100%) rename app/docs/learn/cs/data-structures/array/index.mdx => content/docs/learn/cs/data-structures/array/index.en.mdx (100%) rename app/docs/learn/cs/data-structures/array/index.zh.mdx => content/docs/learn/cs/data-structures/array/index.mdx (100%) rename app/docs/learn/cs/data-structures/index.mdx => content/docs/learn/cs/data-structures/index.en.mdx (100%) rename app/docs/learn/cs/data-structures/index.zh.mdx => content/docs/learn/cs/data-structures/index.mdx (100%) rename app/docs/learn/cs/data-structures/linked-list/01-singly-linked-list.mdx => content/docs/learn/cs/data-structures/linked-list/01-singly-linked-list.en.mdx (100%) rename app/docs/learn/cs/data-structures/linked-list/01-singly-linked-list.zh.mdx => content/docs/learn/cs/data-structures/linked-list/01-singly-linked-list.mdx (100%) rename app/docs/learn/cs/data-structures/linked-list/index.mdx => content/docs/learn/cs/data-structures/linked-list/index.en.mdx (100%) rename app/docs/learn/cs/data-structures/linked-list/index.zh.mdx => content/docs/learn/cs/data-structures/linked-list/index.mdx (100%) rename {app => content}/docs/learn/cs/frontend/frontend-learning/index.en.mdx (100%) rename {app => content}/docs/learn/cs/frontend/frontend-learning/index.mdx (100%) rename {app => content}/docs/learn/cs/frontend/index.en.mdx (100%) rename {app => content}/docs/learn/cs/frontend/index.mdx (100%) rename {app => content}/docs/learn/cs/index.en.mdx (100%) rename {app => content}/docs/learn/cs/index.mdx (100%) rename {app => content}/docs/learn/index.mdx (100%) rename {app => content}/docs/projects/ai-town.en.mdx (100%) rename {app => content}/docs/projects/ai-town.mdx (100%) rename {app => content}/docs/projects/index.mdx (100%) rename {app => content}/docs/projects/multimodal-rl.en.mdx (100%) rename {app => content}/docs/projects/multimodal-rl.mdx (100%) create mode 100644 i18n/navigation.ts create mode 100644 i18n/routing.ts diff --git a/app/admin/community/layout.tsx b/app/[locale]/admin/community/layout.tsx similarity index 100% rename from app/admin/community/layout.tsx rename to app/[locale]/admin/community/layout.tsx diff --git a/app/admin/community/lib.ts b/app/[locale]/admin/community/lib.ts similarity index 96% rename from app/admin/community/lib.ts rename to app/[locale]/admin/community/lib.ts index 463a0dc3..445b95fe 100644 --- a/app/admin/community/lib.ts +++ b/app/[locale]/admin/community/lib.ts @@ -10,7 +10,7 @@ * 对应后端:/api/admin/community/* (走 @SaCheckRole("admin")) */ -import type { SharedLinkView } from "@/app/feed/types"; +import type { SharedLinkView } from "@/app/[locale]/feed/types"; interface ApiResponse { success: boolean; diff --git a/app/admin/community/page.tsx b/app/[locale]/admin/community/page.tsx similarity index 98% rename from app/admin/community/page.tsx rename to app/[locale]/admin/community/page.tsx index 04d4bc86..9415a604 100644 --- a/app/admin/community/page.tsx +++ b/app/[locale]/admin/community/page.tsx @@ -12,8 +12,8 @@ */ import { useEffect, useState } from "react"; -import { AdminGuard } from "@/app/admin/events/AdminGuard"; -import type { SharedLinkView } from "@/app/feed/types"; +import { AdminGuard } from "@/app/[locale]/admin/events/AdminGuard"; +import type { SharedLinkView } from "@/app/[locale]/feed/types"; import { sanitizeExternalUrl, sanitizeMediaUrl } from "@/lib/url-safety"; import { approveLink, listPendingLinks, rejectLink } from "./lib"; diff --git a/app/admin/database/page.tsx b/app/[locale]/admin/database/page.tsx similarity index 100% rename from app/admin/database/page.tsx rename to app/[locale]/admin/database/page.tsx diff --git a/app/admin/events/AdminGuard.tsx b/app/[locale]/admin/events/AdminGuard.tsx similarity index 100% rename from app/admin/events/AdminGuard.tsx rename to app/[locale]/admin/events/AdminGuard.tsx diff --git a/app/admin/events/EventForm.tsx b/app/[locale]/admin/events/EventForm.tsx similarity index 98% rename from app/admin/events/EventForm.tsx rename to app/[locale]/admin/events/EventForm.tsx index b1c4f63a..2e199606 100644 --- a/app/admin/events/EventForm.tsx +++ b/app/[locale]/admin/events/EventForm.tsx @@ -14,7 +14,11 @@ import { useState, useRef, type FormEvent } from "react"; import { useRouter } from "next/navigation"; -import type { EventRequest, EventView, EventStatus } from "@/app/events/types"; +import type { + EventRequest, + EventView, + EventStatus, +} from "@/app/[locale]/events/types"; import { createEvent, updateEvent } from "./lib"; interface Props { diff --git a/app/admin/events/[id]/edit/page.tsx b/app/[locale]/admin/events/[id]/edit/page.tsx similarity index 98% rename from app/admin/events/[id]/edit/page.tsx rename to app/[locale]/admin/events/[id]/edit/page.tsx index cd09da75..378d4629 100644 --- a/app/admin/events/[id]/edit/page.tsx +++ b/app/[locale]/admin/events/[id]/edit/page.tsx @@ -9,7 +9,7 @@ import { use, useEffect, useState } from "react"; import Link from "next/link"; -import type { EventView } from "@/app/events/types"; +import type { EventView } from "@/app/[locale]/events/types"; import { AdminGuard } from "../../AdminGuard"; import { EventForm } from "../../EventForm"; import { getAdminEvent } from "../../lib"; diff --git a/app/admin/events/layout.tsx b/app/[locale]/admin/events/layout.tsx similarity index 78% rename from app/admin/events/layout.tsx rename to app/[locale]/admin/events/layout.tsx index 30c8f8b2..ebe9d607 100644 --- a/app/admin/events/layout.tsx +++ b/app/[locale]/admin/events/layout.tsx @@ -6,6 +6,10 @@ import type { ReactNode } from "react"; * 之前这里单独挂 Header / Footer 是因为当时还没有 /admin/layout.tsx。现在根 admin * 已经有共享 layout,这层只是透传,保留文件是为了 Next 路由分段还能命中。 */ -export default function AdminEventsLayout({ children }: { children: ReactNode }) { +export default function AdminEventsLayout({ + children, +}: { + children: ReactNode; +}) { return <>{children}; } diff --git a/app/admin/events/lib.ts b/app/[locale]/admin/events/lib.ts similarity index 96% rename from app/admin/events/lib.ts rename to app/[locale]/admin/events/lib.ts index 81b2f6a6..a7f5798b 100644 --- a/app/admin/events/lib.ts +++ b/app/[locale]/admin/events/lib.ts @@ -9,7 +9,7 @@ * - 避免 SSR 缓存污染 admin 视角的数据 */ -import type { EventRequest, EventView } from "@/app/events/types"; +import type { EventRequest, EventView } from "@/app/[locale]/events/types"; interface ApiResponse { success: boolean; diff --git a/app/admin/events/new/page.tsx b/app/[locale]/admin/events/new/page.tsx similarity index 100% rename from app/admin/events/new/page.tsx rename to app/[locale]/admin/events/new/page.tsx diff --git a/app/admin/events/page.tsx b/app/[locale]/admin/events/page.tsx similarity index 99% rename from app/admin/events/page.tsx rename to app/[locale]/admin/events/page.tsx index 6dca1c28..f55085fb 100644 --- a/app/admin/events/page.tsx +++ b/app/[locale]/admin/events/page.tsx @@ -9,7 +9,7 @@ import { useEffect, useState } from "react"; import Link from "next/link"; -import type { EventView } from "@/app/events/types"; +import type { EventView } from "@/app/[locale]/events/types"; import { AdminGuard } from "./AdminGuard"; import { deleteEvent, listAdminEvents } from "./lib"; diff --git a/app/admin/layout.tsx b/app/[locale]/admin/layout.tsx similarity index 100% rename from app/admin/layout.tsx rename to app/[locale]/admin/layout.tsx diff --git a/app/admin/page.tsx b/app/[locale]/admin/page.tsx similarity index 100% rename from app/admin/page.tsx rename to app/[locale]/admin/page.tsx diff --git a/app/admin/users/lib.ts b/app/[locale]/admin/users/lib.ts similarity index 100% rename from app/admin/users/lib.ts rename to app/[locale]/admin/users/lib.ts diff --git a/app/admin/users/page.tsx b/app/[locale]/admin/users/page.tsx similarity index 99% rename from app/admin/users/page.tsx rename to app/[locale]/admin/users/page.tsx index 8a5161b5..9be70d29 100644 --- a/app/admin/users/page.tsx +++ b/app/[locale]/admin/users/page.tsx @@ -107,8 +107,8 @@ function AdminUsersInner() { 用户管理

- 勾选 admin 即赋予管理员角色;取消即撤销。superadmin 角色不允许在这里改, - 只能走 DB。 + 勾选 admin 即赋予管理员角色;取消即撤销。superadmin + 角色不允许在这里改, 只能走 DB。

diff --git a/app/docs/[...slug]/page.tsx b/app/[locale]/docs/[...slug]/page.tsx similarity index 57% rename from app/docs/[...slug]/page.tsx rename to app/[locale]/docs/[...slug]/page.tsx index ab699a1c..2a13a164 100644 --- a/app/docs/[...slug]/page.tsx +++ b/app/[locale]/docs/[...slug]/page.tsx @@ -3,6 +3,8 @@ import { SITE_URL } from "@/lib/site-url"; import { DocsPage, DocsBody } from "fumadocs-ui/page"; import { notFound } from "next/navigation"; import type { Metadata } from "next"; +import { setRequestLocale } from "next-intl/server"; +import { hasLocale } from "next-intl"; import { getMDXComponents } from "@/mdx-components"; import { GiscusComments } from "@/app/components/GiscusComments"; import { EditOnGithub } from "@/app/components/EditOnGithub"; @@ -17,71 +19,35 @@ import { LicenseNotice } from "@/app/components/LicenseNotice"; import { PageFeedback } from "@/app/components/PageFeedback"; import { DocHistoryPanel } from "@/app/components/DocHistoryPanel"; import { DocShareButton } from "@/app/components/DocShareButton"; -import { cookies } from "next/headers"; +import { routing } from "@/i18n/routing"; import { type PageData } from "@/app/types/doc"; -// Extract clean text content from MDX - no longer used on client/page side -// content fetching moved to API route for performance interface Param { params: Promise<{ + locale: string; slug?: string[]; }>; } -/** 从 cookie 读取用户语言偏好,未设置时返回 null */ -async function getLocaleFromCookie(): Promise<"zh" | "en" | null> { - const cookieStore = await cookies(); - const val = cookieStore.get("locale")?.value; - if (val === "zh" || val === "en") return val; - return null; -} - -/** - * 根据 locale 尝试加载对应语言版本的文档。 - * 翻译文件命名规则:原文 slug 最后一段加上语言后缀,例如 - * slug = ["ai", "rl"] → 英文版尝试 ["ai", "rl.en"] - * - * 若对应翻译版不存在,fallback 到原文。 - */ -function getPageWithLocale( - slug: string[] | undefined, - locale: "zh" | "en" | null, -) { - const originalPage = source.getPage(slug); - if (!locale || !slug || slug.length === 0) - return { page: originalPage, isFallback: false }; - - const originalLang = - (originalPage?.data as PageData | undefined)?.lang ?? null; - - // 已经是目标语言,直接返回 - if (originalLang === locale) return { page: originalPage, isFallback: false }; - - // 尝试加载翻译版:slug 末尾加语言后缀 - const lastSegment = slug[slug.length - 1]; - const translatedSlug = [...slug.slice(0, -1), `${lastSegment}.${locale}`]; - const translatedPage = source.getPage(translatedSlug); - - if (translatedPage) { - return { page: translatedPage, isFallback: false }; - } - - // 翻译版不存在,fallback 到原文 - return { page: originalPage, isFallback: true }; -} +// 显式声明 force-static:让 Next.js 严格按 generateStaticParams 预渲染 +// 所有 (locale, slug) 组合,未列出的不允许动态生成。 +// 没有这条时,build 表里 ƒ Dynamic 标签会让 docs 走运行时渲染(即使加了 +// setRequestLocale 也不一定 prerender)。 +export const dynamic = "force-static"; export default async function DocPage({ params }: Param) { - const { slug } = await params; - const locale = await getLocaleFromCookie(); - const { page } = getPageWithLocale(slug, locale); - + const { locale, slug } = await params; + if (!hasLocale(routing.locales, locale)) notFound(); + // 启用 SSG(让 next-intl 不去 await cookies/headers) + setRequestLocale(locale); + + // fumadocs i18n 接口:传 locale 后会按 .en / .zh 后缀加载对应文件, + // 找不到时按 source.ts 配的 fallbackLanguage='zh' 回退到原文。 + const page = source.getPage(slug, locale); if (page == null) { notFound(); } - // 静默 fallback:翻译版不存在时直接展示原文,不再显示"暂无英文版"横幅 - // 原因:中文为默认语言,大多数文档本身就是中文;显示 banner 反而让 UI 碍眼 - // 统一通过工具函数生成 Edit 链接,内部已处理中文目录编码 const editUrl = buildDocsEditUrl(page.path); const data = page.data as PageData; @@ -92,10 +58,11 @@ export default async function DocPage({ params }: Param) { getDocContributorsByDocId(docIdFromPage); const Mdx = page.data.body; - // SEO 结构化数据 - const siteUrl = SITE_URL; + // SEO 结构化数据:URL 含 locale 前缀 const slugPath = (slug ?? []).join("/"); - const docUrl = slugPath ? `${siteUrl}/docs/${slugPath}` : `${siteUrl}/docs`; + const docUrl = slugPath + ? `${SITE_URL}/${locale}/docs/${slugPath}` + : `${SITE_URL}/${locale}/docs`; // TechArticle: 让 docs 在 Google 搜索结果上更可能展示为技术文章卡片 const articleJsonLd = { @@ -108,17 +75,17 @@ export default async function DocPage({ params }: Param) { publisher: { "@type": "Organization", name: "Involution Hell", - url: siteUrl, + url: SITE_URL, }, }; - // BreadcrumbList: 按 slug 层级生成面包屑(Google 搜索结果里的那种层级链接) + // BreadcrumbList: 按 slug 层级生成面包屑 const breadcrumbItems = [ - { name: "Involution Hell", url: siteUrl }, - { name: "Docs", url: `${siteUrl}/docs` }, + { name: "Involution Hell", url: `${SITE_URL}/${locale}` }, + { name: "Docs", url: `${SITE_URL}/${locale}/docs` }, ...(slug ?? []).map((seg, idx) => ({ name: decodeURIComponent(seg), - url: `${siteUrl}/docs/${slug!.slice(0, idx + 1).join("/")}`, + url: `${SITE_URL}/${locale}/docs/${slug!.slice(0, idx + 1).join("/")}`, })), ]; const breadcrumbJsonLd = { @@ -178,39 +145,54 @@ export default async function DocPage({ params }: Param) { ); } +/** + * generateStaticParams: 给每个 base slug × 每个 locale 出一份预渲染参数。 + * + * fumadocs 的 source.generateParams('slug', 'lang') 会自动产出这种结构, + * 但我们的 i18n 段名是 'locale'(next-intl 约定),所以 mapping 一下。 + * + * 双语预渲染规模:约 318 base × 2 = 636 页 SSG。fallbackLanguage='zh' + * 让翻译版缺失的 en 页面也能预渲染(直接拿原文)。 + */ export async function generateStaticParams() { - return source.getPages().map((page) => ({ - slug: page.slugs, + return source.generateParams("slug", "lang").map((p) => ({ + locale: p.lang as string, + slug: p.slug as string[], })); } export async function generateMetadata({ params }: Param): Promise { - const { slug } = await params; - const locale = await getLocaleFromCookie(); - // metadata 需与页面主体同语言,避免英文页显示中文 title/desc 造成 SEO 错乱 - const { page } = getPageWithLocale(slug, locale); + const { locale, slug } = await params; + if (!hasLocale(routing.locales, locale)) notFound(); + setRequestLocale(locale); + + const page = source.getPage(slug, locale); if (page == null) { notFound(); } - // 规范化 slug → canonical 路径。用户访问 /docs/learn/ai/rl(原文)或 /docs/learn/ai/rl.en(翻译版) - // 都统一指向原始 slug,避免两个 URL 竞争同一份内容的 PageRank。 + // canonical: 当前 locale 的本语言 URL(每个语言独立 canonical,避免 zh/en + // 互相竞争 PageRank)。 const slugPath = (slug ?? []).join("/"); - const canonical = slugPath ? `/docs/${slugPath}` : "/docs"; - - // hreflang:告诉搜索引擎该文档有哪些语言版本。 - // 翻译版文件命名是 `.en.mdx` / `.zh.mdx`,URL 靠 cookie 切换, - // 两种语言走同一 canonical URL,因此 hreflang 都指向自己。 - const languages: Record = { - "zh-CN": canonical, - "en-US": canonical, - "x-default": canonical, - }; + const canonical = slugPath + ? `/${locale}/docs/${slugPath}` + : `/${locale}/docs`; + + // hreflang:告诉 Google 同一文档的另一语言 URL 在哪。 + const langs: Record = {}; + for (const l of routing.locales) { + const url = slugPath ? `/${l}/docs/${slugPath}` : `/${l}/docs`; + langs[l === "en" ? "en-US" : "zh-CN"] = url; + } + langs["x-default"] = `/${routing.defaultLocale}/docs/${slugPath}`.replace( + /\/$/, + "", + ); return { title: page.data.title, description: page.data.description, - alternates: { canonical, languages }, + alternates: { canonical, languages: langs }, openGraph: { type: "article", title: page.data.title, diff --git a/app/[locale]/docs/layout.tsx b/app/[locale]/docs/layout.tsx new file mode 100644 index 00000000..00c88103 --- /dev/null +++ b/app/[locale]/docs/layout.tsx @@ -0,0 +1,106 @@ +import { source } from "@/lib/source"; +import { DocsLayout } from "fumadocs-ui/layouts/docs"; +import { baseOptions } from "@/lib/layout.shared"; +import type { ReactNode } from "react"; +import { DocsRouteFlag } from "@/app/components/RouteFlags"; +import type { PageTree } from "fumadocs-core/server"; +import { CopyTracking } from "@/app/components/CopyTracking"; +import { DocsPageViewTracker } from "@/app/components/DocsPageViewTracker"; +import { setRequestLocale } from "next-intl/server"; +import { hasLocale } from "next-intl"; +import { notFound } from "next/navigation"; +import { routing } from "@/i18n/routing"; + +/** + * 单 child 文件夹的 hoist 规则。 + * + * 历史背景:learn/ai/ 下有些 folder 只挂了一篇文章(例如某个细分主题只 + * 写了一篇),sidebar 里展开折叠没意义。把这种 folder 替换成它的唯一 + * child page,让 sidebar 更紧凑。 + * + * 限定 learn/ai/ 是因为这是社区里最多"独苗 folder"的子树,其它分区不 + * 强行 hoist 避免误压平正常的层级结构。 + */ +function pruneEmptyFolders(root: PageTree.Root): PageTree.Root { + const transformNode = (node: PageTree.Node): PageTree.Node | null => { + if (node.type === "folder") { + const transformedChildren = node.children + .map(transformNode) + .filter((child): child is PageTree.Node => child !== null); + + const index = node.index ? { ...node.index } : undefined; + + if (transformedChildren.length === 0) { + if (index) return { ...index }; + return null; + } + + if (!index && transformedChildren.length === 1) { + const [onlyChild] = transformedChildren; + if ( + onlyChild.type === "page" && + onlyChild.url.startsWith("/docs/learn/ai/") + ) { + return { ...onlyChild }; + } + } + + return { ...node, index, children: transformedChildren }; + } + if (node.type === "separator") return { ...node }; + return { ...node }; + }; + + const transformRoot = (node: PageTree.Root): PageTree.Root => { + const children = node.children + .map(transformNode) + .filter((child): child is PageTree.Node => child !== null); + return { + ...node, + children, + fallback: node.fallback ? transformRoot(node.fallback) : undefined, + }; + }; + + return transformRoot(root); +} + +interface Props { + children: ReactNode; + params: Promise<{ locale: string }>; +} + +/** + * Docs 子树共享 layout。 + * + * 关键变化(i18n URL 段化): + * 旧版手写 pickVariantsByLocale / filterTreeByLocale,按 cookie 把 + * pageTree 里的 .en / .zh 变体筛成单语 tree。fumadocs i18n 接入后 + * `source.getPageTree(locale)` 已经原生返回单 locale 的 tree,整段 + * 过滤逻辑直接删除,只保留 learn/ai/ 单 child folder 的 hoist 规则。 + */ +export default async function Layout({ children, params }: Props) { + const { locale } = await params; + if (!hasLocale(routing.locales, locale)) notFound(); + setRequestLocale(locale); + + const tree = pruneEmptyFolders(source.getPageTree(locale)); + const options = await baseOptions(); + return ( + <> + + + + + {children} + + + ); +} diff --git a/app/docs/page.tsx b/app/[locale]/docs/page.tsx similarity index 55% rename from app/docs/page.tsx rename to app/[locale]/docs/page.tsx index cddd1069..0a150ab8 100644 --- a/app/docs/page.tsx +++ b/app/[locale]/docs/page.tsx @@ -1,24 +1,30 @@ import { DocsPage, DocsBody } from "fumadocs-ui/page"; import type { Metadata } from "next"; -import { cookies } from "next/headers"; +import { setRequestLocale } from "next-intl/server"; +import { hasLocale } from "next-intl"; +import { notFound } from "next/navigation"; import { SectionIndex } from "@/app/components/docs/SectionIndex"; +import { routing } from "@/i18n/routing"; /** - * /docs 根路由的 landing。Header 导航的 "文档 / Docs" 直接指向 /docs,但原本 - * app/docs/ 下只有 layout.tsx + [...slug]/page.tsx(catch-all 不匹配空 slug), - * 所以 /docs 本身 404。这个文件提供兜底 landing,复用已挂好的 DocsLayout。 + * /[locale]/docs 根路由的 landing。Header 的 "文档 / Docs" 链接指到 /docs, + * 但 docs/[...slug] catch-all 不匹配空 slug,所以 /docs 本身 404。这个文件 + * 提供兜底 landing,复用已挂好的 DocsLayout。 * - * 内容交给 ``(root 不传 → 渲染 pageTree 顶层分区)。所有渲染 - * 逻辑和 community / career/interview-prep/leetcode 两处共用同一个组件,避免 drift。 + * 内容交给 ``(root 不传 → 渲染 pageTree 顶层分区)。所有 + * 渲染逻辑和 community / career/interview-prep/leetcode 两处共用同一个组 + * 件,避免 drift。 */ -async function getLocaleFromCookie(): Promise<"zh" | "en"> { - const cookieStore = await cookies(); - return cookieStore.get("locale")?.value === "en" ? "en" : "zh"; +interface Props { + params: Promise<{ locale: string }>; } -export default async function DocsRootPage() { - const locale = await getLocaleFromCookie(); +export default async function DocsRootPage({ params }: Props) { + const { locale } = await params; + if (!hasLocale(routing.locales, locale)) notFound(); + setRequestLocale(locale); + const heading = locale === "en" ? "Knowledge Base" : "文档总览"; const intro = locale === "en" @@ -38,8 +44,11 @@ export default async function DocsRootPage() { ); } -export async function generateMetadata(): Promise { - const locale = await getLocaleFromCookie(); +export async function generateMetadata({ params }: Props): Promise { + const { locale } = await params; + if (!hasLocale(routing.locales, locale)) notFound(); + setRequestLocale(locale); + return { title: locale === "en" ? "Docs" : "文档", description: diff --git a/app/editor/EditorPageClient.tsx b/app/[locale]/editor/EditorPageClient.tsx similarity index 100% rename from app/editor/EditorPageClient.tsx rename to app/[locale]/editor/EditorPageClient.tsx diff --git a/app/editor/page.tsx b/app/[locale]/editor/page.tsx similarity index 100% rename from app/editor/page.tsx rename to app/[locale]/editor/page.tsx diff --git a/app/events/[id]/InterestButton.tsx b/app/[locale]/events/[id]/InterestButton.tsx similarity index 100% rename from app/events/[id]/InterestButton.tsx rename to app/[locale]/events/[id]/InterestButton.tsx diff --git a/app/events/[id]/page.tsx b/app/[locale]/events/[id]/page.tsx similarity index 100% rename from app/events/[id]/page.tsx rename to app/[locale]/events/[id]/page.tsx diff --git a/app/events/page.tsx b/app/[locale]/events/page.tsx similarity index 100% rename from app/events/page.tsx rename to app/[locale]/events/page.tsx diff --git a/app/events/types.ts b/app/[locale]/events/types.ts similarity index 100% rename from app/events/types.ts rename to app/[locale]/events/types.ts diff --git a/app/feed/components/CategoryTabs.tsx b/app/[locale]/feed/components/CategoryTabs.tsx similarity index 96% rename from app/feed/components/CategoryTabs.tsx rename to app/[locale]/feed/components/CategoryTabs.tsx index 34f42098..762b7a64 100644 --- a/app/feed/components/CategoryTabs.tsx +++ b/app/[locale]/feed/components/CategoryTabs.tsx @@ -8,7 +8,7 @@ import { useRouter, useSearchParams } from "next/navigation"; import { useTranslations } from "next-intl"; -import { type CategorySlug, CATEGORY_SLUGS } from "@/app/feed/types"; +import { type CategorySlug, CATEGORY_SLUGS } from "@/app/[locale]/feed/types"; import { cn } from "@/lib/utils"; export function CategoryTabs() { diff --git a/app/feed/components/FeedAuthWrapper.tsx b/app/[locale]/feed/components/FeedAuthWrapper.tsx similarity index 90% rename from app/feed/components/FeedAuthWrapper.tsx rename to app/[locale]/feed/components/FeedAuthWrapper.tsx index 27cee6f6..fb38bbb7 100644 --- a/app/feed/components/FeedAuthWrapper.tsx +++ b/app/[locale]/feed/components/FeedAuthWrapper.tsx @@ -13,8 +13,8 @@ */ import { useAuth } from "@/lib/use-auth"; -import { LinkCard } from "@/app/feed/components/LinkCard"; -import type { SharedLinkView, CategorySlug } from "@/app/feed/types"; +import { LinkCard } from "@/app/[locale]/feed/components/LinkCard"; +import type { SharedLinkView, CategorySlug } from "@/app/[locale]/feed/types"; interface FeedAuthWrapperProps { links: SharedLinkView[]; diff --git a/app/feed/components/LinkCard.tsx b/app/[locale]/feed/components/LinkCard.tsx similarity index 97% rename from app/feed/components/LinkCard.tsx rename to app/[locale]/feed/components/LinkCard.tsx index cd1c6392..5851d24a 100644 --- a/app/feed/components/LinkCard.tsx +++ b/app/[locale]/feed/components/LinkCard.tsx @@ -7,8 +7,8 @@ */ import { useTranslations } from "next-intl"; -import type { SharedLinkView } from "@/app/feed/types"; -import { ReportButton } from "@/app/feed/components/ReportButton"; +import type { SharedLinkView } from "@/app/[locale]/feed/types"; +import { ReportButton } from "@/app/[locale]/feed/components/ReportButton"; import { Badge } from "@/components/ui/badge"; import { sanitizeMediaUrl } from "@/lib/url-safety"; diff --git a/app/feed/components/ReportButton.tsx b/app/[locale]/feed/components/ReportButton.tsx similarity index 100% rename from app/feed/components/ReportButton.tsx rename to app/[locale]/feed/components/ReportButton.tsx diff --git a/app/feed/page.tsx b/app/[locale]/feed/page.tsx similarity index 95% rename from app/feed/page.tsx rename to app/[locale]/feed/page.tsx index 0c3356fe..6689a45e 100644 --- a/app/feed/page.tsx +++ b/app/[locale]/feed/page.tsx @@ -14,10 +14,10 @@ import { getTranslations } from "next-intl/server"; import { Header } from "@/app/components/Header"; import { Footer } from "@/app/components/Footer"; import { Suspense } from "react"; -import { CategoryTabs } from "@/app/feed/components/CategoryTabs"; -import { FeedAuthWrapper } from "@/app/feed/components/FeedAuthWrapper"; -import type { SharedLinkView, CategorySlug } from "@/app/feed/types"; -import type { ApiResponse } from "@/app/feed/types"; +import { CategoryTabs } from "@/app/[locale]/feed/components/CategoryTabs"; +import { FeedAuthWrapper } from "@/app/[locale]/feed/components/FeedAuthWrapper"; +import type { SharedLinkView, CategorySlug } from "@/app/[locale]/feed/types"; +import type { ApiResponse } from "@/app/[locale]/feed/types"; import Link from "next/link"; export const revalidate = 120; @@ -141,7 +141,7 @@ export default async function FeedPage({ searchParams }: FeedPageProps) { // Server 端预计算 slug → 中文显示名 map。传给 FeedAuthWrapper(client) // 时必须是纯数据(函数 prop 在 Next 16 会报 "Functions cannot be passed to // Client Components")。8 个 slug 一次翻译完毕,零额外开销。 - const { CATEGORY_SLUGS } = await import("@/app/feed/types"); + const { CATEGORY_SLUGS } = await import("@/app/[locale]/feed/types"); const categoryLabels: Partial> = {}; for (const slug of CATEGORY_SLUGS) { try { diff --git a/app/feed/submit/layout.tsx b/app/[locale]/feed/submit/layout.tsx similarity index 100% rename from app/feed/submit/layout.tsx rename to app/[locale]/feed/submit/layout.tsx diff --git a/app/feed/submit/page.tsx b/app/[locale]/feed/submit/page.tsx similarity index 100% rename from app/feed/submit/page.tsx rename to app/[locale]/feed/submit/page.tsx diff --git a/app/feed/types.ts b/app/[locale]/feed/types.ts similarity index 100% rename from app/feed/types.ts rename to app/[locale]/feed/types.ts diff --git a/app/[locale]/layout.tsx b/app/[locale]/layout.tsx new file mode 100644 index 00000000..f3e76e41 --- /dev/null +++ b/app/[locale]/layout.tsx @@ -0,0 +1,91 @@ +import { setRequestLocale, getMessages } from "next-intl/server"; +import { hasLocale, NextIntlClientProvider } from "next-intl"; +import { notFound } from "next/navigation"; +import { RootProvider } from "fumadocs-ui/provider"; +import { ThemeProvider } from "@/app/components/ThemeProvider"; +import { AuthProvider } from "@/lib/use-auth"; +import { CustomSearchDialog } from "@/app/components/CustomSearchDialog"; +import { UmamiIdentity } from "@/app/components/UmamiIdentity"; +import { routing } from "@/i18n/routing"; + +/** + * locale 段 layout:所有 user-facing 路由(含 admin)的最外层包装。 + * + * 关键作用(启用 SSG): + * setRequestLocale(locale) 必须在第一行调用(在任何 next-intl hook 之前)。 + * 它把 locale 写进 next-intl 的 RequestStore,让所有嵌套 RSC 拿到 locale, + * 而不需要再调 cookies() / headers() —— 这是让全树 SSG 的关键开关。 + * + * 缺这一行的话,next-intl 会回退到从 cookies()/headers() 推断 locale, + * 整棵 RSC 树重新变 dynamic,绕了一圈又回到老问题。 + * + * generateStaticParams 双倍出货 zh + en,build 时 Next.js 会按 + * [locale] × 嵌套 generateStaticParams 笛卡尔积预渲染所有页面。 + * + * 这里包了 root layout 移过来的全部 locale-bound provider: + * - NextIntlClientProvider (locale + messages) + * - ThemeProvider / AuthProvider / RootProvider (fumadocs,search api 按 locale 选分片) + * - 主体 main 容器 + * + * inline script: 把 documentElement.lang 改成当前 locale。root layout 写死 + * lang="zh-CN" 作为 SSR fallback,client 端这条脚本会立刻覆盖,确保 a11y + * 工具和 SEO 拿到正确语言标记。 + */ +type Props = { + children: React.ReactNode; + params: Promise<{ locale: string }>; +}; + +export default async function LocaleLayout({ children, params }: Props) { + const { locale } = await params; + if (!hasLocale(routing.locales, locale)) { + notFound(); + } + + // 关键:启用 SSG 的开关。必须在调任何 next-intl hook 之前。 + setRequestLocale(locale); + + const messages = await getMessages(); + const htmlLang = locale === "en" ? "en" : "zh-CN"; + // 搜索索引按 locale 分片(规避 Vercel 单页 ISR 19.07MB 上限) + const searchApi = `/search.${locale}.json`; + + return ( + <> + {/* + SSR 时 root layout 的 html lang 是写死的 "zh-CN"。 + 在客户端立即覆盖为当前 locale,让屏幕阅读器、Google Translate 等 + 立刻拿到正确语言标记。早于 hydration —— 通过同步 inline script 实现。 + */} + - {/* Umami Analytics */} - +