Introduction
After helping thousands of teams internationalize their apps, we've identified the practices that separate smooth, scalable i18n implementations from painful, buggy ones.
Here are 15 rules to follow for successful internationalization.
Code Architecture
1. Extract All User-Facing Strings from Day One
Don't wait until "we need to translate." By then, you'll have thousands of hardcoded strings.
JavaScript1// Bad - hardcoded string 2<button>Submit</button> 3 4// Good - extracted from start 5<button>{t('form.submit')}</button>
Tip: Use IntlPull's CLI to check translation status:
Terminalnpx @intlpullhq/cli status
2. Never Concatenate Strings
Different languages have different word orders. Concatenation breaks translations.
JavaScript1// Bad - breaks in German, Japanese, Arabic... 2const message = "Hello " + name + ", you have " + count + " messages"; 3 4// Good - uses interpolation 5const message = t('greeting', { name, count }); 6// en: "Hello {name}, you have {count} messages" 7// de: "Hallo {name}, Sie haben {count} Nachrichten" 8// ja: "{name}さん、{count}件のメッセージがあります"
3. Use ICU Message Format for Complex Strings
ICU handles plurals, gender, and selection elegantly:
{count, plural,
=0 {No messages}
one {# message}
other {# messages}
}
{gender, select,
male {He liked your post}
female {She liked your post}
other {They liked your post}
}
4. Keep Translation Keys Semantic
Keys should describe content, not location:
JavaScript1// Bad - tied to UI location 2"homepage_header_title" 3"sidebar_menu_item_3" 4 5// Good - describes content 6"welcome.title" 7"navigation.settings" 8"error.network_unavailable"
5. Organize Keys by Feature, Not File
Structure translations by feature domain:
/locales/en.json
{
"auth": {
"login": { ... },
"signup": { ... }
},
"dashboard": {
"overview": { ... },
"settings": { ... }
},
"checkout": {
"cart": { ... },
"payment": { ... }
}
}
Formatting
6. Use Native Formatting APIs
Never hardcode number, date, or currency formats:
JavaScript1// Bad - US-only format 2const date = `${month}/${day}/${year}`; 3const price = `$${amount}`; 4 5// Good - locale-aware 6const date = new Intl.DateTimeFormat(locale).format(dateObj); 7const price = new Intl.NumberFormat(locale, { 8 style: 'currency', 9 currency: userCurrency 10}).format(amount);
Locale differences:
| US | Germany | Japan | |
|---|---|---|---|
| Date | 12/31/2026 | 31.12.2026 | 2026/12/31 |
| Number | 1,234.56 | 1.234,56 | 1,234.56 |
| Currency | $99.99 | 99,99 € | ¥100 |
7. Handle Pluralization Properly
Different languages have different plural rules:
| Language | Plural Forms |
|---|---|
| English | 2 (one, other) |
| French | 2+ (one, other, many) |
| Russian | 3 (one, few, many) |
| Arabic | 6 (zero, one, two, few, many, other) |
JavaScript// Use ICU plural format t('items', { count: 5 }) // Config handles the rules per language
8. Support RTL Languages
Arabic, Hebrew, Persian, and Urdu read right-to-left:
CSS1/* Use logical properties */ 2.card { 3 margin-inline-start: 1rem; /* Not margin-left */ 4 padding-inline-end: 1rem; /* Not padding-right */ 5} 6 7/* Let dir attribute handle layout */ 8[dir="rtl"] .icon { 9 transform: scaleX(-1); 10}
JSX<html dir={isRTL ? 'rtl' : 'ltr'}>
Translation Quality
9. Provide Context for Translators
Translators need to know where and how strings are used:
JSON1{ 2 "save": { 3 "value": "Save", 4 "context": "Button to save user profile changes, appears at bottom of form", 5 "maxLength": 10, 6 "screenshot": "save-button.png" 7 } 8}
10. Use a Glossary
Maintain consistent terminology across translations:
| English | Spanish | German | Context |
|---|---|---|---|
| Dashboard | Panel de control | Dashboard | Main user interface |
| Settings | Configuración | Einstellungen | User preferences |
| Submit | Enviar | Absenden | Form submission button |
11. Leverage Translation Memory
Don't translate the same phrase twice. TM catches:
- Exact matches: "Save changes" = "Save changes"
- Fuzzy matches: "Save changes" ≈ "Save all changes"
ROI: 30-60% reduction in translation costs over time.
Process
12. Automate Translation Sync
Manual syncing wastes time and leads to outdated translations:
Terminal1# Sync translations in CI 2npx @intlpullhq/cli upload 3npx @intlpullhq/cli download 4 5# Or use watch mode for local development 6npx @intlpullhq/cli sync --watch
13. Use Continuous Localization
Don't batch translations. Localize continuously:
YAML1# GitHub Action example 2on: 3 push: 4 branches: [main] 5 paths: 6 - 'src/**' 7 - 'locales/en.json' 8 9jobs: 10 sync: 11 runs-on: ubuntu-latest 12 steps: 13 - uses: actions/checkout@v3 14 - run: npx @intlpullhq/cli upload 15 - run: npx @intlpullhq/cli download 16 - uses: stefanzweifel/git-auto-commit-action@v4
14. Test with Pseudo-Localization
Before real translations, test with pseudo-translations:
"Welcome" → "[Ẃéĺćőḿé !!!!!!]"
Pseudo-localization reveals:
- Hardcoded strings (not transformed)
- Layout issues (text expansion)
- Truncation problems
- Character encoding issues
15. Implement OTA Updates for Mobile
Don't wait for app store releases to fix translations:
Swift// With OTA, translation updates are instant let message = IntlPull.t("welcome.message") // Change in dashboard → users see it immediately
Without OTA: 1-2 week delay for any translation change With OTA: Instant updates, no app release needed
Anti-Patterns to Avoid
Don't: Use String Concatenation
JavaScript// Never do this "Welcome, " + name
Don't: Hardcode Formatting
JavaScript// Never do this date.toLocaleDateString('en-US')
Don't: Split Sentences
JavaScript1// Never do this 2<span>{t('click')}</span> 3<Link>{t('here')}</Link> 4<span>{t('to_continue')}</span>
Don't: Assume Language = Country
JavaScript// Never do this if (country === 'US') language = 'en'; // Spanish speakers in US, Portuguese in US, etc.
Don't: Forget About Text Expansion
CSS1/* Never do this */ 2.button { 3 width: 100px; /* Breaks in German */ 4}
Checklist
Before launching in a new language:
- All strings extracted and translated
- Pluralization tested
- Date/number/currency formatting correct
- RTL layout works (if applicable)
- Images and icons localized
- SEO metadata translated
- Error messages translated
- Email templates translated
- Legal pages translated
- Tested by native speakers
Tools That Help
| Need | Tool |
|---|---|
| Translation Management | IntlPull |
| String Extraction | IntlPull CLI |
| Code Transformation | IntlPull CLI |
| AI Translation | IntlPull (GPT-4, Claude) |
| OTA Updates | IntlPull SDK |
| Pseudo-localization | IntlPull |
Conclusion
Good i18n is about:
- Clean architecture: Extract strings early, use interpolation
- Proper formatting: Let APIs handle locale differences
- Quality translations: Context, glossaries, TM
- Smart process: Automation, continuous flow, testing
Follow these 15 practices and you'll build apps that scale globally without the typical i18n pain.
Ready to level up your i18n? Try IntlPull free and implement these best practices with the right tools.
