Simple and Painless i18n in React.

📅September 04, 2019

🕒9 minute read

🏷

react

i18n

react-intl

Creating small, maintainable apps with React is no problem at all - unless you're completely new to front-end development. Nowadays, it's also fairly easy to scale your React app with Internationalization (I18n). I want to show you how uncomplicated it is to set up a new or existing project to manage I18n.

2023 disclaimer: the title of this article is misleading. i18n is complex and should be planned carefully before being implemented. Although, if you're looking to play around with it or set it up, this is still relevant.

International Flags

I'll be using the react-intl library for this article. react-intl is a library that provides re-usable components and an API to format text in different languages.

Initial setup

In this case, we'll create a simple React app that lets the user switch between languages at any time. Since it's only for languages, this doesn't encompass the whole i18n scope, but it's a good start.

Lets get started with a project set up using (create-react-app).

Create the project:

$ create-react-app intl-test

Within your project, run:

$ npm i react-intl

react-intl setup

After that, we want to wrap our root component with the IntlProvider component, who's job is feeding our language across components.

// ...
import { IntlProvider } from 'react-intl'

class App extends Component {
  render() {
    return (
     <IntlProvider
      messages={{ /* Our texts will go here */ }}
      locale="en"
     >
       <div className="App">
        <h1>Hello World!</h1>
      </div>
     </IntlProvider>
    )
  }
}

With locale and messages in IntlProvider, you can access and modify their value dynamically. We want to play around with the messages prop. This prop receives an object with your defined texts in different languages. These texts are accessed via an id which can be arbitrarily declared.

For example, let's look at a greeting string, "Hello World!". We can give it an id of home.greeting. Lets create a messages.js file in the src directory:

// All the app's messages would go hereexport default {
  en: {
    'home.greeting': 'Hello World!',
  },

  es: {
    'home.greeting': 'Hola Mundo!',
  },
}

This object will be what the messages prop receives in the IntlProvider.

Now, we replace our usually hard-coded string with FormattedMessage which injects the value of the given id.

Lets create a new component Home.js that holds our greeting:

// ...
import { FormattedMessage } from "react-intl";

const Home = () => (
  <div>
    <h1>
      <FormattedMessage id="home.greeting" defaultMessage="Hello World!" />
    </h1>
  </div>
);

Our root App component should now resemble this:

// ...
import Home from "./Home";
import messages from "./messages";

class App extends Component {
  render() {
    return (
      <IntlProvider messages={messages["en"]} locale="en">
        <div className="App">
          <Home />
        </div>
      </IntlProvider>
    );
  }
}

Now we need a way to change languages.

Adding state to the App component can help determine which language is to be displayed.

class App extends Component {
  state = {
    lang: 'en' // default,
    locale: 'en'
  }

  render() {
    return (
      <IntlProvider
        messages={messages[this.state.lang]}
        locale={this.state.locale}
      >
        ...
      </IntlProvider>
    )
  }
}

Notice how on the messages prop, the 'en' property is no longer hard-coded, but dynamically referenced from our state now. So lang can be changed to 'es' or any other defined language.

To change languages via the UI, one option could be adding some buttons that modify this.state.lang on click.

class App extends Component {
  state = {
    lang: 'en' // default,
    locale: 'en'
  }

  handleChangeLanguage = lang => this.setState({ lang })

  render() {
    return (
     <IntlProvider
        messages={messages[this.state.lang]}
        locale={this.state.locale}
     >
       <div className="App">
        <header>
          <button onClick={() => this.handleChangeLanguage('en')}>
            EN
          </button>
          <button onClick={() => this.handleChangeLanguage('es')}>
            ES
          </button>
        </header>

        <Home />
      </div>
     </IntlProvider>
    )
  }
}

No need to pass this.state.lang to other child components as props, react-intl helps out with that!

Of course, no app is THAT simple, but for the sake of practicality, I decided to keep it this minimal. You can add more components with FormattedMessages and every message will transition.

Furthermore, react-intl provides some extra functionality for all kinds of I18n magic like Redux integration (React), format helpers, personalized formatting for dates, currencies, and much more. Check out the docs.

There are many I18n solutions for React and Javascript that might try to solve the same problem. However, there aren't many solutions as straightforward, friendly 🤗 and scalable as react-intl.