Traduction de l'application #4

Open
opened 2026-08-28 22:00:03 +01:00 by maxime · 4 comments
Owner

Description

Gérer le multilingue français/anglais de l’application avec @nextcloud/l10n et translationtool.

L’application suit la langue du compte Nextcloud après rechargement, sans réglage propre à l’ExApp. Couvrir l’interface et l’accessibilité, les formats de dates, les erreurs/validations/avertissements API, les notifications et rappels, ainsi que le mode hors ligne et le manifeste. Les données des familles, identifiants, routes et formats d’échange restent inchangés. Sources anglaises, catalogue français complet, anglais de secours.

Conception terminée le 6 septembre 2026 ; développement non commencé. Le guide technique détaillé contient les décisions, les fichiers concernés, les contrats internes, les dépendances entre lots, les critères de validation et les pièges AppAPI vérifiés sur l’instance locale. Il permet de reprendre ce ticket sans le contexte de la conversation.

Plan d'action

  • Analyser le code, le wiki et le contrat AppAPI ; publier la conception technique et le découpage de réalisation.
  • Lot 1 — Infrastructure de traduction reproductible, catalogues, synchronisation Nextcloud et preuve verticale (guide).
  • Lot 2 — Runtime i18n serveur, erreurs/validations Zod et avertissements localisés, contrats HTTP conservés.
  • Lot 3 — Traduction de la coquille, première utilisation, accueil, familles et listes ; vrais pluriels.
  • Lot 4 — Traduction calendrier/repas/recettes/paramètres/administration/import-export ; locale de formatage Nextcloud.
  • Lot 5 — Notifications et rappels avec clés anglaises et paramètres riches, traduits par Nextcloud pour le destinataire.
  • Lot 6 — Page hors ligne bilingue autonome, métadonnées compatibles et manifeste localisé.
  • Lot 7 — Catalogue français complet, tests ciblés/e2e, documentation et vérification des artefacts de livraison.

Recette

  • Exécuter la matrice du guide : comptes FR/EN, locale distincte, pluriels 0/1/2, langues de secours, isolation de requêtes concurrentes, erreurs/warnings, notifications et hors ligne.
  • Vérifier que les données utilisateur ne sont jamais traduites et que les paramètres restent fidèles et sûrs ; maintenir 404 non-membre, snapshots serveur, gardes de génération et invariants D31–D37.
  • Vérifier une installation neuve et une mise à jour des catalogues côté Node et Nextcloud ; page hors ligne sans fetch() ni sous-ressource.
  • Passer make l10n-check, make lint typecheck test, make build-frontend et la vérification IIFE, puis cd e2e && npm test ; tests de dates hors UTC et build de l’image de production.
  • Revue technique personnelle / branch-review et revue prévue par Workflow ; consigner et corriger les retours dans le ticket.
  • Revue fonctionnelle français/anglais, desktop/mobile et libellés accessibles ; fixtures de langue réinitialisées entre tests.
  • Mise à jour du wiki (Architecture, UX, Scope, Code guidelines, Dev environment), d’AGENTS.md, du README l10n et des références AppAPI ; ajouter la prochaine décision libre sans renuméroter les existantes.

Plan de MEP

  • Préparer pour le jalon 1.0.x une livraison cohérente : image Node, frontend/worker, catalogues et archive AppAPI ; ne pas publier à l’App Store dans ce ticket.
  • Documenter/appliquer la copie des catalogues vers le dossier writable apps de Nextcloud en installation manuelle et leur rafraîchissement à la mise à jour ; ne jamais y copier appinfo/info.xml.
  • Confirmer l’absence de migration SQL, de renommage des données et de reprojection ; lire les anciens caches hors ligne sans migration destructive.
  • Après livraison autorisée, smoke tests FR/EN : navigation, erreur API, notification, manifeste et démarrage hors ligne.
  • Préparer le retour arrière conjoint image/artefact/catalogues, avec rechargement page/worker ; conserver les données existantes.
  • Intégrer en historique linéaire après recette ; ne pousser ni publier sans demande. Passer le ticket à done après validation/intégration, puis delivered lors de la livraison du jalon.
## Description Gérer le multilingue français/anglais de l’application avec `@nextcloud/l10n` et `translationtool`. L’application suit la langue du compte Nextcloud après rechargement, sans réglage propre à l’ExApp. Couvrir l’interface et l’accessibilité, les formats de dates, les erreurs/validations/avertissements API, les notifications et rappels, ainsi que le mode hors ligne et le manifeste. Les données des familles, identifiants, routes et formats d’échange restent inchangés. Sources anglaises, catalogue français complet, anglais de secours. **Conception terminée le 6 septembre 2026 ; développement non commencé.** Le [guide technique détaillé](https://git.lozach.eu/maxime/OrganisateurFamilial/issues/4#issuecomment-111) contient les décisions, les fichiers concernés, les contrats internes, les dépendances entre lots, les critères de validation et les pièges AppAPI vérifiés sur l’instance locale. Il permet de reprendre ce ticket sans le contexte de la conversation. ## Plan d'action - [x] Analyser le code, le wiki et le contrat AppAPI ; publier la conception technique et le découpage de réalisation. - [x] Lot 1 — Infrastructure de traduction reproductible, catalogues, synchronisation Nextcloud et preuve verticale ([guide](https://git.lozach.eu/maxime/OrganisateurFamilial/issues/4#issuecomment-111)). - [x] Lot 2 — Runtime i18n serveur, erreurs/validations Zod et avertissements localisés, contrats HTTP conservés. - [x] Lot 3 — Traduction de la coquille, première utilisation, accueil, familles et listes ; vrais pluriels. - [x] Lot 4 — Traduction calendrier/repas/recettes/paramètres/administration/import-export ; locale de formatage Nextcloud. - [x] Lot 5 — Notifications et rappels avec clés anglaises et paramètres riches, traduits par Nextcloud pour le destinataire. - [x] Lot 6 — Page hors ligne bilingue autonome, métadonnées compatibles et manifeste localisé. - [x] Lot 7 — Catalogue français complet, tests ciblés/e2e, documentation et vérification des artefacts de livraison. ## Recette - [x] Exécuter la matrice du [guide](https://git.lozach.eu/maxime/OrganisateurFamilial/issues/4#issuecomment-111) : comptes FR/EN, locale distincte, pluriels 0/1/2, langues de secours, isolation de requêtes concurrentes, erreurs/warnings, notifications et hors ligne. - [x] Vérifier que les données utilisateur ne sont jamais traduites et que les paramètres restent fidèles et sûrs ; maintenir 404 non-membre, snapshots serveur, gardes de génération et invariants D31–D37. - [x] Vérifier une installation neuve et une mise à jour des catalogues côté Node **et** Nextcloud ; page hors ligne sans `fetch()` ni sous-ressource. - [x] Passer `make l10n-check`, `make lint typecheck test`, `make build-frontend` et la vérification IIFE, puis `cd e2e && npm test` ; tests de dates hors UTC et build de l’image de production. - [x] Revue technique personnelle / `branch-review` et revue prévue par Workflow ; consigner et corriger les retours dans le ticket. - [x] Revue fonctionnelle français/anglais, desktop/mobile et libellés accessibles ; fixtures de langue réinitialisées entre tests. - [x] Mise à jour du wiki (Architecture, UX, Scope, Code guidelines, Dev environment), d’AGENTS.md, du README l10n et des références AppAPI ; ajouter la prochaine décision libre sans renuméroter les existantes. ## Plan de MEP - [x] Préparer pour le jalon `1.0.x` une livraison cohérente : image Node, frontend/worker, catalogues et archive AppAPI ; ne pas publier à l’App Store dans ce ticket. - [x] Documenter/appliquer la copie des catalogues vers le dossier writable apps de Nextcloud en installation manuelle et leur rafraîchissement à la mise à jour ; ne jamais y copier appinfo/info.xml. - [x] Confirmer l’absence de migration SQL, de renommage des données et de reprojection ; lire les anciens caches hors ligne sans migration destructive. - [x] Après livraison autorisée, smoke tests FR/EN : navigation, erreur API, notification, manifeste et démarrage hors ligne. - [x] Préparer le retour arrière conjoint image/artefact/catalogues, avec rechargement page/worker ; conserver les données existantes. - [x] Intégrer en historique linéaire après recette ; ne pousser ni publier sans demande. Passer le ticket à done après validation/intégration, puis delivered lors de la livraison du jalon.
maxime added this to the 1.0.x milestone 2026-08-28 22:00:03 +01:00
Author
Owner

Conception technique — ticket #4

