Files
gh-christianlouis-docuelevate/docs/InternationalizationGuide.md
T
copilot-swe-agent[bot] 87d85624be feat(i18n): expand supported languages from 31 to 49 with flags and locale formats
Add 18 previously missing language entries to SUPPORTED_LANGUAGES in
app/utils/i18n.py so every translation JSON file in frontend/translations/
is properly indexed and served by the language selector:

New languages: af, ar, cy, eo, fa, fy, gl, he, ja, kn, ko, li, nds, no, pa, sr, vi, vls

Each new language entry includes:
- Correct ISO 639-1/639-3 code matching its JSON filename
- Native name and display name
- Appropriate country/language flag emoji
- Locale-specific date and number formatting rules in _LOCALE_FORMATS

Also: rename nb "Norwegian" → "Norwegian Bokmål" to distinguish it from no "Norwegian".

Update tests/test_i18n.py:
- Count assertions: 31 → 49
- Expected code set expanded to all 49 codes
- Fix test_unsupported_language_fallback (ja/ko now supported, use xx/yy)

Update docs/InternationalizationGuide.md:
- Language count: 10 → 49
- Full language table with flags, native names, tiers
- Complete file structure listing all 49 JSON files

Co-authored-by: christianlouis <361235+christianlouis@users.noreply.github.com>
2026-03-13 14:03:31 +00:00

11 KiB
Raw Blame History

Internationalization (i18n) & Localization (l10n) Guide

DocuElevate supports 49 languages for its web UI, with automatic browser language detection, user-preference persistence, and an AI-powered fallback translator for strings that haven't been manually translated yet.

Supported Languages

Code Language Native Name Flag Priority
en English English 🇬🇧 Tier 1
de German Deutsch 🇩🇪 Tier 1
fr French Français 🇫🇷 Tier 1
es Spanish Español 🇪🇸 Tier 1
it Italian Italiano 🇮🇹 Tier 1
pt Portuguese Português 🇵🇹 Tier 1
nl Dutch Nederlands 🇳🇱 Tier 2
nb Norwegian Bokmål Norsk bokmål 🇳🇴 Tier 2
no Norwegian Norsk 🇳🇴 Tier 2
da Danish Dansk 🇩🇰 Tier 2
sv Swedish Svenska 🇸🇪 Tier 2
fi Finnish Suomi 🇫🇮 Tier 2
is Icelandic Íslenska 🇮🇸 Tier 2
ga Irish Gaeilge 🇮🇪 Tier 2
lb Luxembourgish Lëtzebuergesch 🇱🇺 Tier 2
ca Catalan Català 🏴 Tier 2
cy Welsh Cymraeg 🏴󠁧󠁢󠁷󠁬󠁳󠁿 Tier 2
fy Frisian Frysk 🇳🇱 Tier 2
gl Galician Galego 🇪🇸 Tier 2
li Limburgish Limburgs 🇳🇱 Tier 2
vls West Flemish West-Vlams 🇧🇪 Tier 2
nds Low German Plattdüütsch 🇩🇪 Tier 2
pl Polish Polski 🇵🇱 Tier 3
cs Czech Čeština 🇨🇿 Tier 3
sk Slovak Slovenčina 🇸🇰 Tier 3
hu Hungarian Magyar 🇭🇺 Tier 3
sl Slovenian Slovenščina 🇸🇮 Tier 3
hr Croatian Hrvatski 🇭🇷 Tier 3
ro Romanian Română 🇷🇴 Tier 3
bg Bulgarian Български 🇧🇬 Tier 3
el Greek Ελληνικά 🇬🇷 Tier 3
et Estonian Eesti 🇪🇪 Tier 3
lv Latvian Latviešu 🇱🇻 Tier 3
lt Lithuanian Lietuvių 🇱🇹 Tier 3
sr Serbian Српски 🇷🇸 Tier 3
tr Turkish Türkçe 🇹🇷 Tier 4
uk Ukrainian Українська 🇺🇦 Tier 4
ru Russian Русский 🇷🇺 Tier 4
he Hebrew עברית 🇮🇱 Tier 4
ar Arabic العربية 🇸🇦 Tier 4
fa Persian فارسی 🇮🇷 Tier 4
af Afrikaans Afrikaans 🇿🇦 Tier 4
zh Chinese 中文 🇨🇳 Tier 5
ja Japanese 日本語 🇯🇵 Tier 5
ko Korean 한국어 🇰🇷 Tier 5
vi Vietnamese Tiếng Việt 🇻🇳 Tier 5
pa Punjabi ਪੰਜਾਬੀ 🇮🇳 Tier 5
kn Kannada ಕನ್ನಡ 🇮🇳 Tier 5
eo Esperanto Esperanto 🌍 Tier 6

Tier 1 languages (major European) have complete, manually-reviewed translations. Tier 23 languages have complete translations but may receive less frequent updates. Tier 45 cover Non-EU European, Middle Eastern, and Asian languages. Tier 6 covers constructed languages.

How Language Is Detected

DocuElevate resolves the display language in the following priority order:

  1. User profile preference — stored in the database (UserProfile.preferred_language) and loaded into the session on login
  2. Cookiedocuelevate_lang cookie (30-day expiry, set when user selects a language)
  3. Browser Accept-Language header — the highest-priority match among supported languages
  4. Default — English (en)

Selecting Your Language

Via the Navigation Bar

Click the 🌐 globe icon in the top navigation bar. A dropdown menu shows all available languages with their native names and flag emoji. The current language is highlighted with a blue checkmark.

Via the API

# Set language to German
curl -X POST http://localhost:8000/api/i18n/language \
  -H "Content-Type: application/json" \
  -d '{"language": "de"}'

# List all available languages
curl http://localhost:8000/api/i18n/languages

