Most developers live with a curious paradox. We write APIs, set up Docker, debate microservice architecture — yet we hardcode strings directly into the UI:
h1.innerText = "Home";
Then the client says: "We need an Arabic version too."
And suddenly the codebase shatters like glass.
That is where i18n enters the stage.
What does i18n mean?
i18n is shorthand for "internationalization" — there are 18 letters between the "i" and the "n".
But technically, i18n is not just about that.
i18n = Separating text from code and managing language at the system level.
This is not a translation mechanism.
This is an architectural decision.
Why do you need i18n?
If you want to:
- Scale your product
- Do SEO properly
- Target global users
- Add more languages in the future
then i18n is no longer optional.
The hardcoded string problem
Writing this kind of code is easy:
if (lang === "en") {
title.innerText = "Projects";
} else {
title.innerText = "Proyektlər";
}
It works. But it is structurally weak.
- 10 strings → manageable
- 100 strings → messy
- 1000 strings → nightmare
The purpose of i18n is to free your code from language-related changes.
Core i18n logic
The principle is simple:
- Assign keys to UI strings
- Have a separate file per language
- The system replaces each key with the correct translation
A simple JS i18n system
1. Language files
/lang/en.json
{
"nav.home": "Home",
"nav.projects": "Projects",
"hero.title": "Privacy & AI Solutions",
"hero.subtitle": "Scalable digital infrastructure"
}
/lang/az.json
{
"nav.home": "Ana səhifə",
"nav.projects": "Layihələr",
"hero.title": "Privacy & AI Solutions",
"hero.subtitle": "Scalable digital infrastructure"
}
2. HTML
data-i18n="hero.title">h1>
data-i18n="hero.subtitle">p>
3. JS Loader
let currentLang = localStorage.getItem("lang") || "en";
let translations = {};
async function loadLanguage(lang) {
const response = await fetch(`/lang/${lang}.json`);
translations = await response.json();
applyTranslations();
}
function applyTranslations() {
document.querySelectorAll("[data-i18n]").forEach(el => {
const key = el.getAttribute("data-i18n");
el.innerText = translations[key] || key;
});
}
function changeLanguage(lang) {
localStorage.setItem("lang", lang);
loadLanguage(lang);
}
loadLanguage(currentLang);
That is all. No framework. No premium plugin.
Server-Side i18n with PHP
If your backend is PHP, you can handle this on the server side too.
/lang/en.php
"Home",
"nav.projects" => "Projects",
"hero.title" => "Privacy & AI Solutions"
];
index.php
$lang = $_GET['lang'] ?? 'az';
$translations = require "lang/$lang.php";
function t($key) {
global $translations;
return $translations[$key] ?? $key;
}
?>
The server renders it. Much better for SEO.
SEO and Multi-language Routing
The correct approach:
site.com/az site.com/en site.com/ru
Why? Because:
- Google indexes each as a separate page
- You can use
hreflang - Each language can have a unique meta title
This is no longer just translation. This is SEO architecture.
The hard part: Dynamic strings
Simple strings are easy. The complexity comes with interpolation and pluralization.
{"cart.items": "You have {count} items in your cart"
}
JS interpolation:
function translate(key, vars = {}) {
let text = translations[key] || key;
Object.keys(vars).forEach(v => {
text = text.replace(`{${v}}`, vars[v]);
});
return text;
}
translate("cart.items", { count: 3 });
Deeper challenges
i18n is not just about text. It also covers:
- Date formats
- Currency formats
- Time formats
- RTL languages (e.g., Arabic)
- Plural rules
For currency formatting:
new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD'
}).format(1200);
This is where true internationalization begins.
The biggest mistake
Adding i18n as an afterthought.
If you build a project for one language and add i18n later, you will have to refactor all the code.
The right approach: Separate strings from the very beginning.
Why do people get paid for this?
Because people see it as "translation." But i18n is:
- Structural design
- Component architecture
- Scalability
- Prevention of technical debt
This is a code quality indicator.
i18n = Product-level thinking
If a product is built for one language, it is a local project.
If a product is built with i18n:
- It can scale
- It is ready for investment
- It is ready to go international
This is no longer "string swapping." This is system design.
Conclusion
i18n is not complex. It just requires systematic thinking. Every developer can do it.
The key points:
- Separate your strings
- Build the structure
- Plan routing
- Consider SEO
- Think about future language additions
Everything else is implementation.
Programming is often not about algorithms. It is about building the right structure.
i18n is one of those structures.