如何國際化(i18n)您的Preact項目
Henry Lim
Posted on June 18, 2019
🇺🇸 English Version (英文版): https://dev.to/henrylim96/how-to-add-internationalization-i18n-to-your-preact-application-5gd6
什麼是國際化 (i18n)?
國際化 (Internationalization),也被稱為i18n,意思指i和n之间有18个字母。國際化是指修改軟體使之能適應目標市場的語言、地區差異以及技術需要。
在這篇文章中,我們將會使用preact-i18n來國際化您的Preact項目。
步驟 1:設置Preact CLI, 並創建一個新的項目
注: 如果您已經熟悉Preact了,您可以跳到下一步。
如果您還沒有將Preact CLI安裝到您的電腦,請使用以下的命令來安裝Preact CLI。這CLI需要Node.js 版本 6.x 或以上。
$ npm install -g preact-cli
當您已經成功將Preact CLI安裝到您的電腦中,我們將會使用以下的命令來創建一個名為my-project
的項目。在這個項目中,我們將會使用default
模板。
$ preact create default my-project
之後呢,您可以使用以下的命令來啟動本地測試服務器。
$ cd my-project && npm run start
這個時候,我們需要打開我們的遊覽器,並前往http://localhost:8080
, 你將會看到像這樣類似的畫面:
步驟 2:安裝preact-18n
我們將會使用以下的命令來安裝preact-i18n
到您的項目中。
$ npm install --save preact-i18n
preact-i18n
是非常容易使用的。更重要的是, 這preact-i18n
在gzip之後才佔據不到1.3kb的大小。
步驟 3:創建definition文件
當你已將preact-i18n
安裝到您的項目之後,我們將會創建一個definition文件。我們將會把我們要翻譯的文字和句子,儲存在這個JSON文件中。
我們將會把這個definition文件儲存在src/i18n/zh-tw.json
。
{
"home": {
"title": "主頁",
"text": "這是個Home組件。"
}
}
步驟 4:導入IntlProvider及definition文件
接下來,我們將會從src/components
中打開app.js
。我們將會在這個文件中導入IntlProvider
及definition
文件。
import { IntlProvider } from 'preact-i18n';
import definition from '../i18n/zh-tw.json';
步驟 5:把IntlProvider放在項目中最高層級的組件
然後呢,我們將會在把<IntlProvider>
放在項目中最高層級的組件,也就是我們的app.js
。這樣子,我們就能在這Preact項目中的任何一個組件中讀取到definition
文件。
render() {
return(
<IntlProvider definition={definition}>
<div id="app" />
</IntlProvider>
);
}
在這個時候,您的app.js
文件的內容應該是要跟以下的例子類似:
import { h, Component } from 'preact';
import { Router } from 'preact-router';
import Header from './header';
import Home from '../routes/home';
import Profile from '../routes/profile';
// 導入 IntlProvider 及 definition 文件。
import { IntlProvider } from 'preact-i18n';
import definition from '../i18n/zh-tw.json';
export default class App extends Component {
handleRoute = e => {
this.currentUrl = e.url;
};
render() {
return (
// 把 <IntlProvider> 放在項目中最高層級的組件
<IntlProvider definition={definition}>
<div id="app">
<Header />
<Router onChange={this.handleRoute}>
<Home path="/" />
<Profile path="/profile/" user="me" />
<Profile path="/profile/:user" />
</Router>
</div>
</IntlProvider>
);
}
}
步驟 6:使用Text來顯示翻譯字符串文字
我們只差一步就成功了。在以下的例子中,我們將會翻譯主頁(src/routes/home/index.js
)中所有的文字。現在,我們只需要把網頁中的字改成<Text>
。因此,我們將會把<Text>
添加進<h1>
和<p>
裡。
import { Text } from 'preact-i18n';
const Home = () => (
<div>
<h1>
<Text id="home.title">Home</Text>
</h1>
<p>
<Text id="home.text">This is the Home component.</Text>
</p>
</div>
);
export default Home;
後備文字
為了避免網頁中出現空白,我們應該在<Text>
中輸入後備文字。 如果preact-i18n
無法在您的definition
中找到相關的文字或句子,那preact-i18n
將會使用你剛才在 <Text>…</Text>
輸入的後備文字。
<Text id="unknown.definition">This is a fallback text.</Text>
// 這將會渲染: "This is a fallback text."
Localizer 和 MarkupText
如果您是想要翻譯HTML屬性中的文字 (比如說 placeholder=""
或是title=""
等等),您應該使用<Localizer>
,而並不是使用<Text>
。
相反的,如果您是想要在您的翻譯的文字或句子中使用HTML Markup, 您必須使用<MarkupText>
。<MarkupText>
將會把已翻譯好的文字或句子渲染在一個<span>
tag中。
在以下的例子中,我們將會在我們的definition
文件中添加多幾行的代碼。first_name
及last_name
,將會使用在<Localizer>
中的例子。 而我們會在<MarkupText>
中的例子使用link
。
{
"first_name": "名",
"last_name": "姓",
"link": "這是個<a href='https://www.google.com'>連結</a>"
}
在你更新主頁(src/routes/home/index.js
)中的內容之前,記得將Localizer
和MarkupText
導入到該頁中:
import { Text, Localizer, MarkupText } from 'preact-i18n';
const Home = () => (
<div>
<Localizer>
<input placeholder={<Text id="first_name" />} />
</Localizer>
<Localizer>
<input placeholder={<Text id="last_name" />} />
</Localizer>
<MarkupText id="link">
This is a <a href="https://www.google.com">link</a>
</MarkupText>
</div>
);
export default Home;
模板 (Templating)
如果您想要在您的definition中注入一些自定義的字符串,您可以使用fields
屬性來實現。
首先呢,我們需要先更新我們的definition文件。在我們的definition文件中,我們需要將我們要被自定義的字符串替代的文字,更改成像{{count}}
或者是{{total}}
這樣子的佔位符。
{
"page": "{{count}} / {{total}} 頁"
}
之後呢,我們需要在我們的<Text />
中加入fields
屬性。因此,您的代碼應如下所示:
import { Text } from 'preact-i18n';
const Home = () => (
<div>
<h2>
<Text id="page" fields={{ count: 5, total: 10 }}>
5 / 10 Pages
</Text>
</h2>
</div>
);
export default Home;
复数 (Pluralization)
如果您要翻譯的語言有复数的話(比如說像英文:apple / apples),您可以使用以下其中一個方法,來把已翻譯好的文字和句子放進您的definition文件裡。
"key": { "singular":"apple", "plural":"apples" }
"key": { "none":"no apples", "one":"apple", "many":"apples" }
"key": ["apples", "apple"]
在以下的例子中,我們將會把模板和复数的例子結合在一起。但在那之前,我們需要更新我們的definition文件:
{
"apple": {
"singular": "Henry has {{count}} apple.",
"plural":"Henry has {{count}} apples."
}
}
接著,我們將會把以下的代碼粘貼到src/routes/home/index.js
中:
import { Text } from 'preact-i18n';
const Home = () => (
<div>
<p>
<Text id="apple" plural={1} fields={{ count: 1 }} />
</p>
<p>
<Text id="apple" plural={100} fields={{ count: 100 }} />
</p>
</div>
);
export default Home;
根據以上的步驟,你就能在您的Preact項目中使用模板和复数。
動態導入definition文件
在現實情況中,您將會根據用戶的選擇來設定網頁的語言。
您可以使用遊覽器的語言(通過navigator.language
), 或者是讓用戶自己手動更換語言。
然而,為了避免我們將不必要的definition文件導入進去,我們可以使用import()
來實現動態導入definition文件。這樣一來,我們只會導入用戶所選擇的語言所需要的definition文件。
import { Component } from 'preact';
import { IntlProvider } from 'preact-i18n';
import defaultDefinition from '../i18n/zh-tw.json';
export default class App extends Component {
state = {
definition: defaultDefinition
}
changeLanguage = (lang) => {
// 我們可以使用這個函數來更換語言
import(`../i18n/${lang}.json`)
.then(definition => this.setState({ definition }));
};
render({ }, { definition }) {
return (
<IntlProvider definition={definition}>
<div id="app" />
</IntlProvider>
);
}
}
根據以上的例子,我們可以使用這函數:this.changeLanguage("zh-TW")
來導入definition文件並更改網頁的語言。
誰在使用preact-i18n?
我自己的業餘項目: Remote for Slides,正在使用著preact-i18n
。
Remote for Slides是一個漸進式網絡應用程序(PWA) + Chrome 擴充器。這能讓用戶在任何設備上,遠程遙控Google簡報。是時候跟昂貴的翻頁筆說再見了。
Remote for Slides 漸進式網絡應用程序支持多達8個語言,包括了英文、繁體中文、簡體中文、加泰羅尼亞文、西班牙文、 法文、波蘭文、以及Euskera。
在這個項目中,我也使用了我在剛才提到的 "動態導入definition文件" 的方法。這可以避免應用程序導入一些沒使用到的definition文件。這將會提升應用程序性能。
除此之外,Remote for Slides 漸進式網絡應用程序也將會自動地設置語言。這應用程序將會根據遊覽器的語言(navigator.language
)、或者是根據URL中的parameter (ie: s.limhenry.xyz/?hl=zh-tw)來更改語言。 當然,用戶也可以從設置中更改語言。
您可以在這裡知道關於Remote for Slides更多的訊息:
Meet Remote for Slides, a new way to control your presentation slides
Henry Lim ・ Apr 2 '19 ・ 2 min read
更多資源
Posted on June 18, 2019
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.