Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions dev_docs/i18n_url_routing.md
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,36 @@ languages:

`disallow` 用 wildcard 形式 `/*/admin/` 等匹配两种 locale 前缀。

### 段化前旧 URL 的 301(`.en` / `.zh` 后缀)

段化前 locale 是**文件后缀**(`/docs/foo.en`、`/docs/bar.zh`),段化后变成
**URL 段前缀**(`/en/docs/foo`)。GSC 里还存着上百条 `.en`/`.zh` 旧 URL,是最大
一类 404。重定向规则放在 **`next.config.mjs` 的 `redirects()`**,不是 `proxy.ts`:

> `proxy.ts` 的 matcher 排除了 `.*\..*`(任何带点路径都不进中间件),而 `.en`/
> `.zh` 后缀全带点 → 中间件根本碰不到。`next.config` 的 `redirects()` 跑在路由层,
> 不受该排除影响,是带点旧 URL 唯一能 301 兜住的地方。

规则顺序敏感(首匹配命中):`.en`/`.zh` 后缀剥离 + `/index` 剥离排在 IA wildcard
之前;末尾一条 no-locale `/docs/:path*` → `/zh/docs/:path*` 兜底(段化后 canonical
必带 locale 前缀,任何裸 `/docs/...` 都是旧链接)。`:slug(.*)\\.en` 用贪婪捕获吃掉
含 `/` 的多级路径,结尾 `\.en` 做字面锚定。

### 排行榜 / 热榜链接的 canonical URL

`scripts/generate-leaderboard.mts` 从后端 docId 反查 `.source` 文件路径拼 URL。
段化后必须产出 `/<locale>/docs/<slug>`,否则排行榜每条链接都 404。`buildCanonicalDocUrl`
做三件事和路由对齐:① 剥 `.en`/`.zh` 后缀并据此选 locale 前缀;② 去 `/index`;
③ leetcode 走特殊处理。

**leetcode 的坑**:同一题常有英文版(`1234-xxx.en.md`)+ 中文翻译版
(`1234. 中文_translated.md` / `[121]中文_translated.md`)。中文文件名经 fumadocs
i18n 解析后的真实 slug **不可预测**——带 `. ` 点空格的会塌缩、带方括号的能独立成
拼音页,手搓拼音对不齐(`proxy.ts` 的 leetcode slug-map 也踩同样的坑)。而英文文件的
ASCII slug 一定能解析、且只在 `/en` 渲染(`.en.md`,zh 不回退 en)。所以脚本按**题号**
把任何 leetcode 贡献指向英文版 `/en/docs/.../<ascii-slug>`(保证 200),无英文兄弟时
才退回 zh 拼音。改 leetcode 文件命名后,这套靠 `pnpm build` 重新生成。

## proxy 流程

