用Typescript来实现中英文博客
各位2022年新年快乐! 今天我想谈谈与我大多数博客文章不同的东西:我是如何用Typescript来实现我的双语博客的。
自从我在2015年创建这个博客以来,我一直想把它变成中英文的来吸引更多的国内受众,而我终于在2019年底终于实现了这一点。 我的博客的国际化实现可能与大多数人不同,因为我没有使用任何例如i18next的第三方库, 而主要依靠Typescript强大的类型系统。
我的方法可能不是最“正确”与可扩展的方式, 但是我认为对于个人博客网站来说,我的方法是一个非常合适的方案。 它提供了几个重要的优势:
- Typescript的类型系统保证了我不会忘记翻译任何一个条目
- 我可以简单地对不同语言的界面采用不同的排版
- 我不需要单单为了我的博客网站而学习使用一个国际化库
因此,如果你想创建一个多语种的个人网站,我建议你使用类似的方法。
我的博客使用了GatsbyJS静态网页生成器。 静态网页生成器可以在模板的帮助下将Markdown文件转换为Html网页1。
对于博客文章来说,我只需要为每篇文章每种语言都准备单独的markdown文件就可以了。 比如说,你现在所看到中文文字以及这篇文章的英文版就会被储存在不同markdown文件中。 另一方面,在模板UI中仍然有很多文字需要翻译, 例如我在右边栏的自我介绍、不同的菜单选项、以及博客文章的标签。
GatsbyJS使用Javascript作为模板的语言, 所有GatsbyJS的模板都是React组件。 我选择了可以转译为Javascript的Typescript作为我这个博客主要的开发语言。 因此,我很自然地为国际化问题开发了一个Typescript的解决方案,而Gatsby会把所有的React组件以及翻译逻辑都会自动变为静态的HTML。 反过来说,假设你使用一个使用Python的静态网站生成器,那么在理想情况下你应该在Python中实现国际化,这样在载入你的网站时无需再动态加载翻译。
我把大部分国际化逻辑的实现都放在了translation.tsx文件中:
首先,我使用一个 en
对象来存储所有英文翻译条目。
const en = {
ai: "AI",
algorithms: "Algorithms",
archive: "Archive",
...
};
由于en
只是一个普通的对象,我在其中可以储存任意的数据作为条目,例如jsx对象甚至是函数:
all_n_posts: (n: number) => (
<>
All <Link to={`/en/archive`}>{n} posts</Link>
</>
),
有了en
这个对象的定义,我们可以通过typeof
操作符来得到它的类型:
export type Translations = typeof en;
大多数编程语言都不具备类似于typeof
的这种反射能力。有了typeof
,我们就不要自己显式定义 en
对象的类型了。
现在有了Translations
类型,我们可以以此类型来建立一个zh
对象来存储中文:
const zh: Translations = {
ai: "AI",
algorithms: "算法",
archive: "博文目录",
...
};
这样类型系统会确保我不会忘记翻译任何条目。
然后,我们可以把所有语言的翻译都放到一个对象中。 在Javascript组件中,我们会使用这个对象来查询特定的翻译条目:
export const translations = {
en: en,
zh: zh,
};
然后我们使用keyof
操作符来获得包含各种语言的联合类型(union type)。
在我的情况下我会得到"en" | "zh"
。
keyof
可以说是另一个Typescript中非常有意思的反射特性。
由于 keyof
期望的是一个类型而不是一个对象,我们需要在使用 keyof
之前加上另一个 typeof
操作符:
export type Language = keyof typeof translations;
每当我需要显式提供当前语言变量的类型,比如说在将当前语言作为参数传递时,我就会使用的Language
上述类型。
最后,我们使用Object.keys
来获得一个语言列表,通过此我们可以循环遍历所有的语言:
export const languages = Object.keys(translations) as Language[];
我的这个博客只有两种语言,我也不懂除了中英文外的其他语言。
但在我的代码中,除了将英语作为“默认”语言外,并没有对特定语言进行硬编码。
因此如果我需要的话,我可以很简单地为这个网站加上第三个语言:
我只需要定义另外一个类型是Translations
的对象并且把它加入到translations
中。
我们需要将页面的当前语言传递给它的组件来使用翻译。
之后我们可以在我需要翻译的地方使用translations[lang]["entry"]
(用具体需要的条目名来替换entry
)。
同样的方法也适用于函数。我只需要用类似于translations[lang]["all_n_posts"](n)
的方式来调用函数就可以了。
这样我就实现了整个国际化的逻辑!
要添加新的翻译,我只需要向en
和zh
对象中添加相应的翻译条目。
当然,维护一个双语博客最具挑战性的部分始终是翻译实际的博客文章。
而我并不能说我在把博文译回中文这方面做了非常好的工作。
但我希望至少我在技术方面的做法可以提供一些启发。
- GatsbyJS比较特殊,它并不完全按照我所说的静态网页生成器的方法工作。但你可以访问他们的网站了解更多。↩