Introduction
Let's see how to integrate internationalization (i18n) with TE 5 layout, components, and utilities.
Prerequisites
Before starting the project make sure to install the following utilities:
- Node.js (LTS) (12.x.x recommended)
- Code editor. We recommend VSCode
You also need a module bundler. To speed up the project preparation process, check out Quick Start or Integration Guides.
Installation
This guide is based on Vite Starter.
Once we have managed to launch our project, we have to add two dependencies:
- i18next, a very popular internationalization framework.
- i18next-fetch-backend plugin to load resources using the fetch API.
npm install --save-dev i18next i18next-fetch-backend
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 vite app,
json
files for translations, and i18n.js
for
internationalization plugin configuration.
mkdir locales
touch i18n.js locales/en.json locales/pl.json locales/ja.json locales/de.json
mkdir locales
New-Item -ItemType File i18n.js, locales/en.json, locales/pl.json, locales/ja.json, locales/de.json
Step 2
Copy the contents of the following snippets to the appropriate files created in the previous step. If you use TWE Free, remove the code related with Popconfirm component.
{
"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";
import {
Popconfirm,
} from "tw-elements";
const DEFAULT_OPTIONS = {
flagList: {
en: "[background-position:-36px_-26px_!important]",
pl: "[background-position:-72px_-572px_!important]",
ja: "[background-position:-36px_-910px_!important]",
de: "[background-position:0px_-1430px_!important]",
},
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 TWE Free
});
}
_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 TWE Free
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 TWE Free
_initComponents = () => {
const popconfirm = document.getElementById("popconfirm");
new 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 TWE Free
_reinitComponents = () => {
const popconfirm = document.querySelector("#popconfirm");
const popconfirmInstance = Popconfirm.getInstance(popconfirm);
popconfirmInstance.dispose();
this._initComponents();
};
_setPickedLanguageFlag = () => {
const flagIcon = document.getElementById("selected-lang-flag");
const oldFlagClass = flagIcon.classList.value.match(
/\[background-position:(-?\d+px)_(-?\d+px)_!important\]/
)[0];
const newFlagClass = this._options.flagList[this._currentLng];
flagIcon.classList.replace(oldFlagClass, newFlagClass);
};
}
export default Translator;
Step 4
Import the Translator
class and TE components in
main.js
file and initialize them.
import Translator from "./i18n";
import {
Carousel,
Dropdown,
Input,
Collapse,
initTWE,
} from "tw-elements";
initTWE({ Carousel, Dropdown, Input, Collapse });
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 to your app.
<body class="flex flex-col min-h-screen">
<div id="app">
<!-- Main navigation container -->
<nav
class="flex-no-wrap relative flex w-full items-center justify-between bg-[#FBFBFB] py-2 shadow-md shadow-black/5 dark:bg-neutral-600 dark:shadow-black/10 lg:flex-wrap lg:justify-start lg:py-4"
data-twe-navbar-ref
>
<div class="flex w-full flex-wrap items-center justify-between px-3">
<!-- Hamburger button for mobile view -->
<button
class="block border-0 bg-transparent px-2 text-neutral-500 hover:no-underline hover:shadow-none focus:no-underline focus:shadow-none focus:outline-none focus:ring-0 dark:text-neutral-200 lg:hidden"
type="button"
data-twe-collapse-init
data-twe-target="#navbarSupportedContent1"
aria-controls="navbarSupportedContent1"
aria-expanded="false"
aria-label="Toggle navigation"
>
<!-- Hamburger icon -->
<span class="[&>svg]:w-7">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="currentColor"
class="h-7 w-7"
>
<path
fill-rule="evenodd"
d="M3 6.75A.75.75 0 013.75 6h16.5a.75.75 0 010 1.5H3.75A.75.75 0 013 6.75zM3 12a.75.75 0 01.75-.75h16.5a.75.75 0 010 1.5H3.75A.75.75 0 013 12zm0 5.25a.75.75 0 01.75-.75h16.5a.75.75 0 010 1.5H3.75a.75.75 0 01-.75-.75z"
clip-rule="evenodd"
/>
</svg>
</span>
</button>
<!-- Collapsible navigation container -->
<div
class="!visible hidden flex-grow basis-[100%] items-center lg:!flex lg:basis-auto"
id="navbarSupportedContent1"
data-twe-collapse-item
>
<div class="ms-5 flex w-[250px] items-center justify-between">
<div class="relative" data-twe-input-wrapper-init>
<input
type="search"
class="peer block min-h-[auto] w-full rounded border-0 bg-transparent px-3 py-[0.32rem] leading-[1.6] outline-none transition-all duration-200 ease-linear focus:placeholder:opacity-100 peer-focus:text-primary data-[twe-input-state-active]:placeholder:opacity-100 motion-reduce:transition-none dark:text-neutral-200 dark:placeholder:text-neutral-200 dark:peer-focus:text-primary [&:not([data-twe-input-placeholder-active])]:placeholder:opacity-0 dark:autofill:shadow-autofill"
id="exampleSearch2"
placeholder="Type query"
/>
<label
for="exampleSearch2"
class="pointer-events-none absolute left-3 top-0 mb-0 max-w-[90%] origin-[0_0] truncate pt-[0.37rem] leading-[1.6] text-neutral-500 transition-all duration-200 ease-out peer-focus:-translate-y-[0.9rem] peer-focus:scale-[0.8] peer-focus:text-primary peer-data-[twe-input-state-active]:-translate-y-[0.9rem] peer-data-[twe-input-state-active]:scale-[0.8] motion-reduce:transition-none dark:text-neutral-200 dark:peer-focus:text-primary"
data-i18n="search"
></label>
</div>
<!--Search icon-->
<span
class="input-group-text flex items-center whitespace-nowrap rounded px-3 py-1.5 text-center text-base font-normal text-neutral-700 dark:text-neutral-200"
id="basic-addon2"
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
class="h-5 w-5"
>
<path
fill-rule="evenodd"
d="M9 3.5a5.5 5.5 0 100 11 5.5 5.5 0 000-11zM2 9a7 7 0 1112.452 4.391l3.328 3.329a.75.75 0 11-1.06 1.06l-3.329-3.328A7 7 0 012 9z"
clip-rule="evenodd"
/>
</svg>
</span>
</div>
</div>
<!-- Right elements -->
<div class="relative flex items-center">
<!-- Container with two dropdown menus -->
<div class="relative" data-twe-dropdown-ref>
<!-- First dropdown trigger -->
<a
class="hidden-arrow me-4 flex items-center text-neutral-600 transition duration-200 hover:text-neutral-700 hover:ease-in-out focus:text-neutral-700 disabled:text-black/30 motion-reduce:transition-none dark:text-neutral-200 dark:hover:text-neutral-300 dark:focus:text-neutral-300 [&.active]:text-black/90 dark:[&.active]:text-neutral-400"
href="#"
id="dropdownMenuButton1"
role="button"
data-twe-dropdown-toggle-ref
aria-expanded="false"
>
<!-- Dropdown trigger icon -->
<span class="[&>svg]:w-5">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="currentColor"
class="h-5 w-5"
>
<path
fill-rule="evenodd"
d="M5.25 9a6.75 6.75 0 0113.5 0v.75c0 2.123.8 4.057 2.118 5.52a.75.75 0 01-.297 1.206c-1.544.57-3.16.99-4.831 1.243a3.75 3.75 0 11-7.48 0 24.585 24.585 0 01-4.831-1.244.75.75 0 01-.298-1.205A8.217 8.217 0 005.25 9.75V9zm4.502 8.9a2.25 2.25 0 104.496 0 25.057 25.057 0 01-4.496 0z"
clip-rule="evenodd"
/>
</svg>
</span>
<!-- Notification counter -->
<span
class="absolute -mt-4 ms-2.5 rounded-full bg-danger px-[0.35em] py-[0.15em] text-[0.6rem] font-bold leading-none text-white"
>1</span
>
</a>
<!-- First dropdown menu -->
<ul
class="absolute left-auto right-0 w-[160px] z-[1000] float-left m-0 mt-1 hidden min-w-max list-none overflow-hidden rounded-lg border-none bg-white bg-clip-padding text-left text-base shadow-lg dark:bg-neutral-700 data-[twe-dropdown-show]:block"
aria-labelledby="dropdownMenuButton1"
data-twe-dropdown-menu-ref
>
<!-- First dropdown menu items -->
<li>
<a
class="block w-full whitespace-nowrap bg-transparent px-4 py-2 text-sm font-normal text-neutral-700 hover:bg-neutral-100 active:text-neutral-800 active:no-underline disabled:pointer-events-none disabled:bg-transparent disabled:text-neutral-400 dark:text-neutral-200 dark:hover:bg-white/30"
href="#"
data-twe-dropdown-item-ref
data-i18n="news1"
></a>
</li>
<li>
<a
class="block w-full whitespace-nowrap bg-transparent px-4 py-2 text-sm font-normal text-neutral-700 hover:bg-neutral-100 active:text-neutral-800 active:no-underline disabled:pointer-events-none disabled:bg-transparent disabled:text-neutral-400 dark:text-neutral-200 dark:hover:bg-white/30"
href="#"
data-twe-dropdown-item-ref
data-i18n="news2"
></a>
</li>
</ul>
</div>
<!-- Second dropdown container-->
<div class="relative mx-2" data-twe-dropdown-ref>
<a
class="me-4 flex items-center text-gray-500 transition duration-200 hover:text-gray-700 hover:ease-in-out focus:text-gray-700 motion-reduce:transition-none"
href="#"
id="navbarDropdown"
role="button"
data-twe-dropdown-toggle-ref
aria-expanded="false"
>
<span
class="relative inline-block h-[11px] w-4 overflow-hidden bg-gray-200 leading-[11px] decoration-inherit"
>
<span
id="selected-lang-flag"
class="inline-block h-[11px] w-4 content-[''] [background-position:-36px_-26px_!important] [background:url(https://tecdn.b-cdn.net/img/svg/flags.png)_no-repeat_-108px_-1976px]"
></span>
</span>
<span class="w-2 ps-1">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
class="h-5 w-5"
>
<path
fill-rule="evenodd"
d="M5.23 7.21a.75.75 0 011.06.02L10 11.168l3.71-3.938a.75.75 0 111.08 1.04l-4.25 4.5a.75.75 0 01-1.08 0l-4.25-4.5a.75.75 0 01.02-1.06z"
clip-rule="evenodd"
/>
</svg>
</span>
</a>
<ul
class="absolute left-auto right-0 z-[1000] float-left m-0 mt-1 hidden min-w-[10rem] list-none overflow-hidden rounded-lg border-none bg-white bg-clip-padding text-left text-base shadow-lg dark:bg-zinc-700 data-[twe-dropdown-show]:block"
aria-labelledby="navbarDropdown"
data-twe-dropdown-menu-ref
>
<li>
<a
class="block w-full whitespace-nowrap bg-transparent px-4 py-2 text-sm font-normal text-gray-700 hover:bg-gray-100 active:text-zinc-800 active:no-underline disabled:pointer-events-none disabled:bg-transparent disabled:text-gray-400 dark:text-gray-200 dark:hover:bg-white/30"
href="#"
data-twe-dropdown-item-ref
data-i18n-switcher
data-i18n-lang="en"
>
<span
class="relative me-2 inline-block h-[11px] w-4 overflow-hidden bg-gray-200 leading-[11px] decoration-inherit"
>
<span
class="inline-block h-[11px] w-4 content-[''] [background-position:-36px_-26px_!important] [background:url(https://tecdn.b-cdn.net/img/svg/flags.png)_no-repeat_-108px_-1976px]"
></span>
</span>
<span class="me-4">English</span>
</a>
</li>
<li>
<a
class="block w-full whitespace-nowrap bg-transparent px-4 py-2 text-sm font-normal text-gray-700 hover:bg-gray-100 active:text-zinc-800 active:no-underline disabled:pointer-events-none disabled:bg-transparent disabled:text-gray-400 dark:text-gray-200 dark:hover:bg-white/30"
href="#"
data-twe-dropdown-item-ref
data-i18n-switcher
data-i18n-lang="pl"
>
<span
class="relative me-2 inline-block h-[11px] w-4 overflow-hidden bg-gray-200 leading-[11px] decoration-inherit"
>
<span
class="inline-block h-[11px] w-4 content-[''] [background:url(https://tecdn.b-cdn.net/img/svg/flags.png)_no-repeat_-108px_-1976px] [background-position:-72px_-572px_!important]"
></span>
</span>
Polski</a
>
</li>
<li>
<a
class="block w-full whitespace-nowrap bg-transparent px-4 py-2 text-sm font-normal text-gray-700 hover:bg-gray-100 active:text-zinc-800 active:no-underline disabled:pointer-events-none disabled:bg-transparent disabled:text-gray-400 dark:text-gray-200 dark:hover:bg-white/30"
href="#"
data-twe-dropdown-item-ref
data-i18n-switcher
data-i18n-lang="ja"
>
<span
class="relative me-2 inline-block h-[11px] w-4 overflow-hidden bg-gray-200 leading-[11px] decoration-inherit"
>
<span
class="inline-block h-[11px] w-4 content-[''] [background:url(https://tecdn.b-cdn.net/img/svg/flags.png)_no-repeat_-108px_-1976px] [background-position:-36px_-910px_!important]"
></span>
</span>
日本語</a
>
</li>
<li>
<a
class="block w-full whitespace-nowrap bg-transparent px-4 py-2 text-sm font-normal text-gray-700 hover:bg-gray-100 active:text-zinc-800 active:no-underline disabled:pointer-events-none disabled:bg-transparent disabled:text-gray-400 dark:text-gray-200 dark:hover:bg-white/30"
href="#"
data-twe-dropdown-item-ref
data-i18n-switcher
data-i18n-lang="de"
>
<span
class="relative me-2 inline-block h-[11px] w-4 overflow-hidden bg-gray-200 leading-[11px] decoration-inherit"
>
<span
class="inline-block h-[11px] w-4 content-[''] [background:url(https://tecdn.b-cdn.net/img/svg/flags.png)_no-repeat_-108px_-1976px] [background-position:0px_-1430px_!important]"
></span>
</span>
Deutsch</a
>
</li>
</ul>
</div>
<!-- Third dropdown container -->
<div class="relative" data-twe-dropdown-ref>
<!-- Second dropdown trigger -->
<a
class="hidden-arrow flex items-center whitespace-nowrap transition duration-150 ease-in-out motion-reduce:transition-none"
href="#"
id="dropdownMenuButton2"
role="button"
data-twe-dropdown-toggle-ref
aria-expanded="false"
>
<!-- User avatar -->
<img
src="https://tecdn.b-cdn.net/img/new/avatars/2.jpg"
class="rounded-full"
style="height: 25px; width: 25px"
alt=""
loading="lazy"
/>
</a>
<!-- Second dropdown menu -->
<ul
class="absolute left-auto right-0 w-[160px] z-[1000] float-left m-0 mt-1 hidden min-w-max list-none overflow-hidden rounded-lg border-none bg-white bg-clip-padding text-left text-base shadow-lg dark:bg-neutral-700 data-[twe-dropdown-show]:block"
aria-labelledby="dropdownMenuButton2"
data-twe-dropdown-menu-ref
>
<!-- Second dropdown menu items -->
<li>
<a
class="block w-full whitespace-nowrap bg-transparent px-4 py-2 text-sm font-normal text-neutral-700 hover:bg-neutral-100 active:text-neutral-800 active:no-underline disabled:pointer-events-none disabled:bg-transparent disabled:text-neutral-400 dark:text-neutral-200 dark:hover:bg-white/30"
href="#"
data-twe-dropdown-item-ref
data-i18n="profile"
></a>
</li>
<li>
<a
class="block w-full whitespace-nowrap bg-transparent px-4 py-2 text-sm font-normal text-neutral-700 hover:bg-neutral-100 active:text-neutral-800 active:no-underline disabled:pointer-events-none disabled:bg-transparent disabled:text-neutral-400 dark:text-neutral-200 dark:hover:bg-white/30"
href="#"
data-twe-dropdown-item-ref
data-i18n="profileSettings"
></a>
</li>
<li>
<a
class="block w-full whitespace-nowrap bg-transparent px-4 w py-2 text-sm font-normal text-neutral-700 hover:bg-neutral-100 active:text-neutral-800 active:no-underline disabled:pointer-events-none disabled:bg-transparent disabled:text-neutral-400 dark:text-neutral-200 dark:hover:bg-white/30"
href="#"
data-twe-dropdown-item-ref
data-i18n="profileLogout"
></a>
</li>
</ul>
</div>
</div>
</div>
</nav>
<div class="container mx-auto px-[0.75rem]">
<div class="flex mx-auto md:pt-12 md:px-16 lg:w-[50%]">
<div
id="carouselExampleCaptions"
class="relative"
data-twe-carousel-init
data-twe-carousel-slide
>
<!--Carousel indicators-->
<div
class="absolute bottom-0 left-0 right-0 z-[2] mx-[15%] mb-4 flex list-none justify-center p-0"
data-twe-carousel-indicators
>
<button
type="button"
data-twe-target="#carouselExampleCaptions"
data-twe-slide-to="0"
data-twe-carousel-active
class="mx-[3px] box-content h-[3px] w-[30px] flex-initial cursor-pointer border-0 border-y-[10px] border-solid border-transparent bg-white bg-clip-padding p-0 -indent-[999px] opacity-50 transition-opacity duration-[600ms] ease-[cubic-bezier(0.25,0.1,0.25,1.0)] motion-reduce:transition-none"
aria-current="true"
aria-label="Slide 1"
></button>
<button
type="button"
data-twe-target="#carouselExampleCaptions"
data-twe-slide-to="1"
class="mx-[3px] box-content h-[3px] w-[30px] flex-initial cursor-pointer border-0 border-y-[10px] border-solid border-transparent bg-white bg-clip-padding p-0 -indent-[999px] opacity-50 transition-opacity duration-[600ms] ease-[cubic-bezier(0.25,0.1,0.25,1.0)] motion-reduce:transition-none"
aria-label="Slide 2"
></button>
<button
type="button"
data-twe-target="#carouselExampleCaptions"
data-twe-slide-to="2"
class="mx-[3px] box-content h-[3px] w-[30px] flex-initial cursor-pointer border-0 border-y-[10px] border-solid border-transparent bg-white bg-clip-padding p-0 -indent-[999px] opacity-50 transition-opacity duration-[600ms] ease-[cubic-bezier(0.25,0.1,0.25,1.0)] motion-reduce:transition-none"
aria-label="Slide 3"
></button>
</div>
<!--Carousel items-->
<div
class="relative w-full overflow-hidden after:clear-both after:block after:content-['']"
>
<!--First item-->
<div
class="relative float-left -me-[100%] w-full transition-transform duration-[600ms] ease-in-out motion-reduce:transition-none"
data-twe-carousel-active
data-twe-carousel-item
style="backface-visibility: hidden"
>
<img
src="https://tecdn.b-cdn.net/img/Photos/Slides/img%20(15).jpg"
class="block w-full"
alt="..."
/>
<div
class="absolute inset-x-[15%] bottom-5 hidden py-5 text-center text-white md:block"
>
<h5 class="text-xl" data-i18n="slide1Label"></h5>
<p data-i18n="slide1Description"></p>
</div>
</div>
<!--Second item-->
<div
class="relative float-left -me-[100%] hidden w-full transition-transform duration-[600ms] ease-in-out motion-reduce:transition-none"
data-twe-carousel-item
style="backface-visibility: hidden"
>
<img
src="https://tecdn.b-cdn.net/img/Photos/Slides/img%20(22).jpg"
class="block w-full"
alt="..."
/>
<div
class="absolute inset-x-[15%] bottom-5 hidden py-5 text-center text-white md:block"
>
<h5 class="text-xl" data-i18n="slide2Label"></h5>
<p data-i18n="slide2Description"></p>
</div>
</div>
<!--Third item-->
<div
class="relative float-left -me-[100%] hidden w-full transition-transform duration-[600ms] ease-in-out motion-reduce:transition-none"
data-twe-carousel-item
style="backface-visibility: hidden"
>
<img
src="https://tecdn.b-cdn.net/img/Photos/Slides/img%20(23).jpg"
class="block w-full"
alt="..."
/>
<div
class="absolute inset-x-[15%] bottom-5 hidden py-5 text-center text-white md:block"
>
<h5 class="text-xl" data-i18n="slide2Label"></h5>
<p data-i18n="slide2Description"></p>
</div>
</div>
</div>
<!--Carousel controls - prev item-->
<button
class="absolute bottom-0 left-0 top-0 z-[1] flex w-[15%] items-center justify-center border-0 bg-none p-0 text-center text-white opacity-50 transition-opacity duration-150 ease-[cubic-bezier(0.25,0.1,0.25,1.0)] hover:text-white hover:no-underline hover:opacity-90 hover:outline-none focus:text-white focus:no-underline focus:opacity-90 focus:outline-none motion-reduce:transition-none"
type="button"
data-twe-target="#carouselExampleCaptions"
data-twe-slide="prev"
>
<span class="inline-block h-8 w-8">
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="h-6 w-6"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M15.75 19.5L8.25 12l7.5-7.5"
/>
</svg>
</span>
<span
class="!absolute !-m-px !h-px !w-px !overflow-hidden !whitespace-nowrap !border-0 !p-0 ![clip:rect(0,0,0,0)]"
></span>
</button>
<!--Carousel controls - next item-->
<button
class="absolute bottom-0 right-0 top-0 z-[1] flex w-[15%] items-center justify-center border-0 bg-none p-0 text-center text-white opacity-50 transition-opacity duration-150 ease-[cubic-bezier(0.25,0.1,0.25,1.0)] hover:text-white hover:no-underline hover:opacity-90 hover:outline-none focus:text-white focus:no-underline focus:opacity-90 focus:outline-none motion-reduce:transition-none"
type="button"
data-twe-target="#carouselExampleCaptions"
data-twe-slide="next"
>
<span class="inline-block h-8 w-8">
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="h-6 w-6"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M8.25 4.5l7.5 7.5-7.5 7.5"
/>
</svg>
</span>
<span
class="!absolute !-m-px !h-px !w-px !overflow-hidden !whitespace-nowrap !border-0 !p-0 ![clip:rect(0,0,0,0)]"
></span>
</button>
</div>
</div>
<div
class="flex flex-col md:flex-row mt-5 md:pt-9 md:px-10 space-y-7 md:space-x-6 md:space-y-0"
>
<div class="mx-auto lg:w-[50%]">
<div
class="relative overflow-hidden max-w-none lg:max-w-2xl bg-cover bg-[50%] bg-no-repeat"
>
<img
src="https://mdbcdn.b-cdn.net/img/new/standard/city/053.webp"
class="w-full"
/>
<div
class="absolute bottom-0 left-0 right-0 top-0 h-full w-full overflow-hidden bg-fixed"
style="background-color: hsla(0, 0%, 0%, 0.6)"
>
<div class="flex h-full items-center justify-center">
<p class="text-white opacity-100" data-i18n="maskText"></p>
</div>
</div>
</div>
</div>
<div class="mx-auto lg:w-[50%] pb-6 md:pb-0">
<div id="accordionExample">
<div
class="rounded-t-lg border border-neutral-200 bg-white dark:border-neutral-600 dark:bg-neutral-800"
>
<h2 class="mb-0" id="headingOne">
<button
class="group relative flex w-full items-center rounded-t-[15px] border-0 bg-white px-5 py-4 text-left text-base text-neutral-800 transition [overflow-anchor:none] hover:z-[2] focus:z-[3] focus:outline-none dark:bg-neutral-800 dark:text-white [&:not([data-twe-collapse-collapsed])]:bg-white [&:not([data-twe-collapse-collapsed])]:text-primary [&:not([data-twe-collapse-collapsed])]:[box-shadow:inset_0_-1px_0_rgba(229,231,235)] dark:[&:not([data-twe-collapse-collapsed])]:bg-neutral-800 dark:[&:not([data-twe-collapse-collapsed])]:text-primary-400 dark:[&:not([data-twe-collapse-collapsed])]:[box-shadow:inset_0_-1px_0_rgba(75,85,99)]"
type="button"
data-twe-collapse-init
data-twe-target="#collapseOne"
aria-expanded="true"
aria-controls="collapseOne"
>
<span data-i18n="accordion1"></span>
<span
class="ms-auto h-5 w-5 shrink-0 rotate-[-180deg] fill-[#336dec] transition-transform duration-200 ease-in-out group-data-[twe-collapse-collapsed]:rotate-0 group-data-[twe-collapse-collapsed]:fill-[#212529] motion-reduce:transition-none dark:fill-blue-300 dark:group-data-[twe-collapse-collapsed]:fill-white"
>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="h-6 w-6"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M19.5 8.25l-7.5 7.5-7.5-7.5"
/>
</svg>
</span>
</button>
</h2>
<div
id="collapseOne"
class="!visible"
data-twe-collapse-item
data-twe-collapse-show
aria-labelledby="headingOne"
data-twe-parent="#accordionExample"
>
<div class="px-5 py-4" data-i18n="accordionTxt"></div>
</div>
</div>
<div
class="border border-t-0 border-neutral-200 bg-white dark:border-neutral-600 dark:bg-neutral-800"
>
<h2 class="mb-0" id="headingTwo">
<button
class="group relative flex w-full items-center rounded-none border-0 bg-white px-5 py-4 text-left text-base text-neutral-800 transition [overflow-anchor:none] hover:z-[2] focus:z-[3] focus:outline-none dark:bg-neutral-800 dark:text-white [&:not([data-twe-collapse-collapsed])]:bg-white [&:not([data-twe-collapse-collapsed])]:text-primary [&:not([data-twe-collapse-collapsed])]:[box-shadow:inset_0_-1px_0_rgba(229,231,235)] dark:[&:not([data-twe-collapse-collapsed])]:bg-neutral-800 dark:[&:not([data-twe-collapse-collapsed])]:text-primary-400 dark:[&:not([data-twe-collapse-collapsed])]:[box-shadow:inset_0_-1px_0_rgba(75,85,99)]"
type="button"
data-twe-collapse-init
data-twe-collapse-collapsed
data-twe-target="#collapseTwo"
aria-expanded="false"
aria-controls="collapseTwo"
>
<span data-i18n="accordion2"></span>
<span
class="-me-1 ms-auto h-5 w-5 shrink-0 rotate-[-180deg] fill-[#336dec] transition-transform duration-200 ease-in-out group-data-[twe-collapse-collapsed]:me-0 group-data-[twe-collapse-collapsed]:rotate-0 group-data-[twe-collapse-collapsed]:fill-[#212529] motion-reduce:transition-none dark:fill-blue-300 dark:group-data-[twe-collapse-collapsed]:fill-white"
>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="h-6 w-6"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M19.5 8.25l-7.5 7.5-7.5-7.5"
/>
</svg>
</span>
</button>
</h2>
<div
id="collapseTwo"
class="!visible hidden"
data-twe-collapse-item
aria-labelledby="headingTwo"
data-twe-parent="#accordionExample"
>
<div class="px-5 py-4" data-i18n="accordionTxt"></div>
</div>
</div>
<div
class="rounded-b-lg border border-t-0 border-neutral-200 bg-white dark:border-neutral-600 dark:bg-neutral-800"
>
<h2 class="accordion-header mb-0" id="headingThree">
<button
class="group relative flex w-full items-center border-0 bg-white px-5 py-4 text-left text-base text-neutral-800 transition [overflow-anchor:none] hover:z-[2] focus:z-[3] focus:outline-none dark:bg-neutral-800 dark:text-white [&:not([data-twe-collapse-collapsed])]:bg-white [&:not([data-twe-collapse-collapsed])]:text-primary [&:not([data-twe-collapse-collapsed])]:[box-shadow:inset_0_-1px_0_rgba(229,231,235)] dark:[&:not([data-twe-collapse-collapsed])]:bg-neutral-800 dark:[&:not([data-twe-collapse-collapsed])]:text-primary-400 dark:[&:not([data-twe-collapse-collapsed])]:[box-shadow:inset_0_-1px_0_rgba(75,85,99)] data-[twe-collapse-collapsed]:rounded-b-[15px] data-[twe-collapse-collapsed]:transition-none"
type="button"
data-twe-collapse-init
data-twe-collapse-collapsed
data-twe-target="#collapseThree"
aria-expanded="false"
aria-controls="collapseThree"
>
<span data-i18n="accordion3"></span>
<span
class="-me-1 ms-auto h-5 w-5 shrink-0 rotate-[-180deg] fill-[#336dec] transition-transform duration-200 ease-in-out group-data-[twe-collapse-collapsed]:me-0 group-data-[twe-collapse-collapsed]:rotate-0 group-data-[twe-collapse-collapsed]:fill-[#212529] motion-reduce:transition-none dark:fill-blue-300 dark:group-data-[twe-collapse-collapsed]:fill-white"
>
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="h-6 w-6"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M19.5 8.25l-7.5 7.5-7.5-7.5"
/>
</svg>
</span>
</button>
</h2>
<div
id="collapseThree"
class="!visible hidden"
data-twe-collapse-item
aria-labelledby="headingThree"
data-twe-parent="#accordionExample"
>
<div class="px-5 py-4" data-i18n="accordionTxt"></div>
</div>
</div>
</div>
<!-- Remove this button if you are using the TWE Free -->
<button
id="popconfirm"
type="button"
data-twe-ripple-init
data-twe-ripple-color="light"
class="inline-block rounded bg-primary mb-4 mt-4 px-6 pb-2 pt-2.5 text-xs font-medium uppercase leading-normal text-white shadow-[0_4px_9px_-4px_#3b71ca] transition duration-150 ease-in-out hover:bg-primary-600 hover:shadow-[0_8px_9px_-4px_rgba(59,113,202,0.3),0_4px_18px_0_rgba(59,113,202,0.2)] focus:bg-primary-600 focus:shadow-[0_8px_9px_-4px_rgba(59,113,202,0.3),0_4px_18px_0_rgba(59,113,202,0.2)] focus:outline-none focus:ring-0 active:bg-primary-700 active:shadow-[0_8px_9px_-4px_rgba(59,113,202,0.3),0_4px_18px_0_rgba(59,113,202,0.2)] dark:shadow-[0_4px_9px_-4px_rgba(59,113,202,0.5)] dark:hover:shadow-[0_8px_9px_-4px_rgba(59,113,202,0.2),0_4px_18px_0_rgba(59,113,202,0.1)] dark:focus:shadow-[0_8px_9px_-4px_rgba(59,113,202,0.2),0_4px_18px_0_rgba(59,113,202,0.1)] dark:active:shadow-[0_8px_9px_-4px_rgba(59,113,202,0.2),0_4px_18px_0_rgba(59,113,202,0.1)]"
>
Popconfirm
</button>
</div>
</div>
</div>
</div>
<footer
class="bg-neutral-200 text-center dark:bg-neutral-700 lg:text-left mt-auto"
>
<div class="p-4 text-center text-neutral-700 dark:text-neutral-200">
© 2023 Copyright:
<a
class="text-neutral-800 dark:text-neutral-400"
href="https://tw-elements.com/"
>TW elements</a
>
</div>
</footer>
<script type="module" src="/main.js"></script>
</body>
Frontend features
TW Elements plugin:
To create the project we used TW elements plugin, with which we can build basic views very quickly.
Views and Layouts:
In this project:
- We successfully integrated i18next, a very popular internationalization framework. into the Tailwind Elements 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 theTranslator
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.