gestion_table_tournante_libre/src/application/libelles/aides.js

103 lines
4.5 KiB
JavaScript
Raw Normal View History

// © 2026 TechnoLibre (http://www.technolibre.ca)
// License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl)
// Les aides de composition que partagent les tables de l'application : citer,
// compter, énumérer, dater, nommer un état. Ce module n'est pas une table et
// n'exporte aucune TABLE. Une aide qui lit le texte d'une autre clé reçoit
// texte, la fonction de composition que la traduction passe à chaque entrée
// (traduction.js) : elle ne connaît pas l'assemblage des tables.
//
// Les aides ne lisent ni l'horloge ni la langue du système : un horodatage se
// met en mots à partir de sa seule forme, AAAA-MM-JJTHH:MM:SS±HH:MM.
const NOMS_DES_ETATS = new Map([
['brouillon', 'Brouillon'],
['propose', 'Proposé'],
['retenu', 'Retenu'],
['bloque', 'Bloqué'],
]);
/** Valeur d'une table de noms, ou RangeError qui la nomme. */
export function nomDans(table, valeur, quoi) {
if (!table.has(valeur)) throw new RangeError(`libelle : ${quoi} inconnu ${JSON.stringify(valeur)}`);
return table.get(valeur);
}
/** Le nom d'un état du plan (§ 9) ; un état inconnu lève RangeError. */
export const nomEtat = (etat) => nomDans(NOMS_DES_ETATS, etat, 'état');
/** Un texte cité entre guillemets français. */
export const cite = (texte) => `« ${texte} »`;
/** Un compte et son nom, au singulier jusqu'à un, comme le veut le français. */
export const compte = (n, singulier, pluriel) => `${n} ${n <= 1 ? singulier : pluriel}`;
/** Une énumération : « a », « a et b », « a, b et c ». */
export function enumeration(termes) {
if (termes.length <= 1) return termes.join('');
return `${termes.slice(0, -1).join(', ')} et ${termes.at(-1)}`;
}
/** La première lettre en capitale. */
export const capitale = (texte) => texte.charAt(0).toUpperCase() + texte.slice(1);
/** Le chemin de l'élément fautif, rien quand il manque ou désigne la racine. */
export const element = (chemin) => (typeof chemin === 'string' && chemin !== '' ? ` (${chemin})` : '');
/** Le chemin affichable d'une racine, reçue entière ou par son seul chemin. */
export const cheminDe = (racine) => (typeof racine === 'string' ? racine : racine?.chemin);
/**
* Une cause entre parenthèses, rien quand elle manque : son texte quand les
* tables en portent un, sinon la cause telle que le système la nomme.
*/
export function causeEntreParentheses(cause, texte) {
if (cause === null || cause === undefined || cause === '') return '';
return ` (${texte.existe(cause) ? texte(cause) : cause})`;
}
/** Ce qu'un refus d'écriture nomme : le fichier, son dossier, la cause. */
export const ouEcrire = ({ chemin, dossier, cause }, texte) =>
`${cite(chemin)} dans le dossier ${cite(dossier)}${causeEntreParentheses(cause, texte)}`;
/**
* Une raison codée en une phrase : son texte quand les tables en portent un
* et que les détails reçus lui suffisent ; sinon une phrase générique qui la
* cite telle quelle. Un texte qui attend ses propres détails lève ou rend
* « undefined » sans eux : la phrase générique le remplace alors.
*/
export function phraseDeRaison(raison, details, texte) {
const generique = `La raison donnée est ${cite(String(raison))}.`;
if (!texte.existe(raison)) return generique;
try {
const rendu = texte(raison, details);
return typeof rendu === 'string' && !rendu.includes('undefined') ? rendu : generique;
} catch {
return generique;
}
}
/** Des numéros de proposition, un illisible nommé comme tel. */
export const numeros = (ids) => enumeration(ids.map((id) => (id === null ? 'sans numéro lisible' : `n° ${id}`)));
// La forme d'un horodatage : date, heure et minute se lisent, les secondes et
// le décalage se taisent.
const HORODATAGE = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):\d{2}[+-]\d{2}:\d{2}$/;
/**
* Un horodatage AAAA-MM-JJTHH:MM:SS±HH:MM en mots, « le 17 mai 2031 à
* 8 h 05 », à l'heure qu'il porte, par les textes de la mise en forme
* (format.date, format.heure) ; une autre forme se cite telle quelle.
*/
export function dater(horodatage, texte) {
const morceaux = HORODATAGE.exec(String(horodatage));
if (morceaux === null) return String(horodatage);
const [annee, mois, jour, heure, minute] = morceaux.slice(1).map(Number);
if (mois < 1 || mois > 12) return String(horodatage);
return `le ${texte('format.date', { annee, mois, jour })} à ${texte('format.heure', { heure, minute })}`;
}
/** « depuis » et l'horodatage en mots, rien quand il manque. */
export const depuisQuand = (depuis, texte) =>
depuis === null || depuis === undefined ? '' : ` depuis ${dater(depuis, texte)}`;