[Frontend筆記] 使用i18next/vue-i18n + Typescript,讓多語言前端專案的開發更順暢

多語言開發,如果翻譯的文件太肥大、key太多,就需要Typescript來幫忙!

Posted by 李定宇 on Friday, September 30, 2022

使用 i18next/vue-i18n + Typescript,讓多語言前端專案的開發更順暢

前言

筆者之前接手了一個前端專案,有中、日、英三國語言版本,而且文本中的翻譯還有多層嵌套,因此在開發的時候很容易打錯一個字而造成i18n找不到key、畫面顯示錯誤的字。 因此特別搜尋了一下有沒有辦法利用Typescript的特性、讓t(“”)有自動提示的功能呢?

還真的找到有網友分享他在React專案中的解法 ;同時,我也查到Vue專案、官方推薦的使用方式,因此來特別紀錄一下,在React和Vue專案中分別可以怎麼應用。

React的多語言提示

Dependancy: i18next+react-i18next+typescript

首先,假設已經在React專案中設定好 i18nextreact-i18next ,這個專案的架構為:

  • src
    • App.tsx
    • index.tsx
    • i18n
      • index.ts
      • langs
        • en.ts
        • zh.ts
        • jp.ts
  • package.json
  • tsconfig.json

./src/i18n/langs/en.ts內容如下:

export default {
    hello: 'Hello',
    fruit: {
        apple: 'Apple',
        banana: 'Banana'
    }
};

./src/i18n/index.ts

import en from '@/i18n/langs/en'
import jp from '@/i18n/langs/jp'
import zh from '@/i18n/langs/zh'
import i18n from 'i18next'
import { initReactI18next } from 'react-i18next'

export enum LangType {
    ZH_HK = 'zh_hk',
    EN_US = 'en_us',
    JA_JP = 'ja_jp',
}
export const resources = {
    [LangType.EN_US]: {
        translation: en,
    },
    [LangType.ZH_HK]: {
        translation: zh,
    },
    [LangType.JA_JP]: {
        translation: jp,
    },
}
i18n.use(initReactI18next).init({
    resources,
    fallbackLng: LangType.EN_US,
    debug: true,
    interpolation: {
        escapeValue: false,
    },
})

export default i18n

./src/index.ts

import i18n from '@/i18n'
import { I18nextProvider } from 'react-i18next'

///...其他設定
root.render(
    <React.StrictMode>
        <I18nextProvider i18n={i18n}>
        		<App />
        </I18nextProvider>
    </React.StrictMode>,
)

以上是 React 專案使用多語言的基本設定,那麼要如何讓i18n支援Typescript的type提示(目前版本:i18next: ^21.9.2react-i18next: ^11.18.6 )?在根目錄中新增*.d.ts來覆蓋兩者Dependency中的關鍵Type(目前先命名為react-i18n.d.ts)。

主要概念為:

  1. 引入一個翻譯json,使其成為一個標準的Type,並轉換為一個key對應的type

./src/react-i18n.d.ts

// import the original type declarations
import 'react-i18next'

import { LangType, resources } from '@/i18n'
// import all namespaces (for the default language, only)
import en from '@/i18n/langs/en'

type I18nStoreType = typeof en
export type I18nT = {
    (key: keyof I18nStoreType): string
}

  1. 給i18n暴露出來的返回類型(TFunction)來extends。

    declare module 'i18next' {
    // eslint-disable-next-line @typescript-eslint/no-empty-interface
    interface TFuntion extends I18nT {}
    }
    
  2. 覆蓋 react-i18next下的 CustomTypeOptions

    // react-i18next versions higher than 11.11.0
    declare module 'react-i18next' {
    interface CustomTypeOptions {
        resources: typeof resources[LangType.EN_US]
    }
    }
    

基本上就完成了。在組件中使用t("")便會出現Typescript提示: image

Vue的多語言提示

首先,也假設已經在Vue專案中設定好了vue-i18n。而主要邏輯也跟React專案一樣,暴露一個翻譯模板,然後把其轉換為Type,並複寫i18n的type。不過在vue-i18n的官方issue可以看到,vue-i18n還可以對輸入的所有resource進行類型檢查,如果en.ts有一個翻譯但zh.ts沒有,那麼就會在i18n初始化時就會報錯:

  • i18n
    • index.ts
    • langs
      • en.ts
      • zh.ts

./i18n/index.ts中,在基本設定之外,同時也暴露en.ts轉化為Type:

import { createI18n } from 'vue-i18n';
import en from './langs/en.ts';
import zh from './langs/zh.ts';

export type MessageSchema = typeof en;

const i18n = createI18n({
    legacy: false,
    locale: 'en-us',
    globalInjection: true,
    fallbackLocale: 'en-us',
    messages:{
		"en-us":en,
		"zh-tw":zh
    }
});

export default i18n;

./src 下新增ts類型文件,目前命名為vue-i18n.d.ts:

import { DefineLocaleMessage } from 'vue-i18n';
import { MessageSchema } from './i18n';

declare module 'vue-i18n' {
    // eslint-disable-next-line @typescript-eslint/no-empty-interface
    export interface DefineLocaleMessage extends MessageSchema {}
}

透過declare module,來覆蓋npm包的默認類型定義。

這樣一來基本上也算完成了。

ChangeLog

  • 20220930 - 初稿