每个请求 → `proxy.ts` →
Expand Down
100 changes: 72 additions & 28 deletions generated/leetcode-slug-map.json
Original file line number Diff line number Diff line change
@@ -1,30 +1,74 @@
{
"1234. 替换子串得到平衡字符串_translated": "1234-ti-huan-zi-chuan-de-dao-ping-heng-zi-fu-chuan-translated",
"142.环形链表II_translated": "142-huan-xing-lian-biao-iitranslated",
"1653. 使字符串平衡的最少删除次数_translated": "1653-shi-zi-fu-chuan-ping-heng-de-zui-shao-shan-chu-ci-shu-translated",
"1664生成平衡数组的方案数_translated": "1664-sheng-cheng-ping-heng-shu-zu-de-fang-an-shu-translated",
"1825求出 MK 平均值_translated": "1825-qiu-chu-mk-ping-jun-zhi-translated",
"1828统计一个圆中点的数目_translated": "1828-tong-ji-yi-ge-yuan-zhong-dian-de-shu-mu-translated",
"2299强密码检验器II_translated": "2299-qiang-mi-ma-jian-yan-qi-iitranslated",
"2309兼具大小写的最好英文字母_translated": "2309-jian-ju-da-xiao-xie-de-zui-hao-ying-wen-zi-mu-translated",
"2335. 装满杯子需要的最短总时长_translated": "2335-zhuang-man-bei-zi-xu-yao-de-zui-duan-zong-shi-chang-translated",
"2341. 数组能形成多少数对_translated": "2341-shu-zu-neng-xing-cheng-duo-shao-shu-dui-translated",
"2639. 查询网格图中每一列的宽度_translated": "2639-cha-xun-wang-ge-tu-zhong-mei-yi-lie-de-kuan-du-translated",
"2679.矩阵中的和_translated": "2679-ju-zhen-zhong-de-he-translated",
"2894. 分类求和并作差": "2894-fen-lei-qiu-he-bing-zuo-cha",
"3072. 将元素分配到两个数组中 II_translated": "3072-jiang-yuan-su-fen-pei-dao-liang-ge-shu-zu-zhong-iitranslated",
"345. 反转字符串中的元音字母_translated": "345-fan-zhuan-zi-fu-chuan-zhong-de-yuan-yin-zi-mu-translated",
"538.把二叉搜索树转换为累加树_translated": "538-ba-er-cha-sou-suo-shu-zhuan-huan-wei-lei-jia-shu-translated",
"6323. 将钱分给最多的儿童_translated": "6323-jiang-qian-fen-gei-zui-duo-de-er-tong-translated",
"76最小覆盖子串_translated": "76-zui-xiao-fu-gai-zi-chuan-translated",
"994.腐烂的橘子_translated": "994-fu-lan-de-ju-zi-translated",
"[121]买卖股票的最佳时期_translated": "121-mai-mai-gu-piao-de-zui-jia-shi-qi-translated",
"[1333]餐厅过滤器_translated": "1333-can-ting-guo-l-qi-translated",
"[146]LRU 缓存_translated": "146lru-huan-cun-translated",
"[213]打家劫舍 II_translated": "213-da-jia-jie-she-iitranslated",
"[2490]回环句_translated": "2490-hui-huan-ju-translated",
"[2562]找出数组的串联值_translated": "2562-zhao-chu-shu-zu-de-chuan-lian-zhi-translated",
"[2582]递枕头_translated": "2582-di-zhen-tou-translated",
"brief_alternate 作业帮忙_translated": "briefalternate-zuo-ye-bang-mang-translated",
"剑指 Offer II 021. 删除链表的倒数第 n 个结点_translated": "jian-zhi-offerii021-shan-chu-lian-biao-de-dao-shu-di-n-ge-jie-dian-translated"
"byName": {
"1234. 替换子串得到平衡字符串_translated": "/en/docs/career/interview-prep/leetcode/1234-replace-substring-for-balanced-string",
"142.环形链表II_translated": "/en/docs/career/interview-prep/leetcode/142-linked-list-cycle-ii",
"1653. 使字符串平衡的最少删除次数_translated": "/en/docs/career/interview-prep/leetcode/1653-minimum-deletions-to-make-string-balanced",
"1664生成平衡数组的方案数_translated": "/en/docs/career/interview-prep/leetcode/1664-ways-to-make-a-fair-array",
"1825求出 MK 平均值_translated": "/en/docs/career/interview-prep/leetcode/1825-mk-average",
"1828统计一个圆中点的数目_translated": "/en/docs/career/interview-prep/leetcode/1828-queries-on-number-of-points-inside-a-circle",
"2299强密码检验器II_translated": "/en/docs/career/interview-prep/leetcode/2299-strong-password-checker-ii",
"2309兼具大小写的最好英文字母_translated": "/en/docs/career/interview-prep/leetcode/2309-greatest-english-letter-in-upper-and-lower-case",
"2335. 装满杯子需要的最短总时长_translated": "/en/docs/career/interview-prep/leetcode/2335-minimum-amount-of-time-to-fill-cups",
"2341. 数组能形成多少数对_translated": "/en/docs/career/interview-prep/leetcode/2341-maximum-number-of-pairs-in-array",
"2639. 查询网格图中每一列的宽度_translated": "/en/docs/career/interview-prep/leetcode/2639-find-column-width-of-grid",
"2679.矩阵中的和_translated": "/en/docs/career/interview-prep/leetcode/2679-sum-in-a-matrix",
"2894. 分类求和并作差": "/en/docs/career/interview-prep/leetcode/2894-divisible-and-non-divisible-sums-difference",
"3072. 将元素分配到两个数组中 II_translated": "/en/docs/career/interview-prep/leetcode/3072-distribute-elements-into-two-arrays-ii",
"345. 反转字符串中的元音字母_translated": "/en/docs/career/interview-prep/leetcode/345-reverse-vowels-of-a-string",
"538.把二叉搜索树转换为累加树_translated": "/en/docs/career/interview-prep/leetcode/538-convert-bst-to-greater-sum-tree",
"6323. 将钱分给最多的儿童_translated": "/en/docs/career/interview-prep/leetcode/6323-distribute-money-to-maximum-children",
"76最小覆盖子串_translated": "/en/docs/career/interview-prep/leetcode/76-minimum-window-substring",
"994.腐烂的橘子_translated": "/en/docs/career/interview-prep/leetcode/994-rotting-oranges",
"[121]买卖股票的最佳时期_translated": "/en/docs/career/interview-prep/leetcode/121-best-time-to-buy-and-sell-stock",
"[1333]餐厅过滤器_translated": "/en/docs/career/interview-prep/leetcode/1333-filter-restaurants-by-vegan-friendly-price-and-distance",
"[146]LRU 缓存_translated": "/en/docs/career/interview-prep/leetcode/146-lru-cache",
"[213]打家劫舍 II_translated": "/en/docs/career/interview-prep/leetcode/213-house-robber-ii",
"[2490]回环句_translated": "/en/docs/career/interview-prep/leetcode/2490-circular-sentence",
"[2562]找出数组的串联值_translated": "/en/docs/career/interview-prep/leetcode/2562-find-the-array-concatenation-value",
"[2582]递枕头_translated": "/en/docs/career/interview-prep/leetcode/2582-pass-the-pillow",
"brief_alternate 作业帮忙_translated": "/zh/docs/career/interview-prep/leetcode/briefalternate-zuo-ye-bang-mang-translated",
"剑指 Offer II 021. 删除链表的倒数第 n 个结点_translated": "/en/docs/career/interview-prep/leetcode/sword-offer-ii-021-remove-nth-node-from-end-of-list"
},
"byNumber": {
"42": "/en/docs/career/interview-prep/leetcode/42-trapping-rain-water",
"46": "/en/docs/career/interview-prep/leetcode/46-permutations",
"76": "/en/docs/career/interview-prep/leetcode/76-minimum-window-substring",
"80": "/en/docs/career/interview-prep/leetcode/80-remove-duplicates-from-sorted-array-ii",
"93": "/en/docs/career/interview-prep/leetcode/93-restore-ip-addresses",
"121": "/en/docs/career/interview-prep/leetcode/121-best-time-to-buy-and-sell-stock",
"142": "/en/docs/career/interview-prep/leetcode/142-linked-list-cycle-ii",
"146": "/en/docs/career/interview-prep/leetcode/146-lru-cache",
"213": "/en/docs/career/interview-prep/leetcode/213-house-robber-ii",
"219": "/en/docs/career/interview-prep/leetcode/219-contains-duplicate-ii",
"345": "/en/docs/career/interview-prep/leetcode/345-reverse-vowels-of-a-string",
"538": "/en/docs/career/interview-prep/leetcode/538-convert-bst-to-greater-sum-tree",
"994": "/en/docs/career/interview-prep/leetcode/994-rotting-oranges",
"1004": "/en/docs/career/interview-prep/leetcode/1004-max-consecutive-ones-iii",
"1234": "/en/docs/career/interview-prep/leetcode/1234-replace-substring-for-balanced-string",
"1333": "/en/docs/career/interview-prep/leetcode/1333-filter-restaurants-by-vegan-friendly-price-and-distance",
"1545": "/en/docs/career/interview-prep/leetcode/1545-find-kth-bit-in-nth-binary-string",
"1653": "/en/docs/career/interview-prep/leetcode/1653-minimum-deletions-to-make-string-balanced",
"1664": "/en/docs/career/interview-prep/leetcode/1664-ways-to-make-a-fair-array",
"1825": "/en/docs/career/interview-prep/leetcode/1825-mk-average",
"1828": "/en/docs/career/interview-prep/leetcode/1828-queries-on-number-of-points-inside-a-circle",
"2131": "/en/docs/career/interview-prep/leetcode/2131-longest-palindrome-by-concatenating-two-letter-words",
"2241": "/en/docs/career/interview-prep/leetcode/2241-design-an-atm-machine",
"2270": "/en/docs/career/interview-prep/leetcode/2270-number-of-ways-to-split-array",
"2293": "/en/docs/career/interview-prep/leetcode/2293-min-max-game",
"2299": "/en/docs/career/interview-prep/leetcode/2299-strong-password-checker-ii",
"2309": "/en/docs/career/interview-prep/leetcode/2309-greatest-english-letter-in-upper-and-lower-case",
"2335": "/en/docs/career/interview-prep/leetcode/2335-minimum-amount-of-time-to-fill-cups",
"2341": "/en/docs/career/interview-prep/leetcode/2341-maximum-number-of-pairs-in-array",
"2490": "/en/docs/career/interview-prep/leetcode/2490-circular-sentence",
"2562": "/en/docs/career/interview-prep/leetcode/2562-find-the-array-concatenation-value",
"2582": "/en/docs/career/interview-prep/leetcode/2582-pass-the-pillow",
"2639": "/en/docs/career/interview-prep/leetcode/2639-find-column-width-of-grid",
"2679": "/en/docs/career/interview-prep/leetcode/2679-sum-in-a-matrix",
"2894": "/en/docs/career/interview-prep/leetcode/2894-divisible-and-non-divisible-sums-difference",
"3072": "/en/docs/career/interview-prep/leetcode/3072-distribute-elements-into-two-arrays-ii",
"3138": "/en/docs/career/interview-prep/leetcode/3138-minimum-length-of-anagram-concatenation",
"6323": "/en/docs/career/interview-prep/leetcode/6323-distribute-money-to-maximum-children",
"9021": "/en/docs/career/interview-prep/leetcode/9021-tut-3-25t1",
"021": "/en/docs/career/interview-prep/leetcode/sword-offer-ii-021-remove-nth-node-from-end-of-list"
}
}
Loading
Loading