DEV Community πŸ‘©β€πŸ’»πŸ‘¨β€πŸ’»

DEV Community πŸ‘©β€πŸ’»πŸ‘¨β€πŸ’» is a community of 963,274 amazing developers

We're a place where coders share, stay up-to-date and grow their careers.

Create account Log in
Halil Can Ozcelik
Halil Can Ozcelik

Posted on • Updated on

Create a Multi-Language Website with React Context API

In this article, I will try to explain my approach to develop a multi-language website with React Context API. If you are used to reading code better than words, you can examine the example project from this Github repository.

And here is the live POC of the project.
(This link exists on the Github Repo too)

First, I strongly suggest taking a glance at the React Context API and useContext Hook documents from the official React website.

And here we go! This is the folder structure of the project:
Folder Structure

Texts are stored as JSON for each language. You can see the example for English below:

{
  "exploreHeader": "Explore",
  "welcomeDescription": "This is a demo app for multi-language website with React Context API",
  "clickMe": "Click Me",
  "aboutMe": "For more info about the author",
  "buttonClicked": "You clicked to button!"
}
Enter fullscreen mode Exit fullscreen mode

All of them are stored in a dictionary object and it will be shown according to the selected language which I will explain later in this article.

import tr from './tr.json';
import en from './en.json';
import de from './de.json';

export const dictionaryList = { en, tr, de };

export const languageOptions = {
  en: 'English',
  tr: 'Türkçe',
  de: 'Deutsch'
};
Enter fullscreen mode Exit fullscreen mode

The language selector is filled by languageOptions. User can change the language of the website from there.

I will create a context that contains the selected language and dictionary.

import { languageOptions, dictionaryList } from '../languages';

// create the language context with default selected language
export const LanguageContext = createContext({
  userLanguage: 'en',
  dictionary: dictionaryList.en
});
Enter fullscreen mode Exit fullscreen mode

Then define the Context Provider. We can set the selected language and get related texts from the dictionary by this context provider.

// it provides the language context to app
export function LanguageProvider({ children }) {
  const defaultLanguage = window.localStorage.getItem('rcml-lang');
  const [userLanguage, setUserLanguage] = useState(defaultLanguage || 'en');

  const provider = {
    userLanguage,
    dictionary: dictionaryList[userLanguage],
    userLanguageChange: selected => {
      const newLanguage = languageOptions[selected] ? selected : 'en'
      setUserLanguage(newLanguage);
      window.localStorage.setItem('rcml-lang', newLanguage);
    }
  };

  return (
    <LanguageContext.Provider value={provider}>
      {children}
    </LanguageContext.Provider>
  );
};
Enter fullscreen mode Exit fullscreen mode

When the language selector is changed, it will call the userLanguageChange() method of the provider.

You can examine the LanguageSelector.js below:

import React, { useContext } from 'react';

import { languageOptions } from '../languages';
import { LanguageContext } from '../containers/Language';

export default function LanguageSelector() {
  const { userLanguage, userLanguageChange } = useContext(LanguageContext);

  // set selected language by calling context method
  const handleLanguageChange = e => userLanguageChange(e.target.value);

  return (
    <select
      onChange={handleLanguageChange}
      value={userLanguage}
    >
      {Object.entries(languageOptions).map(([id, name]) => (
        <option key={id} value={id}>{name}</option>
      ))}
    </select>
  );
};
Enter fullscreen mode Exit fullscreen mode

And we need to wrap the main component which is App.js with LanguageProvider.

function App() {
  return (
    <LanguageProvider>
      <div className="App">
        <header className="App-header">
          <LanguageSelector />
        </header>

        <Explore />
      </div>
    </LanguageProvider>
  );
}
Enter fullscreen mode Exit fullscreen mode

Then, we define Text component to translate our texts.

// get text according to id & current language
export function Text({ tid }) {
  const languageContext = useContext(LanguageContext);

  return languageContext.dictionary[tid] || tid;
};
Enter fullscreen mode Exit fullscreen mode

Now, we can use this component to gather related text according to the selected language from predefined language objects (which I mentioned at the beginning of the article).
Also, we can call the language context directly to use such as the input placeholder example below.
Here are several usage examples in a component:

export default function Explore() {
  const [clickText, setClickText] = useState();
  const { dictionary } = useContext(LanguageContext);

  const handleClick = () => {
    setClickText(<Text tid="buttonClicked" />);
  }

  return (
    <div>
      <h1><Text tid="exploreHeader" /></h1>
      <p><Text tid="welcomeDescription" /></p>

      <div>
        <input type="text" placeholder={dictionary.enterText} />
        <button onClick={handleClick}>
          <Text tid="clickMe" />
        </button>
        <p>{clickText}</p>
      </div>

      <a href="https://halilcanozcelik.com" target="_blank" rel="noopener noreferrer">
        <Text tid="aboutMe" />
      </a>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

Additionally, the selected language should be stored in the database or local storage of the browsers and context states filled by this option at the beginning. An option of languageOptions can be used for a fallback scenario, I used English (β€œen”) in this project. Also, I have stored the selected language in local storage and reading from there at the beginning. If there is no info, then using browser language as the default language.
I hope it will be helpful.

Top comments (11)

Collapse
 
sercanbozkurt profile image
Sercan

thank you bro :)

Collapse
 
xurify profile image
Xuri

Really helpful post, tesekkur ederim! :)

Collapse
 
bernarda profile image
BernardA

Looks good! Will this work with SSR, specifically NextJS?
Thanks.

Collapse
 
halilcanozcelik profile image
Halil Can Ozcelik Author

Honestly, I didn't try in any SSR project but, I don't see any reason not to work.

Collapse
 
wlaskovic profile image
wlaskovic

Thanks, that was super helpful!
Is it somehow possible to put flag icons instead of country name? I've tried with tag but I'm getting [object Object] instead of images

Collapse
 
halilcanozcelik profile image
Halil Can Ozcelik Author • Edited on

Actually, your issue is about HTML most probably. I suppose you are trying to add img element in option tag of LanguageSelector.js You are most probably getting "Only strings and numbers are supported as option children" error in the console. So, if you want to use flags, you need to change the HTML structure of the LanguageSelector component.

Collapse
 
thanhluantl2304 profile image
thanhluantl2304

That looks good! But how would it be when i want to set the language for the Alert (which is not a component) ???

Collapse
 
giovabiancia profile image
Giovanni Bianciardi

Initial context doesn't change when calling userLanguageChange function.

export const LanguageBisContext = createContext({
userLanguage: "en",
dictionary: dictionaryList.en,
});

Collapse
 
halilcanozcelik profile image
Halil Can Ozcelik Author

I am not sure to understand your point but it is working as expected and here is the demo nice-cliff-047044a03.azurestaticap...

Collapse
 
ahmedkhalildeveloper profile image
AhmedKhalilDeveloper • Edited on

Thank you very much, but there is a problem when using the tag on "placeholder"
It shows the following text: [object Object] Do you have a solution for this?

Collapse
 
halilcanozcelik profile image
Halil Can Ozcelik Author • Edited on

Thank you for the beneficial question. You can use the language context directly. I added an example about it via this commit: github.com/hcoz/react-context-mult...
Also, I will update the article by adding this example.

Need a better mental model for async/await?

Check out this classic DEV post on the subject.

β­οΈπŸŽ€ JavaScript Visualized: Promises & Async/Await

async await