如何基于 Typecho 实现中英双语网站(上)

2025-02-09

1456

1

前言

我在前面谈 Google Adsense 的文章(如 一组数据让大家直观感受一下出海的重要性)中多次提到过,要想让网站中的 Google Adsense 有更高的收益,就一定要考虑出海,而出海就离不开多语言这个话题。理论上网站应该支持尽可能多的语言,但每增加一种语言,网站的复杂度和维护难度都会成倍增加,因此,对我们而言,性价比最高、最常用的无疑还是 中英双语网站

比较遗憾的是,我查过一些资料,也读过 Typecho 的源码,并没有找到一种简单直接的多语言方案(直接部署并维护多个站点除外)。虽然 Typecho 本身就提供了多语言翻译功能,但需要管理员在后台切换,并且切换后会全局生效。也就是说, Typecho 并不能满足 内地显示中文,海外显示英文 这种看似简单的需求,其本质还是仅支持单一语言。换句话说,要想实现常规意义上的多语言网站,就必须修改 Typecho 源码,至少我暂时还没有找到仅扩展 插件主题 就能同时支持多语言的方案。

1. 思路与方案

如果考虑修改源码的话,那可选方案就多了。但结合 Typecho 自身的特点,我这里主要参考了 微信公众号 的方案,如下图所示:

即通过在网址中增加 lang 参数来区分语言,该方案很多其它网站也在使用,又如阿里巴巴的 iconfont

当然,基于个人特定的需求,我的实现也略有不同。以下是我希望达到的效果:

  1. 自动切换语言:用户访问网站时,如果浏览器首选语言为中文(无论简体还是繁体),则显示简体中文,如果浏览器首选语言不是中文,则显示英文;
  2. 手动切换语言:用户访问网站时,可以通过下拉框、单选框等形式,手动切换希望显示的语言;
  3. 文章内容支持切换语言:网站中,除了公用词条外,文章的 标题内容分类标签 等,也应该支持语言切换。

前两条虽然 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 本身只是一个普通的 文本文件 而已,用文本编辑器就可以打开并编辑词条,文本结构如下图所示:

也就是说,我们只需要不断往这个文件中增加 msgidmsgstr 键值对就可以了,直至把所有希望翻译的词条都添加完为止。当然,这里的词条指的是代码中用 _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('&laquo;', '&raquo;'); ?> 生成的分页组件,该功能是 Typecho 内部实现的,修改源码如下图所示:

另一个是搜索页面,改法一样,即在 Router::url('search', ['keywords' => urlencode($filterKeywords)], $this->options->index) 的后面加上 . '?lang=' . $this->getLang() 即可。

页面元数据如 descriptionkeywords等也含有中文,同样在 var\Widget\Archive.php 文件中,可视情况修改一下。

好了,现在是真的可以了,当然,和 微信公众号 相比,这里没有实现记住上一次所选语言的功能,倒不是没考虑到或忘记了,而是我不太想过多依赖 Cookie,而且,该功能对我而言价值似乎也不大,所以就无视了,有需要的可以自行补上。

结语

看到这里,不难发现,Typecho 其实并不是很适合做多语言网站,单语言博客网站才是它的强项,也就是说,不修改源码很难实现这个哪怕看似比较简单的功能,但如果是二次开发的话,基于 Typecho 还是可以节省很多时间的。

至此,我的中英双语网站也算实现一半了,虽然一眼望去,还是满屏的中文,但这已经是目前所能达到的极限了。另一半主要是针对文章内容翻译的,虽然考虑过通过一些翻译库或 API 简单实现,但目测了一下,感觉不太靠谱,要想达到比较满意的效果,可能还是要面临更多源码的修改,这个等我完成以后再分享吧!

相关推荐

  1. 上线了第一个正儿八经的多语言工具站 2025-03-25
  2. 终于收到 Google Adsense 漂洋过海邮寄过来的 PIN 码了 2025-03-13
  3. 为博客网站增加一个简单的算术验证码,防止机器人垃圾评论轰炸 2025-03-12
  4. 如何基于 Typecho 实现中英双语网站(下) 2025-02-20
  5. 如何让网站在微信端打开时不显示谷歌广告? 2025-01-18

本文作者: 老朱

原文链接: 如何基于 Typecho 实现中英双语网站(上)

版权声明: 本站所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!

免责声明: 文中如涉及第三方资源,均来自互联网,仅供学习研究,禁止商业使用,如有侵权,联系我们24小时内删除!

Typecho Google AdSense 出海 双语网站

评论3

😊 😃 😄 😁 😆 😅 😂 🤣 🙂 🙃 😉 😇 😏 😌 😍 😘 😗 😙 😚 😋 😛 😜 😝 😒 😔 😖 😞 😟 😠 😡 😳 😨 😰 😥 😢 😭 😱 😲 😵 😷 🤒 🤕 🤢 😴 🤤 😪 😫 😬 😮 🤲 🤜 🤛 🤚 🤝 🙏 🤞 🤟 🤘 🤙 👌 👍 👎 👊 👏 🙌 👐 💪
  1. Yakito

    😌😌😌

    2025-05-14 00:03
  2. 老孙

    我之前看过一个开源的项目可以直接翻译成英文貌似只用一个js ,我忘记那个项目叫什么了

    2025-02-11 19:14
    1. 老朱

      翻译工具试过一两个,常规的文本还挺好,但专业点的词汇就翻译不了了,比如《浮光》怎么都不会被翻译成《The History》,《香蜜沉沉烬如霜》也不会被翻译成《Ashes of Love》,比较尴尬。🤣

      2025-02-11 21:19