Free UI/UX design course

Learn how to create exceptional designs by reading my new tutorial.

Start learning

Internationalization (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.

What is i18n?

"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."

Source: https://lingoport.com/what-is-i18n/


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:

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 demo


John Doe

About 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.