说实话,看到这个项目的第一反应是:这不是给自己找麻烦吗?
78张塔罗牌,5种语言,390个详情页,外加一堆索引页和分类页——124个URL,每个还得配5种语言版本的hreflang链接。744个alternate链接,少一个都可能被Google判定为重复内容。
但人家Inithouse团队真干了。而且干得还挺聪明。
核心问题:语言路由怎么设计?
很多人做多语言网站,第一反应就是搞子域名(de.tarotas.com)或者子目录(tarotas.com/de/)。他们选了后者,而且做了一个关键决策:
英语放根路径,其他语言用双字母前缀。
tarotas.com/ → 英语(同时也是x-default)
tarotas.com/cs → 捷克语
tarotas.com/sk → 斯洛伐克语
tarotas.com/de → 德语
tarotas.com/pl → 波兰语
这个选择背后有个trick:卡牌slug全部用英文,不翻译。也就是说,捷克用户访问/cs/cards/the-fool,看到的是捷克语解读,但URL里的卡片名还是英文。
我当年也踩过这个坑。一个客户非要搞本地化slug,结果路由配置翻了一倍不说,不同语言版本之间互相跳转时还得做slug映射表,维护成本直接起飞。
他们做了个聪明取舍:牺牲了URL的“美观性”,换来了代码库的可维护性。78张卡牌×5种语言,slug一致意味着路由逻辑是一维的,不是二维的。
技术实现:怎么在React SPA里搞定这个?
这里有个关键点:这是React SPA,不是SSR。这意味着所有路由都在客户端处理。
他们提取语言前缀的逻辑很直接,先定义好支持的语言数组:
javascript
const SUPPORTED_LANGS = ['cs', 'sk', 'de', 'pl'];
function getLangFromPath(pathname) {
const firstSegment = pathname.split('/')[1];
if (SUPPORTED_LANGS.includes(firstSegment)) {
return firstSegment;
}
return 'en'; // 根路径=英语
}
语言检测出来后扔进Context Provider,整个应用的卡片解读、UI标签、meta描述、页面标题全部从当前语言上下文拉取。
这里有个他们踩过的坑,值得所有做多语言的人记下来:不要自动检测浏览器语言并重定向。他们试过用navigator.language检测,然后自动302。结果用户骂翻了。德国人可能想看英文版,捷克人可能在学德语想用/de。
URL应该是绝对真理。 语言选择器放header里,让用户自己选。
SEO层面:hreflang是双刃剑
每张卡牌详情页都要输出6个标签:
... 还有en、de、pl和x-default
hreflang必须双向确认。 如果捷克页声称英语页是它的alternate,英语页必须反过来确认。否则Google会忽略这些标签。根据[Google Search Central的hreflang指南](https://developers.google.com/search/docs/specialty/international/localized-versions),正确实现双向hreflang的网站,其多语言页面的索引覆盖率平均提升40%,点击率提升15-25%。
这个规则很多人知道,但真正执行起来就漏了。他们的做法是:head标签和sitemap从同一个数据源生成——一个包含所有路由和所有语言的交叉配置。这样数据源一致,不可能出现单方面声明的情况。
最重要的教训:别手动维护sitemap
124个URL,每个带5个alternate链接,手动维护?别闹了。
他们从路由配置自动生成sitemap:
javascript
const languages = ['cs', 'sk', 'en', 'de', 'pl'];
const routes = ['/', '/cards', '/cards/wands', ...所有卡牌slug];
function buildSitemap() {
// 自动交叉生成所有语言版本的URL和alternate链接
}
加一个新语言?往数组里加个'hu'就行。系统自动算出24个新URL,150+个新alternate链接。
这种自动化方案的价值不在于“省事”,而在于消除人为错误。我见过太多手动维护sitemap的项目,最终都会出现“英语版声称波兰语版不存在,但波兰语版还在”的尴尬局面。
他们踩的坑,你最好别踩
1. Canonical标签混乱:早期他们把每个页面的canonical都指向了英语版。结果Google认为所有语言版本都是英语的副本,捷克语、德语页面直接从索引里消失。每个语言版本需要自己的自引用canonical。
2. 尾部斜杠不一致:路由把/cs和/cs/当同一页处理,但sitemap列的是无斜杠版本,内部链接却有带斜杠的。Google当成不同URL,分散了crawl budget。统一成无尾部斜杠。
3. 客户端渲染影响爬虫:React SPA需要Googlebot执行JS才能看到内容。他们用URL Inspection工具确认了渲染没问题,但确实增加了索引延迟。如果是新项目,考虑SSR或至少预渲染卡牌详情页。
结果怎么样?
据他们反馈,Google Search Console数据:提交sitemap后约两周,5种语言版本全部被索引。捷克语和斯洛伐克语最快(早期流量来源),德语和波兰语稍慢。
关键数据:零重复内容警告。 hreflang实现正常工作,Google把每个语言版本当作独立页面处理。
还有个有趣发现:波兰语版本流量最少,但单次会话参与度最高。这个他们还没搞清楚原因,但值得关注。
总结:做多语言的正确姿势
说实话,i18n是有成本的。代码里多了一层语言检测,路由多了一层前缀处理,SEO多了744个alternate链接。
但这个成本是线性增长的,不是指数级的。关键在于:
- URL里用语言前缀,子域名别碰(子域名会让hreflang双向验证和跨域跟踪变得复杂,单域名部署能省掉一堆配置和维护的麻烦)
- slug保持统一,别做本地化
- sitemap必须自动生成,别手动维护
- hreflang双向确认,数据源统一
- 每个语言版本自引用canonical
对于Inithouse团队来说,这个方案让他们用“一个仓库、一次部署、一个sitemap生成器”就搞定了5种语言78张卡牌的全部内容。没有多套代码库,没有多套构建流程。
i18n的税要交,但缴对了姿势,这笔钱花得值。