概要
Next.jsの多言語化。
Rosetta ExampleをTypescriptで書いてみたメモ。
// 追記
型を付けたメモで、型をちゃんと付けたので、そちらも参照のこと。
ソース
lib/i18n.tsx
import { createContext, useState, useRef, useEffect } from 'react'
import rosetta from 'rosetta'
const i18n = rosetta()
export const defaultLanguage = 'ja'
export const languages = ['ja', 'en']
export const contentLanguageMap = { ja: 'ja-JP', en: 'en-US' }
export const I18nContext = createContext(null)
// default language
i18n.locale(defaultLanguage)
export default function I18n({ 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 = {
activeLocale: activeLocaleRef.current,
t: (...args) => i18n.t(...(args as [any, ...any[]])),
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>
)
}
hooks/use-i18n.tsx
import { useContext } from 'react'
import { I18nContext } from '~/lib/i18n'
export default function useI18n() {
const i18n = useContext(I18nContext)
return i18n
}
pages/_app.tsx
import * as React from 'react'
import { Provider } from 'react-redux'
import App from 'next/app'
import { ThemeProvider } from '@material-ui/core/styles'
import CssBaseline from '@material-ui/core/CssBaseline'
import Head from 'next/head'
import { setupStore } from '~/store'
import theme from '~/theme'
import '~/styles/global.css'
+ import I18n from '~/lib/i18n'
const store = setupStore()
class MyApp extends App {
componentDidMount() {
// Remove the server-side injected CSS.
const jssStyles = document.querySelector('#jss-server-side')
if (jssStyles) {
jssStyles.parentElement.removeChild(jssStyles)
}
}
public render() {
const { Component, pageProps } = this.props
return (
<Provider store={store}>
<React.Fragment>
<Head>
<title>Create Now</title>
<link rel="icon" href="/favicon.ico" />
</Head>
<ThemeProvider theme={theme}>
{/* CssBaseline kickstart an elegant, consistent, and simple baseline to build upon. */}
<CssBaseline />
+ <I18n lngDict={pageProps.lngDict} locale={pageProps.lng}>
<Component {...pageProps} />
+ </I18n>
</ThemeProvider>
</React.Fragment>
</Provider>
)
}
}
export default MyApp
pages/lostrpg/index.tsx
import { useEffect } from 'react'
import { NextPage } from 'next'
import Head from 'next/head'
import Link from '~/components/atoms/mui/Link'
import Container from '~/components/organisms/lostrpg/LostrpgContainer'
+ import useI18n from '~/hooks/use-i18n'
+ import { contentLanguageMap } from '~/lib/i18n'
+ import EN from '~/locales/en.json'
+ import JA from '~/locales/ja.json'
const Page: NextPage = () => {
+ const i18n = useI18n()
+ useEffect(() => {
+ i18n.locale('ja', JA)
+ }, [])
return (
<Container>
<Head>
<meta
+ httpEquiv="content-language"
+ content={contentLanguageMap[i18n.activeLocale]}
/>
+ <title>{i18n.t('lostrpg.index.title')}</title>
</Head>
<h2>{i18n.t('lostrpg.index.title')}</h2>
<div style={{ padding: '5px' }}>
<a
href="#"
onClick={() => {
+ i18n.locale('en', EN)
}}
>
English
</a>
<a
href="#"
style={{ marginLeft: '10px' }}
onClick={() => {
+ i18n.locale('ja', JA)
}}
>
日本語
</a>
</div>
<ul>
<li>
<Link href="/lostrpg/camps/list">
+ {i18n.t('lostrpg.index.campList')}
</Link>
</li>
</ul>
<Link href="/"> {i18n.t('common.back')}</Link>
</Container>
)
}
export default Page
参考
TypeScript + React で i18n (国際化/多言語) 対応を楽して続けるためのアレコレ
TypeScriptで最低n個の要素を持った配列の型を宣言する方法
rosetta