前言
我在前面谈 Google Adsense 的文章(如 一组数据让大家直观感受一下出海的重要性)中多次提到过,要想让网站中的 Google Adsense 有更高的收益,就一定要考虑出海,而出海就离不开多语言这个话题。理论上网站应该支持尽可能多的语言,但每增加一种语言,网站的复杂度和维护难度都会成倍增加,因此,对我们而言,性价比最高、最常用的无疑还是 中英双语网站。
比较遗憾的是,我查过一些资料,也读过 Typecho 的源码,并没有找到一种简单直接的多语言方案(直接部署并维护多个站点除外)。虽然 Typecho 本身就提供了多语言翻译功能,但需要管理员在后台切换,并且切换后会全局生效。也就是说, Typecho 并不能满足 内地显示中文,海外显示英文 这种看似简单的需求,其本质还是仅支持单一语言。换句话说,要想实现常规意义上的多语言网站,就必须修改 Typecho 源码,至少我暂时还没有找到仅扩展 插件 或 主题 就能同时支持多语言的方案。
1. 思路与方案
如果考虑修改源码的话,那可选方案就多了。但结合 Typecho 自身的特点,我这里主要参考了 微信公众号 的方案,如下图所示:
即通过在网址中增加 lang
参数来区分语言,该方案很多其它网站也在使用,又如阿里巴巴的 iconfont。
当然,基于个人特定的需求,我的实现也略有不同。以下是我希望达到的效果:
- 自动切换语言:用户访问网站时,如果浏览器首选语言为中文(无论简体还是繁体),则显示简体中文,如果浏览器首选语言不是中文,则显示英文;
- 手动切换语言:用户访问网站时,可以通过下拉框、单选框等形式,手动切换希望显示的语言;
- 文章内容支持切换语言:网站中,除了公用词条外,文章的 标题、内容、分类、标签 等,也应该支持语言切换。
前两条虽然 Typecho 默认不支持,但基于 Typecho 自身提供的多语言翻译功能,略微修改源码依然可以实现,这个我会在下文详细介绍。而第三条, Typecho 无论如何都是不支持的,这就必须得另想它法了,这个比较麻烦,而且不同的人、不同的应用场景实现方式也可能不同,我会在下一篇文章中单独给出我的方案。
整个中英双语方案我都是在 一起学笛子 这个网站中落地的,如下图所示:
感兴趣的也可以关注跟踪一下实时效果。
2. 多语言翻译功能
无论是 自动切换语言 还是 手动切换语言,我们首先要解决的就是词条翻译问题,这个上面已经说过了, Typecho 默认就是支持的,我们现在唯一要做的就是了解它的用法。具体可以参考官方文档:https://docs.typecho.org/translate/start,其中还包含一个语言包开源项目:https://github.com/typecho/languages。实际上,也可以直接无视它们,只需下载一个 Poedit 就可以了。
2.1 创建语言包
下载并安装好 Poedit 后,就可以新建语言包文件了,如下图所示:
注意,这里是通过 文件->新建(N)... 创建语言包文件的,而不是利用 POT
模板(需要用到https://github.com/typecho/languages
中的 messages.pot
)创建。然后选择一种语言,我这里用的是美国英语(en_US
)。
点击 确定,保存 到任意位置都可以,我为了方便管理,保存到了项目的 langs
目录(如果不存在,则需要手动创建)下,如下图所示:
事实上,en_US.po
本身只是一个普通的 文本文件 而已,用文本编辑器就可以打开并编辑词条,文本结构如下图所示:
也就是说,我们只需要不断往这个文件中增加 msgid
和 msgstr
键值对就可以了,直至把所有希望翻译的词条都添加完为止。当然,这里的词条指的是代码中用 _t()
(翻译)和 _e()
(翻译并输出)方法包起来的文本,其它文本即使翻译了也无法识别。
由于 Poedit 只能翻译已存在的词条,而不能新增词条(我没有找到),所以我的做法是通过文本编辑器(如VS Code)新增词条,然后通过 Poedit 翻译。当然,如果不希望在多个软件之间来回切换,不用 Poedit 也是可以的。只是使用 Poedit,界面会更加直观,也不用担心误操作,下图是翻译好的界面:
2.2 编译语言包
经过上一节,其实多语言翻译问题就已经完成了,但可惜的是, Typecho 并不直接识别 po
文件,而是识别其编译后的 mo
文件。这时就需要用到 Poedit 的编译功能,操作很简单,直接 编译为MO... 就可以了,如下图所示:
然后把 en_US.mo
文件拷贝到 langs
目录下,就可以通过管理后台切换语言来测试效果了。
由于我们最终需要的是 en_US.mo
而不是 en_US.po
,所以不难发现,如果没有这里的编译过程,Poedit 都是可以不需要的。
3. 语言包初始化
虽然我们上面通过管理后台切换语言测试了翻译效果,但这显然不是我们想要的,它更适合单语言网站,如 中文站 或 英文站。
为了实现不同用户端显示不同语言(手动或自动)这一功能,我们得先看一下源码,看看后台统一设置时是怎么做的:
/** 语言包初始化 */
if ($options->lang && $options->lang != 'zh_CN') {
$dir = defined('__TYPECHO_LANG_DIR__') ? __TYPECHO_LANG_DIR__ : __TYPECHO_ROOT_DIR__ . '/usr/langs';
I18n::setLang($dir . '/' . $options->lang . '.mo');
}
这段代码在 var\Widget\Init.php
文件中,大约位于 84
行左右的位置。大意是在每次页面初始化的时候,判断后台设置的语言是不是 简体中文,如果不是,则从 langs
目录下面读取对应的 .mo
文件来初始化语言包。
了解到这一点,接下来就简单了,只需要将判断条件换一下就可以了,具体实现如下:
/** 语言包初始化 */
// if ($options->lang && $options->lang != 'zh_CN') {
// $dir = defined('__TYPECHO_LANG_DIR__') ? __TYPECHO_LANG_DIR__ : __TYPECHO_ROOT_DIR__ . '/usr/langs';
// I18n::setLang($dir . '/' . $options->lang . '.mo');
// }
$lang = $this->getLang();
if ($lang == 'en_US') {
$dir = defined('__TYPECHO_LANG_DIR__') ? __TYPECHO_LANG_DIR__ : __TYPECHO_ROOT_DIR__ . '/usr/langs';
I18n::setLang($dir . '/en_US.mo');
}
这里的 getLang()
方法就是用来获取当前用户语言的,是我们自己实现的。考虑到后续在很多地方都需要用到,因此,我把这个方法实现在了组件基类 Widget
中,具体代码如下:
function getLang()
{
if ($this->request->is('lang')) {
// 通过QUERY参数指定语言
if (preg_match('/^zh/i', $this->request->get('lang'))) {
$lang = 'zh_CN';
} else {
$lang = 'en_US';
}
return $lang;
}
// 没有手动指定语言,使用浏览器的首选语言
$acceptLang = $_SERVER['HTTP_ACCEPT_LANGUAGE'];
if (!empty($acceptLang) && preg_match('/^zh/i', $acceptLang)) {
$lang = 'zh_CN';
} else {
$lang = 'en_US';
}
return $lang;
}
这段代码的大意是,如果客户端的页面请求中带有 lang
参数,则代表用户手动指定了语言,如果用户指定的是中文(无论是简体还是繁体),则返回 简体中文 页面给用户,否则返回 英文 页面。如果客户端的页面请求中没有带有 lang
参数,则代表用户没有手动指定语言,此时使用浏览器的首选语言,判断逻辑和手动指定时是一样的。
完成这一步,就可以手动修改浏览器首选语言,或手动在网址后面加上 lang
参数来测试语言切换效果了。
4. 手动切换语言
每次访问网站都手动添加 lang
参数显然是不合适的,因此我们需要在页面上增加手动切换语言的下拉框,实现方式有很多,以下是简单的示例代码:
<ul class="dropdown-menu">
<li><a class="dropdown-item" href="?lang=en_US">English</a></li>
<li><a class="dropdown-item" href="?lang=zh_CN">简体中文</a></li>
</ul>
这其实就是两个跳转到当前页面的超链接,只是在超链接的后面加上了一个 lang
参数。但这仅限于上面的两个超链接,网站中的其它超链接依然没有 lang
参数,因此我们还需要在每次切换语言时,将页面中的所有超链接都加上相应的 lang
参数,这就需要在组件基类 Widget
中再增加一个 urlWithLang($url)
方法,具体代码如下:
function urlWithLang($url)
{
echo $url . (strpos($url, '?') === false ? '?' : '&') . 'lang=' . $this->getLang();
}
使用时,只需要将页面中所有生成 url
的地方都调用一下 urlWithLang
方法即可,如将 <?php $categories->permalink(); ?>
替换为 <?php $this->urlWithLang($categories->permalink); ?>
,将 <?php $pages->permalink(); ?>
替换为 <?php $this->urlWithLang($pages->permalink); ?>
等。
这样就可以了吗?NO,还不够,还有两个地方比较特殊,好在都只需修改 var\Widget\Archive.php
中的少量代码即可,一个是由 <?php $this->pageNav('«', '»'); ?>
生成的分页组件,该功能是 Typecho 内部实现的,修改源码如下图所示:
另一个是搜索页面,改法一样,即在 Router::url('search', ['keywords' => urlencode($filterKeywords)], $this->options->index)
的后面加上 . '?lang=' . $this->getLang()
即可。
页面元数据如description
、keywords
等也含有中文,同样在var\Widget\Archive.php
文件中,可视情况修改一下。
好了,现在是真的可以了,当然,和 微信公众号 相比,这里没有实现记住上一次所选语言的功能,倒不是没考虑到或忘记了,而是我不太想过多依赖 Cookie
,而且,该功能对我而言价值似乎也不大,所以就无视了,有需要的可以自行补上。
结语
看到这里,不难发现,Typecho 其实并不是很适合做多语言网站,单语言博客网站才是它的强项,也就是说,不修改源码很难实现这个哪怕看似比较简单的功能,但如果是二次开发的话,基于 Typecho 还是可以节省很多时间的。
至此,我的中英双语网站也算实现一半了,虽然一眼望去,还是满屏的中文,但这已经是目前所能达到的极限了。另一半主要是针对文章内容翻译的,虽然考虑过通过一些翻译库或 API 简单实现,但目测了一下,感觉不太靠谱,要想达到比较满意的效果,可能还是要面临更多源码的修改,这个等我完成以后再分享吧!
评论2
老孙
我之前看过一个开源的项目可以直接翻译成英文貌似只用一个js ,我忘记那个项目叫什么了
老朱
翻译工具试过一两个,常规的文本还挺好,但专业点的词汇就翻译不了了,比如《浮光》怎么都不会被翻译成《The History》,《香蜜沉沉烬如霜》也不会被翻译成《Ashes of Love》,比较尴尬。🤣