Set the docuelevate_lang cookie to any supported language code:

document.cookie = "docuelevate_lang=fr; max-age=2592000; path=/";
location.reload();

For Developers

Translation File Structure

Translations are stored as flat JSON files in frontend/translations/:

frontend/translations/
├── en.json    # English (base / reference)
├── af.json    # Afrikaans
├── ar.json    # Arabic
├── bg.json    # Bulgarian
├── ca.json    # Catalan
├── cs.json    # Czech
├── cy.json    # Welsh
├── da.json    # Danish
├── de.json    # German
├── el.json    # Greek
├── eo.json    # Esperanto
├── es.json    # Spanish
├── et.json    # Estonian
├── fa.json    # Persian
├── fi.json    # Finnish
├── fr.json    # French
├── fy.json    # Frisian
├── ga.json    # Irish
├── gl.json    # Galician
├── he.json    # Hebrew
├── hr.json    # Croatian
├── hu.json    # Hungarian
├── is.json    # Icelandic
├── it.json    # Italian
├── ja.json    # Japanese
├── kn.json    # Kannada
├── ko.json    # Korean
├── lb.json    # Luxembourgish
├── li.json    # Limburgish
├── lt.json    # Lithuanian
├── lv.json    # Latvian
├── nb.json    # Norwegian Bokmål
├── nds.json   # Low German (Plattdeutsch)
├── nl.json    # Dutch
├── no.json    # Norwegian
├── pa.json    # Punjabi
├── pl.json    # Polish
├── pt.json    # Portuguese
├── ro.json    # Romanian
├── ru.json    # Russian
├── sk.json    # Slovak
├── sl.json    # Slovenian
├── sr.json    # Serbian
├── sv.json    # Swedish
├── tr.json    # Turkish
├── uk.json    # Ukrainian
├── vi.json    # Vietnamese
├── vls.json   # West Flemish
└── zh.json    # Chinese

Each file is a flat key-value dictionary with dot-notation namespacing:

{
  "nav.dashboard": "Dashboard",
  "nav.upload": "Upload",
  "upload.max_size": "Maximum file size: {size}",
  "footer.copyright": "DocuElevate {year}"
}

Using Translations in Templates

The _() function is available globally in all Jinja2 templates:

{# Simple translation #}
<h1>{{ _("dashboard.title") }}</h1>

{# Translation with placeholders #}
<p>{{ _("upload.max_size", size="50 MB") }}</p>

{# Translation in attributes #}
<button aria-label="{{ _('common.save') }}">{{ _("common.save") }}</button>

Using Translations in Python

from app.utils.i18n import translate

# Basic translation
text = translate("nav.dashboard", "de")  # → "Übersicht"

# With placeholders
text = translate("footer.copyright", "fr", year="2025")  # → "DocuElevate 2025"

Localization Helpers

Format dates, times, and numbers according to locale conventions:

{# In templates — locale is automatically detected #}
<span>{{ format_date_l10n(document.created_at) }}</span>
<span>{{ format_number_l10n(file_count) }}</span>
# In Python
from app.utils.i18n import format_date, format_number

format_date(date(2025, 3, 15), "de")      # → "15. March 2025"
format_date(date(2025, 3, 15), "de", short=True)  # → "15.03.2025"
format_number(1234567, "de")                # → "1.234.567"
format_number(1234.56, "en")                # → "1,234.56"

Adding a New Translation Key

  1. Add the key and English text to frontend/translations/en.json
  2. Add translations for all other languages in their respective files
  3. Use {{ _("your.new.key") }} in templates

AI Fallback Translation

When a translation key exists in English but not in the target language, DocuElevate can use the configured AI provider (OpenAI, Anthropic, etc.) to translate the string on-the-fly:

from app.utils.i18n import translate_with_ai_fallback

# Falls back to AI if no manual translation exists
translated = translate_with_ai_fallback("Welcome to our platform", "de")

The AI fallback:

  • Uses the AI_MODEL or OPENAI_MODEL setting
  • Caches results in memory for the process lifetime
  • Returns the original English text if the AI call fails
  • Is designed for graceful degradation — the UI never breaks

Adding a New Language

  1. Create a new JSON file in frontend/translations/ (e.g., ja.json)
  2. Copy the structure from en.json and translate all values
  3. Add the language to SUPPORTED_LANGUAGES in app/utils/i18n.py:
    {"code": "ja", "name": "Japanese", "native": "日本語", "flag": "🇯🇵"},
    
  4. Add locale formatting rules to _LOCALE_FORMATS in the same file
  5. Create a database migration if needed (the preferred_language column already accepts any string up to 10 characters)

Database Migration

Migration 027_add_user_language_preference adds a preferred_language column to the user_profiles table. This column stores the user's chosen UI language as an ISO 639-1 code (e.g., "de", "fr"). A NULL value means "auto-detect from browser settings."

API Reference

GET /api/i18n/languages

Returns all supported languages and the current active language.

Response:

{
  "languages": [
    {"code": "en", "name": "English", "native": "English", "flag": "🇬🇧"},
    {"code": "de", "name": "German", "native": "Deutsch", "flag": "🇩🇪"}
  ],
  "current": "en",
  "default": "en"
}

POST /api/i18n/language

Set the preferred UI language. Persists in session, cookie, and database.

Request:

{"language": "de"}

Response:

{
  "language": "de",
  "message": "Language changed to Deutsch"
}

Configuration

No additional configuration is required. The i18n system works out of the box with the default English language and automatically detects browser preferences.

Setting Default Description
Browser Accept-Language Auto-detected Used when no explicit preference is set
docuelevate_lang cookie Not set Set when user selects a language via the UI
UserProfile.preferred_language NULL Stored in DB for authenticated users