Skip to content

多語系

介紹如何使用 @astrolicious/i18n 套件來支援多語系功能。

安裝套件

安裝 @astrolicious/i18n 套件:

bash
yarn add @astrolicious/i18n

然後在 astro.config.ts 中註冊 i18n 套件和設定語系,比如範例中設定網站可以支援繁體中文和英語兩種語系,且預設語系為繁體中文:

ts
import { defineConfig } from 'astro/config'
import i18n from '@astrolicious/i18n'

export default defineConfig({
  integrations: [
    i18n({
      defaultLocale: 'zh-TW',
      locales: ['zh-TW', 'en'],
    }),
  ],
})

Layout 語系設定

在 Layout 檔案中設定語系相關基本設定:

astro
---
import I18nHead from '@astrolicious/i18n/components/I18nHead.astro'
import { getHtmlAttrs, getLocale } from 'i18n:astro'
import type { PageMeta } from '@stephenchenorg/astro/page'
import type { CompanySetting } from '@stephenchenorg/astro/company-setting'

// ...

const locale = getLocale()

const htmlLangMap: Record<string, string> = {
  'zh-TW': 'zh-Hant-TW',
}

const htmlAttrs = getHtmlAttrs()
htmlAttrs.lang = htmlLangMap[locale] || locale
---

<!doctype html>
<html {...htmlAttrs}>
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width" />

    <I18nHead />
  </head>
  <body>
  </body>
</html>

設定多語系路由

@astrolicious/i18n 套件中的路由資料夾是 src/routes,只有在 src/routes 資料夾中的檔案才會套用語系前墜。如果有不想要套用的檔案,比如 API 相關的路由,可以放回 src/pages 資料夾。

src/routes 資料夾中設定多語系路由結構如下:

src/
└── routes/
    ├── about.astro
    ├── index.astro
    └── posts/
        ├── [id].astro
        └── index.astro

對應的路由設定:

zh-TW:
/
/posts/
/posts/1/
/about/

en:
/en/
/en/posts/
/en/posts/1/
/en/about/

設定翻譯檔

src/locales 目錄中新增翻譯檔:

src/locales/en/common.json

json
{
  "hello": "Hello",
  "world": "World",
  "nav_about": "About",
  "nav_home": "Home",
  "nav_posts": "Posts"
}

src/locales/zh-TW/common.json

json
{
  "hello": "你好",
  "world": "世界",
  "nav_about": "關於",
  "nav_home": "首頁",
  "nav_posts": "文章"
}

翻譯文字

使用 t 函數來翻譯文字:

astro
---
import { t, getLocalePath } from 'i18n:astro'
---

<div>
  <h1>{t('hello')}</h1>
  <p>{t('world')}</p>
</div>

語系連結

使用 getLocalePath 函數來生成語系連結:

astro
---
import { t, getLocalePath } from 'i18n:astro'
---

<a href={getLocalePath('/')}>{t('nav_home')}</a>

Layout 增加語系切換選項

在 Layout 中增加語系切換選項:

astro
---
import I18nHead from '@astrolicious/i18n/components/I18nHead.astro'
import { t, getHtmlAttrs, getLocale, getLocalePath, getSwitcherData } from 'i18n:astro'
import type { PageMeta } from '@stephenchenorg/astro/page'
import type { CompanySetting } from '@stephenchenorg/astro/company-setting'

// ...

const pathname = Astro.url.pathname
const switcherData = getSwitcherData()

const switcherLabels: Record<string, string> = {
  en: 'English',
  'zh-TW': '繁體中文',
}
---

<!doctype html>
<html {...htmlAttrs}>
  <body>

    <nav>
      <ul>
        <li class:list={[pathname === getLocalePath('/') && 'active']}>
          <a href={getLocalePath('/')}>{t('nav_home')}</a>
        </li>
        <li class:list={[pathname.startsWith(getLocalePath('/posts')) && 'active']}>
          <a href={getLocalePath('/posts/')}>{t('nav_posts')}</a>
        </li>
        <li class:list={[pathname === getLocalePath('/about/') && 'active']}>
          <a href={getLocalePath('/about/')}>{t('nav_about')}</a>
        </li>
        <li>
          <a href="#">{switcherLabels[locale]}</a>
          <ul>
            {switcherData.map(item => (
              <li>
                <a href={`${item.href.replace(/\/$/, '')}/`}>{switcherLabels[item.locale]}</a>
              </li>
            ))}
          </ul>
        </li>
      </ul>
    </nav>

  </body>
</html>

GraphQL 語系設定

在 GraphQL 請求中設定語系變數,只需在 src/api/index.ts 加入 Content-Language Header 即可,但需注意的是,前端習慣使用 - 連接語系的格式,後端使用 _ 來連接,因此需要轉換過才能使用:

ts
import { getLocale } from 'i18n:astro'

export const graphQLAPI = createGraphQLAPI({
  endpoint: `${siteConfig.apiBaseUrl}/graphql`,
  fetchOptions: () => ({
    headers: {
      'Content-Language': getLocale().replace('-', '_'),
    },
  }),
})

然後在 GraphQL 請求中就會請求對應的語系資料了。

動態參數路由設定

當路由有動態參數時 (例如:src/routes/posts/[slug]),就需要使用 setDynamicParams() 函數來設定動態參數的語系路由,注意需要放在 graphQLAPI() 的後面,因為要先確保 API 請求成功後才設定動態參數:

ts
import { getLocales, setDynamicParams } from 'i18n:astro'

setDynamicParams(getLocales().map(locale => ({
  locale,
  params: { slug },
})))