如何國際化(i18n)您的Preact項目
Tags: preact, web, javascript, internationalization
Published: 2019-06-18 14:45:16

---
title: 如何國際化(i18n)您的Preact項目
published: true
description: 您將會在這篇文章中學會如何使用preact-i18n來国际化 (i18n) 您的Preact項目。
tags: preact, web, javascript, internationalization
cover_image: https://thepracticaldev.s3.amazonaws.com/i/p3mrvhjhx8kvb681d8qx.jpg
canonical_url: https://medium.com/@limhenry/how-to-add-i18n-to-preact-chinese-b49cfaf3a07b
---

🇺🇸 English Version (英文版): https://dev.to/henrylim96/how-to-add-internationalization-i18n-to-your-preact-application-5gd6

---

## 什麼是國際化 (i18n)？

> 國際化 (Internationalization)，也被稱為i18n，意思指i和n之间有18个字母。國際化是指修改軟體使之能適應目標市場的語言、地區差異以及技術需要。

在這篇文章中，我們將會使用[preact-i18n](https://github.com/synacor/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`, 你將會看到像這樣類似的畫面：

![](https://thepracticaldev.s3.amazonaws.com/i/kk6nzdsfzs4nzrlpahvx.jpg)

## 步驟 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`。

```json
{ 
  "home": {
    "title": "主頁", 
    "text": "這是個Home組件。"
  }
}
```

## 步驟 4：導入IntlProvider及definition文件

接下來，我們將會從`src/components`中打開`app.js`。我們將會在這個文件中導入`IntlProvider`及`definition`文件。

```jsx
import { IntlProvider } from 'preact-i18n';
import definition from '../i18n/zh-tw.json';
```

## 步驟 5：把IntlProvider放在項目中最高層級的組件

然後呢，我們將會在把`<IntlProvider>`放在項目中最高層級的組件，也就是我們的`app.js`。這樣子，我們就能在這Preact項目中的任何一個組件中讀取到`definition`文件。

```jsx
render() {
  return(
    <IntlProvider definition={definition}>
      <div id="app" />
    </IntlProvider>
  );
}
```

在這個時候，您的`app.js`文件的內容應該是要跟以下的例子類似:

```jsx
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>`裡。

```jsx
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;
```

![](https://thepracticaldev.s3.amazonaws.com/i/vsgnzsjx6bdg25inkuvj.jpg)

## 後備文字

為了避免網頁中出現空白，我們應該在`<Text>`中輸入後備文字。 如果`preact-i18n`無法在您的`definition`中找到相關的文字或句子，那`preact-i18n`將會使用你剛才在 `<Text>…</Text>`輸入的後備文字。

```html
<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`。

```json
{ 
  "first_name": "名",
  "last_name": "姓",
  "link": "這是個<a href='https://www.google.com'>連結</a>"
}
```

在你更新主頁(`src/routes/home/index.js`)中的內容之前，記得將`Localizer`和`MarkupText`導入到該頁中：

```jsx
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;
```

![](https://thepracticaldev.s3.amazonaws.com/i/opgth3dzvj3mqknojxha.jpg)

## 模板 (Templating)

如果您想要在您的definition中注入一些自定義的字符串，您可以使用`fields`屬性來實現。

首先呢，我們需要先更新我們的definition文件。在我們的definition文件中，我們需要將我們要被自定義的字符串替代的文字，更改成像`{{count}}`或者是`{{total}}`這樣子的佔位符。

```json
{
  "page": "{{count}} / {{total}} 頁"
}
```

之後呢，我們需要在我們的`<Text />`中加入`fields`屬性。因此，您的代碼應如下所示：

```jsx
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;
```

![](https://thepracticaldev.s3.amazonaws.com/i/heuay1pm410dxt5e7jbk.jpg)

## 复数 (Pluralization)

如果您要翻譯的語言有复数的話(比如說像英文：apple / apples)，您可以使用以下其中一個方法，來把已翻譯好的文字和句子放進您的definition文件裡。

* `"key": { "singular":"apple", "plural":"apples" }`
* `"key": { "none":"no apples", "one":"apple", "many":"apples" }`
* `"key": ["apples", "apple"]`

在以下的例子中，我們將會把模板和复数的例子結合在一起。但在那之前，我們需要更新我們的definition文件：

```json
{
  "apple": { 
    "singular": "Henry has {{count}} apple.", 
    "plural":"Henry has {{count}} apples." 
  }
}
```

接著，我們將會把以下的代碼粘貼到`src/routes/home/index.js`中：

```jsx
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;
```
![](https://thepracticaldev.s3.amazonaws.com/i/rqvabjs585uxp2ywggpm.jpg)

根據以上的步驟，你就能在您的Preact項目中使用模板和复数。

---

## 動態導入definition文件

在現實情況中，您將會根據用戶的選擇來設定網頁的語言。

您可以使用遊覽器的語言(通過`navigator.language`)， 或者是讓用戶自己手動更換語言。

然而，為了避免我們將不必要的definition文件導入進去，我們可以使用`import()`來實現動態導入definition文件。這樣一來，我們只會導入用戶所選擇的語言所需要的definition文件。

```jsx
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](https://slides.limhenry.xyz/?hl=zh-tw))來更改語言。 當然，用戶也可以從[設置](https://slides.limhenry.xyz/settings/language)中更改語言。

![](https://thepracticaldev.s3.amazonaws.com/i/wxgg59lyyisatdza3pgl.jpg)

您可以在這裡知道關於Remote for Slides更多的訊息：

{% link henrylim96/meet-remote-for-slides-a-new-way-to-control-your-presentation-slides-1o7n %}

---

## 更多資源

* [preact-i18n](https://github.com/synacor/preact-i18n)
* [Preact CLI](https://github.com/preactjs/preact-cli)
* [Preact](https://preactjs.com/)