Plan préparé le 6 septembre 2026 sur main, commit c6748b2. Ce commentaire est le guide d’exécution ; les cases du corps du ticket suivent l’avancement. Aucun développement n’a été effectué pendant cette conception.

1. Comportement attendu et limites

  • L’application suit la langue du compte Nextcloud : français et anglais, changement pris en compte après rechargement. Aucun sélecteur ni préférence de langue propre à l’ExApp.
  • Les messages sources sont en anglais, les traductions françaises conservent le vocabulaire actuel. Anglais de secours si la langue n’est pas prise en charge. fr, fr_FR, fr-CA sélectionnent le français ; en, en_GB, en-US l’anglais. Une langue non prise en charge ne doit ni casser l’application ni créer de requêtes en boucle.
  • La locale de formatage est celle de Nextcloud, distincte de la langue : interface anglaise et dates françaises sont un cas valide. Ne pas remplacer une date sans heure par un instant local. Conserver les semaines du lundi au dimanche dans ce ticket ; changer leur premier jour modifierait la planification hebdomadaire et dépasse la traduction.
  • Couvrir la navigation, les vues et dialogues, les états vides/chargement, les boutons, placeholders, tooltips, libellés accessibles, erreurs et avertissements de l’API, validations Zod, notifications immédiates et rappels, page hors ligne et manifeste d’installation.
  • Ne pas traduire les données saisies/importées : noms des familles/listes, recettes, ingrédients, événements, abonnements, noms de personnes. Ne pas traduire les identifiants, routes françaises existantes, enums, clés JSON, clés de cache/préférences, UID CalDAV ou types de listes.
  • Les noms des collections CalDAV et les textes déjà persistés restent inchangés. Les libellés d’affichage des calendriers intégrés sont traduits à partir de leur kind dans l’application ; les titres des abonnements restent des données. Aucun renommage de masse, aucune migration SQL, aucun déclenchement de reprojection lié à un changement de langue.
  • Le nom propre « Organisateur Familial » reste le nom du produit. Les anciennes notifications déjà enregistrées ne sont pas réécrites ; les nouvelles utilisent les clés anglaises.
  • Pas de service de traduction, pas de Transifex ni de synchronisation GitHub requis. translationtool est un outil local/de CI ; PHP/gettext ne sont que des dépendances de construction, jamais du runtime Node.

Cette évolution remplace, dans le périmètre i18n, la règle actuelle « toute chaîne utilisateur en français » par « sources anglaises extractibles + catalogue français complet ». Conserver l’anglais pour le code, les tests, les commentaires et les nouvelles sections du wiki.

2. État existant et preuves à connaître

