Internationalization (i18n)

How to use Bootstrap 5 with i18n - free starter

This article will teach you how to integrate i18n with your project. You can start using directional classes with the latest Bootstrap 5.

Let's see how to integrate internationalization (i18n) with MDB 5 layout, components, and utilities.

Live preview

If you need more extensive support and step-by-step guidance, check out our dedicated tutorial.


Prerequisites

Before starting the project make sure to install the following utilities:

You also need a module bundler. To speed up the project preparation process, check out Webpack, Vite, or Parcel integration guide.


Installation

This guide is based on Vite Starter. The starter is fully configured and ready to use. Just download it and install the dependencies using npm install command.

Note: Vite starter uses MDB Free. If you want to use the MDB Pro, you need to install it.

Once we have managed to launch our project, we have to add two dependencies:

        
            
      npm install --save-dev i18next i18next-fetch-backend
      
        
    

Note: If you are using another MDB starters, the file and folder paths will look similar. If you are adding i18n to your existing project or creating a new project from scratch, keep in mind that paths in your case may look different.

Important: In this guide, we use MDB Pro to show you how to translate a Pro component using the component's option. If you don't have access to MDB Pro or want to use MDB Free, you'll need to remove some code. To make it easier for you, we add a comment with the information that this code fragment should be removed if you use MDB Free.


Configuration

At the beginning, we need to create an additional folder and a few files in which we will place translations and configuration for i18n.

Step 1

Create a locales folder inside your src, json files for translations, and i18n.js for internationalization plugin configuration.

        
            
        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 2

Copy the contents of the following snippets to the appropriate files created in the previous step.

Note: Remove popconfirm translations if you are using MDB Free.

        
            
    {
      "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.",
      "popconfirm": {
        "message": "Example text",
        "okText": "Ok",
        "cancelText": "Cancel"
      }
    }
    
        
    
        
            
    {
      "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.",
      "popconfirm": {
        "message": "Przykładowy tekst",
        "okText": "Ok",
        "cancelText": "Anuluj"
      }
    }
    
        
    
        
            
    {
      "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.",
      "popconfirm": {
        "message": "サンプルテキスト",
        "okText": "Ok",
        "cancelText": "キャンセル"
      }
    }
    
        
    
        
            
    {
      "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.",
      "popconfirm": {
        "message": "Beispieltext",
        "okText": "Okay",
        "cancelText": "Abbrechen"
      }
    }
    
        
    

Step 3

Now import i18next in the `i18n.js` file, create the configuration and add the code that will handle the page translation.

        
            
    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();
            this._initComponents(); // Remove this method if you are using the MDB Free version
          });
      }
      
      _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._reinitComponents(); // Remove this method if you are using the MDB Free version
              this._setPickedLanguageFlag();
            });
          })
        });
      }

      _translateAll = () => {
        const elementsToTranslate = document.querySelectorAll('[data-i18n]');

        elementsToTranslate.forEach((el) => {
          const key = el.dataset.i18n;

          el.innerHTML = i18next.t(key);
        })
      }

      // Remove this method if you are using the MDB Free version
      _initComponents = () => {
        const popconfirm = document.querySelector('#popconfirm');

        new mdb.Popconfirm(popconfirm, {
          message: i18next.t('popconfirm.message'),
          cancelText: i18next.t('popconfirm.cancelText'),
          okText: i18next.t('popconfirm.okText'),
        });
      }

      // Remove this method if you are using the MDB Free version
      _reinitComponents = () => {
        const popconfirm = document.querySelector('#popconfirm');

        mdb.Popconfirm.getOrCreateInstance(popconfirm).dispose();
        this._initComponents();
      }

      _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 4

Import the Translator class in src/main.js file and initialize Translator.

        
            
      import Translator from './i18n';

      new Translator;
      
        
    

Example content

After we go through all the previous steps, we can start developing our application. Let's update the content of index.html so that we can check if the app is working properly.

You can try out the example we have prepared for you. Just 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"
                  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"
                      type="button"
                      data-mdb-toggle="collapse"
                      data-mdb-target="#collapseOne"
                      aria-expanded="true"
                      aria-controls="collapseOne"
                    >
                    </button>
                  </h2>
                  <div id="collapseOne" class="accordion-collapse collapse show" 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>
              <!-- Remove this button if you are using MDB Free -->
              <button id="popconfirm" type="button" class="btn btn-primary mt-3">Popconfirm</button>
            </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>
      
        
    

More about i18n

For more information, see the i18next GitHub page. There you can read about other options, bundle optimizations, etc.


Frontend features

MDB UI KIT:

To create the project we used MDB UI KIT, with which we can build basic views very quickly.

Views and Layouts:

In this project:

  • We successfully integrated i18next into the MDB package and can use the appropriate translations.
  • We also added the i18next-fetch-backend plugin to load translations on demand and optimize translations.
  • We created the i18n.js file in which we placed the Translator code.
  • We created a JS Translator class with a basic configuration that will allow us to easily change the most important options when we initialize translations.