生产环境优先用 gettext,它成熟轻量、支持上下文与复数规则,PHP 内置;intl 适合日期数字格式化,非翻译主力。

PHP 多语言切换靠 gettext 还是 intl?
直接说结论:生产环境优先用 gettext,它成熟、轻量、支持上下文和复数规则,且 PHP 内置扩展(gettext 扩展需启用)。intl 更适合处理日期/数字格式化,不是翻译文本的主力工具——硬用 MessageFormatter 做多语言,会绕远路、难维护。
常见错误现象:setlocale(LC_ALL, 'zh_CN.UTF-8') 执行成功,但 gettext('Hello') 仍返回英文原词。原因通常是 MO 文件路径没对、域名没加载、或 locale 名字系统不支持。
-
bindtextdomain('messages', '/path/to/locales')的路径必须是绝对路径,相对路径在 CLI 和 Web 下行为不一致 - 调用
textdomain('messages')必须在所有_()调用前执行一次,且不能动态改域名来“切换语言”——那是设计误区 - MO 文件名必须严格为
messages.mo(对应域名),放在/zh_CN/LC_MESSAGES/这类子目录下
怎么让用户点一下就切语言,且不丢当前页面参数?
核心是把语言标识存在可控制的位置,而不是依赖 session 或 cookie 做全局状态——那样会导致分享链接失效、SEO 友好性差。推荐方案:语言作为 URL 前缀(如 /zh/about、/en/about),配合路由重写 + 语言感知的模板输出。
使用场景:用户从中文页点击「English」按钮,应跳转到当前页面的英文版 URL,而非首页。
立即学习“PHP免费学习笔记(深入)”;
- 用
$_SERVER['REQUEST_URI']解析当前路径,提取 pathinfo 后去掉首段语言码(如有),再拼上目标语言前缀 - 不要用
header('Location: ...')硬跳,先检查目标语言是否在白名单(如['zh', 'en', 'ja']),防 open redirect - 模板里所有文字都走
_(),但语言切换链接本身也要翻译:<a href="https://www.php.cn/link/8b2e2d4d84f88b773325a13afcf63c5e'en')%20?>">= _('English') ?></a>
gettext 在 Windows 和 Docker 下常见失败点
Windows 默认不带 GNU gettext 工具链,Docker 官方 PHP 镜像默认不装 gettext 扩展——这两处最容易卡住,导致本地能跑、上线就回退英文。
性能影响:MO 文件被 mmap 到内存,首次加载稍慢,之后极快;但若每请求都 bindtextdomain + textdomain,会反复触发文件查找,拖慢响应。
- Dockerfile 中要显式安装:
apt-get install -y gettext(Debian)或apk add --no-cache gettext(Alpine),再确认extension=gettext.so在 php.ini 中启用 - Windows 开发者别用
.po编辑器导出的 UTF-8 BOM 版本 MO 文件——gettext会静默失败。用msgfmt -o messages.mo messages.po命令行生成才可靠 - MO 文件权限必须是 web server 可读(如
644),且父目录要有执行权限(755),否则 Linux 下gettext找不到目录
用户语言偏好怎么自动匹配,又不侵犯隐私?
靠 $_SERVER['HTTP_ACCEPT_LANGUAGE'] 做初始猜测可以,但不能当唯一依据——这个 header 可被伪造、浏览器设置混乱、移动端常报错值(如 zh-CN,zh;q=0.9,en;q=0.8 里 zh-CN 不一定代表用户真想看简体中文)。
容易踩的坑:一进站就读取并写死 session 语言,结果用户手动切了语言后,下次访问又跳回自动匹配的语言。
- 只在用户未显式选择语言时,才解析
HTTP_ACCEPT_LANGUAGE,取第一个有效项(如zh-CN→zh),再查白名单做 fallback - 语言选择必须有明确的 Cookie 或 URL 参数锚点(如
?lang=ja),且该参数优先级高于 Accept-Language - 不要存语言到 session 并设 24 小时过期——用户换设备或清 cookie 就失联。更稳妥的是用
setcookie('lang', 'ja', ['expires' => time()+31536000, 'path' => '/', 'sameSite' => 'Lax'])
最麻烦的其实是复数形式和上下文歧义——比如英文 “message” 在单复数、动词/名词间切换,中文看似没这问题,但日语敬体/简体、阿拉伯语右向左排版、希伯来语数字方向,都会让简单替换方案崩掉。这时候你得老老实实写 PO 文件,别想着用数组配置糊弄过去。



















