i18n of React with Lingui.js #3

stereobooster - Nov 18 '18 - - Dev Community

In this post, I want to show how to use Lingui.js to do i18n/l10n of React applications. I will use Node 10.10 and yarn, but I guess npm and other versions of Node would work too. The full source code is here. Each step of the tutorial is done as a separate commit, so you can trace all changes of the code.

Usage with React Router

npm install --save react-router react-router-dom

# or using Yarn
yarn add react-router react-router-dom

Add BrowserRouter, Switch, Route.

import { BrowserRouter, Switch, Route } from "react-router-dom";

// ...

<BrowserRouter>
  <Switch>
    <Route path="/" component={Home} exact />
  </Switch>
</BrowserRouter>;

Add NotFound page.

<Switch>
  <Route path="/" component={Home} exact />
  <Route component={NotFound} />
</Switch>

Add I18nRoutes:

const I18nRoutes = ({ match }) => { /* ... */ }

// ...

<Switch>
  <Route path="/:locale" component={I18nRoutes} />
  <Route component={NotFound} />
</Switch>

It will pick locale from path:

const I18nRoutes = ({ match }) => {
  let { locale } = match.params;
  //...
};

It will check if it is supported locale, if not it will return NotFound page:

if (!supportedLocale(locale)) {
  i18n.activate(defaultLocale);
  return <NotFound />;
}

it is supported locale it will activate given locale and return nested routes:

i18n.activate(locale);
return (
  <Switch>
    <Route path={`${match.path}/`} component={Home} exact />
    <Route component={NotFound} />
  </Switch>
);

Redirect from root path to localised page

Add redirect from root path to localised page based on the browser preferences:

const RootRedirect = () => {
  let [locale] = (
    navigator.language ||
    navigator.browserLanguage ||
    defaultLocale
  ).split("-");
  if (!supportedLocale(locale)) locale = defaultLocale;
  return <Redirect to={`/${locale}`} />;
};

// ...

<Switch>
  <Route path="/" component={RootRedirect} exact />
  <Route path="/:locale" component={I18nRoutes} />
</Switch>;

Reimplement LanguageSwitcher for React Router

The simplest LanguageSwitcher can be implemented with NavLink:

import { NavLink } from "react-router-dom";

export default ({ locales }) =>
  Object.keys(locales).map(locale => {
    const url = `/${locale}`;
    return (
      <NavLink
        key={locale}
        to={url}
        activeClassName={s.selected}
        className={s.link}
      >
        {locales[locale]}
      </NavLink>
    );
  });

This implementation will always redirect to the root of localized pages, for example from /en/some/subpage to /cz.

If we want to change the locale, but preserve path we need to use Route:

import { NavLink, Route } from "react-router-dom";

export default ({ locales }) => (
  <Route
    children={({ match }) =>
      Object.keys(locales).map(locale => {
        const url = match.url.replace(
          new RegExp(`^/${match.params.locale}`),
          `/${locale}`
        );
        // ...
      })
    }
  />
);

Add meta tags with the help of React Helmet

npm install --save react-helmet

# or using Yarn
yarn add react-helmet

The code is very similar to the one we used in "Reimplement LanguageSwitcher for React Router":

import React from "react";
import { Route } from "react-router-dom";
import Helmet from "react-helmet";

export default ({ locales }) => (
  <Route
    children={({ match }) => (
      <Helmet
        htmlAttributes={{ lang: match.params.locale }}
        link={Object.keys(locales).map(locale => {
          const url = match.url.replace(
            new RegExp(`^/${match.params.locale}`),
            `/${locale}`
          );
          return { rel: "alternate", href: url, hreflang: locale };
        })}
      />
    )}
  />
);

Add it to Home component:

import Meta from "./Meta";

export default function Home() {
  return (
    <div style={{ padding: "20px 50px" }}>
      <Meta locales={locales} />
      {/* ... */}
    </div>
  );
}
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .