使用 react-i18next 的瑣碎注意事項

i18next 的 Logo
Credit: i18next

使用套件

"react": ^17.0.1,
"next": 12.0.2,
"next-i18next": ^9.0.0,
"typescript": 4.3.2

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

使用特殊符號與 HTML 語法

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

必須依賴 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 的缺點是無法放置其他元件,結構寫起來很不一樣。

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'.

2021-11-14 最近在 stackoverflow 找到解法:

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

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

做了一份 CodeSandbox 範例:

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

先前在網路上搜尋到的,已經過時:

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

會在最近的版本出現 TS 錯誤:

Type 'ItemProps[]' does not satisfy the constraint 'string | TemplateStringsArray'.
  Property 'raw' is missing in type 'ItemProps[]' but required in type 'TemplateStringsArray'.

.map 也有:

Property 'map' does not exist on type 'TFunctionResult'.
  Property 'map' does not exist on type 'string'.ts(2339)

結論

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