# Architecture Rules - Language & I18N Rules # Rules for internationalization and language handling language_rules: - id: "LANG-001" name: "Only use supported language codes" severity: "error" description: | Only use supported language codes: en, fr, de, lb Never use full language names or invalid codes. SUPPORTED CODES: - en: English (fallback language) - fr: French (default for Luxembourg) - de: German - lb: Luxembourgish WRONG: language = "english" # Use "en" language = "french" # Use "fr" language = "lux" # Use "lb" RIGHT: language = "en" language = "fr" language = "de" language = "lb" pattern: file_pattern: "**/*.py" anti_patterns: - "'english'" - "'french'" - "'german'" - "'luxembourgish'" - "'lux'" - id: "LANG-002" name: "Never use inline Alpine.js x-data with Jinja for language selector" severity: "error" description: | Never put complex JavaScript objects inline in x-data when using Jinja variables. Jinja outputs Python lists, not JSON, which breaks JavaScript parsing. WRONG (inline with Jinja):
RIGHT (function call with single quotes for x-data attribute):
IMPORTANT: Use SINGLE QUOTES for the x-data attribute value because |tojson outputs double quotes in JSON arrays. Single quotes on the outside prevent the JSON double quotes from breaking HTML parsing. The languageSelector function must be defined in: - static/shop/js/shop-layout.js (for storefront) - static/vendor/js/init-alpine.js (for vendor dashboard) - static/admin/js/init-alpine.js (for admin dashboard) pattern: file_pattern: "app/templates/**/*.html" anti_patterns: - 'x-data="{\s*.*languages:' - 'x-data="{\s*.*languageNames:' - 'x-data="{\s*.*languageFlags:' exceptions: - "partials/header.html" - id: "LANG-003" name: "Use tojson correctly for Python lists in templates" severity: "error" description: | When passing Python lists to JavaScript, use tojson appropriately: IN HTML ATTRIBUTES (x-data, data-*, etc.): Use SINGLE QUOTES for the attribute and |tojson for the array:
The single quotes on x-data prevent JSON double quotes from breaking the HTML attribute parsing. IN WRONG (double quotes with tojson - JSON quotes break the attribute):
pattern: file_pattern: "app/templates/**/*.html" required_with_jinja_array: - "|tojson" - id: "LANG-004" name: "Language selector function must be exported to window" severity: "error" description: | The languageSelector function must be defined and exported to window in the appropriate JavaScript file. Required in static/shop/js/shop-layout.js: function languageSelector(currentLang, enabledLanguages) { ... } window.languageSelector = languageSelector; Required in static/vendor/js/init-alpine.js: function languageSelector(currentLang, enabledLanguages) { ... } window.languageSelector = languageSelector; pattern: file_patterns: - "static/shop/js/shop-layout.js" - "static/vendor/js/init-alpine.js" required_patterns: - "function languageSelector" - "window.languageSelector" - id: "LANG-005" name: "Use native language names in language selector" severity: "error" description: | Language names must be in their native form, not English. WRONG (English names): languageNames: { 'fr': 'French', 'de': 'German', 'lb': 'Luxembourgish' } RIGHT (native names): languageNames: { 'en': 'English', 'fr': 'Francais', 'de': 'Deutsch', 'lb': 'Letzebuergesch' } pattern: file_pattern: "static/**/js/**/*.js" anti_patterns: - "'fr':\\s*'French'" - "'de':\\s*'German'" - "'lb':\\s*'Luxembourgish'" - id: "LANG-006" name: "Use correct flag codes for languages" severity: "error" description: | Flag codes must map correctly to flag-icons library codes. CORRECT MAPPINGS: - en -> gb (Great Britain flag for English) - fr -> fr (France flag) - de -> de (Germany flag) - lb -> lu (Luxembourg flag) WRONG: 'en': 'us' # US flag incorrect for general English 'en': 'en' # 'en' is not a valid flag code 'lb': 'lb' # 'lb' is not a valid flag code RIGHT: 'en': 'gb', 'fr': 'fr', 'de': 'de', 'lb': 'lu' pattern: file_pattern: "static/**/js/**/*.js" anti_patterns: - "'en':\\s*'us'" - "'en':\\s*'en'" - "'lb':\\s*'lb'" - id: "LANG-007" name: "Storefront must respect vendor's enabled languages" severity: "error" description: | Shop/storefront templates must only show languages enabled by the vendor. Use vendor.storefront_languages, not a hardcoded list. WRONG (hardcoded): {% set enabled_langs = ['en', 'fr', 'de', 'lb'] %} RIGHT (from vendor config): {% set enabled_langs = vendor.storefront_languages if vendor and vendor.storefront_languages else ['fr', 'de', 'en'] %} pattern: file_pattern: "app/templates/shop/**/*.html" required_for_lang_selector: - "vendor.storefront_languages" - id: "LANG-008" name: "Language API endpoint must use POST method" severity: "error" description: | The language set API must be called with POST method, not GET. WRONG: fetch('/api/v1/language/set?lang=' + lang) fetch('/api/v1/language/set', { method: 'GET' }) RIGHT: fetch('/api/v1/language/set', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ language: lang }) }) pattern: file_pattern: "static/**/js/**/*.js" anti_patterns: - "/language/set.*method.*GET" - "/language/set\\?lang=" - id: "LANG-009" name: "Always provide language default in templates" severity: "error" description: | When accessing request.state.language, always provide a default value. WRONG: {{ request.state.language }} RIGHT: {{ request.state.language|default("fr") }} pattern: file_pattern: "app/templates/**/*.html" anti_patterns: - 'request\\.state\\.language[^|]' required_pattern: 'request\\.state\\.language\\|default' - id: "LANG-010" name: "Translation files must be valid JSON" severity: "error" description: | All translation files in static/locales/ must be valid JSON. No trailing commas, proper quoting, etc. Files required: - static/locales/en.json - static/locales/fr.json - static/locales/de.json - static/locales/lb.json pattern: file_pattern: "static/locales/*.json" check: "valid_json" - id: "LANG-011" name: "Use $t() not I18n.t() in HTML templates" severity: "error" description: | In HTML templates, never use I18n.t() directly. It evaluates once and does NOT re-evaluate when translations finish loading async. WRONG (non-reactive, shows raw key then updates): RIGHT (reactive, updates when translations load): BEST (server-side, zero flash): {{ _('module.key') }} Note: I18n.t() is fine in .js files where it's called inside async callbacks after I18n.init() has completed. pattern: file_pattern: "**/*.html" anti_patterns: - "I18n.t("