Siphonight 0dd5fa7dfd v1.11.0 (web) : inactifs regroupés en bas de la page Traitements + cartes atténuées — miroir Android
- Helper pur treatmentsForDisplay dans js/data/models.js (miroir du Kotlin,
  tri STABLE : actifs d'abord, inactifs après, ordre relatif conservé dans
  chaque groupe) — demande : « peu discernables, à placer à la fin ».
- Rendu : en-tête « Traitements inactifs » (seulement s'il en existe),
  cartes opacity 0.55 en plus du badge — lisibles et cliquables.
- Clés i18n inactive_section FR/EN ; WEB_VERSION alignée sur 1.11.0.
- 4 tests treatments-display.test.js (miroir TreatmentsDisplayTest.kt).

184 tests + E2E verts (check.sh).
2026-09-29 07:30:59 +02:00

515 lines
25 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* ─────────────────────────────────────────────────────────────────────────────
* i18n FR/EN (miroir du système Android : values/strings.xml EN défaut +
* values-fr/strings.xml).
*
* - `t(key, ...args)` : traduction + substitution de placeholders {0},{1}… ;
* - langue persistée dans le store ("system"/"fr"/"en") — "system" suit
* navigator.language (équivalent AppCompatDelegate.setApplicationLocales) ;
* - ⚠️ TOUTE nouvelle string existe dans les DEUX dictionnaires (leçon
* Android §12 : une référence manquante = affichage brut de la clé).
* ─────────────────────────────────────────────────────────────────────────────
*/
import { store } from '../data/store.js';
/** Dictionnaire ANGLAIS (défaut — même convention que values/strings.xml). */
const EN = {
app_name: 'HormoneTrack',
// Navigation
nav_home: 'Home',
nav_chart: 'Charts',
nav_doses: 'Doses',
nav_labs: 'Labs',
nav_treatments: 'Treatments',
settings: 'Settings',
back: 'Back',
save: 'Save',
cancel: 'Cancel',
delete: 'Delete',
ok: 'OK',
add_dose: 'Log dose',
add_lab: 'Add blood test',
add_treatment: 'Add treatment',
edit_treatment: 'Edit treatment',
edit_dose: 'Edit dose',
no_data: 'No data yet',
loading: 'Loading…',
// Avertissement médical
disclaimer: '⚠️ Medical disclaimer: the curves are pharmacokinetic estimates for information purposes — not measurements. Always trust your blood tests and your endocrinologist.',
// Accueil
current_level: 'Current level (estimated)',
no_data_short: 'No data — log an injection to see estimates',
delta_6h: '{0} {1} pg/mL vs 6 h ago',
next_dose: 'Next dose',
next_doses: 'Upcoming doses',
next_dose_days: '{0} d {1} h · {2} ({3})',
logged_today: 'Quick log',
home_chart_title: 'Last 24 h',
home_legend_e2: 'E2 — estradiol (pg/mL)',
home_legend_t: 'T — testosterone (ng/mL)',
open_charts_hint: 'Tap to open full charts',
// Alertes
alert_card_title: 'Threshold crossed (estimate)',
alert_high: '▲ {0} estimated ≈ {1} {2} — above your limit ({3})',
alert_low: '▼ {0} estimated ≈ {1} {2} — below your limit ({3})',
alert_disclaimer: 'Based on the estimated level, not a measurement.',
// Doses
hrt_since: 'On HRT since {0}',
hrt_duration: '{0} months, {1} days — {2} days total',
hrt_duration_days_only: '{0} days',
interval_value: '{0} d since previous',
confirm_delete: 'Delete this entry?',
confirm_delete_treatment: 'Delete this treatment? Its dose history will be removed too (labs are kept).',
// Labs
edit_which_lab: 'Which entry to edit?',
confirm_delete_blood_draw: 'Delete the whole blood draw ({0})?',
// Traitements
inactive: 'inactive',
inactive_section: 'Inactive treatments',
pk_absorption: 'Tmax',
pk_halflife: 'Half-life (h)',
pk_bioavail: 'Bioavailability (0–1)',
treatment_name: 'Name',
treatment_type: 'Type',
treatment_route: 'Route',
ester: 'Ester',
pk_model: 'PK model',
default_dose: 'Standard dose',
dose_unit: 'Unit',
select_preset: 'Select a preset',
type_estradiol: 'Estradiol',
type_antiandrogen: 'Anti-androgen',
type_progestogen: 'Progestogen',
type_other: 'Other',
route_oral: 'Oral',
route_gel: 'Transdermal gel',
route_patch: 'Transdermal patch',
route_injection_im: 'IM injection',
route_injection_sc: 'Subcutaneous injection',
route_other: 'Other route',
model_ese: 'Estrannaise',
model_tfs: 'Transfem Science',
model_whs: 'WHSAH',
// Éditeur : calibration
calibration_title: 'Calibration',
scale_factor: 'Scale factor',
calibration_hint: 'Ratio lab ÷ model prediction. Use “Calibrate from labs” or enable auto-calibration in Settings.',
calibration_auto_override: 'Auto-calibration is ON: this manual factor is ignored on charts display.',
calibrate_from_labs: 'Calibrate from labs',
// Éditeur : posologie / prévision
freq_section: 'Posology (forecast)',
freq_interval: 'Interval (days)',
freq_hint: 'Projects upcoming doses on the chart and schedules reminders on this grid.',
// Éditeur : rappel
reminder: 'Reminder',
reminder_time: 'Reminder time',
// Éditeur : agenda
agenda_section: 'Calendar event',
agenda_hint: 'Recurring event in a local calendar (Android only). Not available in the web app.',
agenda_needs_posology: 'A posology is required for recurring events.',
agenda_permission: 'Requires calendar permissions on Android.',
// Éditeur : actif
active: 'Active',
// Dose dialog
dose_amount: 'Dose amount',
dose_time: 'Date & time',
dose_notes: 'Notes',
ester_default: 'Default ({0})',
no_treatment_hint: 'Create a treatment first (Treatments tab).',
// Lab dialog
lab_e2_value: 'E2 (pg/mL)',
lab_t_value: 'T',
lab_units_hint: 'Accepted T units: ng/mL, ng/dL, ng/L, nmol/L',
lab_timestamp: 'Date & time',
lab_notes: 'Notes',
lab_optional: 'optional',
// Paramètres
language: 'Language',
language_system: 'System',
auto_calibrate: 'Auto-calibration (E2 + T)',
auto_calibrate_hint: 'Adjusts scale factors per ester period and the T model from your labs — applied to charts display, nothing is stored.',
t_model_title: 'Testosterone estimate (manual settings)',
t_model_hint: 'T = floor + (base − floor) / (1 + k·E2). Empirical model — estimate only.',
t_base: 'Base (ng/mL)',
t_floor: 'Floor (ng/mL)',
t_calibrated: 'k calibrated from labs',
alert_section: 'Alert thresholds',
alert_hint: 'Evaluated against the ESTIMATED level on Home (opt-in: empty = no alert). High must be above low.',
alert_e2_high: 'E2 high (pg/mL)',
alert_e2_low: 'E2 low (pg/mL)',
alert_t_high: 'T high (ng/mL)',
alert_t_low: 'T low (ng/mL)',
alert_invalid: 'Invalid values (high must be above low, numbers only)',
saved: 'Saved',
timezone_section: 'Chart timezone',
timezone_hint: 'Timezone used for chart X labels and dose markers (IANA ID, e.g. Europe/Paris). Empty = device timezone.',
timezone_field: 'IANA timezone ID',
timezone_auto: 'Auto (device)',
timezone_invalid: 'Unknown timezone ID',
reminders_section: 'Reminders (web)',
reminders_note: 'Reminders fire via browser notifications while this page is open. No notification when the tab is closed (unlike the Android app).',
notif_grant: 'Enable notifications',
notif_granted: 'Notifications enabled',
notif_denied: 'Notifications blocked by the browser',
backup_section: 'Backup (JSON)',
backup_hint: 'Same format as the Android app — backups are interchangeable in both directions.',
export_json: 'Export JSON',
import_json: 'Import JSON',
export_ok: 'Backup exported',
export_fail: 'Export failed',
import_ok: 'Restored: {0} treatments, {1} doses, {2} labs',
import_fail: 'Import failed (invalid file?)',
import_title: 'Import backup',
import_warning: '⚠️ Importing REPLACES ALL current data (treatments, doses, labs) with the backup content. Continue?',
import_confirm: 'Erase & restore',
logs_section: 'Diagnostic logs',
logs_count: '{0} lines',
logs_export: 'Export logs',
logs_clear: 'Clear',
logs_export_ok: 'Diagnostic logs exported',
logs_export_fail: 'Logs export failed',
about_title: 'About',
settings_version: 'Version {0} (web)',
releases_link: 'Releases',
models_credit: 'PK models belong to their respective authors (Estrannaise, Transfem Science, WHSAH Collective via Mona).',
privacy_note: '100% local: your data never leaves this browser (localStorage). No account, no server.',
// Chart
chart_24h: '24 h',
chart_7j: '7 d',
chart_30j: '30 d',
zoom_in: 'Zoom in',
zoom_out: 'Zoom out',
show_labs: 'Labs',
show_forecast: 'Forecast',
show_extrema: 'Peaks & troughs',
// v1.8.0 : recommandation de prochaine prise de sang (creux stabilisé —
// miroir strings.xml Android ; t() remplace {0}/{1} par les args)
labrec_section: 'Next blood draw (suggested)',
labrec_when: 'At the estimated trough: {0}',
labrec_before_injection: 'Just before your {0} injection of {1} — or simply the day before that slot.',
labrec_stabilized: 'Your {0} has been stable since {1} (~5 terminal half-lives).',
labrec_not_stabilized: 'Your {0} was not stable until {1} (~5 terminal half-lives): this is the first reliable trough after your latest change.',
labrec_disclaimer: 'Estimation from the pharmacokinetic model, not medical advice — blood draw at the trough simply gives the most comparable result. Always follow your endocrinologist.',
labrec_needs_posology: 'Tip: set a Dosage interval on your active injectable E2 treatment (Treatments → edit) and this page will suggest when to do your next blood draw (at the trough, just before the injection).',
show_lab_track: 'Lab track',
// v1.6.0 : prolongation du tracé labs au-delà du dernier lab (opt-in,
// actif seulement quand Lab track est ON — miroir strings.xml Android)
show_lab_track_extend: 'Extend',
// v1.9.0 : nuage d'incertitude du modèle ESE (posterior MCMC de
// estrannaise.js) — exclusif à ESE, off par défaut
show_cloud: 'Cloud',
legend_cloud: 'Dotted cloud = Estrannaise MCMC parameter uncertainty (ESE model only)',
lab_track_extend_warning: '⚠ Simulation only, with no guarantee of matching reality: the extension extrapolates your model from your lab results — which can themselves be inaccurate. Trust your next blood test, not this curve.',
back_to_present: 'Back to present',
legend_ese_e2: 'E2 — Estrannaise (blue)',
legend_ese_t: 'T — Estrannaise (pink)',
legend_tfs_e2: 'E2 — Transfem Science (teal)',
legend_tfs_t: 'T — Transfem Science (pink dashed)',
legend_whs_e2: 'E2 — WHSAH (green)',
legend_whs_t: 'T — WHSAH (purple dashed)',
legend_labs: '● labs (orange) — circle = E2, square = T',
legend_forecast_dose: 'Dashed line + ▼ = projected dose',
legend_extrema: '▲▼ = local peaks & troughs (with estimated values)',
legend_lab_track: 'Lab-anchored curve — model shape forced through your labs (E2 only, between first and last lab)',
// v1.6.0 : légende de la partie PROLONGÉE du tracé labs (série "LABX")
legend_lab_track_extend: '-- Lab track extended past your last lab (model × last lab ratio — estimate, not anchored)',
drag_hint: 'Drag horizontally to pan (right = past, left = future with forecast). Pinch or − / + to zoom (6 h → 300 d).',
// Rappels (bannière + notification)
reminder_notif_title: 'HormoneTrack — dose reminder',
reminder_notif_body: '{0} · {1}{2} — scheduled for {3}',
reminder_log_now: 'Log now',
reminder_snooze: 'Snooze 1 h',
reminder_logged: 'Dose logged from reminder',
// Dialog « Nouveautés »
whats_new_title: 'What\'s new',
whats_new_close: 'Close',
// Demo (hook de test ?demo=1)
demo_banner: 'DEMO DATA (test hook) — this data lives only in this browser.',
};
/** Dictionnaire FRANÇAIS (miroir values-fr/strings.xml). */
const FR = {
app_name: 'HormoneTrack',
nav_home: 'Accueil',
nav_chart: 'Graphiques',
nav_doses: 'Doses',
nav_labs: 'Analyses',
nav_treatments: 'Traitements',
settings: 'Paramètres',
back: 'Retour',
save: 'Enregistrer',
cancel: 'Annuler',
delete: 'Supprimer',
ok: 'OK',
add_dose: 'Loguer une dose',
add_lab: 'Ajouter une analyse',
add_treatment: 'Ajouter un traitement',
edit_treatment: 'Modifier le traitement',
edit_dose: 'Modifier la dose',
no_data: 'Aucune donnée',
loading: 'Chargement…',
disclaimer: '⚠️ Avertissement médical : les courbes sont des estimations pharmacocinétiques à titre informatif — ce ne sont pas des mesures. Fie-toi toujours à tes analyses de sang et aux consignes de ton endocrinologue.',
current_level: 'Niveau actuel (estimé)',
no_data_short: 'Aucune donnée — logue une injection pour voir les estimations',
delta_6h: '{0} {1} pg/mL vs il y a 6 h',
next_dose: 'Prochaine dose',
next_doses: 'Prochaines doses',
next_dose_days: '{0} j {1} h · {2} ({3})',
logged_today: 'Log rapide',
home_chart_title: 'Dernières 24 h',
home_legend_e2: 'E2 — œstradiol (pg/mL)',
home_legend_t: 'T — testostérone (ng/mL)',
open_charts_hint: 'Taper pour ouvrir le graphique complet',
alert_card_title: 'Seuil franchi (estimation)',
alert_high: '▲ {0} estimé ≈ {1} {2} — au-dessus de ta limite ({3})',
alert_low: '▼ {0} estimé ≈ {1} {2} — en dessous de ta limite ({3})',
alert_disclaimer: 'Basé sur le niveau estimé, pas une mesure.',
hrt_since: 'Sous THS depuis le {0}',
hrt_duration: '{0} mois, {1} jours — {2} jours au total',
hrt_duration_days_only: '{0} jours',
interval_value: '{0} j depuis la précédente',
confirm_delete: 'Supprimer cette entrée ?',
confirm_delete_treatment: 'Supprimer ce traitement ? Son historique de doses sera supprimé aussi (les analyses sont conservées).',
edit_which_lab: 'Quelle entrée modifier ?',
confirm_delete_blood_draw: 'Supprimer la prise de sang entière ({0}) ?',
inactive: 'inactive',
inactive_section: 'Traitements inactifs',
pk_absorption: 'Tmax',
pk_halflife: 'Demi-vie (h)',
pk_bioavail: 'Biodisponibilité (0–1)',
treatment_name: 'Nom',
treatment_type: 'Type',
treatment_route: 'Voie',
ester: 'Ester',
pk_model: 'Modèle PK',
default_dose: 'Dose standard',
dose_unit: 'Unité',
select_preset: 'Choisir un preset',
type_estradiol: 'Œstradiol',
type_antiandrogen: 'Anti-androgène',
type_progestogen: 'Progestatif',
type_other: 'Autre',
route_oral: 'Orale',
route_gel: 'Gel transdermique',
route_patch: 'Patch transdermique',
route_injection_im: 'Injection IM',
route_injection_sc: 'Injection SC',
route_other: 'Autre voie',
model_ese: 'Estrannaise',
model_tfs: 'Transfem Science',
model_whs: 'WHSAH',
calibration_title: 'Calibration',
scale_factor: 'Facteur d\'échelle',
calibration_hint: 'Ratio lab ÷ prédiction du modèle. Utilise « Calibrer avec les analyses » ou active la calibration automatique dans Paramètres.',
calibration_auto_override: 'Auto-calibration active : ce facteur manuel est ignoré à l\'affichage des courbes.',
calibrate_from_labs: 'Calibrer avec les analyses',
freq_section: 'Posologie (prévision)',
freq_interval: 'Intervalle (jours)',
freq_hint: 'Projette les doses à venir sur le graphique et cale les rappels sur cette grille.',
reminder: 'Rappel',
reminder_time: 'Heure du rappel',
agenda_section: 'Événement d\'agenda',
agenda_hint: 'Événement récurrent dans un calendrier local (Android uniquement). Non disponible dans l\'app web.',
agenda_needs_posology: 'Une Posologie est requise pour les événements récurrents.',
agenda_permission: 'Nécessite les permissions agenda sur Android.',
active: 'Active',
dose_amount: 'Dose (quantité)',
dose_time: 'Date & heure',
dose_notes: 'Notes',
ester_default: 'Défaut ({0})',
no_treatment_hint: 'Crée d\'abord un traitement (onglet Traitements).',
lab_e2_value: 'E2 (pg/mL)',
lab_t_value: 'T',
lab_units_hint: 'Unités T acceptées : ng/mL, ng/dL, ng/L, nmol/L',
lab_timestamp: 'Date & heure',
lab_notes: 'Notes',
lab_optional: 'optionnel',
language: 'Langue',
language_system: 'Système',
auto_calibrate: 'Calibration automatique (E2 + T)',
auto_calibrate_hint: 'Ajuste les facteurs d\'échelle par période d\'ester et le modèle T depuis tes labs — appliqué à l\'affichage des courbes, rien n\'est stocké.',
t_model_title: 'Estimation T (réglages manuels)',
t_model_hint: 'T = plancher + (base − plancher) / (1 + k·E2). Modèle empirique — estimation seulement.',
t_base: 'Base (ng/mL)',
t_floor: 'Plancher (ng/mL)',
t_calibrated: 'k calibré avec les analyses',
alert_section: 'Seuils d\'alerte',
alert_hint: 'Évalués contre le niveau ESTIMÉ de l\'accueil (opt-in : vide = pas d\'alerte). Haut > bas requis.',
alert_e2_high: 'E2 haute (pg/mL)',
alert_e2_low: 'E2 basse (pg/mL)',
alert_t_high: 'T haute (ng/mL)',
alert_t_low: 'T basse (ng/mL)',
alert_invalid: 'Valeurs invalides (haut > bas requis, nombres seulement)',
saved: 'Enregistré',
timezone_section: 'Fuseau du graphique',
timezone_hint: 'Fuseau des labels X et marqueurs de doses (ID IANA, ex. Europe/Paris). Vide = fuseau du téléphone.',
timezone_field: 'ID de fuseau IANA',
timezone_auto: 'Auto (téléphone)',
timezone_invalid: 'ID de fuseau inconnu',
reminders_section: 'Rappels (web)',
reminders_note: 'Les rappels passent par les notifications du navigateur tant que cette page est ouverte. Pas de notification onglet fermé (contrairement à l\'app Android).',
notif_grant: 'Activer les notifications',
notif_granted: 'Notifications activées',
notif_denied: 'Notifications bloquées par le navigateur',
backup_section: 'Sauvegarde (JSON)',
backup_hint: 'Même format que l\'app Android — les backups sont interchangeables dans les deux sens.',
export_json: 'Exporter JSON',
import_json: 'Importer JSON',
export_ok: 'Sauvegarde exportée',
export_fail: 'Échec de l\'export',
import_ok: 'Restauré : {0} traitements, {1} doses, {2} analyses',
import_fail: 'Échec de l\'import (fichier invalide ?)',
import_title: 'Importer une sauvegarde',
import_warning: '⚠️ L\'import REMPLACE toutes les données actuelles (traitements, doses, analyses) par le contenu de la sauvegarde. Continuer ?',
import_confirm: 'Effacer & restaurer',
logs_section: 'Logs de diagnostic',
logs_count: '{0} lignes',
logs_export: 'Exporter les logs',
logs_clear: 'Effacer',
logs_export_ok: 'Logs de diagnostic exportés',
logs_export_fail: 'Échec de l\'export des logs',
about_title: 'À propos',
settings_version: 'Version {0} (web)',
releases_link: 'Releases',
models_credit: 'Les modèles PK appartiennent à leurs autrices respectives (Estrannaise, Transfem Science, WHSAH Collective via Mona).',
privacy_note: '100 % local : tes données ne quittent jamais ce navigateur (localStorage). Aucun compte, aucun serveur.',
chart_24h: '24 h',
chart_7j: '7 j',
chart_30j: '30 j',
zoom_in: 'Zoom avant',
zoom_out: 'Zoom arrière',
show_labs: 'Labs',
show_forecast: 'Prévision',
show_extrema: 'Pics & creux',
// v1.8.0 : recommandation de prochaine prise de sang (creux stabilisé —
// miroir strings.xml Android)
labrec_section: 'Prochaine prise de sang (suggestion)',
labrec_when: 'Au creux estimé : {0}',
labrec_before_injection: 'Juste avant ton injection de {0} du {1} — ou simplement la veille de ce créneau.',
labrec_stabilized: 'Ton {0} est stabilisé depuis le {1} (~5 demi-vies terminales).',
labrec_not_stabilized: 'Ton {0} n\'était pas stabilisé avant le {1} (~5 demi-vies terminales) : c\'est le premier creux fiable depuis ton dernier changement.',
labrec_disclaimer: 'Estimation du modèle pharmacocinétique, pas un avis médical — la prise au creux donne simplement le résultat le plus comparable. Suis toujours ton endocrinologue.',
labrec_needs_posology: 'Astuce : renseigne une Posologie (intervalle entre prises) sur ton traitement E2 injectable actif (Traitements → éditer) et cette page suggérera quand faire ta prochaine prise de sang (au creux, juste avant l\'injection).',
show_lab_track: 'Tracé labs',
// v1.6.0 : prolongation du tracé labs au-delà du dernier lab (opt-in,
// actif seulement quand Tracé labs est ON — miroir strings.xml Android)
show_lab_track_extend: 'Prolonger',
show_cloud: 'Nuage',
legend_cloud: 'Nuage pointillé = incertitude des paramètres MCMC de l\'Estrannaise (modèle ESE seul)',
lab_track_extend_warning: '⚠ Simple simulation, sans garantie de correspondre au réel : la prolongation extrapole ton modèle à partir de tes résultats de laboratoire — qui peuvent eux-mêmes être erronés. Fie-toi à ta prochaine prise de sang, pas à cette courbe.',
back_to_present: 'Revenir à maintenant',
legend_ese_e2: 'E2 — Estrannaise (bleu)',
legend_ese_t: 'T — Estrannaise (rose)',
legend_tfs_e2: 'E2 — Transfem Science (turquoise)',
legend_tfs_t: 'T — Transfem Science (rose pointillé)',
legend_whs_e2: 'E2 — WHSAH (vert)',
legend_whs_t: 'T — WHSAH (violet pointillé)',
legend_labs: '● labs (orange) — cercle = E2, carré = T',
legend_forecast_dose: 'Ligne pointillée + ▼ = dose projetée',
legend_extrema: '▲▼ = pics/creux locaux (avec valeurs estimées)',
legend_lab_track: 'E2 ancrée sur tes labs — forme du modèle forcée de passer par chaque prise de sang (entre le 1er et le dernier lab)',
// v1.6.0 : légende de la partie PROLONGÉE du tracé labs (série "LABX")
legend_lab_track_extend: '-- Tracé labs prolongé après ton dernier lab (modèle × ratio du dernier lab — estimation, plus ancrée)',
drag_hint: 'Glisser horizontalement pour panoramique (droite = passé, gauche = futur avec la prévision). Pincer ou − / + pour zoomer (6 h → 300 j).',
reminder_notif_title: 'HormoneTrack — rappel de dose',
reminder_notif_body: '{0} · {1}{2} — prévu à {3}',
reminder_log_now: 'Loguer maintenant',
reminder_snooze: 'Reporter 1 h',
reminder_logged: 'Dose loguée depuis le rappel',
whats_new_title: 'Nouveautés',
whats_new_close: 'Fermer',
demo_banner: 'DONNÉES DE DÉMO (hook de test) — ces données ne vivent que dans ce navigateur.',
};
/** Presets (même libellé que les strings Android). */
const EN_PRESETS = {
preset_ev_ese: 'EV injection — Estrannaise',
preset_ec_ese: 'EC injection (oil) — Estrannaise',
preset_eb_ese: 'EB injection — Estrannaise',
preset_eucs_ese: 'EUCS injection (crystal suspension) — Estrannaise',
preset_eu_ese: 'EU injection — Estrannaise',
preset_een_ese: 'EEn injection — Estrannaise',
preset_ev_tfs: 'EV injection — Transfem Science',
preset_eu_tfs: 'EU injection — Transfem Science',
preset_een_tfs: 'EEn injection — Transfem Science',
preset_eb_tfs: 'EB injection — Transfem Science',
preset_ec_tfs: 'EC injection (oil) — Transfem Science',
preset_ecs_tfs: 'EC injection (aqueous suspension) — Transfem Science',
preset_pep_tfs: 'PEP injection — Transfem Science',
preset_ev_whs: 'EV injection — WHSAH',
preset_eu_whs: 'EU injection — WHSAH',
preset_een_whs: 'EEn injection — WHSAH',
preset_eb_whs: 'EB injection — WHSAH',
preset_ec_whs: 'EC injection (oil) — WHSAH',
preset_ecs_whs: 'EC injection (aqueous suspension) — WHSAH',
preset_e2_gel: 'E2 transdermal gel',
preset_e2_patch: 'E2 patch',
preset_e2_oral: 'Oral E2',
preset_cpa: 'Cyproterone acetate (CPA)',
preset_spiro: 'Spironolactone',
preset_bica: 'Bicalutamide',
};
const FR_PRESETS = {
preset_ev_ese: 'Injection EV — Estrannaise',
preset_ec_ese: 'Injection EC (huile) — Estrannaise',
preset_eb_ese: 'Injection EB — Estrannaise',
preset_eucs_ese: 'Injection EUCS (suspension cristalline) — Estrannaise',
preset_eu_ese: 'Injection EU — Estrannaise',
preset_een_ese: 'Injection EEn — Estrannaise',
preset_ev_tfs: 'Injection EV — Transfem Science',
preset_eu_tfs: 'Injection EU — Transfem Science',
preset_een_tfs: 'Injection EEn — Transfem Science',
preset_eb_tfs: 'Injection EB — Transfem Science',
preset_ec_tfs: 'Injection EC (huile) — Transfem Science',
preset_ecs_tfs: 'Injection EC (suspension aqueuse) — Transfem Science',
preset_pep_tfs: 'Injection PEP — Transfem Science',
preset_ev_whs: 'Injection EV — WHSAH',
preset_eu_whs: 'Injection EU — WHSAH',
preset_een_whs: 'Injection EEn — WHSAH',
preset_eb_whs: 'Injection EB — WHSAH',
preset_ec_whs: 'Injection EC (huile) — WHSAH',
preset_ecs_whs: 'Injection EC (suspension aqueuse) — WHSAH',
preset_e2_gel: 'E2 gel transdermique',
preset_e2_patch: 'E2 patch',
preset_e2_oral: 'E2 orale',
preset_cpa: 'Acétate de cyprotérone (CPA)',
preset_spiro: 'Spironolactone',
preset_bica: 'Bicalutamide',
};
const DICTS = {
en: { ...EN, ...EN_PRESETS },
fr: { ...FR, ...FR_PRESETS },
};
/** Langue résolue pour un réglage ("system" suit le navigateur). */
export function resolveLocale(setting) {
if (setting === 'fr' || setting === 'en') return setting;
const nav = typeof navigator !== 'undefined' && navigator.language ? navigator.language.toLowerCase() : 'en';
return nav.startsWith('fr') ? 'fr' : 'en';
}
/**
* Traduit une clé dans la langue COURANTE du store, avec substitution de
* placeholders {0}, {1}…
*
* @param {string} key
* @param {...(string|number)} args
* @returns {string}
*/
export function t(key, ...args) {
const locale = resolveLocale(store.getSettings().language);
let s = DICTS[locale][key] ?? DICTS.en[key] ?? key;
args.forEach((a, i) => {
s = s.replaceAll(`{${i}}`, String(a));
});
return s;
}
/** Clé i18n → libellé dans la langue courante (utile pour les listes). */
export function trKey(key) {
return t(key);
}