Site icon May's Notes

錯誤代碼含有動態數字要怎麼做 i18n?

turned-on MacBook Pro wit programming codes display

一個實作中滿常見的情況,分享一下我自己常用的解決方法。

錯誤訊息中帶有數字

假設請求失敗時會回傳以下錯誤代碼:

VALUE_MUST_BE_BETWEEN_0_AND_10
VALUE_MUST_BE_GREATER_THAN_0
VALUE_MUST_BE_LESS_THAN_10

這其中的數字都是會動態改變的,比如可能會是 VALUE_MUST_BE_BETWEEN_0_AND_100 或者 VALUE_MUST_BE_BETWEEN_10_AND_20

那麼如果要使用 i18n,就必須將這個代碼放進 json 檔中,key名如何對應就是一個問題,因為數字可能會改變。

如果把所有數字可能性都放進 json 檔中就會變成:

"VALUE_MUST_BE_BETWEEN_0_AND_10": "數字必須在0到10之間",
"VALUE_MUST_BE_BETWEEN_0_AND_100": "數字必須在0到100之間",
"VALUE_MUST_BE_BETWEEN_10_AND_20": "數字必須在10到20之間",
...

如果有幾百種上千種組合,就得為每個組合都新增一個 key,非常不現實。

使用正則獲取錯誤訊息中的數字

這種時候就可以使用正則先把錯誤訊息中的數字給取出。

const msg = error.response.data.code
const regex = /VALUE_MUST_BE_BETWEEN(\d+)_AND_(\d+)/
const matches = msg.match(regex)
console.log(matches) // [0, 10]

將錯誤代碼的正則集中在一起管理:

export const errorMap = {
  VALUE_MUST_BE_BETWEEN: {
    id: "VALUE_MUST_BE_BETWEEN",
    regex: /VALUE_MUST_BE_BETWEEN(\d+)_AND_(\d+)/,
  },
  VALUE_MUST_BE_GREATER_THAN: {
    id: "VALUE_MUST_BE_GREATER_THAN",
    regex: /VALUE_MUST_BE_GREATER_THAN_(\d+)/,
  },
  VALUE_MUST_BE_LESS_THAN: {
    id: "VALUE_MUST_BE_LESS_THAN",
    regex: /VALUE_MUST_BE_LESS_THAN_(\d+)/,
  }
}

export const errorMsgKeys = Object.keys(errorMap)
export const handleError = (error) => {
  let msg = error.response.data.code
  const matchedError = errorMsgKeys.find((key) => msg.includes(key))
  if (matchedError) {
    const { id, regex } = errorMap[matchedError]
    const matches = msg.match(regex)
    ...
  }

  return msg
}

那麼獲取到數字之後該如何轉成 i18n 呢?有的錯誤代碼只有一個數,有的卻有兩個數。

i18n 動態變數

基本上所有的 i18n 第三方庫都支持動態變數,比如:

react-intl, react-i18next 可以這麼寫:

{
  "VALUE_MUST_BE_BETWEEN": "數字必須在{min}到{max}之間",
}

i18n-js

{
  "VALUE_MUST_BE_BETWEEN": "數字必須在%{min}到%{max}之間",
}

我平時用 react-i18next 比較多,所以這邊用 react-i18next 的寫法做示範。因此將全部錯誤代碼寫進 i18n json 檔中就會像這樣:

{
  "VALUE_MUST_BE_BETWEEN": "數字必須在{min}到{max}之間",
  "VALUE_MUST_BE_GREATER_THAN": "數字必須大於{value}",
  "VALUE_MUST_BE_LESS_THAN": "數字必須小於{value}"
}

翻譯包含動態數字的錯誤代碼

萬事俱備,現在只剩下將數字放入 i18n 動態變數中了。

將 errorMap 改造一下,把 i18n 中的動態變數也一同寫入:

export const errorMap = {
  VALUE_MUST_BE_BETWEEN: {
    id: "VALUE_MUST_BE_BETWEEN",
    regex: /VALUE_MUST_BE_BETWEEN(\d+)_AND_(\d+)/,
    formatValues: (matches) => ({ min: matches[0], max: matches[1] }),
  },
  VALUE_MUST_BE_GREATER_THAN: {
    id: "VALUE_MUST_BE_GREATER_THAN",
    regex: /VALUE_MUST_BE_GREATER_THAN_(\d+)/,
    formatValues: (matches) => ({ value: matches[0] }),
  },
  VALUE_MUST_BE_LESS_THAN: {
    id: "VALUE_MUST_BE_LESS_THAN",
    regex: /VALUE_MUST_BE_LESS_THAN_(\d+)/,
    formatValues: (matches) => ({ value: matches[0] }),
  }
}

使用 formatValues 將獲取的數字放入對應的 i18n 變數,如此一來便能完成翻譯包含動態數字的錯誤代碼了。

export const handleError = (error) => {
  let msg = error.response.data.code

  const matchedError = errorMsgKeys.find((key) => msg.includes(key))
  if (matchedError) {
    const { id, regex, formatValues } = errorMap[matchedError]
    const matches = msg.match(regex)
    const dynamicValues = matches ? formatValues(matches.slice(1)) : {}
    msg = i18n.t(id, dynamicValues)
  }

  return msg
}
Exit mobile version