Data-i18n for Small Web Projects: A Complete Guide to Internationalization

Modern web applications are used by people across different countries and languages. If your website only supports one language, it limits your audience. This is where internationalization (i18n) comes in.
In this blog, we will explore how to implement a simple and powerful data-i18n system in a small web project using HTML, CSS, and JavaScript, without any heavy frameworks.
What is Internationalization (i18n)?
Internationalization (i18n) is the process of designing your application so it can easily support multiple languages and regions.
The abbreviation i18n means:
i → first letter
18 → number of letters between
n → last letter
Example:
internationalization
i + 18 letters + n
Internationalization allows applications to switch between languages like:
English
French
Spanish
Hindi
Japanese
without rewriting the entire application.
What is data-i18n?
data-i18n is a custom HTML attribute used to mark elements that should be translated.
Instead of hardcoding text in HTML, we assign a translation key.
Example:
<h1 data-i18n="title">Welcome</h1>
<p data-i18n="description">This is a demo app.</p>
Here:
titledescription
are translation keys, not the actual translation.
JavaScript will dynamically replace the text based on the selected language.
How the System Works
The data-i18n system usually follows these steps:
HTML contains translation keys
Translations are stored in JSON files
JavaScript loads the selected language
JavaScript replaces the text dynamically
Flow diagram:
HTML (data-i18n keys)
↓
Translation JSON files
↓
JavaScript loads selected language
↓
Text updated dynamically
Step 1: Create the HTML Structure
First, we mark the text that needs translation.
<!DOCTYPE html>
<html>
<head>
<title>i18n Demo</title>
</head>
<body>
<h1 data-i18n="title">Welcome</h1>
<p data-i18n="description">
This is a demo application.
</p>
<button data-i18n="login">Login</button>
<div>
<button onclick="loadLanguage('en')">English</button>
<button onclick="loadLanguage('fr')">French</button>
</div>
<script src="script.js"></script>
</body>
</html>
Notice that we still include default text so the page does not appear empty before JavaScript loads.
Step 2: Create Translation Files
Create a language folder.
project
│
├── index.html
├── script.js
│
└── lang
├── en.json
└── fr.json
English Translation
lang/en.json
{
"title": "Welcome",
"description": "This is a demo application.",
"login": "Login"
}
French Translation
lang/fr.json
{
"title": "Bienvenue",
"description": "Ceci est une application de démonstration.",
"login": "Connexion"
}
Each key must match the data-i18n value.
Step 3: JavaScript Translation Engine
Now we create a function that loads the selected language and updates the page.
script.js
async function loadLanguage(lang) {
const response = await fetch(`./lang/${lang}.json`);
const translations = await response.json();
const elements = document.querySelectorAll("[data-i18n]");
elements.forEach(element => {
const key = element.getAttribute("data-i18n");
if (translations[key]) {
element.textContent = translations[key];
}
});
}
This script does three things:
Loads the translation JSON file
Finds all elements with
data-i18nReplaces the text with the translated value
Step 4: Language Switching
We can switch languages using buttons.
<button onclick="loadLanguage('en')">English</button>
<button onclick="loadLanguage('fr')">French</button>
When clicked:
loadLanguage("en") → loads English
loadLanguage("fr") → loads French
The page text updates instantly.
Step 5: Saving Language Preference
In real applications we usually save the user's preferred language.
We can use localStorage.
Example:
async function loadLanguage(lang) {
localStorage.setItem("language", lang);
const response = await fetch(`./lang/${lang}.json`);
const translations = await response.json();
document.querySelectorAll("[data-i18n]").forEach(el => {
const key = el.dataset.i18n;
if (translations[key]) {
el.textContent = translations[key];
}
});
}
Auto Load Language on Page Start
window.addEventListener("DOMContentLoaded", () => {
const savedLang = localStorage.getItem("language") || "en";
loadLanguage(savedLang);
});
Now the site remembers the user's language.
Step 6: Translating Attributes
Sometimes we need to translate attributes like:
placeholders
titles
aria labels
Example:
<input type="text" data-i18n-placeholder="search" placeholder="Search">
Translation file:
{
"search": "Search..."
}
JavaScript:
document.querySelectorAll("[data-i18n-placeholder]").forEach(el => {
const key = el.getAttribute("data-i18n-placeholder");
el.placeholder = translations[key];
});
Performance Considerations
For small projects this approach is perfect, but large applications may require optimization.
Possible improvements:
Lazy loading languages
Load only the language being used.
Caching translations
Store translations in memory to avoid repeated network requests.
Preloading common languages
Load frequently used languages on startup.
Advantages of the data-i18n Approach
Simple implementation
No frameworks required.
Clean HTML
Translation logic stays separate from markup.
Lightweight
Only a few lines of JavaScript.
Easy to maintain
Translators can edit JSON files without touching code.
Flexible
Works with any frontend stack.
Limitations
Although useful, the data-i18n approach has some limitations.
No pluralization rules
Languages like Russian or Arabic require complex plural logic.
No date or number formatting
Formatting currencies and dates requires additional libraries.
No nested translations
Large projects often require structured translations.
When to Use a Full i18n Library
If your project becomes large, consider using a professional library like:
i18next
react-i18next
vue-i18n
These tools support:
pluralization
date formatting
dynamic translations
server side rendering
language detection
Example Use Cases
The data-i18n pattern works well for:
Landing pages
SaaS dashboards
Documentation sites
Small web apps
Portfolio websites
MVP products
For example, if you build a multi-language SaaS dashboard, you can translate UI labels like:
Dashboard
Analytics
Settings
Logout
without changing the layout.
Conclusion
Internationalization is an important feature for modern web applications. Even small projects benefit from supporting multiple languages.
The data-i18n approach provides a lightweight and flexible solution for implementing translations using simple HTML attributes and JSON files.
With just a small amount of JavaScript, you can create a system that:
dynamically switches languages
loads translations from JSON files
remembers user preferences
keeps HTML clean and maintainable
As your application grows, you can later migrate to advanced libraries while keeping the same translation structure.



