Advanced i18n Example - Multi-Locale Production Workflows
Production-grade internationalization with 9+ locales, advanced translation features, and RTL support
This example demonstrates building truly global applications with complex translation scenarios: pluralization rules, gender-aware strings, number/date formatting, fallback chains, and right-to-left layout support.
Overview
Basic i18n: English + Spanish + French Advanced i18n: 9 locales with complex rules, fallbacks, and region-specific formatting
Key features:
- 9 complete locales - Every form fully translated
- Advanced Fluent features - Plurals, gender, context-aware strings
- Fallback chains - Portuguese Brazil → Portuguese → Spanish → English
- RTL support - Arabic with proper text direction
- Locale-specific formatting - Currency ($1,234.56 vs 1.234,56€), dates, numbers
- Production workflow - Translation maintenance, coverage tracking, testing
Locales Included
| Locale | Region | Features | Currency | Plurals |
|---|---|---|---|---|
| en-US | United States | Base reference | $ USD | 2 (1 vs other) |
| en-GB | United Kingdom | British English, £ | £ GBP | 2 (1 vs other) |
| es-ES | Spain | Gender agreement, € | € EUR | 2 (1 vs other) |
| es-MX | Mexico | Gender agreement, $ | $ MXN | 2 (1 vs other) |
| pt-BR | Brazil | Fallback: pt → es | R$ BRL | 2 (1 vs other) |
| pt-PT | Portugal | Fallback: pt → es | € EUR | 2 (1 vs other) |
| fr-FR | France | Gender agreement, € | € EUR | 2 (0/1 vs other) |
| ja-JP | Japan | No plurals, no gender | ¥ JPY | 1 (no plurals) |
| ar-SA | Saudi Arabia | RTL text direction, complex plurals | ﷼ SAR | 6 (0, 1, 2, 3-10, 11-99, 100+) |
Directory Structure
17-advanced-i18n/
├── README.md (This file)
├── checkout-form.toml (Example form)
├── test-locales.sh (Test all 9 locales)
├── translations-status.md (Coverage matrix)
├── i18n-config.toml (Fallback chains)
└── locales/
├── en-US/main.ftl (English US - reference)
├── en-GB/main.ftl (English UK)
├── es-ES/main.ftl (Spanish Spain)
├── es-MX/main.ftl (Spanish Mexico)
├── pt-BR/main.ftl (Portuguese Brazil)
├── pt-PT/main.ftl (Portuguese Portugal)
├── fr-FR/main.ftl (French)
├── ja-JP/main.ftl (Japanese)
└── ar-SA/main.ftl (Arabic - RTL)
Example Form: E-Commerce Checkout
checkout-form.toml demonstrates real-world translation challenges:
[[sections]]
title = "Order Summary"
[[sections.elements]]
name = "item_count"
type = "display"
label = "{ $count ->
[one] You have 1 item in your cart
*[other] You have { $count } items in your cart
}"
[[sections.elements]]
name = "subtotal"
type = "display"
label = "Subtotal: { $amount -number }"
[[sections.elements]]
name = "tax"
type = "display"
label = "Tax: { $amount -currency }"
[[sections.elements]]
name = "delivery_date"
type = "display"
label = "Estimated delivery: { $date -date }"
Each locale renders this form correctly:
- English: "You have 5 items" | "Subtotal: $1,234.56" | "Estimated delivery: Tuesday, January 15, 2025"
- French: "Vous avez 5 articles" | "Total: 1.234,56 €" | "Livraison estimée: mardi 15 janvier 2025"
- Arabic: "لديك 5 عناصر" (RTL) | "الإجمالي: 1.234,56 ﷼" | "التسليم المتوقع: الثلاثاء 15 يناير 2025"
Running Examples
Test All Locales
# Run form in each locale (9 iterations)
./test-locales.sh
# Output shows how form renders in each language
Test Specific Locale
# English US (default)
cargo run -p typedialog-tui -- checkout-form.toml
# English UK
LANG=en_GB cargo run -p typedialog-tui -- checkout-form.toml
# Spanish Spain
LANG=es_ES cargo run -p typedialog-tui -- checkout-form.toml
# Arabic (RTL)
LANG=ar_SA cargo run -p typedialog-tui -- checkout-form.toml
# French
LANG=fr_FR cargo run -p typedialog-tui -- checkout-form.toml
# Japanese
LANG=ja_JP cargo run -p typedialog-tui -- checkout-form.toml
# Portuguese Brazil (tests fallback chain)
LANG=pt_BR cargo run -p typedialog-tui -- checkout-form.toml
Check Translation Status
# See which strings are translated in each locale
cat translations-status.md
Fluent Features Demonstrated
1. Pluralization
English (2 plural forms):
items-in-cart =
{ $count ->
[one] You have 1 item
*[other] You have { $count } items
}
French (special rule for 0 and 1):
articles-panier =
{ $count ->
[0] Vous n'avez pas d'articles
[1] Vous avez 1 article
*[other] Vous avez { $count } articles
}
Arabic (6 plural forms):
items-in-cart =
{ $count ->
[0] لا توجد عناصر
[1] عنصر واحد
[2] عنصرين
[3] { $count } عناصر
[4] { $count } عنصر
*[other] { $count } عنصر
}
2. Gender Agreement
Spanish (nouns have gender):
# -gender attribute determines which form to use
guest-welcome = { $name, $gender ->
[m] Bienvenido, { $name }
[f] Bienvenida, { $name }
*[other] Bienvenido, { $name }
}
French:
thank-you = { $gender ->
[m] Merci, Monsieur
[f] Merci, Madame
*[other] Merci
}
3. Number Formatting
# Numbers format per locale automatically
order-total = Total: { $amount -number }
# Output:
# en-US: Total: 1,234.56
# fr-FR: Total: 1.234,56
# de-DE: Total: 1.234,56
# ar-SA: Total: ١٬٢٣٤٫٥٦
4. Date Formatting
# Dates format per locale
ship-date = Ships on { $date -date }
# Output:
# en-US: Ships on 1/15/2025
# fr-FR: Ships on 15/01/2025
# ja-JP: Ships on 2025年1月15日
# ar-SA: Ships on ١٥ يناير ٢٠٢٥
5. Fallback Chains
Portuguese Brazil → Portuguese → Spanish → English
# i18n-config.toml
fallback_chains = [
"pt-BR → pt → es → en-US",
"pt-PT → pt → es → en-US",
"es-MX → es → en-US",
]
If a string is missing from pt-BR/main.ftl:
- Look in
pt-PT/main.ftl - Look in
es/main.ftl - Look in
en-US/main.ftl(fallback to English)
RTL (Right-to-Left) Support
Arabic (ar-SA):
# Direction attribute tells UI to render RTL
welcome = مرحبا بك
.direction = rtl
# Numerals use Arabic-Indic numerals
order-number = رقم الطلب: { $number }
TypeDialog automatically:
- Reverses text direction
- Handles number formatting (Arabic numerals: ٠١٢٣٤٥٦٧٨٩)
- Mirrors UI layout
Translation Maintenance
Workflow
-
Add new string in en-US:
new-feature = This is a new feature -
Mark as untranslated in other locales:
new-feature = [NEEDS TRANSLATION] -
Update status tracking:
./translations-status.md # Shows incomplete translations -
Translate and remove marker:
new-feature = Ceci est une nouvelle fonctionnalité -
Verify coverage:
grep -r "NEEDS TRANSLATION" locales/
Coverage Matrix
translations-status.md shows:
| Locale | Complete | Partial | Missing |
|--------|----------|---------|---------|
| en-US | 100% | 0% | 0% |
| en-GB | 95% | 5% | 0% |
| es-ES | 90% | 10% | 0% |
| ... | ... | ... | ... |
Testing Translations Programmatically
# Validate all Fluent files parse correctly
for locale in locales/*/; do
nickel check "$locale/main.ftl" || echo "Error in $locale"
done
# Test that all form keys are translated in all locales
./test-locales.sh --validate
# Run form and verify output in each locale
for locale in en-US en-GB es-ES pt-BR ar-SA ja-JP; do
echo "Testing $locale..."
LANG="$locale" cargo run -p typedialog -- checkout-form.toml --format json | jq . > /dev/null
done
Key Learning Points
1. Pluralization is Language-Specific
- English: 2 forms (singular 1 vs plural)
- Polish: 4 forms (1, 2-4, 5-21, 22+)
- Arabic: 6 forms (0, 1, 2, 3-10, 11-99, 100+)
- Japanese: 1 form (no plurals)
Cannot use a one-size-fits-all plural function!
2. Gender Agreement Requires Context
Spanish/French/German nouns have gender, affecting:
- Articles (el/la/lo, le/la/l', der/die/das)
- Adjectives (rojo/roja, rouge/rouge, roter/rote)
- Past participles (comido/comida, mangé/mangée)
Cannot just translate strings in isolation!
3. Number Formatting Varies
- US: 1,234.56 (comma thousands, period decimal)
- Europe: 1.234,56 (period thousands, comma decimal)
- India: 12,34,567 (special grouping)
- Arabic: Uses Arabic-Indic numerals (٠١٢٣٤)
Cannot hardcode number formatting!
4. Date Formatting Varies
- en-US: 1/15/2025
- en-GB: 15/01/2025
- fr-FR: 15 janvier 2025
- ja-JP: 2025年1月15日
Cannot use ISO format everywhere!
5. RTL Requires Special Handling
Right-to-left languages (Arabic, Hebrew, Persian):
- Text flows right to left
- Numbers still read left to right
- UI layout must be mirrored
Cannot ignore RTL just because it's a minority use case!
Real-World Best Practices
- Always use Fluent for anything beyond trivial translations
- Test with native speakers - Automated tests miss context
- Plan for fallbacks - Incomplete translations should not crash
- Track coverage - Know which locales are complete
- Separate translation work - Translators shouldn't need to understand code
- Use context - Provide translator notes for ambiguous strings
- Test all locales - Every deployment should verify each language
- Monitor in production - Users will report issues you missed
Troubleshooting
"String appears in English instead of locale"
- Check if fallback chain applies (pt-BR → pt → es → en-US)
- Verify LANG environment variable is set correctly
- Check Fluent file for syntax errors
"Numbers don't format correctly"
- Ensure you're using
-numberfunction in Fluent - Check locale data is available in your system
- Test with:
date +%xto verify locale
"Arabic text appears reversed"
- Add
.direction = rtlattribute in Fluent - Check UI component supports RTL
- Verify numerals format with Arabic-Indic style
"Plural forms incorrect"
- Verify plural rules for language (not just guess English 2-form)
- Check documentation for language's plural rules
- Test with edge cases: 0, 1, 2, 21, 100, 1000
Next Steps
- Add more locales - Use existing locales as templates
- Extract all UI strings - Audit your forms for hardcoded text
- Set up translation platform - Crowdin, Weblate, or similar
- Automate testing - Run
test-locales.shin CI/CD - Monitor translations - Track completion and quality
- Build management UI - For non-developers to manage translations
Key Takeaway: Internationalization is not just translation - it's localizing numbers, dates, plurals, and gender agreement while handling RTL and fallback chains. Use Fluent for complex features and always test with native speakers.