「Crème Brûlée Café」「Привет мир」「重庆火锅」这样的标题总要变成 URL slug,而真正的选择只有三种:音译成纯 ASCII 字母;保留原文字符,让它们被百分号编码;或者手工写一个别的语言的 slug。本指南比较这三种做法,核对 Google 文档的说法,并用我们的 URL slug 生成器 的真实输出,逐一讲解拉丁字母重音、德语变音、西里尔字母、希腊字母和中文拼音,以及常见 slugify 函数在哪些地方悄悄丢了字母。
处理非 ASCII 标题的三种方式
| 做法 | 示例输出 | 优点 | 缺点 |
|---|---|---|---|
| 音译成 ASCII | creme-brulee-cafe、privet-mir、chong-qing-huo-guo | 所有浏览器、应用、CMS 和服务器都支持;容易输入 | 罗马化写法要让读者看得懂;声调和部分区别会丢失 |
| 保留 Unicode | crème-brûlée-café、москва-гид、重庆火锅 | 与读者阅读和搜索时用的词一致 | 凡是链接被编码的地方都会变成很长的百分号编码;用别的键盘不好输入 |
| 手工撰写或翻译 | chongqing-hot-pot | 用词完全可控 | 每个页面都要手工处理;容易前后不一致 |
大多数国际化网站会为每种语言选定一种做法并全站统一。最糟糕的结果是混用:同一个栏目里有的页面是罗马化的,有的不是。
Google 对非英文 URL 怎么说
Google 的 URL 结构指南建议在 URL 中使用受众所用语言的词,「以及适用时音译的词」("and, if applicable, transliterated words")。它的示例里既有德语路径 /lebensmittel/pfefferminz,也有日语路径 /ペパーミント。所以罗马化 URL 和原文字符 URL,Google 都接受。
同一页面还有两条与此相关的技术规则:
- 链接里的非 ASCII 字符要做百分号编码。 你链接到自己的页面时,ASCII 以外的字符应在
href属性中进行百分号编码。Google 给的例子是写gem%C3%BCse,而不是gemüse。 - URL 区分大小写。 Google 把
/APPLE和/apple视为不同的 URL,这也是在所有区分大小写的文字里统一使用小写的又一个理由。
百分号编码会把 slug 变成什么样
URL 只能携带有限的 ASCII 字符,其他字符都要按 UTF-8 字节发送,每个字节写成 % 加两位十六进制数。用浏览器自带的 encodeURI 和 encodeURIComponent 函数处理,结果如下:
| 可读形式 | 编码后 |
|---|---|
https://example.com/café-москва | https://example.com/caf%C3%A9-%D0%BC%D0%BE%D1%81%D0%BA%D0%B2%D0%B0 |
重庆火锅 | %E9%87%8D%E5%BA%86%E7%81%AB%E9%94%85 |
每个带重音的拉丁字母或西里尔字母占 2 个字节(编码后 6 个字符),每个汉字占 3 个字节(编码后 9 个字符)。4 个汉字的 slug 编码后就是 36 个字符。如果读者常在会显示编码形式的应用里分享链接,音译更好读。把任意字符串粘贴进 URL 编码/解码工具,就能看到它的字节,也能把链接解码回可读文字。
拉丁字母的重音和特殊字母
大多数带重音的拉丁字母是「基本字母 + 附加符号」,去掉符号是安全的:é → e、ñ → n、ç → c。少数字母没有可去掉的符号,需要显式映射:ß → ss、æ → ae、ø → o、ł → l、đ → d。我们的引擎两种情况都能处理;德语变音选项见下一节。下面是真实输出,旁边是常见的一行正则 text.toLowerCase().replace(/[^a-z0-9]+/g, '-') 的结果:
| 标题 | 我们的默认 | 简易正则 |
|---|---|---|
| Crème Brûlée Café | creme-brulee-cafe | cr-me-br-l-e-caf- |
| Straße über Ärger öffnen | strasse-uber-arger-offnen | stra-e-ber-rger-ffnen |
| Æbleskiver på Øresund | aebleskiver-pa-oresund | -bleskiver-p-resund |
| Łódź Kraków Gdańsk | lodz-krakow-gdansk | -d-krak-w-gda-sk |
| Đà Nẵng Phở Bò | da-nang-pho-bo | -n-ng-ph-b- |
简易正则处理纯英文没问题,但标题里只要出现一个重音字母,单词就会被拆散。
德语、丹麦语等语言的惯例
有些语言在无法使用特殊字母时有自己的替代写法。德语传统上把 ä、ö、ü 写成 ae、oe、ue,所以 mueller 比 muller 更接近「Müller」。WordPress 也是这个思路:它的 remove_accents() 函数只在德语区域设置(de_DE、de_CH、de_AT)下应用 ä → ae、ö → oe、ü → ue 和 ß → ss,在丹麦语下应用 å → aa、ø → oe。没有区域设置时,WordPress 把 ß 映射为单个 s。
我们的工具对所有语言使用同一套通用映射,另外提供一个「德语变音」开关。开启后,「Müller Größe Übersicht」会变成 mueller-groesse-uebersicht,而不是 muller-grosse-ubersicht;「Straße über Ärger öffnen」会变成 strasse-ueber-aerger-oeffnen,而不是 strasse-uber-arger-offnen。
在我们的映射里,丹麦语的 å 变成 a,ø 变成 o,例如 aebleskiver-pa-oresund。如果需要丹麦语的 aa、oe 写法,请手工修改这些 slug。
停用词也因语言而异。停用词编辑器提供西班牙语、法语和德语预设,会追加到英文列表上:使用西班牙语预设时,「El Mejor Café de la Ciudad」会变成 mejor-cafe-ciudad,而不是 el-mejor-cafe-de-la-ciudad;法语和德语预设分别得到 meilleures-crepes-paris 和 tipps-garten。
西里尔字母和希腊字母
西里尔字母和希腊字母没有可去除的重音,需要一张完整的字母对照表。我们的引擎为每种文字使用一张固定的、英文读者易读的对照表:
| 标题 | Slug |
|---|---|
| Привет мир | privet-mir |
| Щука и Ёж | shchuka-i-yozh |
| Юрий Хрущёв | yuriy-khrushchyov |
| Καλημέρα κόσμε | kalimera-kosme |
这两种文字都有多套罗马化方案,我们用的是一套通用方案,不是某个国家的标准,所以人名拼写可能和护照或地图上的不同。品牌、人名和城市名请手工修改;乌克兰语、塞尔维亚语或保加利亚语文本请抽查几条。
中文:拼音 slug
汉语拼音是中文罗马化的国际标准(ISO 7098)。汉字没有可以去掉的字母,所以普通的 slugify 函数要么直接丢掉汉字,要么需要拼音词典。我们的工具有一个可选的「中文转拼音」设置,只有在文本包含中文时才加载词典:
| 标题 | 拼音模式 | 关闭拼音 | 保留 Unicode |
|---|---|---|---|
| 重庆火锅 | chong-qing-huo-guo | (空) | 重庆火锅 |
| 银行开户指南 | yin-hang-kai-hu-zhi-nan | (空) | 银行开户指南 |
| 绿茶的好处 | lv-cha-de-hao-chu | (空) | 绿茶的好处 |
| 北京 Travel Guide 2026 | bei-jing-travel-guide-2026 | travel-guide-2026 | 北京-travel-guide-2026 |
值得了解的细节:
- 不带声调。 声调符号不是 ASCII,所以会被去掉。
- ü 写作 v,例如
lv-cha。这是拼音输入法的惯例,也有网站写成lu或lyu。 - 多音字按上下文读。 「重」在「重庆」里读 chong,「行」在「银行」里读 hang,因为词典按整词判断。生僻人名仍可能读错。
- 每个音节一个连字符。 想按词连写(例如
chongqing-hotpot)请手工撰写。 - 日语和韩语不一样。 日文汉字会按中文读音转换,假名会被去掉:
東京 ラーメン变成dong-jing。韩文会被去掉。这两种语言请使用「保留 Unicode」或自己写 slug。
常见 slugify 函数对比
不同平台的取舍不同,有些会在没有任何提示的情况下丢掉字母。Django 的 slugify() 先用 NFKD 规范化文本,再丢弃所有非 ASCII 字符。这样能正确去掉重音,但无法分解的字母会彻底消失。下面是 Django 算法和我们的结果对比:
| 标题 | Django 默认 | Django allow_unicode=True | 我们的默认 |
|---|---|---|---|
| Crème Brûlée Café | creme-brulee-cafe | crème-brûlée-café | creme-brulee-cafe |
| Straße über Ärger öffnen | strae-uber-arger-offnen | straße-über-ärger-öffnen | strasse-uber-arger-offnen |
| Æbleskiver på Øresund | bleskiver-pa-resund | æbleskiver-på-øresund | aebleskiver-pa-oresund |
| Привет мир | (空) | привет-мир | privet-mir |
| 重庆火锅 | (空) | 重庆火锅 | chong-qing-huo-guo(拼音模式) |
WordPress 的 sanitize_title_with_dashes() 文档注明它「不会替换带重音的特殊字符」("does not replace special accented characters"),而是做百分号编码;保存文章时会先运行 remove_accents(),应用上文的区域设置规则,所以结果取决于网站语言。
代码:用 JavaScript 和 Python 音译重音字母
如果你只需要在自己的代码里处理拉丁字母部分,做法是:先映射那几个无法分解的字母,再规范化为 NFKD,然后去掉组合符号。对本指南中所有拉丁字母示例,下面的函数都能得到与我们引擎相同的 slug,例如 strasse-uber-arger-offnen 和 aebleskiver-pa-oresund:
const SPECIAL = { ß: 'ss', æ: 'ae', Æ: 'AE', ø: 'o', Ø: 'O', ł: 'l', Ł: 'L', đ: 'd', Đ: 'D', ı: 'i' };
function asciiSlug(text) {
return text
.replace(/[ßæÆøØłŁđĐı]/g, (c) => SPECIAL[c])
.normalize('NFKD')
.replace(/[\u0300-\u036f]/g, '')
.toLowerCase()
.replace(/[^a-z0-9]+/g, '-')
.replace(/^-+|-+$/g, '');
}
asciiSlug('Łódź Kraków Gdańsk'); // 'lodz-krakow-gdansk'import re
import unicodedata
SPECIAL = str.maketrans({"ß": "ss", "æ": "ae", "Æ": "AE", "ø": "o", "Ø": "O",
"ł": "l", "Ł": "L", "đ": "d", "Đ": "D", "ı": "i"})
def ascii_slug(text: str) -> str:
text = unicodedata.normalize("NFKD", text.translate(SPECIAL))
text = "".join(c for c in text if not unicodedata.combining(c)).lower()
return re.sub(r"[^a-z0-9]+", "-", text).strip("-")
ascii_slug("Straße über Ärger öffnen") # 'strasse-uber-arger-offnen'它们不处理西里尔字母、希腊字母和中文。这些文字请使用完整的音译表或库,或者直接用我们的工具。我们的 Slugify 正则与代码片段 指南提供了五种语言的完整函数。
注意
常见错误
- 对国际化标题使用只认 ASCII 的正则。 如上表所示,
[^a-z0-9]+会把「Crème」变成cr-me。 - 悄悄丢字母。 NFKD 加「忽略非 ASCII」会丢掉 ß、æ、ø、ł、đ,还会把西里尔字母和中文标题变成空。请用你发布的每种语言的真实标题测试 slug 函数。
- 发布空 slug。 我们的工具会标出每个 slug 为空的标题;在你自己的代码里,请加一个兜底方案。
- 混用方案。 网站做到一半从
muller换成mueller,或从 Unicode 换成拼音,会造成 URL 不一致,以后修复还得做重定向。 - 重复编码。 对已经编码过的 slug 再编码一次,会把
%C3%A9变成%25C3%25A9。存储可读的 slug,在生成链接时只编码一次。
多语言 slug 检查清单
[ ] 每个语言栏目只用一种做法:音译、保留 Unicode 或翻译
[ ] 拉丁字母重音已去掉(é -> e),特殊字母已映射(ß -> ss、æ -> ae、ł -> l)
[ ] 德语内容:确定 ä -> a 还是 ä -> ae,并全站统一
[ ] 西里尔字母和希腊字母用同一张表罗马化;人名已手工核对
[ ] 中文:开启拼音模式,并检查输出里有没有读错的名字
[ ] 日语和韩语:保留 Unicode 或手写 slug
[ ] 没有空 slug;没有悄悄丢掉的字母
[ ] href 中的非 ASCII 链接只做一次百分号编码
[ ] 在不改变意思的前提下去掉了该语言的停用词
[ ] slug 全小写,且在所在目录内唯一在工具里试一试
URL slug 生成器 提供「智能音译」「德语变音」「中文转拼音」和「保留 Unicode」开关,还有西班牙语、法语和德语停用词预设。批量 CSV Slug 生成器 可以把它们应用到整张表格。想让罗马化后的 slug 保持简短,请参考我们的 URL slug 长度最佳实践。如果只想去掉人名或普通文本里的重音、而不生成 slug,请用去除重音符号工具。拼音与汉字网址的详细对比,见 中文网址 slug 指南。
参考资料
- Google 搜索中心:URL structure best practices for Google Search
- WordPress 开发者资源:remove_accents() 和 sanitize_title_with_dashes()
- Django 文档:django.utils.text.slugify
- ISO:ISO 7098:2015, Romanization of Chinese
- MDN:encodeURI()
FAQ
常见问题
URL 应该音译还是保留原文字符?
对 Google 来说两种都可以:它建议使用受众语言的词,也接受音译的词。如果链接经常被分享,或需要用别的键盘输入,就音译;如果读者使用这种文字,而且你的平台对 UTF-8 的处理始终一致,就保留原文字符。
URL slug 里的 ü 应该写成 u 还是 ue?
德语内容在无法使用变音字母时,传统写法是 ue,所以 mueller 更接近 Müller。其他语言通常写成 u。全站选定一条规则并坚持使用。
中文标题怎么生成 slug?
在我们的工具里开启「中文转拼音」:重庆火锅会变成 chong-qing-huo-guo,不带声调。你也可以用「保留 Unicode」保留汉字,或者手工写一个英文 slug。
为什么生成的 slug 是空的?
标题里只有函数无法罗马化的字符,例如关闭拼音时的中文、日文假名或韩文。中文请开启拼音,或切换到「保留 Unicode」,也可以手工写 slug。
