Commit 55de6479 authored by Jordan Grossemy's avatar Jordan Grossemy
Browse files

Lot 2 : les écrans qu'on ouvre le jour J

Main courante, conduite, fiche d'action, salons, messages directs,
mobilisation, première heure, cellules, tableau des actions, échéances
réglementaires, pilotage, recensement de sécurité, canaux, cockpit.

336 chaînes enveloppées, 242 traductions neuves — certaines phrases servent
deux écrans. La couverture passe de vingt-et-un à trente-neuf pour cent, et le
cliquet suit.

── Sept masquages de « t » levés

« const t = Math.abs(m) », « const t = setTimeout(…) », « choix.map((t, i) » :
sept variables locales portaient le nom d'une seule lettre que la traduction
réclame. Elles s'appellent désormais d'après ce qu'elles contiennent —
« minutes », « minuteur », « tuile », « cle », « tiers » — ce que le code y
gagne de toute façon.

── Le vocabulaire, arrêté une fois

Un dictionnaire se juge à sa constance plus qu'à ses trouvailles. « Main
courante » est « incident log » partout, « cellule » est « cell », « salon » est
« room », « mobilisation » est « call-out », « dossier de préparation » est
« preparedness dossier », « point de situation » est « situation report ». Ces
choix venaient des deux cent quarante-huit entrées d'origine ; ce lot les suit
plutôt que d'en inventer de meilleurs.

── Ce que ce lot laisse derrière lui

Les fragments : un texte coupé par un élément — « {n} accusé(s) », « Composer
{celluleId ? … : …} » — ne s'enveloppe pas d'un seul appel. Ils demandent la
substitution « {nom} » que « t() » sait faire, et une relecture cas par cas.
Ils sont comptés dans le dénominateur : c'est pour cela que trente-neuf pour
cent, et non davantage, sur des écrans qui semblent traduits.

Vérifié en anglais dans le navigateur, sur la première heure : le texte bascule,
et ce qui n'est pas encore enveloppé retombe en français — le repli voulu, qui
laisse l'écran utilisable pendant que la traduction avance.

Co-Authored-By: Claude (RCA)
parent 2637ff27
Loading
Loading
Loading
Loading
Loading
+1 −1
Changes for apps/api/src/verification/interface.ts: 1 added line, 1 removed line.
Original line number Diff line number Diff line
@@ -159,7 +159,7 @@ export async function verifierInterface(r: Rapport): Promise<void> {
  // laisser la couverture reculer. Ce plancher se relève au fil des lots — il
  // ne se baisse jamais. Tant qu'il n'atteint pas 100, EX-21 n'est pas tenue,
  // et le chiffre le dit à chaque exécution.
  const PLANCHER = 21;
  const PLANCHER = 39;
  r.verifie("EX-21",
    `${employees.size} chaînes visibles sur ${visibles} passent par la traduction`
    + ` (${couverture} %, plancher ${PLANCHER} %)`,
+22 −11
Changes for apps/web/src/ecrans/ActionDetail.tsx: 22 added lines, 11 removed lines.
Original line number Diff line number Diff line
@@ -3,6 +3,7 @@ import { ChoixPorteur, type Affectation } from "../coque/ChoixPorteur.js";
import { useQueryClient } from "@tanstack/react-query";
import { api, ErreurApi } from "../api.js";
import { Historique } from "../coque/Historique.js";
import { useT } from "../langues/index.js";

interface Action {
  id: string; intitule: string; responsable_libelle: string | null; statut: string;
@@ -33,6 +34,7 @@ const pourChamp = (iso: string | null) => {
export function ActionDetail({
  criseId, action, surFermeture,
}: { criseId: string; action: Action; surFermeture: () => void }) {
  const t = useT();
  const client = useQueryClient();
  const [erreur, definirErreur] = useState<string | null>(null);
  const [succes, definirSucces] = useState<string | null>(null);
@@ -69,7 +71,7 @@ export function ActionDetail({
    <div className="carte" style={{ marginTop: 16 }}>
      <div className="ligne">
        <span className="etiq">{action.intitule}</span>
        <button className="discret petit pousse" onClick={surFermeture}>Fermer</button>
        <button className="discret petit pousse" onClick={surFermeture}>{t("Fermer")}</button>
      </div>

      {erreur && <div className="erreur" style={{ marginTop: 8 }}>{erreur}</div>}
@@ -77,10 +79,16 @@ export function ActionDetail({

      {/* ── Qui la porte ──────────────────────────────────────────────────── */}
      <div style={{ marginTop: 12 }}>
        <span className="etiq">Responsable</span>
        <span className="etiq">{t("Responsable")}</span>
        <p className="aide" style={{ marginTop: 2 }}>
          Les cellules d’abord, puis l’annuaire gelé, puis les membres de l’organisation.
          Quelqu’un sans compte se désigne aussi : l’action portera son nom en clair.
          {t("Les cellules d’abord, "
                                                                  + "puis l’annuaire gelé, "
                                                                  + "puis les membres de "
                                                                  + "l’organisation. "
                                                                  + "Quelqu’un sans compte se "
                                                                  + "désigne aussi : l’action "
                                                                  + "portera son nom en "
                                                                  + "clair.")}
        </p>
        <div style={{ marginTop: 8 }}>
          <ChoixPorteur criseId={criseId} surChoix={definirPorteur}
@@ -91,29 +99,32 @@ export function ActionDetail({
                ? { responsableId: porteur.responsableId }
                : { responsableLibelle: porteur.responsableLibelle },
              "Action réaffectée.")}>
            Réaffecter
            {t("Réaffecter")}
          </button>
        </div>
      </div>

      {/* ── Quand elle est due ────────────────────────────────────────────── */}
      <div style={{ marginTop: 16 }}>
        <span className="etiq">Échéance</span>
        <span className="etiq">{t("Échéance")}</span>
        <div className="ligne" style={{ gap: 8, marginTop: 8, flexWrap: "wrap" }}>
          <input type="datetime-local" value={echeance}
            onChange={(e) => definirEcheance(e.target.value)} />
          {echeance && (
            <button className="discret petit" onClick={() => definirEcheance("")}>
              Retirer l’échéance
              {t("Retirer "
                                                                                                   + "l’échéance")}
            </button>
          )}
        </div>
        <input style={{ marginTop: 8, maxWidth: 420 }} value={motif}
          placeholder="Pourquoi l’échéance change — le prestataire a repoussé, l’arbitrage tarde…"
          placeholder={t("Pourquoi l’échéance change — le prestataire a repoussé, l’arbitrage "
                       + "tarde…")}
          onChange={(e) => definirMotif(e.target.value)} />
        <div className="aide">
          Trois caractères au minimum. Une échéance qui bouge sans raison inscrite ne se
          défend pas au retour d’expérience.
          {t("Trois caractères au minimum. Une échéance qui "
                                           + "bouge sans raison inscrite ne se défend pas au "
                                           + "retour d’expérience.")}
        </div>
        <button className="accent petit" style={{ marginTop: 8 }}
          disabled={envoi || !echeanceChange || motif.trim().length < 3}
@@ -121,7 +132,7 @@ export function ActionDetail({
            { echeance: echeance ? new Date(echeance).toISOString() : null,
              motif: motif.trim() },
            "Échéance modifiée.")}>
          Enregistrer l’échéance
          {t("Enregistrer l’échéance")}
        </button>
      </div>

+127 −32
Changes for apps/web/src/ecrans/CanauxCrise.tsx: 127 added lines, 32 removed lines.
Original line number Diff line number Diff line
import { useState } from "react";
import { useQuery, useQueryClient } from "@tanstack/react-query";
import { api } from "../api.js";
import { useT } from "../langues/index.js";

interface SalonListe {
  id: string; libelle: string; genre: string; prive: boolean;
@@ -24,6 +25,7 @@ interface SalonListe {
 * messages directs et les groupes, qui ne passent pas par le journal du tout.
 */
export function CanauxCrise({ criseId }: { criseId: string }) {
  const t = useT();
  const client = useQueryClient();
  const [ouvert, definirOuvert] = useState(false);
  const [libelle, definirLibelle] = useState("");
@@ -97,8 +99,8 @@ export function CanauxCrise({ criseId }: { criseId: string }) {
  return (
    <div className="scene">
      <header>
        <div className="etiq">Canaux</div>
        <h1>Ce qui est ouvert, ce qui ne l’est pas</h1>
        <div className="etiq">{t("Canaux")}</div>
        <h1>{t("Ce qui est ouvert, ce qui ne l’est pas")}</h1>
      </header>

      <div className="contenu">
@@ -106,7 +108,7 @@ export function CanauxCrise({ criseId }: { criseId: string }) {

        <div className="carte" style={{ marginBottom: 16 }}>
          <div className="ligne">
            <div className="etiq">Nouveau canal</div>
            <div className="etiq">{t("Nouveau canal")}</div>
            <button className="accent pousse" onClick={() => definirOuvert(!ouvert)}>
              {ouvert ? "Annuler" : "Créer un canal"}
            </button>
@@ -114,30 +116,88 @@ export function CanauxCrise({ criseId }: { criseId: string }) {

          {ouvert && (
            <div style={{ marginTop: 12 }}>
              <label htmlFor="canal-nom">Nom du canal</label>
              <input id="canal-nom" value={libelle} placeholder="Juridique et assurances"
              <label htmlFor="canal-nom">{t("Nom du canal")}</label>
              <input id="canal-nom" value={libelle} placeholder={t("Juridique et assurances")}
                onChange={(e) => definirLibelle(e.target.value)} />

              <label className="ligne" style={{ gap: 8, marginTop: 10, cursor: "pointer" }}>
                <input type="checkbox" checked={avecVoix}
                  onChange={(e) => definirAvecVoix(e.target.checked)} />
                Ouvrir aussi sa salle visio
                {t("Ouvrir "
                                                                                           + "aussi "
                                                                                           + "sa "
                                                                                           + "salle "
                                                                                           + "visio")}
              </label>

              <label className="ligne" style={{ gap: 8, marginTop: 6, cursor: "pointer" }}>
                <input type="checkbox" checked={prive}
                  onChange={(e) => definirPrive(e.target.checked)} />
                Canal privé
                {t("Canal "
                                                                                        + "privé")}
              </label>

              {prive && (
                <div style={{ marginTop: 8 }}>
                  <p className="aide" style={{ marginTop: 0 }}>
                    Réservé aux personnes cochées — vous compris. Les autres ne le
                    verront pas dans leur liste. Ce qui s'y écrira restera néanmoins
                    au journal de crise et au dossier de clôture&nbsp;: privé veut dire
                    « qui peut lire », jamais « qui ne sera pas enregistré ». Pour un
                    échange hors dossier, c'est un message direct qu'il faut.
                    {t("Réservé "
                                                                                      + "aux "
                                                                                      + "personnes "
                                                                                      + "cochées "
                                                                                      + "— "
                                                                                      + "vous "
                                                                                      + "compris. "
                                                                                      + "Les "
                                                                                      + "autres "
                                                                                      + "ne "
                                                                                      + "le "
                                                                                      + "verront "
                                                                                      + "pas "
                                                                                      + "dans "
                                                                                      + "leur "
                                                                                      + "liste. "
                                                                                      + "Ce "
                                                                                      + "qui "
                                                                                      + "s'y "
                                                                                      + "écrira "
                                                                                      + "restera "
                                                                                      + "néanmoins "
                                                                                      + "au "
                                                                                      + "journal "
                                                                                      + "de "
                                                                                      + "crise "
                                                                                      + "et "
                                                                                      + "au "
                                                                                      + "dossier "
                                                                                      + "de "
                                                                                      + "clôture : "
                                                                                      + "privé "
                                                                                      + "veut "
                                                                                      + "dire "
                                                                                      + "« "
                                                                                      + "qui "
                                                                                      + "peut "
                                                                                      + "lire "
                                                                                      + "», "
                                                                                      + "jamais "
                                                                                      + "« "
                                                                                      + "qui "
                                                                                      + "ne "
                                                                                      + "sera "
                                                                                      + "pas "
                                                                                      + "enregistré "
                                                                                      + "». "
                                                                                      + "Pour "
                                                                                      + "un "
                                                                                      + "échange "
                                                                                      + "hors "
                                                                                      + "dossier, "
                                                                                      + "c'est "
                                                                                      + "un "
                                                                                      + "message "
                                                                                      + "direct "
                                                                                      + "qu'il "
                                                                                      + "faut.")}
                  </p>
                  {disponibles.map((m) => (
                    <label key={m.compte_id} className="ligne"
@@ -154,16 +214,16 @@ export function CanauxCrise({ criseId }: { criseId: string }) {
              <button className="accent" style={{ marginTop: 12 }}
                disabled={libelle.trim().length < 2}
                onClick={() => void creer()}>
                Créer
                {t("Créer")}
              </button>
            </div>
          )}
        </div>

        <h2>Canaux existants</h2>
        <h2>{t("Canaux existants")}</h2>
        <table>
          <thead>
            <tr><th>Canal</th><th>Genre</th><th>Visibilité</th><th /></tr>
            <tr><th>{t("Canal")}</th><th>{t("Genre")}</th><th>{t("Visibilité")}</th><th /></tr>
          </thead>
          <tbody>
            {(salons.data?.salons ?? []).map((s) => (
@@ -172,20 +232,23 @@ export function CanauxCrise({ criseId }: { criseId: string }) {
                <td className="discret">{s.genre}</td>
                <td>
                  {s.prive
                    ? <span className="pastille alerte sans-point">privé</span>
                    : <span className="discret">ouvert à la crise</span>}
                    ? <span className="pastille alerte sans-point">{t("privé")}</span>
                    : <span className="discret">{t("ouvert à la crise")}</span>}
                </td>
                <td>
                  {s.prive ? (
                    <button className="petit"
                      onClick={() => void appliquerAcces(s.id, false, [])}>
                      Rouvrir à la crise
                      {t("Rouvrir "
                                                                                                    + "à "
                                                                                                    + "la "
                                                                                                    + "crise")}
                    </button>
                  ) : (
                    <button className="petit"
                      onClick={() => { definirEdite(edite === s.id ? null : s.id);
                        definirChoisis([]); }}>
                      Passer en privé
                      {t("Passer en privé")}
                    </button>
                  )}
                  {/* Clore n'est pas passer en privé : le canal disparaît des
@@ -194,16 +257,19 @@ export function CanauxCrise({ criseId }: { criseId: string }) {
                      ponctuels s'accumulaient jusqu'à la clôture de la crise. */}
                  <button className="danger petit" style={{ marginLeft: 6 }}
                    onClick={() => definirClos(clos === s.id ? null : s.id)}>
                    Clore
                    {t("Clore")}
                  </button>
                  {clos === s.id && (
                    <div style={{ marginTop: 8, minWidth: 280 }}>
                      <input value={motifCloture} maxLength={400}
                        placeholder="Motif (facultatif) — sujet épuisé, créé par erreur…"
                        placeholder={t("Motif (facultatif) — sujet épuisé, créé par erreur…")}
                        onChange={(e) => definirMotifCloture(e.target.value)} />
                      <div className="aide">
                        Le canal quitte les conversations vivantes. Tout ce qui y a été
                        écrit reste au journal.
                        {t("Le canal quitte les "
                                                                       + "conversations "
                                                                       + "vivantes. Tout ce "
                                                                       + "qui y a été écrit "
                                                                       + "reste au journal.")}
                      </div>
                      <div className="ligne" style={{ gap: 6, marginTop: 6 }}>
                        <button className="danger petit"
@@ -217,19 +283,39 @@ export function CanauxCrise({ criseId }: { criseId: string }) {
                              ]); })
                            .catch(() => definirErreur(
                              "Le canal n'a pas pu être clos."))}>
                          Clore le canal
                          {t("Clore "
                                                                                               + "le "
                                                                                               + "canal")}
                        </button>
                        <button className="discret petit"
                          onClick={() => definirClos(null)}>Annuler</button>
                          onClick={() => definirClos(null)}>{t("Annuler")}</button>
                      </div>
                    </div>
                  )}
                  {edite === s.id && (
                    <div style={{ marginTop: 8 }}>
                      <p className="aide" style={{ marginTop: 0 }}>
                        Qui garde l'accès&nbsp;? Vous restez membre d'office. Les messages
                        déjà écrits restent au journal&nbsp;: fermer protège la suite,
                        pas le passé.
                        {t("Qui "
                                                                                              + "garde "
                                                                                              + "l'accès ? "
                                                                                              + "Vous "
                                                                                              + "restez "
                                                                                              + "membre "
                                                                                              + "d'office. "
                                                                                              + "Les "
                                                                                              + "messages "
                                                                                              + "déjà "
                                                                                              + "écrits "
                                                                                              + "restent "
                                                                                              + "au "
                                                                                              + "journal : "
                                                                                              + "fermer "
                                                                                              + "protège "
                                                                                              + "la "
                                                                                              + "suite, "
                                                                                              + "pas "
                                                                                              + "le "
                                                                                              + "passé.")}
                      </p>
                      {disponibles.map((m) => (
                        <label key={m.compte_id} className="ligne"
@@ -243,7 +329,9 @@ export function CanauxCrise({ criseId }: { criseId: string }) {
                      ))}
                      <button className="petit" style={{ marginTop: 6 }}
                        onClick={() => void appliquerAcces(s.id, true, choisis)}>
                        Fermer le canal
                        {t("Fermer "
                                                                                                            + "le "
                                                                                                            + "canal")}
                      </button>
                    </div>
                  )}
@@ -253,9 +341,16 @@ export function CanauxCrise({ criseId }: { criseId: string }) {
          </tbody>
        </table>
        <p className="aide" style={{ marginTop: 10 }}>
          Un canal privé disparaît de la liste des personnes qui n'en sont pas —
          il n'y apparaît pas grisé. Signaler l'existence d'un canal fermé
          reviendrait à en divulguer le sujet à qui n'y a pas droit.
          {t("Un canal privé "
                                                                   + "disparaît de la liste "
                                                                   + "des personnes qui n'en "
                                                                   + "sont pas — il n'y "
                                                                   + "apparaît pas grisé. "
                                                                   + "Signaler l'existence "
                                                                   + "d'un canal fermé "
                                                                   + "reviendrait à en "
                                                                   + "divulguer le sujet à "
                                                                   + "qui n'y a pas droit.")}
        </p>
      </div>
    </div>
+72 −19

File changed.

Preview size limit exceeded, changes collapsed.

+39 −24

File changed.

Preview size limit exceeded, changes collapsed.

Loading