DEV Community

Mike Eling
Mike Eling

Posted on

Multilingualism in applications

Thumbnail


Hello, Guten Tag, Dzień dobry, Bonjour, Salve, السَّلَامُ عَلَيْكُمْ!

About a year ago we needed some kind of translation management system for our project at work. We've built a very simple internal library, which fed with an object containing translations, spit with translated text.

All the translations were stored in a JSON file and then we moved to YAML - which actually is converted to JSON at build anyway so JavaScript can correctly interpret it.

The YAML file looks like below.

unique.yes:
   de_DE: Ja
   en_US: Yes
   pl_PL: Tak
unique.no:
   de_DE: Nein
   en_US: No
   pl_PL: Nie
unique.example:
   de_DE: Beispiel
   en_US: Example
   pl_PL: Przykład
Enter fullscreen mode Exit fullscreen mode

Everything is working just fine.. except that we now have a file of around 10 000 lines of translations, which are horribly hard to maintain and the fact that the application is still in development made me worry.

We've been trying to refactor the translations, built tools, which helped sorting but it didn't do much, there is still chaos in our translations.

One day while using JSS, I've came up with an idea to implement translations like it's done with styles in JSS and…

✨ tada ✨

Littera was borned!


Introduction

Littera (/ˈlit.te.ra/) is a very simple mechanism which takes an object with keys representing a language and values representing a specific string in that language and returns only the string for the active language. For instance, you take the sentence "This is an example" which will be the value for "en_US" key and you repeat the same for each language.

{
    "en_US": "This is an example",
    "de_DE": "Das ist ein Beispiel",
    "pl_PL": "To przykład"
}
Enter fullscreen mode Exit fullscreen mode

Setup

Now that we know how the core concept works, we can setup Littera. To make use of the library, you have to wrap your components with a context and provide the active language. For this we will use the LitteraProvider. You can pass it 3 props:

  • language  - the active language (string)
  • preset  - set of global translations (object)
  • setLanguage - function to change the active language programically (function)

We will use create-react-app as an example. Remember to install Littera using npm or yarn before you jump into the code.

npm install react-littera
yarn add react-littera

Let's go, import the provider in App.js file wrapping all your components like shown below.

import React, { useState } from "react";
import ReactDOM from "react-dom";

import LitteraProvider from "react-littera";
function App() {
  const [language, setLanguage] = useState("en_US");

  return (
    <div className="App">
      <LitteraProvider language={language} setLanguage={setLanguage}>
        <YourComponents />
      </LitteraProvider>
    </div>
  );
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
Enter fullscreen mode Exit fullscreen mode

And that's it, Littera is ready to be used in your components.

I would suggest, you use Redux to handle the active language state.

Usage

Now let's create an example component to showcase the power of Littera. Our component gonna be a button, which clicked will switch the language.

First we create a basic functional component returning a button.

import React from 'react';

const SuperButton = () => {
    return <button>Example</button>;
}

export default SuperButton;
Enter fullscreen mode Exit fullscreen mode

Now we have to import the useLittera hook, declare an object with our translations and make use of the hook.

import React from 'react';
import { useLittera } from 'react-littera';

const translations = {
    example: {
        en_US: "Example",
        de_DE: "Beispiel",
        pl_PL: "Przykład"
    }
};

const SuperButton = () => {
     const [translated, language, setLanguage] = useLittera(translations);

     const switchLanguage = () => setLanguage("de_DE");

     return <button onClick={switchLanguage}>{translated.example}</button>;
}

export default SuperButton;
Enter fullscreen mode Exit fullscreen mode

Global translations

As mentioned above, we can pass a preset to LitteraProvider. It can be used the same way JSS handles it's theming. Simply make a function out of the translations constant with the preset attribute and returning an object, then use the attribute to reuse the existing preset.

const translations = preset => ({
    example: {
        en_US: `This is an ${preset.example}`,
        de_DE: `Das ist ein ${preset.example}`,
        pl_PL: `To ${preset.example}`
    }
});
Enter fullscreen mode Exit fullscreen mode

Hook or HOC?

Littera supports hooks and HOCs. Both can be used with the same context but let's be real, class components are pretty much extinct. 😅

Anyway an example of both can be found on CodeSandbox.

HOC: https://codesandbox.io/s/6299pk9r1r
Hook: https://codesandbox.io/s/ywl2lm8r4z

Conclusion

There are probably better methods to manage translations in large applications but Littera will fill your needs if you're building an one-pager. If you want to reach a greater audience, give Littera a try. 💖

You can fork the source code on GitHub and maybe we will squash few bugs together! 
https://github.com/DRFR0ST/react-littera

Oldest comments (0)