gettext 多语言生效需同时满足系统 locale 安装、php 扩展启用、目录结构严格匹配 setlocale 值、每次请求重初始化三步,缺一不可。

gettext 是 PHP 里最稳的多语言方案,但不是 require 一下就能用——它卡在系统层、PHP 扩展、目录结构、调用顺序四个地方。配错一个,_("Hello") 就返回空或原串。
gettext 环境没起来:setlocale() 返回 false 或 gettext() 不生效
根本原因不是代码写错了,而是系统压根不认你写的 zh_CN.UTF-8 这种 locale 名。PHP 的 setlocale() 是直接透传给操作系统的,系统没装,就返回 false。
- 先查系统有没有:
locale -a | grep zh_CN(Linux);没结果就得用sudo locale-gen zh_CN.UTF-8补上 - PHP 必须启用扩展:运行
php -m | grep gettext,没输出就去php.ini解开extension=gettext注释,重启 PHP-FPM 或 Apache -
setlocale(LC_ALL, 'zh_CN.UTF-8')中的字符串必须和locale -a输出的**完全一致**,少个.UTF-8、大小写错、用下划线代替短横线,都可能失败 - 别信 Docker 镜像默认带全 locale——Alpine 需要
apk add --no-cache gettext locale-all,Debian/Ubuntu 镜像也要手动locale-gen
bindtextdomain() 路径对了,但翻译还是不加载
gettext 不是靠文件名找语言,而是靠当前 setlocale() 设置的 locale 值,拼出子目录路径去读 .mo 文件。路径对但结构错,照样扑空。
- 目录结构必须严格为:
/locale/zh_CN.UTF-8/LC_MESSAGES/messages.mo(注意:不是zh_CN,也不是zh_CN.utf8,得跟setlocale传入值完全一致) -
bindtextdomain('messages', __DIR__ . '/locale')的第二个参数必须是**绝对路径**,相对路径在 CLI 和 Web 下行为不一致,极易翻车 -
messages.mo文件权限需为 644,且 Web 进程有读取权限;常见错误是msgfmt编译失败却没报错,生成的是空文件 - 用
msgunfmt messages.mo可反编译验证内容是否正常
语言切换后翻译没变:只改 $_SESSION['lang'] 不够
gettext 没有“自动监听 session 变更”这回事。每次语言切换,都得重走三步:重设 locale → 重绑路径 → 重设 domain。漏一步,后续所有 _() 都还在旧语言上下文中。
- 典型错误写法:
$_SESSION['lang'] = 'ja_JP.UTF-8';后直接跳转,没调setlocale()等函数 - 正确做法:把初始化逻辑封装成函数,比如
setup_i18n($locale),每次请求入口(如index.php或中间件)都调一次 - 避免在模板里反复调用
setlocale()—— 它有开销,且多次调用可能被系统忽略(尤其 LC_ALL) - 如果用 URL 参数切换(如
?lang=ja),记得校验该 locale 是否在白名单里,防止任意 locale 注入导致setlocale失败或安全风险
复数、上下文等高级功能用不上:因为没调对函数
gettext 支持远不止单字符串翻译。ngettext() 处理复数,pgettext() 区分上下文,但它们依赖 .po 文件里正确的语法定义,且 PHP 层必须用对函数。
- 复数示例:
echo ngettext('File', 'Files', $count);—— 第三个参数是数字,gettext 根据当前 locale 的复数规则决定用哪个字符串 - 上下文示例:
pgettext('button', 'Save')和pgettext('menu', 'Save')在 .po 文件里是两条独立条目,不会冲突 - 别手写
if ($count === 1) { ... } else { ... }—— 这绕过了 gettext 的复数机制,无法被 Poedit 提取,也不支持其他语言的复杂复数规则(如阿拉伯语有6种) - 所有占位符一律用
sprintf(_("%s uploaded %d files"), $user, $count),别拼接字符串,否则无法提取、无法本地化顺序
最易被忽略的一点:gettext 的性能优势来自 .mo 文件的二进制结构和内存映射,但前提是 bind_textdomain_codeset('messages', 'UTF-8') 显式设置编码,否则某些系统会默认用 locale 的原始编码,导致中文乱码或匹配失败。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











