- chart.js : chip « Prolonger » (off par défaut, désactivé tant que Tracé labs est off), split série "LAB" (ancrée) / "LABX" (prolongée, rose atténué rgba(194,24,91,0.55)), légendes dédiées (clés LAB ET LABX skippées de la boucle — miroir leçon #63), AVERTISSEMENT « simple simulation, sans garantie de correspondre au réel, labs potentiellement erronés » affiché sous la légende tant que la prolongation est visible (condition factorisée : jamais de courbe prolongée sans avertissement). - i18n : show_lab_track_extend, legend_lab_track_extend, lab_track_extend_warning (FR/EN synchronisées). - E2E : scénario v1.6.0 (chip désactivé → activation → légende + avertissement → disparition à la désactivation) ; assertions version 1.6.0.
481 lines
22 KiB
JavaScript
481 lines
22 KiB
JavaScript
/**
|
||
* ─────────────────────────────────────────────────────────────────────────────
|
||
* 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_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',
|
||
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',
|
||
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',
|
||
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_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',
|
||
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',
|
||
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',
|
||
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_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_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);
|
||
}
|