其实 Typecho 本身是具有多语言功能的,而且也支持加载多个翻译文件。那么,能不能在主题或者插件中用上这个系统功能呢?我翻了翻代码发现这样做还挺容易的……
怎么启用系统的多语言功能?
一般情况下在 Typecho 的管理后台是找不到任何更改语言的选项的,那是因为到目前为止官方的 Typecho 安装包都是不附带任何翻译文件的,需要你自己去手动补上,官方的 repo 在这里:typecho/languages 。
那么根据官方文档,要想启用多语言功能,你需要从官方 repo 下载你需要语言的 .po
文件(如果没有,那么你需要下载 messages.pot
文件自己翻译),然后你要自己将 .po
文件转换为 .mo
文件然后上传到 Typecho 的 /usr/langs
目录下(你还需要自己新建文件夹),于是你就可以在 Typecho 管理后台的基本设置中看到高贵的「语言」选项了。

这个功能怎么调用?
随便翻一翻 Typecho 的源代码,你就可以发现基本上所有需要输出的文字都会用上 _e
(翻译并输出)或者 _t
(翻译后返回)函数,这些函数会进一步调用类 Typecho_I18n
相关的操作实现翻译。那么在主题与插件中也可以通过这个函数来用上 Typecho 系统的多语言功能了。

翻看类 Typecho_I18n
的代码还可以找到「设置语言项」和「增加语言项」以及初始化的部分:
/**
* 初始化语言文件
*
* @access private
*/
private static function init()
{
/** GetText支持 */
if (false === self::$_loaded && self::$_lang && file_exists(self::$_lang)) {
self::$_loaded = new Typecho_I18n_GetTextMulti(self::$_lang);
}
}
/**
* 设置语言项
*
* @access public
* @param string $lang 配置信息
* @return void
*/
public static function setLang($lang)
{
self::$_lang = $lang;
}
/**
* 增加语言项
*
* @access public
* @param string $lang 语言名称
* @return void
*/
public static function addLang($lang)
{
self::$_loaded->addFile($lang);
}
代码部分在这里就不继续深究了。这里的类 Typecho_I18n_GetTextMulti
直接支持同时加载多个 .mo
文件,在需要的时候会逐个文件查找翻译,而 Typecho_I18n::addLang
可以直接传入 .mo
文件的路径以加载更多的翻译文件,那么主题或插件就可以通过这个操作来载入自己的翻译文件实现多语言功能。我在 MDr 主题中的实现是这么写的:
if ($options->lang && $options->lang != 'zh_CN')
if (file_exists(__MDR_LANG__ . '/' . $options->lang . '.mo')) {
Typecho_I18n::translate('');
Typecho_I18n::addLang(__MDR_LANG__ . '/' . $options->lang . '.mo');
}
因为在加载主题 function.php
的时候 Typecho_I18n
还没有初始化,而 Typecho_I18n::init
又是个私有函数,在这里我只能通过 Typecho_I18n::translate
来使它初始化,然后加载主题的翻译文件。
发现了一个小问题?
那么在进一步测试的时候 Typecho 的多语言功能并没有按预期给出对应的翻译,大概是 Typecho(1.1-17.10.30-release)的一个小 bug 吧,大致描述一下。根据上述的实现方法我尝试载入了额外的 mo 文件,通过断点测试确认了这样一番操作已经把额外的 mo 文件添加到 Typecho_I18n_GetTextMulti
里了:

但是后续测试都未能达到预期(把增加的 mo 文件中字符串替换),后续 debug 后我在 Typecho_I18n_GetTextMulti
和 Typecho_I18n_GetText
之间发现了个问题导致 Typecho 并没有在额外的 mo 文件中查找对应的翻译:Typecho_I18n_GetTextMulti L59~L69 中用了 $count
来判断是否找到了对应的翻译,但它没有初始值;
public function translate($string)
{
foreach ($this->_handles as $handle) {
$string = $handle->translate($string, $count);
if (-1 != $count) {
break;
}
}
return $string;
}
在默认安装最新稳定版不对 Typecho 做改动的情况下 Typecho_I18n_GetText
的 $enable_cache
会设定为 true
,所以在 Typecho_I18n_GetText::translate
里跳过了对 $num
的操作,查找完第一个 mo 文件后返回 Typecho_I18n_GetTextMulti
满足 -1 != $count
就跳出了循环,不会去比较接下来的 mo 库了。Typecho_I18n_GetText L265~L285:
public function translate($string, &$num)
{
if ($this->short_circuit)
return $string;
$this->load_tables();
if ($this->enable_cache) { //__construct 中默认将 enable_cache 赋值为 true
// Caching enabled, get translated string from cache
if (array_key_exists($string, $this->cache_translations))
return $this->cache_translations[$string];
else
return $string; //在 cache 中找不到就之间 return 原字符串,没对 $num 的操作
} else {
// Caching not enabled, try to find string
$num = $this->find_string($string);
if ($num == -1)
return $string;
else
return $this->get_translation_string($num);
}
}
并且通过测试 $enable_cache = false
时可以达到我的预期,或者换个思路给 $num
赋个初始值也可以达到预期。我在 GitHub 上发了一个 issue( typecho#1118 )和一个 pr( typecho#1119 ),目前已经被合并解决了,那么现在在最新的 Typecho 开发版中是不存在问题了。
小尾巴。
那么实现方式有了,大概不久的将来,支持多语言的 MDr-petals 就会发布她的第一个正式版了。至于如何操作 .pot
.po
.mo
文件,在这里推荐下 Poedit 这个软件,网络上都有详尽教程。