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:
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!"
}
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'
};
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
});
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>
);
};
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>
);
};
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>
);
}
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;
};
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>
);
}
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)
thank you bro :)
Initial context doesn't change when calling userLanguageChange function.
export const LanguageBisContext = createContext({
userLanguage: "en",
dictionary: dictionaryList.en,
});
I am not sure to understand your point but it is working as expected and here is the demo nice-cliff-047044a03.azurestaticap...
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
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.
Looks good! Will this work with SSR, specifically NextJS?
Thanks.
Honestly, I didn't try in any SSR project but, I don't see any reason not to work.
Really helpful post, tesekkur ederim! :)
That looks good! But how would it be when i want to set the language for the Alert (which is not a component) ???
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?
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.