Internationalization (i18n) and localization (l10n) often feel like “later” problems — right up until they’re not.
As developers, we’ve all done something like this:
<button>Order now</button>
Or buried inside a template:
<p>Welcome back, {{ user.name }}!</p>
It works fine — until your product team says,
“We’re launching in Japan, Germany, and the Middle East next quarter.”
Suddenly, every hardcoded string turns into technical debt. Engineers scramble to extract text, designers fight broken layouts, QA chases missing translations, and releases slip.
That’s the moment you stop and ask: So, what’s i18n vs l10n — and why is it critical?
In short:
Internationalization (i18n) is how you prepare your app to support multiple languages.
Localization (l10n) is how you adapt your app to each language and culture.
Understanding the difference is key to building global-ready products — so let’s break it down with real examples and developer-friendly guidance.
Hi! I’m a developer with deep experience in building scalable applications, including time in the game development industry at companies like Plarium, known for MMO RPG titles where localization is critical. Every project I worked on — from games to enterprise apps — had to support internationalization (i18n) and localization (l10n) from the ground up.
Now, I’m using that experience to improve how apps are localized. I’m building an AI-powered service that enhances localization pipelines, and I’ve seen firsthand how getting i18n and l10n right can accelerate global growth — or become a blocker.
In this post, we’ll explore:
- The difference between i18n and l10n (with real examples)
- Best practices to future-proof your codebase
- How AI can automate and streamline localization at scale
What is i18n? (Internationalization)
Internationalization is the process of designing and building your software so it can support multiple languages, regions, and cultural formats without needing code changes later.
Think of it as laying the groundwork that makes your app ready for any language — the logic, structure, and flexibility needed to support global users.
1. Extract All User-Facing Strings
Hardcoded strings are your biggest enemy when preparing an app for localization. They hide in your templates and components, making translation painful and error-prone. That’s why the first step in proper internationalization is extracting all user-facing text into structured files — most commonly JSON.
JSON has become the de facto format for localization in web development, especially with JavaScript-based frameworks like React, Angular, and Vue. It’s simple, lightweight, easy to version in Git, and works well with popular i18n libraries.
But more importantly, JSON supports nested keys, allowing you to organize translations in a way that mirrors your application’s structure.
{
"auth": {
"login": {
"title": "Sign in to your account",
"submit": "Log in",
"forgot": "Forgot your password?",
"errors": {
"required": "This field is required",
"invalid": "Invalid email or password"
}
}
}
}
🧑💻 In Code (React + i18next):
import { useTranslation } from 'react-i18next';
const LoginForm = () => {
const { t } = useTranslation();
return (
<form>
<h1>{t('auth.login.title')}</h1>
<input placeholder={t('auth.login.forgot')} />
<button>{t('auth.login.submit')}</button>
</form>
);
};
This way, your components remain language-agnostic — and localization becomes a plug-and-play process.
You can use tools like i18next-parser (for React/Vue) or ngx-translate-extract (for Angular) to automatically extract strings.
Why i18next?
Among many i18n libraries, i18next stands out as the most widely adopted in the JavaScript ecosystem — for good reason:
- Works seamlessly with React, Vue, Angular, Next.js, and others
- Supports namespaces, lazy loading, pluralization, context, and interpolation
- Built-in support for JSON format with nested keys
- Actively maintained with a large community and ecosystem (e.g., react-i18next, i18next-parser, i18next-browser-languagedetector)
- Compatible with AI-powered translation workflows and external services (like l10n.dev 😉)
In contrast, formats like .po, .xliff, or .properties are more common in older or enterprise environments and may require additional tooling or conversion layers.
2. Use Meaningful Keys — Not Sentences or Raw IDs
When defining your translation keys, avoid using full text strings or generic IDs. Instead, use descriptive, meaningful keys that reflect the context and structure of your application.
✅ Good:
"form.error.required": "This field is required"
🚫 Bad:
"This field is required": "Ce champ est requis"
🚫 Also Bad:
"msg_001": "This field is required"
Why it matters:
- Reusable: A key like form.error.required can be used across multiple forms without duplication.
- Maintainable: If the English text changes, the key stays the same — so translations don’t need to be redone.
- Clear: Developers and translators can quickly understand the purpose of the key from its name.
- Stable: Avoids unnecessary changes in translation files and version control noise.
Use dot notation to reflect app structure (form.login.button) and avoid long, flat key files.
3. Split into Namespaces — Avoid One Giant JSON File
As your app grows, so do your translations. Splitting them into namespaces (e.g., auth.json, dashboard.json, common.json) improves:
- Maintainability
- Load time (you can lazy-load only needed namespaces)
- Collaboration (teams can own parts of the translation structure)
// locales/en/auth.json
{
"login.title": "Sign in to your account"
}
i18next.use(initReactI18next).init({
ns: ['auth', 'common'],
defaultNS: 'common',
});
What Is Localization (l10n)?
Localization isn’t just about translating words — it’s about adapting your entire user interface to feel native in another language and culture. That includes grammar, plural rules, date formats, name conventions, and even tone.
So why do the next practicies matter?
Because if developers don’t structure things properly — like using plural forms, contexts, or interpolation — translators can’t do their job well. The result? Awkward translations, broken layouts, or content that simply doesn’t make sense to your users.
Even the best AI translator or localization team can’t fix a poor foundation. You need to give them the right keys, metadata, and flexibility — and that’s your job during i18n.
4. Interpolation — Insert Dynamic Values into Translations
Interpolation lets you inject dynamic data (like user names, numbers, and dates) into your translated strings without hardcoding them. This keeps translations reusable, secure, and easier to maintain.
✅ Good Example:
{
"welcome": "Welcome, {{name}}!",
"order_summary": "Total: {{amount}} {{currency}}"
}
In your React code:
t('welcome', { name: 'Anton' }) // → Welcome, Anton!
t('order_summary', { amount: 25, currency: 'USD'}) // → Total: 25 USD
🔐 i18next escapes all values by default to protect against XSS. You can disable it if needed with:
{ interpolation: { escapeValue: false } }
🚫 What Should Not Be Translated
When preparing your app for localization, it’s just as important to know what not to include in your translatable strings:
- ❌ Dates and times should not be translated manually — instead, format them to user’s locale at runtime.
- ❌ Dynamic data like numbers, prices, and currencies should use interpolation. Translators should never rewrite actual values like 25 USD.
- ❌ Proper names (e.g. names, brands, company names) should stay intact.
- ✅ Only historical or well-known proper names (e.g., “Christopher Columbus”) may be translated. Converting the name from one script to another, for example: Steve Jobs -> Cтив Джобс
How l10n.dev Handles This
l10n.dev translates i18n JSON files with AI that understands context — it intelligently preserves placeholders like {{name}}, {{count}}, and {{amount}}, and avoids awkward or literal translations that are common with basic machine translation.
- Understands placeholder meaning and keeps dynamic data intact.
- Automatically converts date and number formats to match the target locale — especially useful when translating historical or factual content like “Founded on July 4, 1776” → “Основан 4 июля 1776 г.”.
- Knows what not to translate, like user names, currencies, and runtime values, reducing risk and saving time.
- Ensures translations feel natural and localized, not robotic or confusing.
This makes l10n.dev a smarter choice for developers — giving you production-ready translations that respect both your code and your users.
5. Handle Plural Forms Properly
Different languages treat plurals differently. English is simple:
{
"days_count_one": "{{count}} day",
"days_count_other": "{{count}} days"
}
But Slavic languages like Russian, Polish, and Ukrainian require 3 forms:
- one → 1 день
- few → 2 дня, 3 дня, 4 дня
- other → 0 дней, 5 дней, 11 дней, etc.
✅ No need to worry — i18next handles these rules for you. You just define the forms in your translation files, and i18next picks the right one based on the current language and count value.
Example:
{
"days_count_one": "{{count}} день",
"days_count_few": "{{count}} дня",
"days_count_other": "{{count}} дней"
}
And in code:
t('days_count', { count: 3 })
That’s it — i18next figures out the rest
Of course, developers aren’t translators — so it’s perfectly normal to have only _one and _other for English. But when your app is translated into languages with complex plural rules, the localization team should expand those keys as needed (e.g., add _few, _many, etc.).
In theory, the localization team should handle this.
But in reality, many translators are native speakers with no knowledge of i18next or plural formatting logic. If they see two keys in the source, they’ll likely return just two — even when the target language requires more.
This creates bugs or incorrect grammar in your app.
✅ That’s why l10n.dev, an AI-powered i18n translation service, automatically adds all required plural forms for the target language during translation. So even if your source file is minimal, the translated result is fully structured — and your app works correctly in every locale, with no guesswork or missing forms.
6. Context — Handling gender, tone, or grammatical cases
Context is used when a translation changes based on a situation — like gender, formal/informal speech, or other grammatical variations.
Example: Gender-based context
{
"user_info": "User info",
"user_info_male": "His info",
"user_info_female": "Her info"
}
In code:
t('user_info', { context: 'male' }) // → His info
t('user_info', { context: 'female' }) // → Her info
This works by appending _contextValue to the base key. i18next will automatically resolve the correct variant.
Common use cases for context:
- Gender (he/she/they)
- Formal vs informal (_formal, _informal)
- Language-specific word forms (e.g., in Slavic languages)
- Role-based phrasing (admin vs user)
You can combine both
{
"greeting": "Hello, {{name}}!",
"greeting_formal": "Good day, {{name}}!"
}
t('greeting', { name: 'Anton', context: 'formal' })
// → Good day, Anton!
Why This Matters
- Interpolation keeps your UI dynamic and localized
- Context handles linguistic nuances correctly
- Both help translators deliver accurate, natural-sounding results — without complex logic in your code
And yes — i18next handles all the fallback logic for you, so you don’t have to worry if a context variant is missing.
How AI Makes It Easier
AI-powered localization service like l10n.dev take the complexity out of localization by:
- Translating with context awareness, not just word-for-word.
- Preserving placeholders and interpolations ({{count}}, {{name}}, etc.) so translations remain functional.
- Automatically adjusting date/number formats to the target locale.
- Ensuring proper pluralization, even for complex languages like Russian, Polish, or Arabic.
- Respecting original key ordering in files for cleaner diffs and easier reviews.
- Letting you translate only new strings, so you don’t retranslate your whole app every time.
- Keeping html, tabs, line breaks in translated text.
All you need to do is follow best practices for i18n — and let AI handle the rest.
Thanks for reading!
Top comments (0)