---
phase: 12-i18n-bugfixes
plan: "02"
type: execute
wave: 2
depends_on: ["12-01"]
files_modified:
- imptune/templates/base.html
- imptune/templates/dashboard.html
- imptune/templates/printers.html
- imptune/templates/printers_new.html
- imptune/templates/clients.html
- imptune/templates/client_detail.html
- imptune/templates/drivers.html
- imptune/templates/packages.html
- imptune/templates/printer_detail.html
- imptune/templates/partials/printer_list.html
- imptune/templates/partials/printer_edit_modal.html
- imptune/templates/partials/client_list.html
- imptune/templates/partials/driver_list.html
autonomous: false
requirements: []
must_haves:
truths:
- "Switching the language toggle causes ALL visible page labels (headings, buttons, table headers, form labels, empty states) to update immediately — no hardcoded English or French string remains"
- "Switching to French on dashboard shows 'Tableau de bord', 'Pilotes', 'Imprimantes' etc.; switching to English shows 'Dashboard', 'Drivers', 'Printers'"
- "All pages (dashboard, printers list, printers/new, clients, drivers, packages, printer detail, client detail) update when the toggle is clicked"
- "The non-E2E test suite still passes after the template changes"
artifacts:
- path: "imptune/templates/base.html"
provides: "Extended translation dictionary with all new keys"
min_lines: 150
- path: "imptune/templates/dashboard.html"
provides: "All strings wired to x-text/$store.i18n.t()"
- path: "imptune/templates/printers_new.html"
provides: "All form labels wired to i18n"
- path: "imptune/templates/partials/printer_edit_modal.html"
provides: "All modal labels wired to i18n"
- path: "imptune/templates/partials/printer_list.html"
provides: "Table headers and buttons wired to i18n"
key_links:
- from: "any template"
to: "Alpine.store('i18n').t('key')"
via: "x-text binding or :title/:aria-label binding"
pattern: "store\\.i18n\\.t\\("
---
Wire every hardcoded UI string in every template to the Alpine i18n store, so switching the FR/EN toggle updates all labels, buttons, headings, and messages instantly across all pages.
Purpose: Phase 11 implemented the toggle mechanism and translated nav links, but template bodies still contain hardcoded English. This plan completes the translation coverage.
Output: All templates fully wired; extended translation dictionary in base.html; passing test suite.
@C:/Users/SebastienQUEROL/.claude/get-shit-done/workflows/execute-plan.md
@C:/Users/SebastienQUEROL/.claude/get-shit-done/templates/summary.md
@.planning/PROJECT.md
@.planning/ROADMAP.md
@.planning/STATE.md
@.planning/phases/11-ui-enhancements/11-03-SUMMARY.md
@.planning/phases/12-i18n-bugfixes/12-01-SUMMARY.md
Existing translation keys already in base.html (FR + EN both):
dashboard, drivers, printers, clients, packages,
add_printer, add_client, printer_library,
edit, delete, save, cancel,
upload_driver, printer_name, ip_address, port_name,
driver, duplex_mode, one_sided, long_edge, short_edge,
color_mode, paper_size, collate, client,
edit_printer, no_printers, no_clients,
client_list, name, created, back_to_printers,
theme_label, lang_label
Pattern for elements OUTSIDE an x-data parent:
Dashboard
Pattern for elements INSIDE an existing x-data parent:
Printer Library
Pattern for static fallback text (shown before Alpine hydrates):
Keep the English literal as the element's inner text — Alpine replaces it on hydration.
Example:
Task 1: Add missing translation keys to base.html dictionaryimptune/templates/base.html
Extend the translations.fr and translations.en objects in base.html with all keys needed by templates that currently have hardcoded strings. Add the following keys to BOTH fr and en objects:
New keys to add (add to BOTH fr and en translation objects):
// Dashboard page
new_printer: fr='Nouvelle imprimante', en='New Printer'
upload_driver_btn: fr='Télécharger un pilote', en='Upload Driver'
export_package: fr='Exporter un paquet', en='Export Package'
recent_activity: fr='Activité récente', en='Recent Activity'
recent_printers: fr='Imprimantes récentes', en='Recent Printers'
recent_packages: fr='Paquets récents', en='Recent Packages'
no_packages: fr='Aucun paquet exporté pour l\'instant.', en='No packages exported yet.'
// Note: no_printers key already exists
// Printers new page
add_printer_title: fr='Ajouter une imprimante', en='Add Printer'
back_to_printer_library: fr='← Retour à la bibliothèque', en='← Back to Printer Library'
save_printer: fr='Enregistrer l\'imprimante', en='Save Printer'
upload_new_driver: fr='Télécharger un nouveau pilote', en='Upload New Driver'
no_driver_option: fr='-- Aucun pilote --', en='-- No driver --'
unassigned_option: fr='-- Non assigné --', en='-- Unassigned --'
// Printer list table headers
th_name: fr='Nom', en='Name'
th_ip: fr='Adresse IP', en='IP Address'
th_port: fr='Port', en='Port'
th_driver: fr='Pilote', en='Driver'
th_duplex: fr='Recto-verso', en='Duplex'
th_color: fr='Couleur', en='Color'
th_paper: fr='Format', en='Paper'
th_collate: fr='Assemblage', en='Collate'
th_actions: fr='Actions', en='Actions'
yes: fr='Oui', en='Yes'
no: fr='Non', en='No'
no_driver_assigned: fr='—', en='—'
// Clients page
client_name_label: fr='Nom du client', en='Client Name'
add_client_section: fr='Ajouter un client', en='Add Client'
client_list_section: fr='Liste des clients', en='Client List'
// Client detail page
back_to_clients: fr='← Tous les clients', en='← All Clients'
printers_section: fr='Imprimantes', en='Printers'
// Drivers page
drivers_title: fr='Pilotes', en='Drivers'
upload_driver_section: fr='Télécharger un package de pilote', en='Upload Driver Package'
driver_library: fr='Bibliothèque de pilotes', en='Driver Library'
uploading: fr='Téléchargement...', en='Uploading...'
upload_btn: fr='Télécharger', en='Upload'
driver_filename: fr='Nom du fichier', en='Filename'
driver_names_col: fr='Nom(s) du pilote', en='Driver Name(s)'
architecture: fr='Architecture', en='Architecture'
uploaded_at: fr='Téléchargé le', en='Uploaded'
unknown: fr='Inconnu', en='Unknown'
no_drivers: fr='Aucun pilote téléchargé.', en='No drivers uploaded yet.'
// Packages page
packages_title: fr='Paquets', en='Packages'
packages_description: fr='Imprimantes avec pilotes assignés — prêtes pour l\'export.', en='Printers with drivers assigned — ready for deployment package export.'
printer_col: fr='Imprimante', en='Printer'
client_col: fr='Client', en='Client'
driver_col: fr='Pilote', en='Driver'
downloads_col: fr='Téléchargements', en='Downloads'
no_packages_ready: fr='Aucune imprimante prête. Assignez un pilote pour activer l\'export.', en='No package-ready printers yet. Assign a driver to a printer to enable package export.'
// Printer detail page
configuration: fr='Configuration', en='Configuration'
duplex_mode_label: fr='Mode recto-verso', en='Duplex Mode'
color_mode_label: fr='Mode couleur', en='Color Mode'
color_value: fr='Couleur', en='Color'
grayscale_value: fr='Niveaux de gris', en='Grayscale'
paper_size_label: fr='Format papier', en='Paper Size'
collate_label: fr='Assemblage', en='Collate'
client_label: fr='Client', en='Client'
unassigned: fr='Non assigné', en='Unassigned'
driver_section: fr='Pilote', en='Driver'
package_label: fr='Package', en='Package'
driver_names_label: fr='Nom(s) du pilote', en='Driver Name(s)'
architecture_label: fr='Architecture', en='Architecture'
no_driver_detail: fr='Aucun pilote assigné', en='No driver assigned'
intune_commands: fr='Commandes Intune', en='Intune Commands'
install_cmd_label: fr='Commande d\'installation', en='Install command'
uninstall_cmd_label: fr='Commande de désinstallation', en='Uninstall command'
copy: fr='Copier', en='Copy'
copied: fr='Copié !', en='Copied!'
scripts_section: fr='Scripts', en='Scripts'
download_install: fr='Télécharger le script d\'installation', en='Download Install Script'
download_uninstall: fr='Télécharger le script de désinstallation', en='Download Uninstall Script'
download_detect: fr='Télécharger le script de détection', en='Download Detect Script'
export_section: fr='Export', en='Export'
download_ninja: fr='Télécharger NinjaRMM ZIP', en='Download NinjaRMM ZIP'
download_intunewin: fr='Télécharger .intunewin', en='Download .intunewin'
icon_section: fr='Icône', en='Icon'
icon_uploaded: fr='Icône téléchargée', en='Icon uploaded'
upload_icon: fr='Télécharger l\'icône', en='Upload Icon'
back_to_printers_btn: fr='Retour aux imprimantes', en='Back to Printers'
// Edit modal
edit_printer_title: fr='Modifier l\'imprimante', en='Edit Printer'
// Driver upload section (in printers_new.html)
driver_package_label: fr='Package de pilote (ZIP contenant .inf + fichiers pilote)', en='Driver Package (ZIP containing .inf + driver files)'
Add each of these to the `fr` object and the `en` object in the translations structure. Maintain the same indentation and formatting. Do NOT remove any existing keys.
cd /c/Users/SebastienQUEROL/Documents/projets/ImpTune && python -m pytest tests/test_static.py -x -q 2>&1 | tail -10
- base.html translations objects contain all new keys in both fr and en
- No existing keys removed
- test_static.py still passes
Task 2: Wire all template strings to i18n store
imptune/templates/dashboard.html,
imptune/templates/printers.html,
imptune/templates/printers_new.html,
imptune/templates/clients.html,
imptune/templates/client_detail.html,
imptune/templates/drivers.html,
imptune/templates/packages.html,
imptune/templates/printer_detail.html,
imptune/templates/partials/printer_list.html,
imptune/templates/partials/printer_edit_modal.html,
imptune/templates/partials/client_list.html,
imptune/templates/partials/driver_list.html
Wire every hardcoded UI string in every template to the Alpine i18n store using the x-text binding pattern. The x-data directive is required only on elements that are NOT already inside an x-data parent scope.
Rules:
- Elements with no x-data ancestor: add `x-data x-text="$store.i18n.t('key')"` inline on the element
- Elements inside an existing x-data parent: add `x-text="$store.i18n.t('key')"` only (no x-data needed)
- For attribute text (e.g. placeholder, title, aria-label): use `:placeholder="$store.i18n.t('key')"` inside an x-data scope
- Keep a static fallback text inside the element (shown before Alpine hydrates): e.g. ``
- For Jinja2-rendered values (like `{{ printer.name }}`, `{{ 'Yes' if p.color_mode else 'No' }}`): the Yes/No values should use Alpine ternary: `:x-text` is not valid — instead wrap in a `` and use `x-text="$store.i18n.t({{ 'yes' if p.color_mode else 'no' }})"` — WAIT: Jinja2 can't embed Alpine keys. Use a data attribute trick: `
` elements with `x-show`:
For printer_list.html Yes/No cells: Use Alpine x-show with Jinja2 condition:
Color: `
{{ 'Oui' if p.color_mode else 'Non' }}
` is WRONG (hardcoded FR).
Correct approach: keep Jinja2 rendering but pass a data attribute, then let Alpine read it:
{{ 'Yes' if p.color_mode else 'No' }}
For printer_detail.html conditional strings (Color/Grayscale, Yes/No):
{{ "Color" if printer.color_mode else "Grayscale" }}
For printer_detail.html copy button text (already has x-data="{ copiedInstall: false }"):
x-text="copiedInstall ? $store.i18n.t('copied') : $store.i18n.t('copy')"
For printer_detail.html Unassigned:
{{ printer.client.name if printer.client_id else "Unassigned" }}
— WRONG: Jinja2 emitting Alpine keys is fragile. Correct approach:
If printer.client_id: render `
{{ printer.client.name }}
` (client name is data, not translatable)
If not: render `
Unassigned
`
Use Jinja2 if/else for this:
{% if printer.client_id %}
{{ printer.client.name }}
{% else %}
Unassigned
{% endif %}
Specific changes per file:
**dashboard.html**:
- `