PHP基于gettexts实现多语言i18n利用PoEdit
错误问题
有时候, 添加翻译, 不生效的话. 充值FPM即可.
今天在使用的过程中遇到一个问题:
在原有的XX.po文件中, 更新检索需要翻译的内容时报错.
提示:
poedit错误, 更新条目失败, XXX
细节中,编目中的条目可能不正确.
更新条目失败XXXXX.
经过查看.编目 > 属性中的源路径和源关键字的配置.发现
源路径指定位置错误, 应该指定到该项目的跟目录, (原理就是)将所有
XXX.PHP文件和XXX.phtml文件_()包起来的全部内容.
但是, 此时的路径指定到了 /User/liuhao/www/项目目录/conf/lang/en_US/下
此时,当然是匹配不到任何东西的, 所以报错.
同时也没有排除项目下的public下的静态文件, 如JS和CSS之类.
同时,源标识符_()也没有.所以报错.
完善着三个选项后, 问题解决.
说明
PHP 低版本需要开启gettext扩展
gettext简介:
GNU gettext是翻译项目的重要一步,它提供了一个工作框架,由一些集成的工具和文档组成,帮助程序员、翻译人员和最终用户实现程序的国际化和本地化。用 Gettext的方式实现多语言得到了广泛的支持,著名的BLOG程序wordpress的国际化就是用的GNU gettext。
大致原理:
GNU gettext使用PO或MO文件来实现国际化和本地化。PO的意思是Portable Object,是一种文本结构,可以方便的由人们阅读和修改。MO是Machine Object的简写,MO文件是PO文件的二进制形态。一般来说,一个PO或MO文件对应于一种语言,如果一个程序要支持多种语言,每一种语言都需要自己 的PO或MO文件。
注意:
所谓的gettext()在PHP中即为_()函数.两个函数应该是相当于 implode和join的关系.
必须的PHP部分:
# 域名,可以任意取个有意义的名字,不过要跟相应的XXX.mo文件的文件名相同(不包括扩展名)。
$domain = 'default' ;
# 设置一个服务器环境变量,环境变量仅存活在当前请求期间
putenv('LANG=' . $lang);
# 指定要用的语系,如:en_US、zh_CN、zh_TW,应当与/language/LC_MESSAGES/test.po这里的language相同
# 如果在该步骤,指定后缀名.utf8,就不需要bind_textdomain_codeset指定UTF-8了.
# 如:setlocale(LC_ALL, $lang . '.utf8');
setlocale(LC_ALL, $lang');
# 设置某个域的mo文件路径
bindtextdomain($domain, APPLICATION_PATH."/conf/lang/");
# 设置mo文件的编码为UTF-8
# 如果在setlocale()指定后缀名.utf8就不需要这里了,如:setlocale(LC_ALL, $lang . '.utf8');
bind_textdomain_codeset($domain , 'UTF-8' );
# 设置gettext();函数从哪个域去寻找mo文件
textdomain($domain);
PoEdit使用方式
建设XX.po文件的流程, 和提取需要翻译的文字.
注意:
提取要翻译的内容时, 一定要排除静态文件.
1. 选择文件, 新建文件, 选择语言, 保存文件到指定的目录, 该目录下回生成XXX.po文件.
2. 打开该XXX.po文件
3. 选择XX中提取, (其中有一行注释: 你可以直接从源码中提取可翻译的字符串)
4. 在源路径中下面的"+"添加要提取的目录.如果有要排除的路径如: public下的静态文件,添加目录即可.
5. 源关键字中添加关键字, 如: _(), 一般PHP中就是用_()作为要翻译的内容添这个就好.
6. 然后, PoEdit就会将目录中所有,_()标识过的可读取的内容,检索出来.
7. 选中需要翻译的内容, 在下面翻译的输入框中输入对应的内容即可.
8. 完毕之后保存即可
注意:
将po文件和mo文件放入需要放入的项目目录 时
要注意:
/LC_MESSAGES/这一层目录名,必须这样写.暂未找到自定义之法
XXX.po的文件名必须和之前
bindtextdomain();bind_textdomain_codeset;textdomain();
定义的域的名称一样.
/locale/language/LC_MESSAGES/test.po
/locale/language/LC_MESSAGES/test.mo
如我们放入的是简体中文,则放入:
/locale/zh_CN/LC_MESSAGES/test.po
/locale/zh_CN/LC_MESSAGES/test.mo
如我们放入的是繁体中文,则放入:
/locale/zh_TW/LC_MESSAGES/test.po
/locale/zh_TW/LC_MESSAGES/test.mo
我的案例
注释:
我写的一个简单的案例, 使用面向过程,没有进行封装, 和使用static来避免多次生成.
目的在于, 更加直观, 后期如果忘了, 我觉得看这里是最好回忆的.
# 多语言
# 这里的语言标识, 使用zh-cn 和en-us之类的用"-"线, 是为了,和$_SERVER['HTTP_ACCEPT_LANGUAGE']请求头中的"-"线,
# 后面就就不用来用正则替换$_SERVER['HTTP_ACCEPT_LANGUAGE']中的"-"为"_"了,同时兼容性也更强
# 百度爬虫, 来访问设置为中文
if (isset($_SERVER['HTTP_USER_AGENT']) && strpos($_SERVER['HTTP_USER_AGENT'], 'aiduspider')) {
$_GET['lang'] = 'zh-cn';
}
# 支持的语言列表
$supportLang = array(
'zh-cn' => 'zh_CN', # 中文
'en-us' => 'en_US' # 英文
);
# 通过URL传来的GET自然语言,参数为优先级最高
if(isset($_GET['lang']))
{
$lang = $_GET['lang'];
# cookie中保存的自然语言, 其次
}else if(isset($_COOKIE['lang']))
{
$lang = $_COOKIE['lang'];
# 获取浏览器自然语言, 最后
}else{
$lang = strtolower(substr($_SERVER['HTTP_ACCEPT_LANGUAGE'], 0, 5));
}
# 如果浏览器支持不完善,或被篡改导致未命中服务器支持的语言类型, 默认为英文
$lang = strtolower($lang);
$lang = isset($supportLang[$lang]) ? $supportLang[$lang]: 'en_US';
# 设置cookie, 记录下次访问的默认语言
setcookie('lang', $lang, null,'/');
putenv("LANG={$lang}" );
# 设置本地语言,如zh_CN en_US
setlocale(LC_ALL, $lang );
# 设置域的名称
$domain = 'default' ;
# 绑定域,自动检索的目录
bindtextdomain ( $domain , APPLICATION_PATH."/conf/lang/");
# 绑定默认编码类型
bind_textdomain_codeset($domain , 'UTF-8' );
# 设置gettext()即_()函数从哪个域去找mo文件,用来替换gettext()中的内容
textdomain($domain );
简单案例
<?
$lang = $_GET [ 'lang' ];
if ( $lang == 'zh_CN' ){
putenv('LANG=zh_CN' );
setlocale(LC_ALL, 'zh_CN' ); //指定要用的语系,如:en_US、zh_CN、zh_TW应当与语言目录的名称相同
}elseif ( $lang == 'en_US' ) {
putenv('LANG=en_US' );
setlocale(LC_ALL, 'en_US' ); //指定要用的语系,如:en_US、zh_CN、zh_TW应当与语言目录的名称相同
}
$domain = 'default' ; # 域名,可以任意取个有意义的名字,不过要跟相应的.mo文件的文件名相同(不包括扩展名)。
bindtextdomain ( $domain , "conf/lang/" ); # 设置某个域的mo文件路径
bind_textdomain_codeset($domain , 'UTF-8' ); # 设置mo文件的编码为UTF-8
textdomain($domain ); # 设置gettext()函数在PHP中相当于_()从哪个域去找mo文件
# 输出测试
echo gettext('你好'); # 也可以用_('你好')
?>
典型案例
<?php
/**
* 多语言类
*/
class Lang
{
# 全部语种
public static $alllang = array('en_US' => 1, 'zh_CN' => 1, 'ja_JP' => 1);
/**
* 设置语言
* @return string
*/
static function langcookie()
{
static $lang;
if(!$lang){
# 当前语种
if (isset($_SERVER['HTTP_USER_AGENT']) && strpos($_SERVER['HTTP_USER_AGENT'], 'aiduspider')) {
$_GET['lang'] = 'zh_CN';
}
# GET
isset($_GET['lang'], self::$alllang[$_GET['lang']]) && $lang = $_GET['lang'];
# COOKIE
if (empty($lang) && isset($_COOKIE['lang'], self::$alllang[$_COOKIE['lang']])) {
$lang = $_COOKIE['lang'];
}
# LANGUAGE
if (empty($lang) && isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) {
$l = substr($_SERVER['HTTP_ACCEPT_LANGUAGE'], 0, 4);
if (preg_match("/zh-c/i", $l)) $lang = 'zh_CN';
elseif (preg_match("/en/i", $l)) $lang = 'en_US';
}
empty($lang) && $lang = 'zh_CN';
setcookie('lang', $_COOKIE['lang'] = $lang, time() + 86400 * 3650, '/');
}
return $lang;
}
/**
* 处理语言
*/
static function lang()
{
# 判断用户自然语言类型
$lang = self::langcookie();
# 调用自然语言
if (isset(self::$alllang[$lang])) {
# 设置一个环境变量,环境变量仅存活在当前请求期间
putenv('LANG=' . $lang);
# 指定要用的语系,如:en_US、zh_CN、zh_TW, 语言目录应当与此处相同.
setlocale(LC_ALL, $lang . '.utf8');
# 设置某个域的mo文件路径
bindtextdomain("default", APPLICATION_PATH . "/conf/lang/");
# 设置gettext()即_()函数从哪个域去找mo文件
textdomain("default");
}
}
}
Lang::lang()
XXX.po文件剖析
由此可见
前面一段是头文件,标识创建时间,文件语言内容,版本,MINI类型等信息.
msgid 为源内容
msgstr 为替换的内容
即为, 将原内容, 替换为替换内容.
文件内容:
msgid ""
msgstr ""
"Project-Id-Version: \n"
"POT-Creation-Date: 2017-09-20 00:18+0800\n"
"PO-Revision-Date: 2017-09-20 01:02+0800\n"
"Last-Translator: \n"
"Language-Team: \n"
"Language: en_US\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Generator: Poedit 2.0.3\n"
"X-Poedit-Basepath: ../../..\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Poedit-KeywordsList: _\n"
"X-Poedit-SearchPath-0: .\n"
"X-Poedit-SearchPathExcluded-0: public\n"
#: application/controllers/News.php:62
msgid "参数错误"
msgstr "en参数错误"
#: application/controllers/News.php:66 application/controllers/News.php:68
msgid "您访问的页面不存在或已删除!"
msgstr "en您访问的页面不存在或已删除!"
#: application/views/index/index.phtml:18
msgid "我们的业务"
msgstr "en我们的业务"
#: application/views/index/index.phtml:19
msgid "我们致力于为世界提供最好的区块链产品和服务"
msgstr "en我们致力于为世界提供最好的区块链产品和服务"