The Jinja |tojson filter outputs JSON with double quotes. When used
inside a double-quoted HTML attribute, these quotes break the attribute
parsing causing "expected expression, got '}'" errors.
Solution: Use single quotes for x-data attributes so JSON double quotes
don't conflict:
<div x-data='languageSelector("fr", {{ langs|tojson }})'>
Updated:
- language_selector.html macro (all 3 variants)
- shop/base.html language selector
- LANG-002 and LANG-003 architecture rules documentation
- Validator to detect double-quoted x-data with tojson
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
250 lines
7.8 KiB
YAML
250 lines
7.8 KiB
YAML
# 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):
|
|
<div x-data="{
|
|
languages: {{ vendor.storefront_languages }},
|
|
...
|
|
}">
|
|
|
|
RIGHT (function call with single quotes for x-data attribute):
|
|
<div x-data='languageSelector("{{ lang }}", {{ langs|tojson }})'>
|
|
|
|
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:
|
|
<div x-data='languageSelector("fr", {{ langs|tojson }})'>
|
|
<!-- Renders: x-data='languageSelector("fr", ["fr", "de"])' -->
|
|
|
|
The single quotes on x-data prevent JSON double quotes from
|
|
breaking the HTML attribute parsing.
|
|
|
|
IN <script> BLOCKS:
|
|
Use |tojson|safe - raw JSON is valid inside script tags:
|
|
<script>
|
|
const languages = {{ vendor.storefront_languages|tojson|safe }};
|
|
</script>
|
|
|
|
WRONG (double quotes with tojson - JSON quotes break the attribute):
|
|
<div x-data="init({{ data|tojson }})">
|
|
<!-- Outputs: x-data="init(["a", "b"])" - breaks at first [ -->
|
|
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_pattern: "static/shop/js/shop-layout.js"
|
|
required_patterns:
|
|
- "function languageSelector"
|
|
- "window.languageSelector"
|
|
file_pattern: "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 }} <!-- May be None -->
|
|
|
|
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"
|