intl扩展真正加载需验证php -m | grep intl或extension_loaded('intl')返回true,常见失败原因包括PHP-FPM与CLI配置不一致、php.ini中extension=intl被注释、extension_dir路径错误或intl.so文件实际不存在。

确认 intl 扩展已真正加载
很多问题其实卡在扩展没生效,而不是用法错。运行 php -m | grep intl 或 var_dump(extension_loaded('intl')),输出 bool(true) 才算到位。常见坑是:yum 安装了 php-intl,但 PHP-FPM 用的是另一套配置(比如 Webtatic 的 php72w-intl),或者 php.ini 里 extension=intl.so 被注释、extension_dir 路径不对、so 文件实际不存在。CentOS 下典型路径是 /usr/lib64/php/modules/intl.so,用 find / -name intl.so 2>/dev/null 精确定位更可靠。
MessageFormatter 做带参数的翻译,别用下划线命名占位符
MessageFormatter 是最常用也最容易翻车的组件。它不接受含下划线的占位符名,比如 "{user_name} logged in" 会直接报 U_ILLEGAL_ARGUMENT_ERROR。必须改用驼峰或美元符号:"{userName} logged in" 或 "{userName} logged in"。模板字符串里所有占位符都得是字母、数字、$ 开头,且首字符必须是字母。
- 正确写法:
$fmt = new MessageFormatter('zh_CN', '您有 {count, number} 条新消息'); - 传参必须是索引数组或关联数组,但 key 名要和占位符完全一致:
$fmt->format(['count' => 5]) - 复数处理需 ICU 支持,中文无单复数变化,但英文模板得写成
"{count, plural, one{# message} other{# messages}}",否则format([1])和format([2])输出一样
NumberFormatter 和 IntlDateFormatter 不看 setlocale(),只认构造时传的 locale
setlocale(LC_ALL, 'zh_CN.UTF-8') 对 NumberFormatter 和 IntlDateFormatter 完全无效——它们的行为只取决于实例化时传入的 locale 字符串。这是最常被混淆的点。
- 想让数字用英文标点(如
123,456.78),就用new NumberFormatter('en_US', NumberFormatter::DECIMAL),哪怕服务器 locale 是中文 - 想格式化日期为「2024年03月15日」,必须用
new IntlDateFormatter('zh_CN', ..., ...),且模式得写成'y年M月d日'(注意是小写 y、大写 M/d,不是 Y/m/D) - 解析用户输入的日期字符串时,
IntlDateFormatter::parse()极其严格:输入'2024-03-15'就不能用'yyyy-MM-dd HH:mm:ss'模式,否则返回false;带中文字符如「2024年03月15日」必须显式用'y年M月d日'模式,且不能多空格、少标点
Locale 类和 ResourceBundle 适合语言包管理,但别指望自动 fallback
Locale::acceptFromHttp($_SERVER['HTTP_ACCEPT_LANGUAGE']) 只返回浏览器声明的首选语言(如 zh-CN),不会自动降级到 zh 或 en。如果你只有 zh_CN 和 en_US 两套翻译,而用户发来 zh-TW,就得自己做 fallback 判断。
立即学习“PHP免费学习笔记(深入)”;
-
ResourceBundle加载 .dat 文件(由 .ini 编译而来)比数组更高效,但构建流程麻烦:需用genrb工具编译,且 PHP 默认不开启resourcebundle类(虽属 intl 扩展,但部分安装不含) - 更现实的做法是用简单数组 + 显式 fallback:
$lang = in_array($req, ['zh_CN', 'zh_TW']) ? 'zh_CN' : 'en_US'; -
Locale::getPrimaryLanguage('zh_HK')返回zh,可用来做粗粒度归类,但无法替代业务层的语言选择逻辑
intl 的核心是“每个 formatter 实例绑定一个 locale”,不是全局切换。所有格式化行为都从构造那一刻锁定,后续改系统 locale 或 PHP locale 都不影响。这点和 gettext 完全不同,混用必出问题。



