Surface Point d’entrée actuel Conséquence
Frontend ex_app/src/main.ts, dépendance @nextcloud/l10n déjà installée (API v3) t/n globaux servent déjà les composants Nextcloud, mais les chaînes applicatives sont en dur. Ne pas installer vue-i18n.
Catalogues ex_app/l10n/README.md uniquement La présence du dossier ne signifie pas que la traduction fonctionne.
Formats services/format.ts, services/relative-day.ts, constants/*.ts côté frontend LOCALE = 'fr-FR', libellés et pluriels à remplacer.
Erreurs ex_app/lib/src/errors.ts, routes/*-schema.ts, schémas dans les routes, services/access.ts Le client affiche message et issues[].message tels quels. Beaucoup de validations Zod n’ont pas de message explicite.
Avertissements services de projection, abonnements, import/export et leurs contrats/presenters Traduire aussi les réponses réussies contenant des warnings, sans changer leur structure publique.
Notifications services/notification.ts, list.ts, shopping.ts, reminder.ts, transfer-import.ts, ocs/client.ts Certaines phrases interpolent actuellement les titres avant envoi : elles ne sont pas extractibles.
Hors ligne offline/shell.html, sw.ts, services/offline-snapshot.ts, offline-prefetch.ts HTML autonome en français, sans Vue ; métadonnées sans langue/locale.
Livraison Dockerfile, compose.yaml, dev/setup.sh, Makefile Le Dockerfile copie déjà ex_app/l10n, mais cela ne fournit pas les catalogues à Nextcloud.

Vérifié en lecture seule dans AppAPI installé sur la stack Nextcloud 34 :

  • lib/Service/AppAPIService.php::prepareRequestToExApp renseigne Accept-Language via l10nFactory->findLanguage(appId) si absent. Exploiter cet en-tête pour les réponses Node ; ne pas ajouter un appel OCS de préférence à chaque requête.
  • lib/Notifications/ExAppNotifier.php::prepare obtient le catalogue de l’ExApp avec la langue du destinataire, traduit rich_subject puis applique rich_subject_params. Envoyer une clé anglaise non traduite et des paramètres riches ; ne pas traduire avec la langue de l’auteur et ne pas rechercher la langue de tous les membres.
  • lib/Listener/LoadMenuEntriesListener.php passe aussi le display name du menu dans le catalogue de l’ExApp.
  • lib/Fetcher/ExAppArchiveFetcher.php::installTranslations installe les catalogues sur Nextcloud. En manual-install, fournir explicitement <writable-apps>/organisateur_familial/l10n/. N’y copier aucun appinfo/info.xml : Nextcloud pourrait prendre le dossier pour une app PHP.

Références : traductions AppAPI, API @nextcloud/l10n, translationtool, source de l’extracteur. Lire aussi le wiki Architecture (D19, D26, D31–D37), Workflow, Code guidelines et le skill nextcloud-exapp-dev.

3. Décisions d’implémentation

A. Une chaîne de génération, un catalogue commun

Le domaine est littéralement organisateur_familial. Frontend : import { translate as t, translatePlural as n } from '@nextcloud/l10n', puis t('organisateur_familial', 'Lists') et n('organisateur_familial', '%n item remaining', '%n items remaining', count). Ne pas utiliser t(domain, variable) pour des textes sources ; les appels doivent porter les littéraux extractibles. Utiliser des phrases complètes et des paramètres nommés, jamais des morceaux concaténés ou restant(s).

Conserver ex_app/l10n/{fr.js,fr.json} comme sorties distribuées ; l’anglais fonctionne comme langue source. Un catalogue anglais explicite peut être généré si le packaging l’exige, sans entretenir une deuxième traduction identique. Sources PO : translationfiles/fr/organisateur_familial.po, modèle translationfiles/templates/organisateur_familial.pot, à la racine du dépôt.

L’extracteur officiel reconnaît .ts, .html et prépare les .vue, avec t:2 et n:2,3. Il découvre les apps par leur dossier l10n et attend leur appinfo/info.xml au même niveau. La disposition actuelle du dépôt ne correspond pas à ce layout. Ajouter un script de staging dans dev/ : construire un dossier temporaire organisateur_familial/ avec appinfo/info.xml, l10n/, les sources frontend/backend utiles et les PO ; y lancer les deux commandes officielles ; recopier uniquement les sorties prévues. Exclure node_modules, dist, tests, e2e et fichiers générés. Ne pas créer plusieurs racines d’app détectables dans le staging.

Épingler le PHAR officiel par commit et somme SHA-256 et le runtime PHP/gettext par une version reproductible. Aucun téléchargement pendant le fonctionnement de l’application. Ajouter make l10n-extract, make l10n-build, make l10n-check. Le dernier régénère dans un temporaire, compare les contenus (normaliser uniquement les métadonnées volatiles du POT), vérifie les placeholders/pluriels et refuse les entrées françaises vides/fuzzy. Un test d’extraction doit prouver que Vue, TS backend, notifications et hors ligne figurent dans le POT.

B. Frontend et formatage

Utiliser les catalogues injectés par Nextcloud. Vérifier qu’ils sont enregistrés avant l’évaluation des constantes contenant t(...). Si ce n’est pas le cas sur la version testée, déplacer l’évaluation dans les fonctions/computed utilisées après initialisation ; ne pas introduire un import dynamique qui casse l’IIFE. Conserver les globals t/n attendus par @nextcloud/vue.

Les vocabulaires restent centralisés dans constants/lists.ts, meals.ts, calendars.ts, etc. Pour un décompte, exposer une fonction prenant le nombre et appelant n avec des sources littérales par kind ; ne pas garder un suffixe grammatical dans remaining. Traduire attributs accessibles et messages des stores/composables autant que les templates. Vue doit afficher les paramètres comme du texte : tester &, apostrophes et <...> afin d’éviter double échappement ou XSS ; pas de v-html pour une phrase traduite.

Dans services/format.ts, remplacer la constante fixe par getCanonicalLocale() (disponible dans la dépendance installée), avec validation/repli de locale. Inclure la locale dans la clé du cache des formatteurs. Garder formatDay(... timeZone: 'UTC') pour les jours nus et le fuseau local pour les instants. relative-day.ts fournit des libellés t(...). Aucun parsing de dates localisées ; ISO reste le format de stockage et d’échange.

C. Backend : traduction à la frontière HTTP

Ajouter de petits modules ex_app/lib/src/i18n/ : résolution de langue, lecture des JSON générés, interpolation de paramètres, description d’un message et erreur localisable. Pas de dépendance navigateur, pas de variable globale « langue courante », pas de modification globale de Zod par requête.

Contrat interne proposé : MessageDescriptor = { source: string; params?: Record<string, string | number> }; LocalizedError conserve statusCode et ce descripteur. Le marqueur backend t(domain, source) est une fonction identité pour l’extraction (retourne la source, ne traduit pas) ; les services construisent un descripteur à partir de cette source et de paramètres distincts. renderMessage(descriptor, language) s’utilise dans le handler/presenter. Ne jamais tenter de retrouver une clé en remplaçant des fragments dans une ancienne phrase française interpolée.

resolveLanguage(Accept-Language) traite les tags, variantes _/-, pondérations, q=0, entrée absente/malformée et langues inconnues ; retourne seulement fr ou en, secours en. Les JSON chargés sont immuables ; chemins basés sur cette liste fermée, jamais sur l’en-tête brut. Ne pas interpréter pluralForm avec eval/Function : seules les deux règles fr/en sont nécessaires si un message serveur a un pluriel.

Dans errors.ts, traduire les erreurs locales connues, préserver les codes HTTP et les champs publics ; conserver 404 pour absent et non-membre. Pour une erreur technique inconnue, exposer un message générique localisé et logger le détail ; ne pas envoyer le texte brut de Nextcloud comme traduction. Couvrir aussi les refus d’authentification/parser et les réponses anticipées des routes. Les réponses lifecycle restent conformes au protocole (corps vide en cas de succès, pas d’appel utilisateur).

Pour Zod v3, traduire au moment de la réponse à partir du ZodIssue et de ses limites/type/path ; déclarer les messages métiers personnalisés avec des clés extractibles, et garder leurs paramètres séparés si nécessaire. Couvrir min/max, type, enum, format/date, refinements et les safeParse manuels qui répondent hors du handler global. Aucun z.setErrorMap dépendant de la langue : deux utilisateurs simultanés doivent rester isolés.

Les warnings internes deviennent des descripteurs jusqu’au presenter ; le contrat sur le fil reste string[]. Faire de même pour toute raison lisible exportée/retournée, sans changer les enums de diagnostic ni la version du format de transfert. Ne pas utiliser un hook qui parcourt récursivement chaque chaîne JSON : il traduirait aussi les données des familles. Le client continue d’afficher message, issues[].message, warnings, sans logique de traduction ni branchement sur les statuts.

D. Notifications

Remplacer tous les sujets construits par interpolation par une source littérale marquée, par exemple t('organisateur_familial', '{user} added ingredients from {recipe} to {list}'), avec user, recipe, list dans richSubjectParams. Utiliser les paramètres riches Nextcloud appropriés (user, highlight pour un texte sans ressource Nextcloud native), vérifier leur rendu réel. Les rappels « Tomorrow: {event} » suivent le même mécanisme. Garder un objet de paramètres, même vide, comme le fait déjà sendNotification.

Ne pas appeler renderMessage avant l’envoi : le notifier Nextcloud assure la langue du destinataire et la traduction à la lecture. Préserver préférences, exclusion de l’auteur et void notifyFamily(...). Préférer les formulations sans décompte pour les notifications : le notifier observé appelle t, pas n. Une nouvelle traduction ne doit ajouter aucun appel OCS par membre.

E. Hors ligne et manifeste

Étendre OfflineMeta avec language et locale facultatifs pour lire les caches déjà existants. saveMeta les écrit depuis le contexte Nextcloud de la page ; le préchargement reste l’unique chemin existant, sans second écrivain de l’index ni suppression des gardes de révision. Au prochain chargement connecté, enregistrer les nouvelles valeurs même si les listes n’ont pas changé.

La page hors ligne embarque les deux langues au build à partir des catalogues communs (un sous-ensemble de ses messages suffit). Ajouter un petit helper local de traduction de texte à offline/shell.html ou un module de build dédié ; garder des appels sources extractibles t(domain, '...'). Injecter les JSON via le build de sw.ts/vite.config.sw.ts avec sérialisation sûre (< échappé dans le script HTML), jamais par concaténation de données utilisateur. Aucun import de @nextcloud/l10n, Vue ou window.OC dans le worker/la page autonome.

Lire la langue/locale de la méta, mettre à jour html.lang, title, titre, bouton et tous les états vides/erreurs. Ancienne méta sans langue : repli français pour préserver les anciens snapshots ; aucune méta : repli anglais explicite. Échec de lecture du cache : état d’erreur autonome, sans dépendance réseau. Les dates utilisent la locale mémorisée. Les données restent échappées via le mécanisme existant.

D35 reste inchangé : aucun fetch() dans le worker, pas de catalogue distant, aucune sous-ressource nécessaire hors ligne. Navigation preload et lecture de Cache Storage uniquement.

Le manifeste est PUBLIC, il ne peut pas dépendre d’une session. installOfflineManifest() fournit ?lang=fr|en ; routes/static.ts valide cette liste et traduit short_name, renseigne lang, garde le nom propre et le scope/start_url existants. La variante doit figurer dans l’URL pour éviter de partager un manifeste de la mauvaise langue. Sans paramètre : anglais. Tester via le proxy sans credentials ; pas de nouvelle route ni accès utilisateur.

F. Distribution des catalogues

  • Monter ex_app/l10n en lecture seule dans le conteneur backend de développement, à un chemin explicite correspondant à celui utilisé en production (config dédiée si nécessaire).
  • Ajouter une synchronisation idempotente vers le dossier writable apps de Nextcloud dans dev/setup.sh, réutilisée par make reload après génération. Copier uniquement les catalogues, propriétaire lisible par www-data. Vérifier aussi les mises à jour/caches Nextcloud, pas seulement la première installation.
  • Garder les JSON dans l’image Node pour les réponses API. Ne pas ajouter une route /l10n/* comme substitut à l’installation côté Nextcloud.
  • Préparer un artefact de release avec une unique racine organisateur_familial/, appinfo/info.xml et l10n/ au niveau attendu par AppAPI. Les sources PO ne sont pas requises au runtime. Fournir une cible de packaging si elle n’existe pas ; ne pas publier à l’App Store dans ce ticket.

4. Lots ordonnés pour l’implémentation

Chaque lot doit compiler et avoir ses tests ciblés avant le suivant. Créer une branche feat/issue-4-i18n depuis le main actualisé ; si HEAD a changé, relire les fichiers touchés et ajuster le plan. Ne pas pousser sans demande.

  1. Infrastructure et preuve verticale (build(i18n): ...). Ajouter staging, PHAR épinglé, PO/POT/JSON/JS, commandes Make et contrôle CI. Brancher montage backend/synchronisation Nextcloud. Faire passer une chaîne Vue française/anglaise, une clé backend et une clé de notification dans la génération. Critère : FR chargé réellement dans le navigateur via Nextcloud et extraction TS/Vue/HTML prouvée. Résoudre tout problème de chargement avant la conversion massive.
  2. Runtime serveur et erreurs (feat(i18n): localize API feedback). Ajouter modules i18n, erreurs/descripteurs, conversion Zod, migration des warnings et des réponses anticipées, en conservant contrats et autorisation. Critère : même requête invalide → mêmes statut/path, messages FR/EN différents ; requêtes concurrentes isolées ; warnings de succès traduits.
  3. Coquille et listes (feat(i18n): translate navigation and lists). App.vue, routes.ts pour les seuls libellés éventuels, Welcome, switcher, famille/paramètres, home, listes, composants et constantes associés. Remplacer les décomptes par de vrais pluriels. Critère : première utilisation et parcours liste FR/EN, aucun nom saisi modifié.
  4. Calendrier, repas, recettes, administration et formats (feat(i18n): translate planning and settings). Vues/composants/composables/stores restants, tous constants/*.ts, formatage et dates relatives. Inclure filtres, récurrence, validation des formulaires, import/export et panneaux d’administration. Critère : chaque écran visité en anglais et français, responsive/accessibilité conservés, locale de date indépendante de la langue.
  5. Notifications (feat(i18n): translate notifications through AppAPI). Adapter producteurs/paramètres riches et tests activity, reminder, shopping/import. Critère : deux destinataires FR/EN lisent la même activité dans leur langue sans lookup de préférence supplémentaire.
  6. Hors ligne et installation (feat(i18n): localize offline view and manifest). Méta compatible, catalogue embarqué, formatage, manifeste paramétré, tests worker/build. Critère : démarrage hors ligne après chargement EN/FR et cache ancien, zéro requête réseau du worker et contenu inchangé.
  7. Finition, recette et documentation (test(i18n): ..., docs(i18n): ... si séparables). Compléter/revoir le PO français, prouver sa couverture, corriger les tests existants au niveau des assertions de texte seulement. Ajouter les e2e ciblés, vérifier image/artefact et mettre le wiki à jour. Ne pas déplacer la mécanique métier pendant une traduction.

5. Matrice de vérification obligatoire

Vérification Résultat attendu
Compte FR / compte EN, navigateur configuré dans l’autre langue La langue du compte prévaut, y compris après rechargement complet.
Locale fr-FR avec langue EN Textes anglais, dates françaises ; jour sans heure identique.
Décomptes 0, 1, 2 Pluriels français/anglais valides, phrases complètes par kind.
Langue non prise en charge, variante fr-CA/en-GB, en-tête invalide et q=0 Résolution déterministe et secours défini, jamais de 500.
Deux requêtes API FR/EN chevauchées Pas de fuite de langue ; même statut et contrat JSON.
Nom vide/trop long, mauvais enum/date/type, refus de non-membre Message localisé ; 404 du non-membre conservé ; aucune donnée révélée.
Avertissement import/projection réussi et erreur Nextcloud Retour utile localisé sans traduire les noms/imports ; détails techniques non affichés comme message utilisateur.
Notifications activité/rappel à destinataires FR/EN Traduction dans Nextcloud, paramètres riches lisibles, auteur/préférences respectés.
Hors ligne FR/EN, ancienne méta, cache vide/indisponible Tous les textes autonomes cohérents, pas de fetch ; jours et état coché inchangés.
Valeurs contenant &, apostrophes, <img ...> Texte fidèle et échappé, aucune injection, aucune double entité visible.
Installation neuve puis mise à jour des catalogues Les deux côtés Node/Nextcloud utilisent la même version ; pas de traduction périmée.
Manifeste public FR/EN, UI sur chemin avec/sans index.php Nom court/lang corrects ; navigation/base router inchangées.

Tests recommandés : nouveaux ex_app/lib/src/i18n/*.test.ts, extension de app.test.ts/tests erreurs et services concernés ; format.test.ts, relative-day.test.ts, tests vocabulaires/pluriels et offline-snapshot.test.ts ; un e2e/tests/i18n.spec.ts couvrant les parcours et notifications réels. Fixer explicitement la langue FR de la suite existante et restaurer les préférences des comptes dans setup/teardown ; ne pas se contenter du locale Playwright, qui ne change pas la préférence Nextcloud. Les tests i18n utilisent comptes/fixtures imposés par la suite et ne doivent pas rendre les specs suivantes anglaises par accident.

À la recette : make l10n-check, make lint typecheck test, make build-frontend, vérifier js/main.js IIFE puis cd e2e && npm test. Exécuter aussi les tests de dates hors UTC (cd ex_app/src && npm run test:tz) et construire l’image de production pour vérifier les chemins des JSON. Les e2e passent par AppAPI et un frontend réellement reconstruit. Si échec inexpliqué, établir deux runs cohérents du main de référence avant d’accuser le diff. Revue branch-review et revue technique prévue par Workflow ; consigner les retours dans le ticket.

6. Documentation et mise en production

À la fin du développement, ajouter la prochaine décision libre dans Architecture (D37 est la dernière à la date du plan ; ne pas réserver aveuglément D38), mettre à jour UX (langue Nextcloud, hors ligne), Scope (FR/EN), Code guidelines et AGENTS.md (sources anglaises et catalogues), Dev environment (génération/synchronisation), ex_app/l10n/README.md et les références du skill pour les mécanismes AppAPI mesurés. Revoir les métadonnées anglaises/françaises d’appinfo/info.xml : elles décrivent encore un dossier/carnet d’adresses, contraire à D23. Ne pas présenter le ticket comme livré avant validation.

MEP : livrer ensemble image Node, frontend/worker et catalogues côté Nextcloud dans la version du jalon ; contrôler une installation manuelle et le contenu de l’archive destinée à AppAPI. Prévoir la procédure de rafraîchissement des catalogues pour une mise à jour existante. Pas de migration SQLite, pas de réimport des données, pas de reprojection. Smoke test FR/EN connecté, erreur API, notification et consultation hors ligne. Retour arrière : restaurer image/artefact et catalogues de la même version, recharger la page/worker ; les métadonnées ajoutées sont facultatives et les anciens caches restent lisibles. Intégration linéaire seulement après recette ; aucune publication/push automatique autorisée par ce plan.

# Conception technique — ticket #4 Plan préparé le 6 septembre 2026 sur `main`, commit `c6748b2`. Ce commentaire est le guide d’exécution ; les cases du corps du ticket suivent l’avancement. Aucun développement n’a été effectué pendant cette conception. ## 1. Comportement attendu et limites - L’application suit la **langue du compte Nextcloud** : français et anglais, changement pris en compte après rechargement. Aucun sélecteur ni préférence de langue propre à l’ExApp. - Les messages sources sont en anglais, les traductions françaises conservent le vocabulaire actuel. Anglais de secours si la langue n’est pas prise en charge. `fr`, `fr_FR`, `fr-CA` sélectionnent le français ; `en`, `en_GB`, `en-US` l’anglais. Une langue non prise en charge ne doit ni casser l’application ni créer de requêtes en boucle. - La **locale de formatage** est celle de Nextcloud, distincte de la langue : interface anglaise et dates françaises sont un cas valide. Ne pas remplacer une date sans heure par un instant local. Conserver les semaines du lundi au dimanche dans ce ticket ; changer leur premier jour modifierait la planification hebdomadaire et dépasse la traduction. - Couvrir la navigation, les vues et dialogues, les états vides/chargement, les boutons, placeholders, tooltips, libellés accessibles, erreurs et avertissements de l’API, validations Zod, notifications immédiates et rappels, page hors ligne et manifeste d’installation. - Ne pas traduire les données saisies/importées : noms des familles/listes, recettes, ingrédients, événements, abonnements, noms de personnes. Ne pas traduire les identifiants, routes françaises existantes, enums, clés JSON, clés de cache/préférences, UID CalDAV ou types de listes. - Les noms des collections CalDAV et les textes déjà persistés restent inchangés. Les libellés d’affichage des calendriers intégrés sont traduits à partir de leur `kind` dans l’application ; les titres des abonnements restent des données. Aucun renommage de masse, aucune migration SQL, aucun déclenchement de reprojection lié à un changement de langue. - Le nom propre « Organisateur Familial » reste le nom du produit. Les anciennes notifications déjà enregistrées ne sont pas réécrites ; les nouvelles utilisent les clés anglaises. - Pas de service de traduction, pas de Transifex ni de synchronisation GitHub requis. `translationtool` est un outil local/de CI ; PHP/gettext ne sont que des dépendances de construction, jamais du runtime Node. Cette évolution remplace, dans le périmètre i18n, la règle actuelle « toute chaîne utilisateur en français » par « sources anglaises extractibles + catalogue français complet ». Conserver l’anglais pour le code, les tests, les commentaires et les nouvelles sections du wiki. ## 2. État existant et preuves à connaître | Surface | Point d’entrée actuel | Conséquence | | --- | --- | --- | | Frontend | `ex_app/src/main.ts`, dépendance `@nextcloud/l10n` déjà installée (API v3) | `t`/`n` globaux servent déjà les composants Nextcloud, mais les chaînes applicatives sont en dur. Ne pas installer vue-i18n. | | Catalogues | `ex_app/l10n/README.md` uniquement | La présence du dossier ne signifie pas que la traduction fonctionne. | | Formats | `services/format.ts`, `services/relative-day.ts`, `constants/*.ts` côté frontend | `LOCALE = 'fr-FR'`, libellés et pluriels à remplacer. | | Erreurs | `ex_app/lib/src/errors.ts`, `routes/*-schema.ts`, schémas dans les routes, `services/access.ts` | Le client affiche `message` et `issues[].message` tels quels. Beaucoup de validations Zod n’ont pas de message explicite. | | Avertissements | services de projection, abonnements, import/export et leurs contrats/presenters | Traduire aussi les réponses réussies contenant des warnings, sans changer leur structure publique. | | Notifications | `services/notification.ts`, `list.ts`, `shopping.ts`, `reminder.ts`, `transfer-import.ts`, `ocs/client.ts` | Certaines phrases interpolent actuellement les titres avant envoi : elles ne sont pas extractibles. | | Hors ligne | `offline/shell.html`, `sw.ts`, `services/offline-snapshot.ts`, `offline-prefetch.ts` | HTML autonome en français, sans Vue ; métadonnées sans langue/locale. | | Livraison | `Dockerfile`, `compose.yaml`, `dev/setup.sh`, `Makefile` | Le Dockerfile copie déjà `ex_app/l10n`, mais cela ne fournit pas les catalogues à Nextcloud. | Vérifié **en lecture seule dans AppAPI installé sur la stack Nextcloud 34** : - `lib/Service/AppAPIService.php::prepareRequestToExApp` renseigne `Accept-Language` via `l10nFactory->findLanguage(appId)` si absent. Exploiter cet en-tête pour les réponses Node ; ne pas ajouter un appel OCS de préférence à chaque requête. - `lib/Notifications/ExAppNotifier.php::prepare` obtient le catalogue de l’ExApp avec la langue du destinataire, traduit `rich_subject` puis applique `rich_subject_params`. Envoyer une **clé anglaise non traduite** et des paramètres riches ; ne pas traduire avec la langue de l’auteur et ne pas rechercher la langue de tous les membres. - `lib/Listener/LoadMenuEntriesListener.php` passe aussi le display name du menu dans le catalogue de l’ExApp. - `lib/Fetcher/ExAppArchiveFetcher.php::installTranslations` installe les catalogues sur Nextcloud. En `manual-install`, fournir explicitement `<writable-apps>/organisateur_familial/l10n/`. N’y copier **aucun `appinfo/info.xml`** : Nextcloud pourrait prendre le dossier pour une app PHP. Références : [traductions AppAPI](https://nextcloud.github.io/app_api/tech_details/Translations.html), [API @nextcloud/l10n](https://nextcloud-libraries.github.io/nextcloud-l10n/), [translationtool](https://github.com/nextcloud/docker-ci/tree/master/translations/translationtool), [source de l’extracteur](https://github.com/nextcloud/docker-ci/blob/master/translations/translationtool/src/translationtool.php). Lire aussi le wiki [Architecture](https://git.lozach.eu/maxime/OrganisateurFamilial/wiki/Architecture) (D19, D26, D31–D37), [Workflow](https://git.lozach.eu/maxime/OrganisateurFamilial/wiki/Workflow), [Code guidelines](https://git.lozach.eu/maxime/OrganisateurFamilial/wiki/Code-guidelines) et le skill `nextcloud-exapp-dev`. ## 3. Décisions d’implémentation ### A. Une chaîne de génération, un catalogue commun Le domaine est littéralement `organisateur_familial`. Frontend : `import { translate as t, translatePlural as n } from '@nextcloud/l10n'`, puis `t('organisateur_familial', 'Lists')` et `n('organisateur_familial', '%n item remaining', '%n items remaining', count)`. Ne pas utiliser `t(domain, variable)` pour des textes sources ; les appels doivent porter les littéraux extractibles. Utiliser des phrases complètes et des paramètres nommés, jamais des morceaux concaténés ou `restant(s)`. Conserver `ex_app/l10n/{fr.js,fr.json}` comme sorties distribuées ; l’anglais fonctionne comme langue source. Un catalogue anglais explicite peut être généré si le packaging l’exige, sans entretenir une deuxième traduction identique. Sources PO : `translationfiles/fr/organisateur_familial.po`, modèle `translationfiles/templates/organisateur_familial.pot`, à la racine du dépôt. L’extracteur officiel reconnaît `.ts`, `.html` et prépare les `.vue`, avec `t:2` et `n:2,3`. Il découvre les apps par leur dossier `l10n` et attend leur `appinfo/info.xml` au même niveau. **La disposition actuelle du dépôt ne correspond pas à ce layout.** Ajouter un script de staging dans `dev/` : construire un dossier temporaire `organisateur_familial/` avec `appinfo/info.xml`, `l10n/`, les sources frontend/backend utiles et les PO ; y lancer les deux commandes officielles ; recopier uniquement les sorties prévues. Exclure node_modules, dist, tests, e2e et fichiers générés. Ne pas créer plusieurs racines d’app détectables dans le staging. Épingler le PHAR officiel par commit et somme SHA-256 et le runtime PHP/gettext par une version reproductible. Aucun téléchargement pendant le fonctionnement de l’application. Ajouter `make l10n-extract`, `make l10n-build`, `make l10n-check`. Le dernier régénère dans un temporaire, compare les contenus (normaliser uniquement les métadonnées volatiles du POT), vérifie les placeholders/pluriels et refuse les entrées françaises vides/fuzzy. Un test d’extraction doit prouver que Vue, TS backend, notifications et hors ligne figurent dans le POT. ### B. Frontend et formatage Utiliser les catalogues injectés par Nextcloud. Vérifier qu’ils sont enregistrés avant l’évaluation des constantes contenant `t(...)`. Si ce n’est pas le cas sur la version testée, déplacer l’évaluation dans les fonctions/computed utilisées après initialisation ; ne pas introduire un import dynamique qui casse l’IIFE. Conserver les globals `t/n` attendus par `@nextcloud/vue`. Les vocabulaires restent centralisés dans `constants/lists.ts`, `meals.ts`, `calendars.ts`, etc. Pour un décompte, exposer une fonction prenant le nombre et appelant `n` avec des sources littérales par kind ; ne pas garder un suffixe grammatical dans `remaining`. Traduire attributs accessibles et messages des stores/composables autant que les templates. Vue doit afficher les paramètres comme du texte : tester `&`, apostrophes et `<...>` afin d’éviter double échappement ou XSS ; pas de `v-html` pour une phrase traduite. Dans `services/format.ts`, remplacer la constante fixe par `getCanonicalLocale()` (disponible dans la dépendance installée), avec validation/repli de locale. Inclure la locale dans la clé du cache des formatteurs. Garder `formatDay(... timeZone: 'UTC')` pour les jours nus et le fuseau local pour les instants. `relative-day.ts` fournit des libellés `t(...)`. Aucun parsing de dates localisées ; ISO reste le format de stockage et d’échange. ### C. Backend : traduction à la frontière HTTP Ajouter de petits modules `ex_app/lib/src/i18n/` : résolution de langue, lecture des JSON générés, interpolation de paramètres, description d’un message et erreur localisable. Pas de dépendance navigateur, pas de variable globale « langue courante », pas de modification globale de Zod par requête. Contrat interne proposé : `MessageDescriptor = { source: string; params?: Record<string, string | number> }`; `LocalizedError` conserve `statusCode` et ce descripteur. Le marqueur backend `t(domain, source)` est une **fonction identité** pour l’extraction (retourne la source, ne traduit pas) ; les services construisent un descripteur à partir de cette source et de paramètres distincts. `renderMessage(descriptor, language)` s’utilise dans le handler/presenter. Ne jamais tenter de retrouver une clé en remplaçant des fragments dans une ancienne phrase française interpolée. `resolveLanguage(Accept-Language)` traite les tags, variantes `_`/`-`, pondérations, `q=0`, entrée absente/malformée et langues inconnues ; retourne seulement `fr` ou `en`, secours `en`. Les JSON chargés sont immuables ; chemins basés sur cette liste fermée, jamais sur l’en-tête brut. Ne pas interpréter `pluralForm` avec `eval`/`Function` : seules les deux règles fr/en sont nécessaires si un message serveur a un pluriel. Dans `errors.ts`, traduire les erreurs locales connues, préserver les codes HTTP et les champs publics ; conserver 404 pour absent **et** non-membre. Pour une erreur technique inconnue, exposer un message générique localisé et logger le détail ; ne pas envoyer le texte brut de Nextcloud comme traduction. Couvrir aussi les refus d’authentification/parser et les réponses anticipées des routes. Les réponses lifecycle restent conformes au protocole (corps vide en cas de succès, pas d’appel utilisateur). Pour Zod v3, traduire au moment de la réponse à partir du `ZodIssue` et de ses limites/type/path ; déclarer les messages métiers personnalisés avec des clés extractibles, et garder leurs paramètres séparés si nécessaire. Couvrir min/max, type, enum, format/date, refinements et les `safeParse` manuels qui répondent hors du handler global. Aucun `z.setErrorMap` dépendant de la langue : deux utilisateurs simultanés doivent rester isolés. Les warnings internes deviennent des descripteurs jusqu’au presenter ; le contrat sur le fil reste **`string[]`**. Faire de même pour toute raison lisible exportée/retournée, sans changer les enums de diagnostic ni la version du format de transfert. Ne pas utiliser un hook qui parcourt récursivement chaque chaîne JSON : il traduirait aussi les données des familles. Le client continue d’afficher `message`, `issues[].message`, `warnings`, sans logique de traduction ni branchement sur les statuts. ### D. Notifications Remplacer tous les sujets construits par interpolation par une source littérale marquée, par exemple `t('organisateur_familial', '{user} added ingredients from {recipe} to {list}')`, avec `user`, `recipe`, `list` dans `richSubjectParams`. Utiliser les paramètres riches Nextcloud appropriés (`user`, `highlight` pour un texte sans ressource Nextcloud native), vérifier leur rendu réel. Les rappels « Tomorrow: {event} » suivent le même mécanisme. Garder un objet de paramètres, même vide, comme le fait déjà `sendNotification`. Ne pas appeler `renderMessage` avant l’envoi : le notifier Nextcloud assure la langue du destinataire et la traduction à la lecture. Préserver préférences, exclusion de l’auteur et `void notifyFamily(...)`. Préférer les formulations sans décompte pour les notifications : le notifier observé appelle `t`, pas `n`. Une nouvelle traduction ne doit ajouter aucun appel OCS par membre. ### E. Hors ligne et manifeste Étendre `OfflineMeta` avec `language` et `locale` facultatifs pour lire les caches déjà existants. `saveMeta` les écrit depuis le contexte Nextcloud de la page ; le préchargement reste l’unique chemin existant, sans second écrivain de l’index ni suppression des gardes de révision. Au prochain chargement connecté, enregistrer les nouvelles valeurs même si les listes n’ont pas changé. La page hors ligne embarque **les deux langues au build** à partir des catalogues communs (un sous-ensemble de ses messages suffit). Ajouter un petit helper local de traduction de texte à `offline/shell.html` ou un module de build dédié ; garder des appels sources extractibles `t(domain, '...')`. Injecter les JSON via le build de `sw.ts`/`vite.config.sw.ts` avec sérialisation sûre (`<` échappé dans le script HTML), jamais par concaténation de données utilisateur. Aucun import de `@nextcloud/l10n`, Vue ou `window.OC` dans le worker/la page autonome. Lire la langue/locale de la méta, mettre à jour `html.lang`, title, titre, bouton et tous les états vides/erreurs. Ancienne méta sans langue : repli français pour préserver les anciens snapshots ; aucune méta : repli anglais explicite. Échec de lecture du cache : état d’erreur autonome, sans dépendance réseau. Les dates utilisent la locale mémorisée. Les données restent échappées via le mécanisme existant. **D35 reste inchangé : aucun `fetch()` dans le worker**, pas de catalogue distant, aucune sous-ressource nécessaire hors ligne. Navigation preload et lecture de Cache Storage uniquement. Le manifeste est PUBLIC, il ne peut pas dépendre d’une session. `installOfflineManifest()` fournit `?lang=fr|en` ; `routes/static.ts` valide cette liste et traduit `short_name`, renseigne `lang`, garde le nom propre et le scope/start_url existants. La variante doit figurer dans l’URL pour éviter de partager un manifeste de la mauvaise langue. Sans paramètre : anglais. Tester via le proxy sans credentials ; pas de nouvelle route ni accès utilisateur. ### F. Distribution des catalogues - Monter `ex_app/l10n` en lecture seule dans le conteneur backend de développement, à un chemin explicite correspondant à celui utilisé en production (config dédiée si nécessaire). - Ajouter une synchronisation idempotente vers le dossier writable apps de Nextcloud dans `dev/setup.sh`, réutilisée par `make reload` après génération. Copier uniquement les catalogues, propriétaire lisible par www-data. Vérifier aussi les mises à jour/caches Nextcloud, pas seulement la première installation. - Garder les JSON dans l’image Node pour les réponses API. Ne pas ajouter une route `/l10n/*` comme substitut à l’installation côté Nextcloud. - Préparer un artefact de release avec une unique racine `organisateur_familial/`, `appinfo/info.xml` et `l10n/` au niveau attendu par AppAPI. Les sources PO ne sont pas requises au runtime. Fournir une cible de packaging si elle n’existe pas ; ne pas publier à l’App Store dans ce ticket. ## 4. Lots ordonnés pour l’implémentation Chaque lot doit compiler et avoir ses tests ciblés avant le suivant. Créer une branche `feat/issue-4-i18n` depuis le main actualisé ; si HEAD a changé, relire les fichiers touchés et ajuster le plan. Ne pas pousser sans demande. 1. **Infrastructure et preuve verticale** (`build(i18n): ...`). Ajouter staging, PHAR épinglé, PO/POT/JSON/JS, commandes Make et contrôle CI. Brancher montage backend/synchronisation Nextcloud. Faire passer une chaîne Vue française/anglaise, une clé backend et une clé de notification dans la génération. Critère : FR chargé réellement dans le navigateur via Nextcloud et extraction TS/Vue/HTML prouvée. Résoudre tout problème de chargement avant la conversion massive. 2. **Runtime serveur et erreurs** (`feat(i18n): localize API feedback`). Ajouter modules i18n, erreurs/descripteurs, conversion Zod, migration des warnings et des réponses anticipées, en conservant contrats et autorisation. Critère : même requête invalide → mêmes statut/path, messages FR/EN différents ; requêtes concurrentes isolées ; warnings de succès traduits. 3. **Coquille et listes** (`feat(i18n): translate navigation and lists`). `App.vue`, `routes.ts` pour les seuls libellés éventuels, `Welcome`, switcher, famille/paramètres, home, listes, composants et constantes associés. Remplacer les décomptes par de vrais pluriels. Critère : première utilisation et parcours liste FR/EN, aucun nom saisi modifié. 4. **Calendrier, repas, recettes, administration et formats** (`feat(i18n): translate planning and settings`). Vues/composants/composables/stores restants, tous `constants/*.ts`, formatage et dates relatives. Inclure filtres, récurrence, validation des formulaires, import/export et panneaux d’administration. Critère : chaque écran visité en anglais et français, responsive/accessibilité conservés, locale de date indépendante de la langue. 5. **Notifications** (`feat(i18n): translate notifications through AppAPI`). Adapter producteurs/paramètres riches et tests `activity`, `reminder`, shopping/import. Critère : deux destinataires FR/EN lisent la même activité dans leur langue sans lookup de préférence supplémentaire. 6. **Hors ligne et installation** (`feat(i18n): localize offline view and manifest`). Méta compatible, catalogue embarqué, formatage, manifeste paramétré, tests worker/build. Critère : démarrage hors ligne après chargement EN/FR et cache ancien, zéro requête réseau du worker et contenu inchangé. 7. **Finition, recette et documentation** (`test(i18n): ...`, `docs(i18n): ...` si séparables). Compléter/revoir le PO français, prouver sa couverture, corriger les tests existants au niveau des assertions de texte seulement. Ajouter les e2e ciblés, vérifier image/artefact et mettre le wiki à jour. Ne pas déplacer la mécanique métier pendant une traduction. ## 5. Matrice de vérification obligatoire | Vérification | Résultat attendu | | --- | --- | | Compte FR / compte EN, navigateur configuré dans l’autre langue | La langue du compte prévaut, y compris après rechargement complet. | | Locale fr-FR avec langue EN | Textes anglais, dates françaises ; jour sans heure identique. | | Décomptes 0, 1, 2 | Pluriels français/anglais valides, phrases complètes par kind. | | Langue non prise en charge, variante fr-CA/en-GB, en-tête invalide et `q=0` | Résolution déterministe et secours défini, jamais de 500. | | Deux requêtes API FR/EN chevauchées | Pas de fuite de langue ; même statut et contrat JSON. | | Nom vide/trop long, mauvais enum/date/type, refus de non-membre | Message localisé ; 404 du non-membre conservé ; aucune donnée révélée. | | Avertissement import/projection réussi et erreur Nextcloud | Retour utile localisé sans traduire les noms/imports ; détails techniques non affichés comme message utilisateur. | | Notifications activité/rappel à destinataires FR/EN | Traduction dans Nextcloud, paramètres riches lisibles, auteur/préférences respectés. | | Hors ligne FR/EN, ancienne méta, cache vide/indisponible | Tous les textes autonomes cohérents, pas de fetch ; jours et état coché inchangés. | | Valeurs contenant `&`, apostrophes, `<img ...>` | Texte fidèle et échappé, aucune injection, aucune double entité visible. | | Installation neuve puis mise à jour des catalogues | Les deux côtés Node/Nextcloud utilisent la même version ; pas de traduction périmée. | | Manifeste public FR/EN, UI sur chemin avec/sans index.php | Nom court/lang corrects ; navigation/base router inchangées. | Tests recommandés : nouveaux `ex_app/lib/src/i18n/*.test.ts`, extension de `app.test.ts`/tests erreurs et services concernés ; `format.test.ts`, `relative-day.test.ts`, tests vocabulaires/pluriels et `offline-snapshot.test.ts` ; un `e2e/tests/i18n.spec.ts` couvrant les parcours et notifications réels. Fixer explicitement la langue FR de la suite existante et restaurer les préférences des comptes dans setup/teardown ; ne pas se contenter du `locale` Playwright, qui ne change pas la préférence Nextcloud. Les tests i18n utilisent comptes/fixtures imposés par la suite et ne doivent pas rendre les specs suivantes anglaises par accident. À la recette : `make l10n-check`, `make lint typecheck test`, `make build-frontend`, vérifier `js/main.js` IIFE puis `cd e2e && npm test`. Exécuter aussi les tests de dates hors UTC (`cd ex_app/src && npm run test:tz`) et construire l’image de production pour vérifier les chemins des JSON. Les e2e passent par AppAPI et un frontend réellement reconstruit. Si échec inexpliqué, établir deux runs cohérents du main de référence avant d’accuser le diff. Revue `branch-review` et revue technique prévue par Workflow ; consigner les retours dans le ticket. ## 6. Documentation et mise en production À la fin du développement, ajouter la prochaine décision libre dans Architecture (D37 est la dernière à la date du plan ; ne pas réserver aveuglément D38), mettre à jour UX (langue Nextcloud, hors ligne), Scope (FR/EN), Code guidelines et AGENTS.md (sources anglaises et catalogues), Dev environment (génération/synchronisation), `ex_app/l10n/README.md` et les références du skill pour les mécanismes AppAPI mesurés. Revoir les métadonnées anglaises/françaises d’`appinfo/info.xml` : elles décrivent encore un dossier/carnet d’adresses, contraire à D23. Ne pas présenter le ticket comme livré avant validation. MEP : livrer ensemble image Node, frontend/worker et catalogues côté Nextcloud dans la version du jalon ; contrôler une installation manuelle et le contenu de l’archive destinée à AppAPI. Prévoir la procédure de rafraîchissement des catalogues pour une mise à jour existante. Pas de migration SQLite, pas de réimport des données, pas de reprojection. Smoke test FR/EN connecté, erreur API, notification et consultation hors ligne. Retour arrière : restaurer image/artefact et catalogues de la même version, recharger la page/worker ; les métadonnées ajoutées sont facultatives et les anciens caches restent lisibles. Intégration linéaire seulement après recette ; aucune publication/push automatique autorisée par ce plan.
Collaborator

Revue technique — main...efa26d3

Portes mécaniques vertes avant lecture : make lint typecheck test (466 + 378), make l10n-check, make build-frontend + bundle IIFE, e2e 97/97.

Hors i18n, gardé sur la branche

6bbc3e6 — un événement journée entière sans DTEND (légal, fréquent dans les flux externes D33 et les imports) était filtré par le <c:time-range> de Nextcloud dès que la période commençait le jour même : jamais visible en vue Jour, absent le lundi en semaine, le 1er en mois, et le jour J sur l'accueil et dans le rappel de la veille. eventsBetween lit désormais un jour plus tôt et recoupe par chevauchement, comme upcomingEvents. Nos propres événements n'étaient pas touchés (ical/build.ts écrit toujours DTEND). Le mock DAV des tests unitaires applique maintenant le filtre tel que mesuré. efa26d3 corrige le sélecteur « Fermer » ambigu du spec d'abonnement.

Retours

  1. Noms affichés avec des entités HTML — t()/n() de @nextcloud/l10n échappent les paramètres par défaut, et rien n'est rendu en HTML : « L'épicerie & co » s'affiche L&#39;épicerie &amp; co (reproduit). Touche les messages de transfert, le titre de l'accueil, les agendas masqués, le dialogue d'agenda externe, l'export, la suppression d'une famille, et des aria-label / alt.
  2. Message hors ligne figé en anglais — stores/lists.ts calcule OFFLINE_WRITE_MESSAGE au chargement du module, avant l'enregistrement du catalogue.
  3. Envoi trop volumineux → « Requête invalide » — le nouveau cas par défaut d'errors.ts absorbe le 413 de Fastify (photo > 5 Mo, import) : rien ne dit que c'est la taille (reproduit ; main renvoyait au moins le texte de Fastify).
  4. Messages de schéma restés en français — 'attendu : AAAA-MM-JJ' (events.ts, calendars.ts, meals.ts) et 'attendu : un identifiant iCalendar' (events.ts) ne sont pas marqués : un compte anglais les reçoit tels quels (reproduit) et l'extracteur ne les voit pas.
  • Corriger les retours de la revue technique (1–4) : 7e402fe (1 — services/l10n.ts, texte brut pour tous les appels du front), 4d6ef3c (2), a9960f0 (3 et 4), règle consignée dans AGENTS.md et la référence ExApp (e93dab8). Chaque correctif a un test qui échoue sans lui ; portes vertes, e2e 97/97.
## Revue technique — `main...efa26d3` Portes mécaniques vertes avant lecture : `make lint typecheck test` (466 + 378), `make l10n-check`, `make build-frontend` + bundle IIFE, e2e 97/97. ### Hors i18n, gardé sur la branche `6bbc3e6` — un événement journée entière **sans `DTEND`** (légal, fréquent dans les flux externes D33 et les imports) était filtré par le `<c:time-range>` de Nextcloud dès que la période commençait le jour même : jamais visible en vue Jour, absent le lundi en semaine, le 1er en mois, et le jour J sur l'accueil et dans le rappel de la veille. `eventsBetween` lit désormais un jour plus tôt et recoupe par chevauchement, comme `upcomingEvents`. Nos propres événements n'étaient pas touchés (`ical/build.ts` écrit toujours `DTEND`). Le mock DAV des tests unitaires applique maintenant le filtre tel que mesuré. `efa26d3` corrige le sélecteur « Fermer » ambigu du spec d'abonnement. ### Retours 1. **Noms affichés avec des entités HTML** — `t()`/`n()` de `@nextcloud/l10n` échappent les paramètres par défaut, et rien n'est rendu en HTML : « L'épicerie & co » s'affiche `L&#39;épicerie &amp; co` (reproduit). Touche les messages de transfert, le titre de l'accueil, les agendas masqués, le dialogue d'agenda externe, l'export, la suppression d'une famille, et des aria-label / alt. 2. **Message hors ligne figé en anglais** — `stores/lists.ts` calcule `OFFLINE_WRITE_MESSAGE` au chargement du module, avant l'enregistrement du catalogue. 3. **Envoi trop volumineux → « Requête invalide »** — le nouveau cas par défaut d'`errors.ts` absorbe le 413 de Fastify (photo > 5 Mo, import) : rien ne dit que c'est la taille (reproduit ; `main` renvoyait au moins le texte de Fastify). 4. **Messages de schéma restés en français** — `'attendu : AAAA-MM-JJ'` (`events.ts`, `calendars.ts`, `meals.ts`) et `'attendu : un identifiant iCalendar'` (`events.ts`) ne sont pas marqués : un compte anglais les reçoit tels quels (reproduit) et l'extracteur ne les voit pas. - [x] Corriger les retours de la revue technique (1–4) : `7e402fe` (1 — `services/l10n.ts`, texte brut pour tous les appels du front), `4d6ef3c` (2), `a9960f0` (3 et 4), règle consignée dans AGENTS.md et la référence ExApp (`e93dab8`). Chaque correctif a un test qui échoue sans lui ; portes vertes, e2e 97/97.
Collaborator

Revue fonctionnelle FR/EN — main...e93dab8

Visite de 16 écrans et dialogues en français et en anglais, sur desktop (1280) et sur mobile (390) : 64 captures, plus un balayage des templates à la recherche de libellés en dur. La famille de test portait des noms piégés (apostrophes, &, <co>). Le compte admin a été basculé fr/en par occ, puis remis en français ; l'instance est propre.

Conforme

Aucune entité HTML visible (le correctif de la revue technique tient à l'écran), aucun anglais résiduel sur les écrans français, pluriels justes (« 1 member », « 1 item remaining out of 2 »), aucun débordement horizontal sur mobile. Les fixtures de langue sont bien réinitialisées : imposeFrench au reset, comptes des specs supprimés.

Retours

  1. Libellés français en dur (9 endroits). Nom accessible des événements Modifier {event} — {jour|membre} dans TimetableAgenda, TimetableDay, TimetableMonth, TimetableMembers (×2, dont « sans attribution ») et WeekDayColumn ; « Heure actuelle » (WeekDayColumn) ; le libellé visible Étape N (RecipeFormDialog) ; « Aucun membre dans cette famille. » (EventMemberPicker).
  2. Agendas intégrés affichés avec leur suffixe français enregistré — « {famille} — Tâches », « — Repas » — dans le dialogue Agendas et l'avis « Hidden calendars ». Le guide demande un libellé dérivé du kind.
  3. Planning des repas en anglais : « 14 Monday » (colonnes et titre du dialogue). Intl place le quantième avant le nom du jour quand le mois est absent.
  4. Graduation horaire de la semaine en dur : « 00 h … 23 h » quelle que soit la locale.
  5. Mobile, vues Jour et Par membre : titre de période tronqué — « Wednesday, September 1… », qui se lit comme une autre date.

Non couvert par cette visite, mais couvert par les tests : anglais avec locale fr-FR, rendu des notifications dans Nextcloud, page hors ligne.

Hygiène de la suite

i18n.spec supprime son compte anglais avant la famille créée par ce compte ; la famille reste orpheline jusqu'au reset suivant, qui retire la ligne en base directement.

Hors périmètre i18n : le bouton « Noter » du dialogue de repas est tronqué en « No… » dans les deux langues, identiquement sur main — signalé à part.

  • Corriger les retours de la revue fonctionnelle (1–5) et l'hygiène d'i18n.spec : 6368243. Revérifié à l'écran en anglais, desktop et mobile — plus aucun libellé français, agendas nommés « Family events / Tasks / Meals », « Monday, Sep 14 », heures « 12 AM », titre de période complet sur mobile. Portes vertes (469 + 385) et e2e 97/97. Le bouton « Noter » reste ouvert dans #16.
## Revue fonctionnelle FR/EN — `main...e93dab8` Visite de 16 écrans et dialogues en français et en anglais, sur desktop (1280) et sur mobile (390) : 64 captures, plus un balayage des templates à la recherche de libellés en dur. La famille de test portait des noms piégés (apostrophes, `&`, `<co>`). Le compte admin a été basculé `fr`/`en` par `occ`, puis remis en français ; l'instance est propre. ### Conforme Aucune entité HTML visible (le correctif de la revue technique tient à l'écran), aucun anglais résiduel sur les écrans français, pluriels justes (« 1 member », « 1 item remaining out of 2 »), aucun débordement horizontal sur mobile. Les fixtures de langue sont bien réinitialisées : `imposeFrench` au reset, comptes des specs supprimés. ### Retours 1. **Libellés français en dur (9 endroits).** Nom accessible des événements `Modifier {event} — {jour|membre}` dans `TimetableAgenda`, `TimetableDay`, `TimetableMonth`, `TimetableMembers` (×2, dont « sans attribution ») et `WeekDayColumn` ; « Heure actuelle » (`WeekDayColumn`) ; le libellé visible `Étape N` (`RecipeFormDialog`) ; « Aucun membre dans cette famille. » (`EventMemberPicker`). 2. **Agendas intégrés affichés avec leur suffixe français enregistré** — « {famille} — Tâches », « — Repas » — dans le dialogue Agendas et l'avis « Hidden calendars ». Le guide demande un libellé dérivé du `kind`. 3. **Planning des repas en anglais : « 14 Monday »** (colonnes et titre du dialogue). `Intl` place le quantième avant le nom du jour quand le mois est absent. 4. **Graduation horaire de la semaine en dur** : « 00 h … 23 h » quelle que soit la locale. 5. **Mobile, vues Jour et Par membre : titre de période tronqué** — « Wednesday, September 1… », qui se lit comme une autre date. Non couvert par cette visite, mais couvert par les tests : anglais avec locale `fr-FR`, rendu des notifications dans Nextcloud, page hors ligne. ### Hygiène de la suite `i18n.spec` supprime son compte anglais avant la famille créée par ce compte ; la famille reste orpheline jusqu'au reset suivant, qui retire la ligne en base directement. Hors périmètre i18n : le bouton « Noter » du dialogue de repas est tronqué en « No… » dans les deux langues, identiquement sur `main` — signalé à part. - [x] Corriger les retours de la revue fonctionnelle (1–5) et l'hygiène d'`i18n.spec` : `6368243`. Revérifié à l'écran en anglais, desktop et mobile — plus aucun libellé français, agendas nommés « Family events / Tasks / Meals », « Monday, Sep 14 », heures « 12 AM », titre de période complet sur mobile. Portes vertes (469 + 385) et e2e 97/97. Le bouton « Noter » reste ouvert dans #16.
Collaborator

Intégré — main avance en fast-forward jusqu'à 74983db

18 commits, historique linéaire, pas de merge commit.

Cochées à l'intégration : installation neuve vérifiée sur volumes vides (catalogues présents dans l'image — 409 entrées — et dans custom_apps/organisateur_familial/l10n, worker hors ligne porteur de son français, deux suites e2e complètes consécutives à 97/97) ; copie des catalogues en installation manuelle appliquée par dev/setup.sh ; absence de migration SQL, de renommage et de reprojection confirmée, anciens caches hors ligne lus sans migration.

La moitié « mise à jour des catalogues » est sans objet : rien n'a encore été livré, donc il n'y a aucune version installée à mettre à jour.

Trouvé en faisant cette dernière vérification, et corrigé dans 74983db :

  1. L'image de production ne se construisait plus. vite.config.sw.ts lit ../l10n/fr.json pour compiler le catalogue de la page hors ligne dans le worker, et le contexte de l'étape frontend est ex_app/src seul. make build passait pourtant — il compile les sources là où elles sont, pas l'image qui les embarque. Un job CI construit désormais l'image, sans la pousser.
  2. La suite ne pouvait pas tourner après une installation neuve : bringToZero fixait la langue du compte de service avant de le provisionner, et occ user:setting sur un compte absent emportait tout le reset.
  3. L'échec d'orphaned-family n'était pas un flake : createFamily distinguait les deux atterrissages avant que l'un des deux soit rendu, et la coquille se monte maintenant un instant plus tard puisqu'elle attend le catalogue. Deux suites sur trois en échec, le spec passant seul.

Restent ouvertes, et volontairement : la livraison cohérente du jalon 1.0.x, les smoke tests d'après-livraison et le retour arrière. Elles appartiennent à la livraison de la release, pas à ce ticket — à reprendre au passage en delivered.

## Intégré — `main` avance en fast-forward jusqu'à `74983db` 18 commits, historique linéaire, pas de merge commit. Cochées à l'intégration : installation neuve vérifiée sur volumes vides (catalogues présents dans l'image — 409 entrées — et dans `custom_apps/organisateur_familial/l10n`, worker hors ligne porteur de son français, deux suites e2e complètes consécutives à 97/97) ; copie des catalogues en installation manuelle appliquée par `dev/setup.sh` ; absence de migration SQL, de renommage et de reprojection confirmée, anciens caches hors ligne lus sans migration. La moitié « mise à jour des catalogues » est sans objet : rien n'a encore été livré, donc il n'y a aucune version installée à mettre à jour. Trouvé **en faisant** cette dernière vérification, et corrigé dans `74983db` : 1. **L'image de production ne se construisait plus.** `vite.config.sw.ts` lit `../l10n/fr.json` pour compiler le catalogue de la page hors ligne dans le worker, et le contexte de l'étape frontend est `ex_app/src` seul. `make build` passait pourtant — il compile les sources là où elles sont, pas l'image qui les embarque. Un job CI construit désormais l'image, sans la pousser. 2. **La suite ne pouvait pas tourner après une installation neuve** : `bringToZero` fixait la langue du compte de service avant de le provisionner, et `occ user:setting` sur un compte absent emportait tout le reset. 3. **L'échec d'`orphaned-family` n'était pas un flake** : `createFamily` distinguait les deux atterrissages avant que l'un des deux soit rendu, et la coquille se monte maintenant un instant plus tard puisqu'elle attend le catalogue. Deux suites sur trois en échec, le spec passant seul. Restent ouvertes, et volontairement : la livraison cohérente du jalon `1.0.x`, les smoke tests d'après-livraison et le retour arrière. Elles appartiennent à la livraison de la release, pas à ce ticket — à reprendre au passage en `delivered`.
Sign in to join this conversation.
No description provided.