应按路径>查询参数>accept-language顺序提取语言并白名单校验,fallback至en;所有html输出需htmlspecialchars转义;hreflang须双向对称、iso合规、url可访问;路由用正则约束语言前缀;文案统一json缓存管理并fallback至en;响应头content-language须匹配当前语言。

ThinkPHP里怎么安全提取当前语言并 fallback 到 en
别信 $_SERVER['HTTP_ACCEPT_LANGUAGE'],它不可控、不可靠、还容易被伪造。真正该用的顺序是:路径 > 查询参数 > Accept-Language(仅作兜底)。
常见错误现象:/zh/about 页面却输出英文 description;/fr-CH 路径进来直接 500;用户手动改 URL 为 /xx/ 导致 XSS 输出。
- 用正则从
$_SERVER['REQUEST_URI']提取前缀,比如preg_match('#^/([a-z]{2})/#', $_SERVER['REQUEST_URI'], $m),匹配到就取$m[1] - 没匹配上再看
$_GET['lang'],但必须白名单校验:in_array($lang, ['en', 'zh', 'ja', 'ko'], true) - Accept-Language 只用来 fallback,且只取第一个两字母码(如
fr-CH,fr;q=0.9→fr),再过一遍白名单 - 最终没一个合法,就硬设为
en,不空、不猜、不跳转 - 所有输出到 HTML 的文案,必须过
htmlspecialchars($text, ENT_QUOTES, 'UTF-8'),特别是<meta name="description">的 content 值
hreflang 标签怎么动态生成才不被 Google 忽略
hreflang 不是“写全了就完事”,它必须双向对称、URL 可访问、语言码严格匹配 ISO 标准,否则整组标签会被搜索引擎直接丢弃。
常见错误现象:中文页写了 hreflang="zh" 指向 /zh/about,但英文页没反向声明;用了 zh-CN 却路径是 /zh/;某语言下线了,hreflang 还挂在其他页面上。
- 语言配置统一用数组管理,例如:
['en' => ['prefix' => 'en', 'locale' => 'en-US'], 'zh' => ['prefix' => 'zh', 'locale' => 'zh-Hans']] - 当前语言确定后,遍历这个数组,为每个有效语言生成一条
<link rel="alternate" hreflang="xxx" href="https://example.com/xxx/path"> -
hreflang值用$lang(如zh)或$config[$lang]['locale'](如zh-Hans),但必须和你实际部署的语言版本完全一致 - 生成前检查目标 URL 是否真实存在(可用
file_get_contents()或 HEAD 请求简单探测),若返回非 200 就跳过该条 - 不要在模板里硬写死 hreflang,ThinkPHP 控制器中用
$this->assign('hreflangs', $links)传入,模板里循环输出
ThinkPHP 路由怎么配才利于多语言 SEO
路由不是越短越好,而是要让语言标识可预测、可抓取、不冲突。子域名和目录模式都能用,但目录模式(/zh/)更易维护,也更适合中小项目。
使用场景:需要百度、Google 同时收录 /en/product/123 和 /zh/product/123,且互不干扰。
- 在
route/route.php中定义带语言前缀的资源路由:Route::group(':lang', function () { Route::resource('product', 'index/product'); }); - 加正则约束防止非法路径:
Route::pattern(['lang' => '[a-z]{2}']);,拒绝/zh-CN/或/english/这类写法 - 避免把语言前缀塞进中间位置(如
/product/zh/123),爬虫难识别,也破坏语义层级 - 所有语言版本共用同一套控制器逻辑,但通过
$lang参数加载对应文案、模板变量、甚至数据库字段(如title_zh) - 禁用基于语言头的自动重定向——
/fr/product/123必须原样返回法文内容,哪怕浏览器发来en-US
多语言 Meta 标签怎么避免文案散落、重复和 XSS
Meta 标签不是“换几个字符串”,而是“换数据源”。把翻译文本写死在控制器、模板、配置文件三处,等于给自己埋雷。
性能影响:每次请求都 file_get_contents('lang/zh.json') 会拖慢首屏;全量加载所有语言文案又浪费内存。
- 文案统一存 JSON 文件,按语言分目录:
lang/zh.json、lang/en.json,结构为:{"title": "首页", "description": "这里是中文首页描述"} - 用 ThinkPHP 缓存读取:
cache('lang_zh', function () { return json_decode(file_get_contents(APP_PATH . 'lang/zh.json'), true); }); - 控制器中根据当前
$lang加载对应缓存键,再$this->assign('seo', $texts) - 模板里用:
<title>= htmlspecialchars($seo['title'] ?? '默认标题') ?></title>,不拼接、不 echo raw - 如果某语言缺字段,fallback 到
en对应值,而不是留空或报错
最常被忽略的一点:Content-Language 响应头必须和当前语言一致,比如返回中文页就要发 Content-Language: zh-Hans。这个头不写,hreflang 效果打折扣,部分爬虫还会降权。ThinkPHP 在控制器末尾加 header('Content-Language: ' . $lang . '-Hans'); 即可,注意 $lang 是已校验过的值。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











