Overview

Let’s talk about React.js!

Well, of course, Python is my mother tongue and the ultimate language, right? (again)

But when I set up React.js last time, it was so much fun that I decided to add i18n (multilingual support) to the starter kit from the last post!

This time, we’ll:

  • Introduce the translation extensions i18next and react-i18next
  • Add i18next-parser to make creating translation files more efficient

Let’s get started!

 

Introducing the Translation Extensions

As always, I’ll put the configuration right here to keep things self-contained in my notes... but if you want to do it properly, you can just follow the official Quick Start guide below:

# i18next: An internationalization library. The engine. You can use it with plain JavaScript.
# react-i18next: A library to integrate the above into React.
yarn add i18next react-i18next

Let’s prepare the translation json files.

mkdir -p ./src/locales/en
mkdir -p ./src/locales/ja

echo '{}' > ./src/locales/en/translation.json
echo '{}' > ./src/locales/ja/translation.json

Then, prepare the file to load and configure the translation json files.

mkdir -p ./src/i18n

echo "
import EN_TRANSLATION from '@/locales/en/translation.json'
import JA_TRANSLATION from '@/locales/ja/translation.json'
import i18n from 'i18next'
import { initReactI18next } from 'react-i18next'

const resources = {
  ja: {
    translation: JA_TRANSLATION,
  },
  en: {
    translation: EN_TRANSLATION,
  },
}

i18n
  .use(initReactI18next)
  .init({
    resources,
    lng: 'en',
    interpolation: {
      escapeValue: false
    }
  })

export default i18n
" > ./src/i18n/index.ts

In the entry point of the React app (main.tsx), load the configuration file.

echo "
import App from '@/App'
import '@/i18n'
import '@/index.css'
import React from 'react'
import ReactDOM from 'react-dom/client'
import { BrowserRouter } from 'react-router-dom'

ReactDOM.createRoot(document.getElementById('root')!).render(
  <React.StrictMode>
    <BrowserRouter basename='/my-react'>
      <App />
    </BrowserRouter>
  </React.StrictMode>
)
" > ./src/main.tsx

 

Using the Translation Extension

Here’s how to use useTranslation and t. The translation json files are still empty, but when that’s the case, the key will be displayed as-is. No problem! I noticed that reference sites often use unique key strings for translations, but in the json files, you can actually use Japanese as keys. I think that’s totally fine, since it makes the components more readable!

echo "
import Button from '@/components/Button'
import formatDate from '@/utils/formatDate'
import { useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'

function HomePage() {
  const { t } = useTranslation()
  const [formattedDate, setFormattedDate] = useState<string>('')

  useEffect(() => {
    const currentDate = new Date()
    const dateStr = formatDate(currentDate)
    setFormattedDate(dateStr)
  }, [])

  const handleClick = () => {
    alert(t('Button がクリックされた!'))
  }

  return (
    <div>
      <h1>{t('Welcome to the Home Page')} - {formattedDate}</h1>
      <Button label={t('Click Me')} onClick={handleClick} />
    </div>
  )
}

export default HomePage
" > ./src/pages/HomePage.tsx

 

Introducing a Tool to Automatically Fill Translation json Files

This tool scans the t function in your components and automatically fills in the translation json files for you. Using this tool, you can work like this:

  • Write all the strings in the components using t
  • Run the tool
  • The json file will be filled with combinations like text to translate: initial value
  • Then, just open the json file and translate all the “text to translate” entries one by one

Super easy, right?

yarn add --dev i18next-parser

Let’s add the configuration file below. There are more detailed settings in the official README, so it might be a good idea to copy those for future reference.

echo "
export default {
  contextSeparator: '_',
  createOldCatalogs: true,
  defaultNamespace: 'translation',
  defaultValue: (locale, namespace, key) => {
    return \`\${key}\` // This is a change from the default setting.
  },
  indentation: 2,
  keepRemoved: false,
  keySeparator: '.',
  lexers: {
    hbs: ['HandlebarsLexer'],
    handlebars: ['HandlebarsLexer'],
    htm: ['HTMLLexer'],
    html: ['HTMLLexer'],
    mjs: ['JavascriptLexer'],
    js: ['JavascriptLexer'],
    ts: ['JavascriptLexer'],
    jsx: ['JsxLexer'],
    tsx: ['JsxLexer'],
    default: ['JavascriptLexer'],
  },
  lineEnding: 'auto',
  locales: ['ja', 'en'], // This is a change from the default setting.
  namespaceSeparator: ':',
  output: 'src/locales/\$LOCALE/\$NAMESPACE.json', // This is a change from the default setting.
  pluralSeparator: '_',
  input: undefined,
  sort: false,
  verbose: false,
  failOnWarnings: false,
  failOnUpdate: false,
  customValueTemplate: null,
  resetDefaultValueLocale: null,
  i18nextOptions: null,
  yamlOptions: null,
}
" > ./i18next-parser.config.js

Run this command:

yarn run i18next "./src/App.tsx" "./src/**/*.tsx"

# If you're worried that the config isn’t being loaded, try this↓
yarn run i18next "./src/App.tsx" "./src/**/*.tsx" --config "./i18next-parser.config.js"

And voilà, the translation json files will be filled up like magic. Ahh, efficiency!

{
  "Welcome to the Home Page": "Welcome to the Home Page",
  "Click Me": "Click Me",
  "Button がクリックされた!": "Button がクリックされた!"
}