使用 i18next 的瑣碎注意事項

i18next 的標誌

環境與套件

"react": ^17.0.1,
"next": 12.1.6,
"next-i18next": ^11.0.0,
"typescript": 4.5.5

覺得 i18next 的文件寫得夠完整,所以在決定作品集要做多國語系時,用這一款來輔助,避免重新發明輪胎,next-i18next 又做好許多 Next.jsi18next 之間的整合。然而,使用上還是有向技術社群求助的經驗,在這一篇集合起來。


接下來的內容


TypeScript 的陣列問題

在語系檔裡面,會有使用陣列的情境:

"document":
{
  "items":
  [
    {
      "name":
        "name 0",
      "description":
        "description 0"
    },
    {
      "name":
        "name 1",
      "description":
        "description 1"
    }
  ]
}

如果按照直覺來設定陣列型別:

type ItemProps = {
  name: string;
  description: string;
  index: number;
};

{t('document.items', {returnObjects: true}).map(({ name, description, index }: ItemProps) => (
  <li key={index}>
     <strong dangerouslySetInnerHTML={{__html: name}} />
     <Paragraph dangerouslySetInnerHTML={{__html: description}} />
   </li>
   )
)}

就會遇到錯誤訊息:

Property 'map' does not exist on type 'string'.

在 stackoverflow 找到解法:

{t<string, ItemProps[]>('document.items', {returnObjects: true}).map(({ name, description, index }) => (
  <li key={index}>
     <strong dangerouslySetInnerHTML={{__html: name}} />
     <Paragraph dangerouslySetInnerHTML={{__html: description}} />
   </li>
   )
)}

並且從前端社群得知這是設定為泛型,因為 i18next 的 returnObjects 允許回傳任何形式的物件,這是在告知型別檢查器說:這裡是陣列。

直接看放在 CodeSandbox 的範例程式碼:

CodeSandbox 連結

來源:Typescript i18next does not satisfy the constraint 'string | TemplateStringsArray NextJS


使用特殊符號與 HTML 語法

撰寫 HTML 的時候,遇到特殊符號,例如 ©,會去 Character Entity Reference 查詢(關鍵字打 html5 entity),並且寫成 &copy;。這樣寫在語系檔裡面,就會照樣輸出 &copy;

必須依賴 React 的 dangerouslySetInnerHTML 來顯示:

<ContentTitle dangerouslySetInnerHTML={{__html: t('intro.title')}} />

若在語系檔裡帶 HTML,也必須用這個方法來顯示,並且在雙引號加上 escape:

{
"description":
  "Get started with <a class=\"paragraph-link\" href=\"https://...\" target=\"_blank\" rel=\"noopener\">Onboarding</a>."
}

dangerouslySetInnerHTML 的缺點是無法放置其他元件,結構寫起來很不一樣。


結論

react-i18next說明文件上其實有 TypeScript 型別設定的教學,不過跟著做之後,在使用 next-i18next 的 Next.js 專案沒有效果(全部都是 any)。


💬 討論

新增討論 👆連至 Github 的 Discussions 參與