The Confusion
Developers hear these terms thrown around:
- "We need to globalize the app"
- "Add localization for Europe"
- "Is the codebase internationalized?"
What's the difference? Are they the same thing?
No. They're related but distinct. Here's the simple breakdown:
| Term | Abbreviation | Meaning | Who Does It |
|---|---|---|---|
| Internationalization | i18n | Design your app to support multiple languages | Developers |
| Localization | L10n | Translate content for specific markets | Translators |
| Globalization | G11n | The overall strategy | Product + Marketing |
Think of it this way:
- i18n = Building a car with modular steering (left or right)
- L10n = Installing left-hand steering for UK, right-hand for US
- G11n = Deciding which countries to sell cars in
Let's break each one down.
Internationalization (i18n)
Definition: Designing software architecture to support multiple languages and regions without code changes.
This is what developers do before translating anything.
What i18n Includes
1. Extract user-facing text from code:
JSX1// ❌ Not internationalized 2<button>Save</button> 3 4// ✅ Internationalized 5<button>{t('save')}</button>
2. Support different data formats:
- Dates: US (MM/DD/YYYY) vs Europe (DD/MM/YYYY) vs ISO (YYYY-MM-DD)
- Numbers: 1,234.56 (US) vs 1.234,56 (Germany)
- Currencies: $99 vs 99€ vs ¥100
3. Handle text directionality:
- Left-to-right (LTR): English, Spanish, French
- Right-to-left (RTL): Arabic, Hebrew, Persian
4. Support pluralization:
- English: 1 item, 2 items (2 forms)
- Polish: 1 przedmiot, 2 przedmioty, 5 przedmiotów (3 forms)
- Arabic: 0, 1, 2, 3-10, 11-99, 100+ (6 forms!)
5. Allow text expansion:
- German is ~30% longer than English
- UI must accommodate: "Save" → "Speichern" (10 chars vs 4)
i18n in Practice
React example:
TSX1import { useTranslation } from 'react-i18next'; 2import { useFormatter } from '@/lib/intl'; 3 4function ProductCard({ product }) { 5 const { t } = useTranslation('common'); 6 const { formatCurrency, formatDate } = useFormatter(); 7 8 return ( 9 <div> 10 <h3>{product.name}</h3> 11 {/* Internationalized string */} 12 <p>{t('product.price_label')}: {formatCurrency(product.price)}</p> 13 14 {/* Internationalized date */} 15 <time>{formatDate(product.releaseDate)}</time> 16 17 {/* Internationalized plural */} 18 <span>{t('product.reviews', { count: product.reviewCount })}</span> 19 </div> 20 ); 21}
Translation files:
JSON1// en.json 2{ 3 "product": { 4 "price_label": "Price", 5 "reviews": "{{count}} review", 6 "reviews_plural": "{{count}} reviews" 7 } 8} 9 10// de.json 11{ 12 "product": { 13 "price_label": "Preis", 14 "reviews": "{{count}} Bewertung", 15 "reviews_plural": "{{count}} Bewertungen" 16 } 17}
You do internationalization once. Then you can add languages without touching code.
Localization (L10n)
Definition: Adapting content for a specific locale (language + region).
This is what translators do. But it's more than just translation.
What L10n Includes
1. Translation (obvious)
- "Save" → "Guardar" (Spanish)
- "Hello" → "你好" (Chinese)
2. Cultural adaptation
- Colors: White = purity (West), mourning (China)
- Images: Showing skin in conservative markets
- Icons: Thumbs up 👍 offensive in Middle East
- Examples: Use local names, not "John Smith"
3. Regional formatting
- Currency: $ in US, € in Europe, £ in UK (even though all English)
- Addresses: US has ZIP, UK has postcode
- Phone numbers: Different formats globally
4. Legal/compliance
- GDPR for EU
- Different privacy policies per region
- Terms of service variations
5. Content changes
- Product names: Coca-Cola is 可口可乐 (Kěkǒukělè, "tasty fun") in Chinese
- Slogans adapted, not translated literally
- Different features per market (WeChat Pay for China, not US)
L10n Example: Spanish for Spain vs Mexico
Same language, different locale:
| String | Spain (es-ES) | Mexico (es-MX) |
|---|---|---|
| "Computer" | "Ordenador" | "Computadora" |
| "Car" | "Coche" | "Carro" |
| "You (formal)" | "Usted" | "Tú" (more casual) |
| Currency | € (Euro) | $ (Peso) |
You need separate es-ES.json and es-MX.json files.
L10n in Code
TSX1// Load locale-specific content 2import { getLocale } from 'next-intl/server'; 3 4async function getLocalizedContent() { 5 const locale = await getLocale(); 6 7 // Different content per locale 8 const pricing = { 9 'en-US': { currency: 'USD', price: 99 }, 10 'en-GB': { currency: 'GBP', price: 79 }, 11 'es-ES': { currency: 'EUR', price: 89 }, 12 'es-MX': { currency: 'MXN', price: 1799 } 13 }; 14 15 return pricing[locale]; 16}
When L10n Goes Wrong
Pepsi in China:
- English slogan: "Come alive with Pepsi"
- Chinese translation: "Pepsi brings your ancestors back from the dead"
- Epic fail.
KFC in China:
- English: "Finger-lickin' good"
- Chinese: "Eat your fingers off"
- Also epic fail.
L10n isn't just translation; it's cultural adaptation.
Globalization (G11n)
Definition: The overall business strategy of going global.
This is what product and marketing teams do.
G11n Includes
1. Market research
- Which countries to target?
- What's the TAM (total addressable market)?
- Local competition?
2. Product adaptation
- Do we need different features per region?
- Should we remove features that won't work locally?
3. Distribution strategy
- How do we reach customers? (App stores, web, partnerships)
- Payment methods: Credit cards (US), Alipay (China), Paytm (India)
- Marketing channels: Google Ads (global), Baidu (China), Yandex (Russia)
4. Compliance
- GDPR (EU)
- CCPA (California)
- Data residency requirements (Russia, China)
5. Operations
- Local customer support
- Localized documentation
- Regional offices
G11n Example: Uber
Uber's globalization strategy:
- Core product: Same worldwide (ride-hailing)
- Localized features: Cash payments in India (low credit card adoption)
- Localized pricing: Dynamic, per city
- Localized partners: Different driver onboarding per region
- Localized marketing: Different campaigns per country
They didn't just translate the app; they adapted their business model.
How They Work Together
Here's a real-world flow:
Phase 1: Internationalize (Developers)
Terminal1# Month 1-2 2- Extract all hardcoded strings 3- Use i18n library (react-i18next, next-intl) 4- Support date/number/currency formatting 5- Test with RTL languages 6- Handle text expansion
Output: App is ready for translation. Still only in English, but architected for multi-language.
Phase 2: Localize (Translators)
Terminal1# Month 3 2- Translate strings to Spanish, French, German 3- Adapt images/icons for cultural fit 4- Localize marketing copy (not literal translation) 5- Legal team adapts Terms of Service
Output: App now works in 4 languages.
Phase 3: Globalize (Business)
Terminal1# Month 4-6 2- Launch in target markets 3- Local marketing campaigns 4- Partner with local payment providers 5- Set up regional customer support 6- Monitor and iterate
Output: You're selling globally.
Glocalization (The Modern Approach)
Glocalization = Globalization + Localization
Think globally, act locally.
Examples
McDonald's:
- Global brand, global menu core (Big Mac)
- Local adaptations: McSpicy Paneer (India), Teriyaki Burger (Japan)
Netflix:
- Global platform, global content library
- Local content: Korean dramas, Spanish series, French films
- Local pricing: ₹199/month (India) vs $15.99/month (US)
Airbnb:
- Global platform
- Local regulations: Different rules per city
- Local payment methods: Alipay, local bank transfers
Glocalization in Code
TSX1// Feature flags per market 2const features = { 3 'us': { paymentMethods: ['card', 'paypal'], showTipping: true }, 4 'cn': { paymentMethods: ['alipay', 'wechat'], showTipping: false }, 5 'in': { paymentMethods: ['card', 'upi', 'paytm'], showTipping: false } 6}; 7 8function CheckoutPage() { 9 const locale = useLocale(); 10 const marketFeatures = features[locale.split('-')[1].toLowerCase()]; 11 12 return ( 13 <div> 14 {marketFeatures.paymentMethods.map(method => ( 15 <PaymentOption key={method} type={method} /> 16 ))} 17 {marketFeatures.showTipping && <TipSelector />} 18 </div> 19 ); 20}
Technical Implementation
Let's build a properly i18n'd, localized, and globalized React app.
Step 1: Internationalization (Code Architecture)
Terminalnpm install react-i18next i18next
TSX1// i18n.ts 2import i18n from 'i18next'; 3import { initReactI18next } from 'react-i18next'; 4 5i18n.use(initReactI18next).init({ 6 resources: { 7 en: { translation: require('./locales/en.json') }, 8 es: { translation: require('./locales/es.json') }, 9 de: { translation: require('./locales/de.json') } 10 }, 11 lng: 'en', 12 fallbackLng: 'en', 13 interpolation: { escapeValue: false } 14}); 15 16export default i18n;
TSX1// Component with proper i18n 2import { useTranslation } from 'react-i18next'; 3 4function HomePage() { 5 const { t, i18n } = useTranslation('common'); 6 7 // Internationalized: supports any language 8 return ( 9 <div dir={i18n.dir()}> {/* RTL support */} 10 <h1>{t('home.title')}</h1> 11 <p>{t('home.subtitle', { name: 'User' })}</p> 12 13 {/* Date formatting */} 14 <time>{new Intl.DateTimeFormat(i18n.language).format(new Date())}</time> 15 16 {/* Number formatting */} 17 <span>{new Intl.NumberFormat(i18n.language).format(1234567)}</span> 18 </div> 19 ); 20}
Step 2: Localization (Content)
Create locale-specific content:
JSON1// locales/en-US.json 2{ 3 "home": { 4 "title": "Welcome to Our App", 5 "cta": "Get Started Free", 6 "features": { 7 "speed": "Lightning fast", 8 "security": "Bank-level security" 9 } 10 }, 11 "pricing": { 12 "currency": "USD", 13 "monthly": "$99/month" 14 } 15}
JSON1// locales/en-GB.json 2{ 3 "home": { 4 "title": "Welcome to Our App", 5 "cta": "Get Started Free", // Same as US 6 "features": { 7 "speed": "Lightning fast", 8 "security": "Bank-level security" 9 } 10 }, 11 "pricing": { 12 "currency": "GBP", 13 "monthly": "£79/month" // Different price/currency 14 } 15}
JSON1// locales/es-MX.json 2{ 3 "home": { 4 "title": "Bienvenido a Nuestra App", 5 "cta": "Comienza Gratis", 6 "features": { 7 "speed": "Increíblemente rápido", 8 "security": "Seguridad de nivel bancario" 9 } 10 }, 11 "pricing": { 12 "currency": "MXN", 13 "monthly": "$1,799/mes" 14 } 15}
Step 3: Globalization (Market-Specific Features)
TSX1// Market configuration 2const markets = { 3 'us': { 4 paymentMethods: ['stripe', 'paypal'], 5 supportedCurrencies: ['USD'], 6 complianceDocuments: ['privacy-policy-ccpa'], 7 features: { 8 tipping: true, 9 cashPayments: false 10 } 11 }, 12 'eu': { 13 paymentMethods: ['stripe'], 14 supportedCurrencies: ['EUR', 'GBP'], 15 complianceDocuments: ['privacy-policy-gdpr', 'cookie-policy'], 16 features: { 17 tipping: false, 18 cashPayments: false 19 } 20 }, 21 'in': { 22 paymentMethods: ['razorpay', 'paytm', 'upi'], 23 supportedCurrencies: ['INR'], 24 complianceDocuments: ['privacy-policy'], 25 features: { 26 tipping: false, 27 cashPayments: true 28 } 29 } 30}; 31 32// Component 33function CheckoutPage() { 34 const locale = useLocale(); // 'en-US', 'en-GB', 'hi-IN', etc. 35 const market = locale.split('-')[1].toLowerCase(); 36 const config = markets[market] || markets['us']; 37 38 return ( 39 <div> 40 <PaymentMethods methods={config.paymentMethods} /> 41 {config.features.cashPayments && <CashOption />} 42 {config.features.tipping && <TipSelector />} 43 </div> 44 ); 45}
Common Mistakes
1. Conflating i18n with L10n
Wrong: "We internationalized the app to Spanish."
Right: "We internationalized the app (it now supports multiple languages), then localized it to Spanish."
Internationalization is language-agnostic. Localization is language-specific.
2. Ignoring Regional Variants
Don't treat "Spanish" as one thing. It's:
es-ES(Spain)es-MX(Mexico)es-AR(Argentina)es-CO(Colombia)
Each has vocabulary differences.
3. Translating Literally
Bad localization: English: "Don't put all your eggs in one basket" German (literal): "Lege nicht alle Eier in einen Korb" (meaningless) German (localized): "Setze nicht alles auf eine Karte" (correct idiom)
Use professional translators who understand cultural context.
4. Forgetting Locale Detection
TSX1// ❌ Bad: Force English 2const locale = 'en'; 3 4// ✅ Good: Detect from browser 5const locale = navigator.language || 'en'; 6 7// ✅ Better: Let user choose, remember preference 8const locale = localStorage.getItem('locale') || navigator.language || 'en';
5. Not Testing RTL
If you support Arabic/Hebrew, test RTL:
CSS/* Use logical properties */ margin-inline-start: 1rem; /* Not margin-left */ padding-inline-end: 1rem; /* Not padding-right */
TSX// Set direction attribute <html dir={i18n.dir()}> {/* 'ltr' or 'rtl' */}
Best Practices
1. Namespace Translations
JSON1// ❌ Flat structure (hard to manage) 2{ 3 "button_save": "Save", 4 "button_cancel": "Cancel", 5 "button_delete": "Delete" 6} 7 8// ✅ Namespaced 9{ 10 "buttons": { 11 "save": "Save", 12 "cancel": "Cancel", 13 "delete": "Delete" 14 }, 15 "forms": { 16 "email_label": "Email", 17 "password_label": "Password" 18 } 19}
2. Use Locale-Specific Routes
/en/pricing (English)
/es/precios (Spanish - translated URL)
/de/preise (German - translated URL)
Next.js config:
TypeScript1// next.config.ts 2export default { 3 i18n: { 4 locales: ['en', 'es', 'de'], 5 defaultLocale: 'en', 6 localeDetection: true 7 } 8};
3. Implement Fallbacks
TSX1// If key missing in Spanish, fall back to English 2i18n.init({ 3 fallbackLng: 'en', 4 // If 'es-MX' missing, try 'es', then 'en' 5 fallbackLng: { 6 'es-MX': ['es', 'en'], 7 'es-AR': ['es', 'en'], 8 'default': ['en'] 9 } 10});
4. Version Control Translations
Store translations in Git. Track changes:
Terminalgit log locales/es.json # See who changed what translation when
Or use a TMS with Git sync (like IntlPull).
5. Automate Translation Workflows
Terminal1# IntlPull example 2npx @intlpullhq/cli upload # Upload new English strings 3# Translators work in web UI 4npx @intlpullhq/cli download # Download translated strings
Avoids manual JSON editing by translators.
Real-World Case Study: SaaS App
Scenario: React SaaS app, launching in US, Europe, Latin America.
Phase 1: Internationalization (Week 1-2)
Terminalnpm install next-intl
- Extracted 1,200 strings from components
- Set up
next-intlwith locale detection - Implemented date/number formatting with
IntlAPI - Tested with pseudo-localization (caught UI bugs)
Result: App ready for translation.
Phase 2: Localization (Week 3-4)
- Hired translators for ES, FR, DE
- Translated strings (1,200 × 3 = 3,600 translations)
- Adapted marketing copy (not literal translation)
- Localized images (different photos for EU market)
Cost: $0.08/word × 1,200 words × 3 languages = $288
Result: App available in 4 languages.
Phase 3: Globalization (Week 5-8)
- Integrated Stripe for US/EU, MercadoPago for Latin America
- Set up GDPR compliance for EU (cookie banners, data deletion)
- Localized pricing ($ for US, € for EU, MXN for Mexico)
- Launched regional marketing campaigns
Revenue impact: +180% from international markets in Q1.
When to Do What
| Stage | When |
|---|---|
| i18n | Day 1 (or refactor existing app before translating) |
| L10n | Before launching in new market |
| G11n | Ongoing (as you expand to new markets) |
Don't translate before internationalizing. You'll have to refactor everything later.
Tools for Each Phase
Internationalization (i18n):
- React: react-i18next, react-intl
- Next.js: next-intl, next-i18next
- Vue: vue-i18n
- Svelte: svelte-i18n
Localization (L10n):
- Translation: DeepL API, ChatGPT, human translators
- Management: IntlPull, Lokalise, Phrase, Crowdin
Globalization (G11n):
- Payments: Stripe (global), MercadoPago (LATAM), Razorpay (India)
- Analytics: Mixpanel, Amplitude (multi-region support)
- Compliance: OneTrust (GDPR), TrustArc
Ready to go global?
Try IntlPull. Handles i18n strings, L10n workflows, and G11n rollout with OTA updates. Start translating in 5 minutes.
Or build your own i18n system if you're comfortable managing translation files manually.
