2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Next.jsをRosettaで多言語対応(i18n)して型に気を付けたメモ

Last updated at Posted at 2020-05-03

概要

前回はとりあえずコンパイラを通すために適当に型をつけてしまっていた
今回、型をきちんとつけてやることで、定義ファイルにないものを使うとエラーが起きるように修正した。

この時点のソース

ソース

  • typeof JAで、日本語の定義ファイルをベースとして型つけを行う
    • 他の言語に追加忘れたときの検知可能
  • keyof Dictで、定義ファイルのプロパティを引数にとるようにする
    • タイポの検知
    • 未定義のものは使用できないようにする
  • [defaultLanguage, 'en'] as const
    • const assertion.TypeScript 3.4^.
    • 配列の要素の型をstring[]ではなくreadonly["ja", "en"]とリテラル型にできる
  • typeof languages[number]
    • リテラルの配列をUnion Typeに変換.
    • T[number]は配列Tに対してnumber型のプロパティ名でアクセスできるプロパティの型 = 配列Tの要素の型
  • { [K in Language]: string }
    • Mapped Type
    • オブジェクトはLanguageで定義されたプロパティを持ち、その型はstringである
src/lib/i18n.tsx
import { createContext, useState, useRef, useEffect } from 'react'
import rosetta from 'rosetta'
// import rosetta from 'rosetta/debug';

// 日本語をベース
+ import JA from '~/locales/ja.json'
+ type Dict = typeof JA
+ export const defaultLanguage = 'ja'
+ export const languages = [defaultLanguage, 'en'] as const
+ type Language = typeof languages[number]
+ export const contentLanguageMap: { [K in Language]: string } = {
+   ja: 'ja-JP',
+   en: 'en-US',
+ }

export const I18nContext = createContext(null)

// default language
i18n.locale(defaultLanguage)


+ export type I18n = {
+   activeLocale: Language
+   t: (...args: [keyof Dict | (string | number)[], any?, Language?]) => string
+   locale: (l: Language, dict: Dict) => void
+ }


+ const I18n: React.FC<{
+   locale: Language
+   lngDict: Dict
+ }> = ({ children, locale, lngDict }) => {
  const [activeDict, setActiveDict] = useState(() => lngDict)
  const activeLocaleRef = useRef(locale || defaultLanguage)
  const [, setTick] = useState(0)
  const firstRender = useRef(true)

  // for initial SSR render
  if (locale && firstRender.current === true) {
    firstRender.current = false
    i18n.locale(locale)
    i18n.set(locale, activeDict)
  }

  useEffect(() => {
    if (locale) {
      i18n.locale(locale)
      i18n.set(locale, activeDict)
      activeLocaleRef.current = locale
      // force rerender
      setTick((tick) => tick + 1)
    }
  }, [locale, activeDict])


+   const i18nWrapper: I18n = {
    activeLocale: activeLocaleRef.current,
    t: (...args) => i18n.t(...args),
    locale: (l, dict) => {
      i18n.locale(l)
      activeLocaleRef.current = l
      if (dict) {
        i18n.set(l, dict)
        setActiveDict(dict)
      } else {
        setTick((tick) => tick + 1)
      }
    },
  }

  return (
    <I18nContext.Provider value={i18nWrapper}>{children}</I18nContext.Provider>
  )
}
export default I18n
hooks/use-i18n.tsx
import { useContext } from 'react'
import { I18nContext, I18n } from '~/lib/i18n'

export default function useI18n() {
  const i18n: I18n = useContext(I18nContext)
  return i18n
}
  • フラットなjsonに変更
  • 単語の区切りは_で表現
    • キーに.を使うと、rosettaがオブジェクトのプロパティと解釈してしまう
    • .以外なら何でもいい
location/ja.json
{
  "lostrpg_index_title": "LOSTRPG サポートページ",
  "lostrpg_index_campList": "キャンプ一覧",
  "lostrpg_camps_list_title": "LOSTRPG キャンプ一覧",
  "lostrpg_camps_list_campName": "キャンプ名",
  "lostrpg_camps_list_campList": "キャンプ一覧",
  "common_back": "戻る",
  "common_create": "新規作成"
}
  • 利用例
    <Container>
      <Head>
        <meta
          httpEquiv="content-language"
          content={contentLanguageMap[i18n.activeLocale]}
        />
-        <title>{i18n.t('lostrpg.index.title')}</title>
+        <title>{i18n.t('lostrpg_index_title')}</title>
      </Head>

参考

rosetta
TypeScript: 配列の値をString Literal Typesとして使う
TypeScriptの型入門
純 typescript
next.js rosetta example

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?