# Language & Internationalization (i18n) Implementation ## Overview This document describes the implementation of multi-language support for the Orion platform. The system supports four languages (English, French, German, Luxembourgish) with flexible configuration at store, user, and customer levels. ## Supported Languages | Code | Language | Native Name | Flag | |------|----------|-------------|------| | `en` | English | English | GB | | `fr` | French | Francais | FR | | `de` | German | Deutsch | DE | | `lb` | Luxembourgish | Letzebuerg | LU | **Default Language**: French (`fr`) - reflecting the Luxembourg market context. ## Database Changes ### Migration: `fcfdc02d5138_add_language_settings_to_store_user_customer` #### Stores Table New columns added to `stores`: ```sql ALTER TABLE stores ADD COLUMN default_language VARCHAR(5) NOT NULL DEFAULT 'fr'; ALTER TABLE stores ADD COLUMN dashboard_language VARCHAR(5) NOT NULL DEFAULT 'fr'; ALTER TABLE stores ADD COLUMN storefront_language VARCHAR(5) NOT NULL DEFAULT 'fr'; ALTER TABLE stores ADD COLUMN storefront_languages JSON NOT NULL DEFAULT '["fr", "de", "en"]'; ``` | Column | Type | Description | |--------|------|-------------| | `default_language` | VARCHAR(5) | Fallback language for content when translation unavailable | | `dashboard_language` | VARCHAR(5) | Default UI language for store dashboard | | `storefront_language` | VARCHAR(5) | Default language for customer-facing shop | | `storefront_languages` | JSON | Array of enabled languages for storefront selector | #### Users Table ```sql ALTER TABLE users ADD COLUMN preferred_language VARCHAR(5) NULL; ``` | Column | Type | Description | |--------|------|-------------| | `preferred_language` | VARCHAR(5) | User's preferred dashboard language (overrides store setting) | #### Customers Table ```sql ALTER TABLE customers ADD COLUMN preferred_language VARCHAR(5) NULL; ``` | Column | Type | Description | |--------|------|-------------| | `preferred_language` | VARCHAR(5) | Customer's preferred shop language (overrides store setting) | ## Architecture ### Language Resolution Flow #### Store Dashboard ``` User preferred_language | v (if not set) Store dashboard_language | v (if not set) System DEFAULT_LANGUAGE (fr) ``` #### Storefront ``` Customer preferred_language | v (if not set) Session/Cookie language | v (if not set) Store storefront_language | v (if not set) Browser Accept-Language header | v (if not supported) System DEFAULT_LANGUAGE (fr) ``` ### Files Created/Modified #### New Files | File | Description | |------|-------------| | `app/utils/i18n.py` | Core i18n utilities, translation loading, language resolution | | `middleware/language.py` | Language detection middleware | | `app/api/v1/shared/language.py` | Language API endpoints | | `app/templates/shared/macros/language_selector.html` | UI components for language selection | | `static/locales/en.json` | English translations | | `static/locales/fr.json` | French translations | | `static/locales/de.json` | German translations | | `static/locales/lb.json` | Luxembourgish translations | #### Modified Files | File | Changes | |------|---------| | `models/database/store.py` | Added language settings columns | | `models/database/user.py` | Added `preferred_language` column | | `models/database/customer.py` | Added `preferred_language` column | | `models/schema/store.py` | Added language fields to Pydantic schemas | | `models/schema/auth.py` | Added `preferred_language` to user schemas | | `models/schema/customer.py` | Added `preferred_language` to customer schemas | | `main.py` | Registered LanguageMiddleware | | `app/api/main.py` | Registered language API router | ## API Endpoints ### Language API (`/api/v1/language`) | Endpoint | Method | Description | |----------|--------|-------------| | `/api/v1/language/set` | POST | Set language preference (cookie) | | `/api/v1/language/current` | GET | Get current language info | | `/api/v1/language/list` | GET | List all available languages | | `/api/v1/language/clear` | DELETE | Clear language preference | ### Request/Response Examples #### Set Language ```http POST /api/v1/language/set Content-Type: application/json { "language": "de" } ``` Response: ```json { "success": true, "language": "de", "message": "Language set to Deutsch" } ``` ## Translation Files Translation files are stored in `static/locales/{lang}.json` with the following structure: ```json { "common": { "save": "Save", "cancel": "Cancel" }, "auth": { "login": "Login", "logout": "Logout" }, "nav": { "dashboard": "Dashboard", "products": "Products" } } ``` ### Key Naming Convention - Use dot notation for nested keys: `common.save`, `auth.login` - Use snake_case for key names: `product_name`, `order_status` - Group by feature/section: `products.add_product`, `orders.confirm_order` ## Template Integration ### Using Translations in Jinja2 ```jinja2 {# Import the translation function #} {% from 'shared/macros/language_selector.html' import language_selector %} {# Use translation function #} {{ _('common.save') }} {# With interpolation #} {{ _('welcome.message', name=user.name) }} {# Language selector component #} {{ language_selector( current_language=request.state.language, enabled_languages=store.storefront_languages ) }} ``` ### Request State Variables The LanguageMiddleware sets these on `request.state`: | Variable | Type | Description | |----------|------|-------------| | `language` | str | Resolved language code | | `language_info` | dict | Additional info (source, cookie value, browser value) | ## Middleware Order The LanguageMiddleware must run **after** ContextMiddleware (to know the context type) and **before** ThemeContextMiddleware. Execution order (request flow): 1. LoggingMiddleware 2. StoreContextMiddleware 3. ContextMiddleware 4. **LanguageMiddleware** <-- Detects language 5. ThemeContextMiddleware 6. FastAPI Router ## UI Components ### Full Language Selector ```jinja2 {{ language_selector( current_language='fr', enabled_languages=['fr', 'de', 'en'], position='right', show_label=true ) }} ``` ### Compact Selector (Flag Only) ```jinja2 {{ language_selector_compact( current_language='fr', enabled_languages=['fr', 'de', 'en'] ) }} ``` ### Language Settings Form For store settings pages: ```jinja2 {{ language_settings_form( current_settings={ 'default_language': store.default_language, 'dashboard_language': store.dashboard_language, 'storefront_language': store.storefront_language, 'storefront_languages': store.storefront_languages } ) }} ``` ## Flag Icons The language selector uses [flag-icons](https://flagicons.lipis.dev/) CSS library. Ensure this is included in your base template: ```html ``` Usage: `` for French flag. ## Testing ### Manual Testing Checklist - [ ] Language cookie is set when selecting language - [ ] Page reloads with correct language after selection - [ ] Store dashboard respects user's preferred_language - [ ] Storefront respects customer's preferred_language - [ ] Browser language detection works (clear cookie, use browser with different language) - [ ] Fallback to default language works for unsupported languages - [ ] Language selector shows only enabled languages on storefront ### API Testing ```bash # Set language curl -X POST http://localhost:8000/api/v1/language/set \ -H "Content-Type: application/json" \ -d '{"language": "de"}' # Get current language curl http://localhost:8000/api/v1/language/current # List languages curl http://localhost:8000/api/v1/language/list ``` ## CMS Template i18n All platform-facing templates use the `_()` translation function with keys from module-scoped locale files. ### Locale File Structure CMS locale files are at `app/modules/cms/locales/{en,fr,de,lb}.json` with 340 keys organized as: ``` platform.nav.* — Navigation labels platform.hero.* — Default homepage hero section platform.pricing.* — Default homepage pricing section platform.features.* — Default homepage feature labels platform.signup.* — Signup flow platform.success.* — Post-signup success page platform.cta.* — Call-to-action section platform.content_page.* — Content page UI chrome platform.footer.* — Footer labels platform.modern.* — Modern homepage template (~90 keys) platform.minimal.* — Minimal homepage template (17 keys) platform.find_shop.* — Find-your-shop page platform.addons.* — Add-on pricing labels messages.* — Flash/toast messages confirmations.* — Confirmation dialogs permissions.* — Permission labels features.* — Feature gate labels menu.* — Menu section labels ``` ### Template Usage ```jinja2 {# Simple key lookup #} {{ _("cms.platform.modern.hero_title_1") }} {# With interpolation #} {{ _("cms.platform.modern.cta_trial", trial_days=14) }} ``` ### Translated Templates (Complete) | Template | Keys Used | Status | |----------|-----------|--------| | `homepage-default.html` + section partials | `platform.hero.*`, `platform.pricing.*`, etc. | Fully translated | | `homepage-modern.html` | `platform.modern.*` (~90 keys) | Fully translated | | `homepage-minimal.html` | `platform.minimal.*` (17 keys) | Fully translated | | `signup.html` | `platform.signup.*` | Fully translated | | `signup-success.html` | `platform.success.*` | Fully translated | | `content-page.html` | `platform.content_page.*` | Fully translated | | `find-shop.html` | `platform.find_shop.*` | Fully translated | | `base.html` | `platform.nav.*`, `platform.footer.*` | Fully translated | ### CMS Section Translations (TranslatableText Pattern) Homepage sections stored in the `ContentPage.sections` JSON column use the `TranslatableText` pattern: ```json { "hero": { "title": {"translations": {"fr": "Bienvenue", "en": "Welcome", "de": "Willkommen", "lb": "Wëllkomm"}} } } ``` Resolution in templates via `_t()` macro: ```jinja2 {% macro _t(field, fallback='') %} {%- if field and field.translations -%} {{ field.translations.get(lang) or field.translations.get(default_lang) or fallback }} {%- endif -%} {% endmacro %} ``` ## Multi-Language Content Pages ### Migration: `cms_002_add_title_content_translations` Added two nullable JSON columns to `content_pages`: ```sql ALTER TABLE content_pages ADD COLUMN title_translations JSON; ALTER TABLE content_pages ADD COLUMN content_translations JSON; ``` ### Data Format ```json { "en": "About Us", "fr": "À propos", "de": "Über uns", "lb": "Iwwer eis" } ``` ### Model API ```python # Get translated title with fallback chain: # title_translations[lang] → title_translations[default_lang] → self.title page.get_translated_title(lang="de", default_lang="fr") # Same for content: page.get_translated_content(lang="de", default_lang="fr") ``` ### Template Usage In `content-page.html`: ```jinja2 {% set _lang = current_language|default('fr') %} {% set _page_title = page.get_translated_title(_lang) %} {% set _page_content = page.get_translated_content(_lang) %}

