TypeDialog/examples/17-advanced-i18n
Jesús Pérez 8149523e5b
Some checks failed
CI / Lint (bash) (push) Has been cancelled
CI / Lint (markdown) (push) Has been cancelled
CI / Lint (nickel) (push) Has been cancelled
CI / Lint (nushell) (push) Has been cancelled
CI / Lint (rust) (push) Has been cancelled
CI / Benchmark (push) Has been cancelled
CI / Security Audit (push) Has been cancelled
CI / License Compliance (push) Has been cancelled
CI / Code Coverage (push) Has been cancelled
CI / Test (macos-latest) (push) Has been cancelled
CI / Test (ubuntu-latest) (push) Has been cancelled
CI / Test (windows-latest) (push) Has been cancelled
CI / Build (macos-latest) (push) Has been cancelled
CI / Build (ubuntu-latest) (push) Has been cancelled
CI / Build (windows-latest) (push) Has been cancelled
chore: new examples
2026-01-12 03:31:00 +00:00
..
2026-01-12 03:31:00 +00:00
2026-01-12 03:31:00 +00:00
2026-01-12 03:31:00 +00:00
2026-01-12 03:31:00 +00:00
2026-01-12 03:31:00 +00:00
2026-01-12 03:31:00 +00:00

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:

  1. Look in pt-PT/main.ftl
  2. Look in es/main.ftl
  3. 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

  1. Add new string in en-US:

    new-feature = This is a new feature
    
  2. Mark as untranslated in other locales:

    new-feature = [NEEDS TRANSLATION]
    
  3. Update status tracking:

    ./translations-status.md  # Shows incomplete translations
    
  4. Translate and remove marker:

    new-feature = Ceci est une nouvelle fonctionnalité
    
  5. 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

  1. Always use Fluent for anything beyond trivial translations
  2. Test with native speakers - Automated tests miss context
  3. Plan for fallbacks - Incomplete translations should not crash
  4. Track coverage - Know which locales are complete
  5. Separate translation work - Translators shouldn't need to understand code
  6. Use context - Provide translator notes for ambiguous strings
  7. Test all locales - Every deployment should verify each language
  8. 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 -number function in Fluent
  • Check locale data is available in your system
  • Test with: date +%x to verify locale

"Arabic text appears reversed"

  • Add .direction = rtl attribute 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

  1. Add more locales - Use existing locales as templates
  2. Extract all UI strings - Audit your forms for hardcoded text
  3. Set up translation platform - Crowdin, Weblate, or similar
  4. Automate testing - Run test-locales.sh in CI/CD
  5. Monitor translations - Track completion and quality
  6. 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.