Free UI/UX design course
Learn how to create exceptional designs by reading my new tutorial.
Start learningInternationalization (i18n)
Thanks to the MDB, and our Vite starter, translating Bootstrap projects into other languages is child's play.
In this lesson we will learn how to do this using the i18n process.
"Internationalization (i18n) is the process of preparing software so that it can support local languages and cultural settings. An internationalized product supports the requirements of local markets around the world, functioning more appropriately based on local norms and better meeting in-country user expectations."
Prerequisites
In this tutorial we will be using the Vite starter. If you have done the previous lessons in this section, you do not need to prepare anything. If you haven't, go back to the Vite lesson and follow all the steps, then come back here.
Step 1 - install dependencies
We need 2 dependencies:
- i18next, a very popular internationalization framework.
- i18next-fetch-backend plugin to load resources using the fetch API.
To install it, launch the terminal in the directory with your Vite starter, and type this command:
npm install --save-dev i18next i18next-fetch-backend
Step 2 - prepare structure
At the beginning, we need to create an additional folder and a few files in which we will place translations and configuration for i18n.
Create a locales
folder inside your src
,
json
files for translations,
and i18n.js
for internationalization plugin configuration.
You can do it simply by copying and pasting the commands below into your terminal:
mkdir src/locales
touch src/js/i18n.js src/locales/en.json src/locales/pl.json src/locales/ja.json src/locales/de.json
Step 3 - add translated content
Copy the contents of the following snippets to the appropriate files created in the previous step:
{
"language": "English",
"date": "Date",
"question": "Please select a language",
"search": "Search",
"news1": "Some news",
"news2": "Another news",
"profile": "My profile",
"profileSettings": "Settings",
"profileLogout": "Logout",
"accordion1": "Item - One",
"accordion2": "Item - Two",
"accordion3": "Item - Three",
"accordionTxt": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam vitae magna lacus. Fusce pretium urna id tellus ornare sagittis. Sed ac sagittis nibh, nec vehicula dolor. Cras et posuere mi",
"maskText": "Can you see me?",
"slide1Label": "First slide label",
"slide2Label": "Second slide label",
"slide3Label": "Third slide label",
"slide1Description": "English - Nulla vitae elit libero, a pharetra augue mollis interdum.",
"slide2Description": "English - Lorem ipsum dolor sit amet, consectetur adipiscing elit.",
"slide3Description": "English - Praesent commodo cursus magna, vel scelerisque nisl consectetur."
}
{
"language": "Polski",
"date": "Data",
"question": "Prosze wybrać język",
"search": "Wyszukaj",
"news1": "Wiadomości",
"news2": "Inne wiadomości",
"profile": "Mój profil",
"profileSettings": "Ustawienia",
"profileLogout": "Wyloguj",
"accordion1": "Przedmiot - Jeden",
"accordion2": "Przedmiot - Dwa",
"accordion3": "Przedmiot - Trzy",
"accordionTxt": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam vitae magna lacus. Fusce pretium urna id tellus ornare sagittis. Sed ac sagittis nibh, nec vehicula dolor. Cras et posuere mi",
"maskText": "Czy mnie widzisz?",
"slide1Label": "Etykieta pierwszego slajdu",
"slide2Label": "Etykieta drugiego slajdu",
"slide3Label": "Etykieta trzeciego slajdu",
"slide1Description": "Polski - Nulla vitae elit libero, a pharetra augue mollis interdum.",
"slide2Description": "Polski - Lorem ipsum dolor sit amet, consectetur adipiscing elit.",
"slide3Description": "Polski - Praesent commodo cursus magna, vel scelerisque nisl consectetur."
}
{
"language": "日本語",
"date": "日にち",
"question": "言語を選択してください",
"search": "サーチ",
"news1": "ニュース",
"news2": "別のニュース",
"profile": "プロフィール",
"profileSettings": "セッティング",
"profileLogout": "ログアウト",
"accordion1": "アイテム - 1",
"accordion2": "アイテム - 2",
"accordion3": "アイテム - 3",
"accordionTxt": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam vitae magna lacus. Fusce pretium urna id tellus ornare sagittis. Sed ac sagittis nibh, nec vehicula dolor. Cras et posuere mi",
"maskText": "私がみえますか?",
"slide1Label": "1 枚目のスライド ラベル",
"slide2Label": "2 番目のスライド ラベル",
"slide3Label": "3 番目のスライド ラベル",
"slide1Description": "日本語 - Nulla vitae elit libero, a pharetra augue mollis interdum.",
"slide2Description": "日本語 - Lorem ipsum dolor sit amet, consectetur adipiscing elit.",
"slide3Description": "日本語 - Praesent commodo cursus magna, vel scelerisque nisl consectetur."
}
{
"language": "Deutsch",
"date": "Datum",
"question": "Bitte wähle eine Sprache",
"search": "Suchen",
"news1": "Nachrichten",
"news2": "Andere Nachrichten",
"profile": "Mein Profil",
"profileSettings": "Einstellungen",
"profileLogout": "Ausloggen",
"accordion1": "Post - Ein",
"accordion2": "Post - Zwei",
"accordion3": "Post - Drei",
"accordionTxt": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam vitae magna lacus. Fusce pretium urna id tellus ornare sagittis. Sed ac sagittis nibh, nec vehicula dolor. Cras et posuere mi",
"maskText": "Können Sie mich sehen?",
"slide1Label": "Etikett des ersten Objektträgers",
"slide2Label": "Etikett des zweiten Objektträgers",
"slide3Label": "Etikett des dritten Objektträgers",
"slide1Description": "Deutsch - Nulla vitae elit libero, a pharetra augue mollis interdum.",
"slide2Description": "Deutsch - Lorem ipsum dolor sit amet, consectetur adipiscing elit.",
"slide3Description": "Deutsch - Praesent commodo cursus magna, vel scelerisque nisl consectetur."
}
Step 4 - import i18next and finish the configuration
Now inside the /src/js/i18n.js
file we need to import i18next, set up the configuration and add the code that will handle switching between the languages:
import i18next from 'i18next';
import Fetch from 'i18next-fetch-backend';
const DEFAULT_OPTIONS = {
flagList: {
en: 'flag-united-kingdom',
pl: 'flag-poland',
ja: 'flag-japan',
de: 'flag-germany',
},
preloadLngs: ['en'],
fallbackLng: "en",
loadPath: 'locales/{{lng}}.json',
}
class Translator {
constructor(options = {}) {
this._options = {...DEFAULT_OPTIONS, ...options}
this._currentLng = this._options.fallbackLng;
this._i18nextInit();
this._listenToLangChange();
}
_i18nextInit() {
i18next
.use(Fetch)
.init({
fallbackLng: this._options.fallbackLng,
preload: this._options.preloadLngs,
backend: {
loadPath: this._options.loadPath,
stringify: JSON.stringify,
}
}).then(() => {
this._translateAll();
});
}
_listenToLangChange = () => {
const langSwitchers = document.querySelectorAll('[data-i18n-switcher]');
langSwitchers.forEach((langSwitcher) => {
langSwitcher.addEventListener('click', () => {
this._currentLng = langSwitcher.dataset.i18nLang;
i18next.changeLanguage(this._currentLng).then(() => {
this._translateAll();
this._setPickedLanguageFlag();
});
})
});
}
_translateAll = () => {
const elementsToTranslate = document.querySelectorAll('[data-i18n]');
elementsToTranslate.forEach((el) => {
const key = el.dataset.i18n;
el.innerHTML = i18next.t(key);
})
}
_setPickedLanguageFlag = () => {
const flagIcon = document.getElementById('selected-lang-flag');
const oldFlagClass = flagIcon.classList.value.match(/\bflag-\S+/)[0];
const newFlagClass = this._options.flagList[this._currentLng]
flagIcon.classList.replace(oldFlagClass, newFlagClass);
};
}
export default Translator;
Step 5 - import the Translator
Next, inside the /src/js/i18n.js
file, just above MDB files, import the Translator
class to initialize Translator
.
import Translator from './i18n';
new Translator;
import '../scss/styles.scss';
import * as mdb from 'mdb-ui-kit'; // lib
window.mdb = mdb;
Step 6 - add example HTML content
After we go through all the previous steps, we can start developing our multilingual application. Let's update the content of index.html
so that we can check if the app is working properly.
Add the code below inside the <body>
tag:
<nav class="navbar navbar-expand-lg navbar-light bg-light">
<div class="container-fluid">
<form class="d-none d-md-flex input-group w-auto my-auto">
<div class="form-outline">
<input type="text" id="search" class="form-control" />
<label data-i18n="search" class="form-label" for="search"></label>
</div>
<span class="input-group-text border-0">
<i class="fas fa-search"></i>
</span>
</form>
<div class="navbar-nav ms-auto d-flex flex-row">
<!-- Notifications -->
<div class="dropdown">
<a class="text-reset me-3 dropdown-toggle hidden-arrow" href="#" id="navbarDropdownMenuLink" role="button"
data-mdb-toggle="dropdown" aria-expanded="false">
<i class="fas fa-bell"></i>
<span class="badge rounded-pill badge-notification bg-danger">1</span>
</a>
<ul class="dropdown-menu dropdown-menu-end" aria-labelledby="navbarDropdownMenuLink">
<li>
<a data-i18n="news1" class="dropdown-item" href="#"></a>
</li>
<li>
<a data-i18n="news2" class="dropdown-item" href="#"></a>
</li>
</ul>
</div>
<div class="nav-item dropdown">
<a class="text-reset me-3 dropdown-toggle" href="#" id="navbarDropdown" role="button"
data-mdb-toggle="dropdown" aria-expanded="false">
<i id="selected-lang-flag" class="flag-united-kingdom flag m-0"></i>
</a>
<ul class="dropdown-menu" aria-labelledby="navbarDropdown">
<li>
<a data-i18n-switcher data-i18n-lang="en" class="dropdown-item" href="#"><i
class="flag-united-kingdom flag"></i>English</a>
</li>
<li>
<a data-i18n-switcher data-i18n-lang="pl" class="dropdown-item" href="#"><i
class="flag-poland flag"></i>Polski</a>
</li>
<li>
<a data-i18n-switcher data-i18n-lang="ja" class="dropdown-item" href="#"><i
class="flag-japan flag"></i>日本語</a>
</li>
<li>
<a data-i18n-switcher data-i18n-lang="de" class="dropdown-item" href="#"><i
class="flag-germany flag"></i>Deutsch</a>
</li>
</ul>
</div>
<!-- Avatar -->
<div class="dropdown">
<a class="dropdown-toggle d-flex align-items-center text-reset" href="#" id="navbarDropdownMenuAvatar"
role="button" data-mdb-toggle="dropdown" aria-expanded="false">
<img src="https://mdbcdn.b-cdn.net/img/new/avatars/2.webp" class="rounded-circle" height="25"
alt="Black and White Portrait of a Man" loading="lazy" />
</a>
<ul class="dropdown-menu dropdown-menu-end" aria-labelledby="navbarDropdownMenuAvatar">
<li>
<a data-i18n="profile" class="dropdown-item" href="#"></a>
</li>
<li>
<a data-i18n="profileSettings" class="dropdown-item" href="#"></a>
</li>
<li>
<a data-i18n="profileLogout" class="dropdown-item" href="#"></a>
</li>
</ul>
</div>
</div>
</div>
</nav>
<div class="container">
<div class="row">
<div class="col-lg-6 mx-auto my-5">
<div id="carouselBasicExample" class="carousel slide carousel-fade" data-mdb-ride="carousel">
<div class="carousel-indicators">
<button type="button" data-mdb-target="#carouselBasicExample" data-mdb-slide-to="0" class="active"
aria-current="true" aria-label="Slide 1"></button>
<button type="button" data-mdb-target="#carouselBasicExample" data-mdb-slide-to="1"
aria-label="Slide 2"></button>
<button type="button" data-mdb-target="#carouselBasicExample" data-mdb-slide-to="2"
aria-label="Slide 3"></button>
</div>
<div class="carousel-inner">
<div class="carousel-item active">
<img src="https://mdbcdn.b-cdn.net/img/Photos/Slides/img%20(15).webp" class="d-block w-100"
alt="Sunset Over the City" />
<div class="carousel-caption d-none d-md-block">
<h5 data-i18n="slide1Label"></h5>
<p data-i18n="slide1Description"></p>
</div>
</div>
<div class="carousel-item">
<img src="https://mdbcdn.b-cdn.net/img/Photos/Slides/img%20(22).webp" class="d-block w-100"
alt="Canyon at Nigh" />
<div class="carousel-caption d-none d-md-block">
<h5 data-i18n="slide2Label"></h5>
<p data-i18n="slide2Description"></p>
</div>
</div>
<div class="carousel-item">
<img src="https://mdbcdn.b-cdn.net/img/Photos/Slides/img%20(23).webp" class="d-block w-100"
alt="Cliff Above a Stormy Sea" />
<div class="carousel-caption d-none d-md-block">
<h5 data-i18n="slide3Label"></h5>
<p data-i18n="slide3Description"></p>
</div>
</div>
</div>
<button class="carousel-control-prev" type="button" data-mdb-target="#carouselBasicExample"
data-mdb-slide="prev">
<span class="carousel-control-prev-icon" aria-hidden="true"></span>
</button>
<button class="carousel-control-next" type="button" data-mdb-target="#carouselBasicExample"
data-mdb-slide="next">
<span class="carousel-control-next-icon" aria-hidden="true"></span>
</button>
</div>
</div>
</div>
<div class="row mb-5">
<div class="col-lg-6 mb-4 mb-lg-0">
<section className="mx-auto d-flex align-items-center">
<div class="bg-image">
<img src="https://mdbcdn.b-cdn.net/img/new/standard/city/053.webp" class="w-100" alt="Sample" />
<div class="mask" style="background-color: rgba(0, 0, 0, 0.6)">
<div class="d-flex justify-content-center align-items-center h-100">
<p data-i18n="maskText" class="text-white mb-0"></p>
</div>
</div>
</div>
</section>
</div>
<div class="col-lg-6 mb-4 mb-lg-0">
<div class="accordion" id="accordionExample">
<div class="accordion-item">
<h2 class="accordion-header" id="headingOne">
<button data-i18n="accordion1" class="accordion-button collapsed" type="button" data-mdb-toggle="collapse"
data-mdb-target="#collapseOne" aria-expanded="false" aria-controls="collapseOne">
</button>
</h2>
<div id="collapseOne" class="accordion-collapse collapse" aria-labelledby="headingOne"
data-mdb-parent="#accordionExample">
<div data-i18n="accordionTxt" class="accordion-body"></div>
</div>
</div>
<div class="accordion-item">
<h2 class="accordion-header" id="headingTwo">
<button data-i18n="accordion2" class="accordion-button collapsed" type="button" data-mdb-toggle="collapse"
data-mdb-target="#collapseTwo" aria-expanded="false" aria-controls="collapseTwo">
</button>
</h2>
<div id="collapseTwo" class="accordion-collapse collapse" aria-labelledby="headingTwo"
data-mdb-parent="#accordionExample">
<div data-i18n="accordionTxt" class="accordion-body"></div>
</div>
</div>
<div class="accordion-item">
<h2 class="accordion-header" id="headingThree">
<button data-i18n="accordion3" class="accordion-button collapsed" type="button" data-mdb-toggle="collapse"
data-mdb-target="#collapseThree" aria-expanded="false" aria-controls="collapseThree">
</button>
</h2>
<div id="collapseThree" class="accordion-collapse collapse" aria-labelledby="headingThree"
data-mdb-parent="#accordionExample">
<div data-i18n="accordionTxt" class="accordion-body"></div>
</div>
</div>
</div>
</div>
</div>
</div>
<footer class="bg-light text-center text-lg-start">
<!-- Copyright -->
<div class="text-center p-3" style="background-color: rgba(0, 0, 0, 0.2);">
© 2022 Copyright:
<a class="text-dark" href="https://mdbootstrap.com/">MDBootstrap.com</a>
</div>
<!-- Copyright -->
</footer>
And done! When you see the live preview in your browser, click on the flag icon in the upper right corner. The content of the entire page will change the language.
You can see a working demo by clicking the button below:
Internationalization demoAbout author
Michal Szymanski
Co Founder at MDBootstrap / Listed in Forbes „30 under 30" / Open-source enthusiast / Dancer, nerd & book lover.
Author of hundreds of articles on programming, business, marketing and productivity. In the past, an educator working with troubled youth in orphanages and correctional facilities.