{{ _page_title }}

{{ _page_content | safe }} ``` In `base.html` (nav/footer): ```jinja2 {{ page.get_translated_title(current_language|default('fr')) }} ``` ### Seed Script The seed script (`scripts/seed/create_default_content_pages.py`) uses the `tt()` helper: ```python def tt(en, fr, de, lb=None): """Build a language-keyed dict for title_translations.""" d = {"en": en, "fr": fr, "de": de} if lb: d["lb"] = lb return d ``` All platform pages, legal pages, and store defaults include `title_translations` with en/fr/de/lb values. ## Future Enhancements 1. **Admin Language Support**: Currently admin is English-only. The system is designed to easily add admin language support later. 2. **Translation Management UI**: Add a language-tabbed title/content editor to the CMS admin for editing `title_translations` and `content_translations`. 3. **RTL Language Support**: The `is_rtl_language()` function is ready for future RTL language support (Arabic, Hebrew, etc.). 4. **Auto-Translation**: Integration with translation APIs for automatic content translation. 5. **Content Translation**: Translate `content_translations` for platform marketing pages (currently only titles are translated; content remains English-only in the seed data). ## Rollback To rollback the content page translations: ```bash alembic downgrade cms_001 ``` This will remove `title_translations` and `content_translations` from `content_pages`. To rollback the base language settings: ```bash alembic downgrade -1 ``` This will remove: - `default_language`, `dashboard_language`, `storefront_language`, `storefront_languages` from `stores` - `preferred_language` from `users` - `preferred_language` from `customers`