Files
orion/.architecture-rules/language.yaml
Samir Boulahtit 9920430b9e fix: correct tojson|safe usage in templates and update validator
- Remove |safe from |tojson in HTML attributes (x-data) - quotes must
  become " for browsers to parse correctly
- Update LANG-002 and LANG-003 architecture rules to document correct
  |tojson usage patterns:
  - HTML attributes: |tojson (no |safe)
  - Script blocks: |tojson|safe
- Fix validator to warn when |tojson|safe is used in x-data (breaks
  HTML attribute parsing)
- Improve code quality across services, APIs, and tests

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-13 22:59:51 +01:00

251 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 with tojson - NO safe in HTML attributes):
<div x-data="languageSelector('{{ lang }}', {{ langs|tojson }})">
NOTE: Use |tojson (without |safe) in HTML attributes so quotes
become &quot; which the browser correctly decodes. Using |safe
would output raw quotes that break the attribute 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 |tojson WITHOUT |safe - quotes become &quot; which browsers decode:
<div x-data="languageSelector('fr', {{ langs|tojson }})">
<!-- Renders: x-data="languageSelector('fr', [&quot;fr&quot;, &quot;de&quot;])" -->
IN <script> BLOCKS:
Use |tojson|safe - raw JSON is valid inside script tags:
<script>
const languages = {{ vendor.storefront_languages|tojson|safe }};
</script>
WRONG (raw output - Python syntax not valid JS):
languages: {{ vendor.storefront_languages }}
<!-- Outputs: ['fr', 'de'] - single quotes invalid in JSON -->
WRONG (|safe in HTML attributes - breaks attribute parsing):
<div x-data="init({{ data|tojson|safe }})">
<!-- Raw 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_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"