From 4adb9113bcd71d47234548bfe2d3e195709bed05 Mon Sep 17 00:00:00 2001 From: German Khokhlov Date: Wed, 28 Oct 2026 16:40:53 +0400 Subject: [PATCH] i18n(fr): apply review round 1 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Apply the accepted findings from the three-model French review: ground track reads « route », wildfire containment « circonscrit », downrange « distance au sol », tankers « navire-citerne », plus wording fixes for lanes, timestamps, wind sector, radio, CCTV and voice strings. Shots rebuilt by a pack's legacy bootstrap carry no sourcePackId, so the first Nepal shot titles stayed English; the scene's applied-pack marker now vouches for the pack's required titles. Co-Authored-By: Claude Opus 5.5 --- docs/translations/fr.md | 116 ++++++++++---------- src/i18n/locales/fr.json | 148 +++++++++++++------------- src/scenes/recipes.js | 24 +++-- src/scenes/sceneTitleDisplay.test.mjs | 23 ++++ 4 files changed, 173 insertions(+), 138 deletions(-) diff --git a/docs/translations/fr.md b/docs/translations/fr.md index 948479e6a..695d1b48f 100644 --- a/docs/translations/fr.md +++ b/docs/translations/fr.md @@ -11,7 +11,7 @@ Translation source: abiyeenzo (#907); the French part of meirm's #312 was used f - Sentence case, not Title Case: "Live Flights" → « Vols en direct ». UPPERCASE English stays UPPERCASE, accents included: « ÉTAT », « PRÉC. ». - Typography: a narrow no-break space (U+202F) goes before `:` `;` `?` `!` and `%`, and inside « » quotes: `Supprimer le plan « {shot} » ?`. It keeps the sign on the same line as the word before it. Do not type a plain space there. Ellipsis is `…`, not `...`. Dashes keep their spaces: « flux indisponible — réessayez ». - Short labels, chips and HUD readouts stay short. A standard abbreviation with a period beats a wrapped line: PRÉC., SUIV., RÉINIT., ENREG., INDISP., DIST. -- The HUD codes HDG, BRG, CRS, FOV, DEST, SPD and ALT stay in English everywhere, as in cockpit avionics. Words are translated: RANGE → « PORTÉE », heading in a sentence → « cap ». +- The HUD codes HDG, BRG, CRS, FOV, DEST, SPD and ALT stay in English everywhere, as in cockpit avionics. Words are translated: RANGE → « PORTÉE », heading in a sentence → « cap », ground-track course → « route » (« ROUTE 270° »). - ON and OFF stay as they are on HUD toggles and chips (« CCTV ON », « PROJECTION OFF »); in a sentence use « activé » / « désactivé ». - Keyboard keys stay in English: Space, Esc, Enter, Tab, Shift, Backspace, H, 1–9 (« Maintenez Space pour parler », « ESC pour fermer »). - Units: km, m, km/h, m/s, nd (knots), ft (feet), h, min, s, with a space before the unit: « 250 km », « il y a 5 min »; in UPPERCASE text KM, KM/S, ND, FT, MIN. MW, dBZ, hPa and `°` stay. Decimal comma in fixed numbers: « 28,2710° N », « M4,4 ». Money: « 12 k$ », « 3 M$ », « 2 Md$ ». @@ -32,60 +32,60 @@ The `many` form is the `other` text with « de » (« d' » before a vowel) afte ## Glossary -| English | Français | -| ---------------------------------------------- | --------------------------------------------------------------- | -| layer / data layer / DATA LAYERS (panel title) | couche / couche de données / COUCHES | -| feed / source / data source | flux / source / source de données | -| LIVE (status) / live data | DIRECT / données en direct | -| SIMULATED | SIMULÉ | -| stale / degraded / unavailable | obsolète / dégradé / indisponible | -| retry / retrying | nouvel essai / réessayez | -| rate-limited | limite de requêtes atteinte | -| key (API key) / POWER UP / POWERED UP | clé (clé API) / ALLUMER / ALLUMÉ | -| Provider Settings | paramètres des fournisseurs | -| enable / disable | activer / désactiver | -| globe / map / map source / basemap / map stack | globe / carte / source de carte / fond de carte / fond de carte | -| tile (map) | tuile | -| aircraft / flight / military flights | aéronef (avion in short copy) / vol / vols militaires | -| vessel, ship | navire | -| satellite / shell (Starlink) / DENSE | satellite / couche de la constellation / DENSE | -| launch / launch site / space mission | lancement / site de lancement / mission spatiale | -| ascent / replay / payload / stage / downrange | ascension / rejeu / charge utile / étage / distance parcourue | -| contact / tracking / track (HUD) | contact / suivi / piste | -| installation / site | installation / site | -| mapped / observed | cartographié / observé | -| camera / CCTV camera / ALPR camera | caméra / caméra CCTV / caméra LAPI | -| earthquake | séisme | -| active fire (FIRMS detection) / wildfire | feu actif / incendie | -| contained (wildfire) | maîtrisé | -| advisory (NHC/CPHC cyclone) | bulletin | -| forecast / lead hours / valid / issued | prévision / échéances / valide / émise | -| AQHI (Canada) | CQAS (cote air santé) | -| radio station / tune | station / régler | -| bikeshare / transit | vélos partagés / transports en commun | -| route (directions) / turn (maneuver) | itinéraire / manœuvre | -| scene / shot / scene recipe | scène / plan / recette de scène | -| bundle / data pack / anchor | archive / paquet de données / ancre | -| story beat | étape du récit | -| briefing / panel / dock | briefing / panneau / dock | -| cockpit | cockpit | -| preset / visual preset | préréglage / préréglage visuel | -| Scope (circular viewport mask) / keyhole | lunette / ouverture | -| detection overlay | surcouche de détection | -| Clean UI | vue épurée | -| Street Level (layer) / Street View (Google) | Niveau rue / Street View | -| Recent Imagery / box | imagerie récente / zone | -| pin (Draw tool) / pin (Recent Imagery) | épingle / épingler | -| Cyber (HUD layout, effect) | Cyber | -| heading, course / altitude / ground speed | cap / altitude / vitesse sol | -| HDG / BRG / CRS / FOV / DEST / SPD / ALT | unchanged | -| RANGE / PITCH / HGT (CCTV calibration chips) | PORTÉE / INCL. / HAUT. | -| PREV / PREVIOUS / NEXT (chips) | PRÉC. / PRÉC. / SUIV. | -| Previous / Next (button text) | Précédent / Suivant | -| L / R (relative bearing) | G / D | -| SIG / NEWS / LOCAL (briefing tabs) | SIG / ACTUS / LOCAL | -| compass points (N, NNE, …, W, NW) | N, NNE, …, O, NO | -| NOT AN ALL-CLEAR | PAS UN FEU VERT | -| AI agent | agent IA | -| share (a view or link) | partager | -| click / click to … | cliquez / cliquer pour … | +| English | Français | +| --------------------------------------------------------- | ----------------------------------------------------------------------- | +| layer / data layer / DATA LAYERS (panel title) | couche / couche de données / COUCHES | +| feed / source / data source | flux / source / source de données | +| LIVE (status) / live data | DIRECT / données en direct | +| SIMULATED | SIMULÉ | +| stale / degraded / unavailable | obsolète / dégradé / indisponible | +| retry / retrying | nouvel essai / réessayez | +| rate-limited | limite de requêtes atteinte | +| key (API key) / POWER UP / POWERED UP | clé (clé API) / ALLUMER / ALLUMÉ | +| Provider Settings | paramètres des fournisseurs | +| enable / disable | activer / désactiver | +| globe / map / map source / basemap / map stack | globe / carte / source de carte / fond de carte / fond de carte | +| tile (map) | tuile | +| aircraft / flight / military flights | aéronef (avion in short copy) / vol / vols militaires | +| vessel, ship / tanker (AIS type) | navire / navire-citerne | +| satellite / shell (Starlink) / DENSE | satellite / couche de la constellation / DENSE | +| launch / launch site / space mission | lancement / site de lancement / mission spatiale | +| ascent / replay / payload / stage / downrange | ascension / rejeu / charge utile / étage / distance au sol (DIST. SOL) | +| contact / tracking / track (HUD) | contact / suivi / piste | +| installation / site | installation / site | +| mapped / observed | cartographié / observé | +| camera / CCTV camera / ALPR camera | caméra / caméra CCTV / caméra LAPI | +| earthquake | séisme | +| active fire (FIRMS detection) / wildfire | feu actif / incendie | +| contained / containment (wildfire) | circonscrit / part circonscrite (not « maîtrisé », which is controlled) | +| advisory (NHC/CPHC cyclone) | bulletin | +| forecast / lead hours / valid / issued | prévision / échéances / valide / émise | +| AQHI (Canada) | CQAS (cote air santé) | +| radio station / tune | station / régler | +| bikeshare / transit | vélos partagés / transports en commun | +| route (directions) / turn (maneuver) | itinéraire / manœuvre | +| scene / shot / scene recipe | scène / plan / recette de scène | +| bundle / data pack / anchor | archive / paquet de données / ancre | +| story beat | étape du récit | +| briefing / panel / dock | briefing / panneau / dock | +| cockpit | cockpit | +| preset / visual preset | préréglage / préréglage visuel | +| Scope (circular viewport mask) / keyhole | lunette / ouverture | +| detection overlay | surcouche de détection | +| Clean UI | vue épurée | +| Street Level (layer) / Street View (Google) | Niveau rue / Street View | +| Recent Imagery / box | imagerie récente / zone | +| pin (Draw tool) / pin (Recent Imagery) | épingle / épingler | +| Cyber (HUD layout, effect) | Cyber | +| heading / course (ground track) / altitude / ground speed | cap / route / altitude / vitesse sol | +| HDG / BRG / CRS / FOV / DEST / SPD / ALT | unchanged | +| RANGE / PITCH / HGT (CCTV calibration chips) | PORTÉE / INCL. / HAUT. | +| PREV / PREVIOUS / NEXT (chips) | PRÉC. / PRÉC. / SUIV. | +| Previous / Next (button text) | Précédent / Suivant | +| L / R (relative bearing) | G / D | +| SIG / NEWS / LOCAL (briefing tabs) | SIG / ACTUS / LOCAL | +| compass points (N, NNE, …, W, NW) | N, NNE, …, O, NO | +| NOT AN ALL-CLEAR | PAS UN FEU VERT | +| AI agent | agent IA | +| share (a view or link) | partager | +| click / click to … | cliquez / cliquer pour … | diff --git a/src/i18n/locales/fr.json b/src/i18n/locales/fr.json index 1ae43fb08..883621f94 100644 --- a/src/i18n/locales/fr.json +++ b/src/i18n/locales/fr.json @@ -3,9 +3,9 @@ "Enter to place the pin, Esc to cancel.": "Enter pour poser l'épingle, Esc pour annuler.", "Click where the pin goes.": "Cliquez à l'endroit où poser l'épingle.", "Click {count} more points.": { - "one": "Cliquez encore {count} point.", - "many": "Cliquez encore {count} de points.", - "other": "Cliquez encore {count} points." + "one": "Cliquez pour ajouter encore {count} point.", + "many": "Cliquez pour ajouter encore {count} de points.", + "other": "Cliquez pour ajouter encore {count} points." }, "Those points are in a line — move one off it to enclose an area.": "Ces points sont alignés — déplacez-en un hors de la ligne pour délimiter une zone.", "That line has no length — click somewhere further away.": "Cette ligne n'a pas de longueur — cliquez plus loin.", @@ -43,13 +43,13 @@ "General emergency": "Urgence générale", "Unlawful interference": "Intervention illicite", "Radio failure": "Panne radio", - "Downed aircraft": "Aéronef abattu", + "Downed aircraft": "Aéronef accidenté", "Minimum fuel": "Carburant minimum", "Lifeguard (medical priority)": "Lifeguard (priorité médicale)", "Rasuwagadhi witness video": "Vidéo d'un témoin à Rasuwagadhi", "Bhote Koshi valley footage": "Images de la vallée de la Bhote Koshi", "Flood footage": "Images de la crue", - "Compilation and casualty claims": "Compilation et bilans de victimes non vérifiés", + "Compilation and casualty claims": "Compilation et bilans de victimes", "SOURCE GEOCONFIRMED · WORLD-PINNED": "SOURCE GÉOCONFIRMÉE · ÉPINGLÉE SUR LA CARTE", "SOURCE CREDITED · LOCATION UNRESOLVED": "SOURCE CRÉDITÉE · LIEU NON DÉTERMINÉ", "REFERENCE ONLY · CLAIMS UNVERIFIED": "RÉFÉRENCE SEULEMENT · AFFIRMATIONS NON VÉRIFIÉES", @@ -62,7 +62,7 @@ "Second landslide": "Second glissement de terrain", "Gyirong border gate": "Poste frontière de Gyirong", "Border gate": "Poste frontière", - "Timure evidence cluster": "Groupe d'éléments de preuve de Timure", + "Timure evidence cluster": "Groupe de preuves de Timure", "Mailung / Upper Trishuli bridge": "Mailung / pont d'Upper Trishuli", "Dandagaun viewpoint restaurant": "Restaurant-belvédère de Dandagaun", "Dandagaun viewpoint": "Belvédère de Dandagaun", @@ -85,11 +85,11 @@ "LOADING X POST…": "CHARGEMENT DU POST X…", "The X post is unavailable or cannot be embedded.": "Le post X est indisponible ou ne peut pas être intégré.", "X POST · PROVIDER CONTROLS": "POST X · COMMANDES DU FOURNISSEUR", - "{reason} OPEN ORIGINAL.": "{reason} OUVREZ L'ORIGINAL.", + "{reason} OPEN ORIGINAL.": "{reason} OUVRIR L'ORIGINAL.", "X embed unavailable": "Intégration X indisponible", "FACEBOOK · PROVIDER CONTROLS": "FACEBOOK · COMMANDES DU FOURNISSEUR", "Facebook embed unavailable": "Intégration Facebook indisponible", - "AUTOPLAY BLOCKED · USE PROVIDER CONTROLS": "LECTURE AUTO BLOQUÉE · UTILISEZ LES COMMANDES DU FOURNISSEUR", + "AUTOPLAY BLOCKED · USE PROVIDER CONTROLS": "LECTURE AUTO BLOQUÉE · UTILISER LES COMMANDES DU FOURNISSEUR", "Embedded {provider} media for {source}": "Média {provider} intégré pour {source}", "witness source": "source témoin", "Witness source": "Source témoin", @@ -99,7 +99,7 @@ "{provider} · PROVIDER CONTROLS": "{provider} · COMMANDES DU FOURNISSEUR", "YOUTUBE {state} · OPEN ORIGINAL": "YOUTUBE {state} · OUVRIR L'ORIGINAL", "INITIATION": "DÉCLENCHEMENT", - "DAMMING": "OBSTRUCTION", + "DAMMING": "BARRAGE", "SECONDARY FAILURE": "RUPTURE SECONDAIRE", "BORDER ARRIVAL": "ARRIVÉE À LA FRONTIÈRE", "PROPAGATION": "PROPAGATION", @@ -293,7 +293,7 @@ "HIGH-SPEED": "GRANDE VITESSE", "PASSENGER": "PASSAGERS", "CARGO": "CARGO", - "TANKER": "PÉTROLIER", + "TANKER": "NAVIRE-CITERNE", "OTHER": "AUTRE", "Included in bundle": "Inclus dans l'archive", "Missing bundle file — reimport its bundle": "Fichier absent de l'archive — réimportez son archive", @@ -393,7 +393,7 @@ "Optional paid token — public access works without one": "Jeton payant facultatif — l'accès public fonctionne sans", "Free key — register, paste, done": "Clé gratuite — inscrivez-vous, collez-la, c'est tout", "browser-side": "côté navigateur", - "This key runs in the browser by design — restrict it at the provider (see SECURITY.md)": "Cette clé s'exécute dans le navigateur par conception — restreignez-la chez le fournisseur (voir SECURITY.md)", + "This key runs in the browser by design — restrict it at the provider (see SECURITY.md)": "Cette clé est conçue pour être utilisée dans le navigateur — restreignez son usage chez le fournisseur (voir SECURITY.md)", "configured externally": "configurée ailleurs", "Supplied by your environment, Keychain, or launcher — change it where it was set": "Fournie par votre environnement, le Trousseau ou le lanceur — modifiez-la là où elle a été définie", "MANAGE ↗": "GÉRER ↗", @@ -464,14 +464,14 @@ "Camera badges": "Badges de caméras", "Cyan cameras turn coral when selected. Wedges illustrate mapped direction, not measured coverage. Nearby cameras may be outside the screen.": "Les caméras cyan deviennent corail une fois sélectionnées. Les secteurs illustrent l'orientation cartographiée, pas une couverture mesurée. Des caméras proches peuvent être hors de l'écran.", "{count} nearby · {onScreen} on screen": { - "one": "{count} proche · {onScreen} à l'écran", - "many": "{count} de caméras proches · {onScreen} à l'écran", - "other": "{count} proches · {onScreen} à l'écran" + "one": "{count} à proximité · {onScreen} à l'écran", + "many": "{count} à proximité · {onScreen} à l'écran", + "other": "{count} à proximité · {onScreen} à l'écran" }, "{count} nearby": { - "one": "{count} proche", - "many": "{count} de caméras proches", - "other": "{count} proches" + "one": "{count} à proximité", + "many": "{count} à proximité", + "other": "{count} à proximité" }, "retrying mapped ALPR cameras": "nouvel essai des caméras LAPI cartographiées", "loading mapped ALPR cameras": "chargement des caméras LAPI cartographiées", @@ -489,7 +489,7 @@ "CONTEXT READY": "CONTEXTE PRÊT", "GLOBAL CONTEXT OFF": "CONTEXTE GLOBAL OFF", "SELECT A FLIGHT, VESSEL, OR MAPPED INSTALLATION": "CHOISIR UN VOL, UN NAVIRE OU UNE INSTALLATION CARTOGRAPHIÉE", - "ENABLE TO LOAD OBSERVED / MAPPED PROXIMITY": "ACTIVER POUR CHARGER LA PROXIMITÉ OBSERVÉE / CARTOGRAPHIÉE", + "ENABLE TO LOAD OBSERVED / MAPPED PROXIMITY": "ACTIVER POUR VOIR LES CONTACTS PROCHES OBSERVÉS / CARTOGRAPHIÉS", "Unavailable": "Indisponible", "Focus {label}": "Centrer sur {label}", "Global Context navigation": "Navigation du Contexte global", @@ -561,9 +561,9 @@ "many": "{count} de cyclones actifs", "other": "{count} cyclones actifs" }, - "Position as of {time}": "Position à {time}", + "Position as of {time}": "Position au {time}", "Maximum sustained wind: {wind} · Pressure: {pressure}": "Vent maximal soutenu : {wind} · Pression : {pressure}", - "{name} · {classification} · Advisory {number} · issued {time}": "{name} · {classification} · Bulletin {number} · émis à {time}", + "{name} · {classification} · Advisory {number} · issued {time}": "{name} · {classification} · Bulletin {number} · émis le {time}", "No active NHC/CPHC systems": "Aucun système actif NHC/CPHC", "Advisories unavailable": "Bulletins indisponibles", "Cyclones · NHC / CPHC": "Cyclones · NHC / CPHC", @@ -610,7 +610,7 @@ "lane direction|slightly right": "de droite", "lane direction|sharply left": "la plus à gauche", "lane direction|sharply right": "la plus à droite", - "lane direction|straight": "centrale", + "lane direction|straight": "qui va tout droit", "ordinal|{number}st": "{number}re", "ordinal|{number}nd": "{number}e", "ordinal|{number}rd": "{number}e", @@ -669,7 +669,7 @@ "Continue onto {road}": "Continuez sur {road}", "maneuver|Continue": "Continuez", "{mode} — reroute for {modeLower}": "{mode} — recalculer l'itinéraire : {modeLower}", - "CLICK MAP": "CLIQUEZ LA CARTE", + "CLICK MAP": "CLIQUER SUR LA CARTE", "SET A": "PLACER A", "Click a spot on the globe to place A (click again to cancel)": "Cliquez sur un point du globe pour placer A (cliquez à nouveau pour annuler)", "Then click the globe to place the start": "Puis cliquez sur le globe pour placer le départ", @@ -716,7 +716,7 @@ "FIRE": "FEU", "FIRES": "FEUX", "max {value} MW": "max {value} MW", - "new {value}": "nouveau {value}", + "new {value}": "dernier il y a {value}", "{value}°N": "{value}°N", "{value}°S": "{value}°S", "{value}°E": "{value}°E", @@ -742,7 +742,7 @@ "Kinetic 30d": "Cinétique 30 j", "Border Crisis": "Crise frontalière", "Iran Picture": "Situation Iran", - "Iran Kinetic": "Iran cinétique", + "Iran Kinetic": "Cinétique Iran", "Yemen FLOT": "FLOT Yémen", "Latest Drop": "Dernière publication", "GhostMaps file could not be read": "Impossible de lire le fichier GhostMaps", @@ -817,7 +817,7 @@ "UNSPECIFIED": "NON PRÉCISÉ", "+{count} additional payload records": "+{count} enregistrements de charge utile supplémentaires", "PAYLOAD DATA UNAVAILABLE": "DONNÉES DE CHARGE UTILE INDISPONIBLES", - "{value} KM DOWNRANGE": "{value} KM PARCOURUS", + "{value} KM DOWNRANGE": "DIST. SOL : {value} KM", "POSITION UNAVAILABLE": "POSITION INDISPONIBLE", "FLIGHT {count}": "VOL {count}", "REUSED": "RÉUTILISÉ", @@ -858,7 +858,7 @@ "SHOW ALL / DESELECT": "TOUT AFFICHER / DÉSÉLECTIONNER", "Pause replay": "Mettre le rejeu en pause", "Cancel replay": "Annuler le rejeu", - "EST. DOWNRANGE": "DIST. ESTIMÉE", + "EST. DOWNRANGE": "DIST. SOL ESTIMÉE", "PLANNED · ": "PRÉVU · ", "SUPPLIED TRAJECTORY POINTS": "POINTS DE TRAJECTOIRE FOURNIS", "RECONSTRUCTED ESTIMATE": "ESTIMATION RECONSTITUÉE", @@ -921,8 +921,8 @@ "many": "{acres} d'acres", "other": "{acres} acres" }, - "{percent}% contained": "maîtrisé à {percent} %", - "containment unknown": "maîtrise inconnue", + "{percent}% contained": "circonscrit à {percent} %", + "containment unknown": "part circonscrite inconnue", "{cause} cause": "cause : {cause}", "{personnel} personnel": { "one": "{personnel} intervenant", @@ -939,11 +939,11 @@ "Unnamed incident": "Incident sans nom", "Open {title} on InciWeb": "Ouvrir {title} sur InciWeb", "Perimeter source unavailable": "Source des périmètres indisponible", - "Not contained or unknown": "Non maîtrisé ou inconnu", - "Under 50% contained": "Maîtrisé à moins de 50 %", - "50–99% contained": "Maîtrisé à 50–99 %", - "Fully contained": "Entièrement maîtrisé", - "Colour shows reported containment. Perimeters are simplified to about 100 m.": "La couleur indique le niveau de maîtrise signalé. Les périmètres sont simplifiés à environ 100 m.", + "Not contained or unknown": "Non circonscrit ou inconnu", + "Under 50% contained": "Circonscrit à moins de 50 %", + "50–99% contained": "Circonscrit à 50–99 %", + "Fully contained": "Entièrement circonscrit", + "Colour shows reported containment. Perimeters are simplified to about 100 m.": "La couleur indique la part du périmètre déclarée circonscrite. Les périmètres sont simplifiés à environ 100 m.", "Malformed perimeter snapshot": "Instantané des périmètres mal formé", "radio tag|All": "Toutes", "News": "Actualités", @@ -1125,8 +1125,8 @@ "OpenFreeMap tiles timed out": "Délai dépassé pour les tuiles OpenFreeMap", "OpenFreeMap tiles unavailable": "Tuiles OpenFreeMap indisponibles", "occupancy|empty": "vide", - "occupancy|many seats available": "nombreuses places assises", - "occupancy|few seats available": "peu de places assises", + "occupancy|many seats available": "nombreuses places assises libres", + "occupancy|few seats available": "peu de places assises libres", "occupancy|standing room only": "places debout seulement", "occupancy|crushed standing room only": "bondé, places debout seulement", "occupancy|full": "complet", @@ -1184,7 +1184,7 @@ "Hidden by this map source · choose a globe map": "Masqué avec cette source de carte · choisissez une carte du globe", "Weather image HTTP {status}": "Image météo : HTTP {status}", "Weather HTTP {status}": "Météo : HTTP {status}", - "Frame unavailable; previous observation retained": "Image indisponible ; observation précédente conservée", + "Frame unavailable; previous observation retained": "Image indisponible ; précédente conservée", "Weather imagery unavailable": "Imagerie météo indisponible", "Weather source unavailable; previous observation retained": "Source météo indisponible ; observation précédente conservée", "Weather unavailable": "Météo indisponible", @@ -1217,7 +1217,7 @@ "Reduced motion · manual history available": "Animations réduites · historique parcourable manuellement", "Rain radar · US": "Radar de pluie · États-Unis", "Lightning density · 15 min": "Densité de foudre · 15 min", - "Satellite clouds": "Nuages satellite", + "Satellite clouds": "Nuages par satellite", "Americas + Pacific": "Amériques + Pacifique", "Global · 60°S–60°N": "Monde · 60°S–60°N", "North America": "Amérique du Nord", @@ -1231,11 +1231,11 @@ "Hourly global mosaic; usually 2–3 hours delayed": "Mosaïque mondiale horaire ; retard habituel de 2 à 3 heures", "GOES regional clouds; approximately 5-minute updates": "Nuages régionaux GOES ; mise à jour toutes les 5 minutes environ", "Clouds only": "Nuages seuls", - "Apply the full cloud brightness key; 100% filter strength": "Appliquer toute la clé de luminosité des nuages ; intensité du filtre 100 %", + "Apply the full cloud brightness key; 100% filter strength": "Appliquer pleinement le filtre de luminosité des nuages ; intensité du filtre 100 %", "Full": "Complète", "Show the complete infrared image; 0% filter strength": "Afficher l'image infrarouge complète ; intensité du filtre 0 %", - "Soft": "Douce", - "Vivid": "Vive", + "Soft": "Faible", + "Vivid": "Forte", "Image opacity; does not alter the observed values": "Opacité de l'image ; ne modifie pas les valeurs observées", "View US radar": "Voir le radar des États-Unis", "View Americas & Pacific": "Voir Amériques et Pacifique", @@ -1325,7 +1325,7 @@ "A location reading needs a loaded forecast and the Earth at the center of the view.": "Une lecture ponctuelle nécessite une prévision chargée et la Terre au centre de la vue.", "Interpolated model forecast on an approximately 1° grid. Broad weather patterns, not a street-level measurement.": "Prévision de modèle interpolée sur une grille d'environ 1°. Tendances météo générales, pas une mesure à l'échelle de la rue.", " · calm": " · calme", - " from {direction}": " de {direction}", + " from {direction}": " · secteur {direction}", "WIND AT {coordinates}": "VENT À {coordinates}", "{model} · valid {time}": "{model} · valide {time}", "Wind speed field unavailable": "Champ de vitesse du vent indisponible", @@ -1428,7 +1428,7 @@ "Upper Valley Collapse": "Effondrement dans la haute vallée", "Debris-Dammed Lake": "Lac de barrage formé par les débris", "Nepal-China Border Gate": "Poste frontière Népal-Chine", - "Timure Evidence Cluster": "Groupe d'éléments de preuve de Timure", + "Timure Evidence Cluster": "Groupe de preuves de Timure", "Syabru Besi Passage": "Passage à Syabru Besi", "Bidur / Trishuli Consequence": "Conséquences à Bidur / Trishuli", "Final view": "Vue finale", @@ -1597,14 +1597,14 @@ "FRAME · UNAVAILABLE": "IMAGE · INDISPONIBLE", "CALIBRATED": "CALIBRÉE", "CURATED": "SÉLECTIONNÉE", - "RAW PRIOR": "VALEUR BRUTE", + "RAW PRIOR": "ESTIM. INITIALE", "CCTV ON": "CCTV ON", "CCTV OFF": "CCTV OFF", "VIEWSHED ON": "ZONE VISIBLE ON", "COVERAGE ON": "COUVERTURE ON", "COVERAGE OFF": "COUVERTURE OFF", - "AUTO HOP ON": "AUTO-SAUT ON", - "AUTO HOP OFF": "AUTO-SAUT OFF", + "AUTO HOP ON": "SAUT AUTO ON", + "AUTO HOP OFF": "SAUT AUTO OFF", "PROJECTION ON": "PROJECTION ON", "PROJECTION OFF": "PROJECTION OFF", "CAL · EDITED (UNSAVED)": "CAL · MODIFIÉE (NON ENREGISTRÉE)", @@ -1622,7 +1622,7 @@ "many": "{count} de caméras chargées · activer CCTV pour les utiliser", "other": "{count} caméras chargées · activer CCTV pour les utiliser" }, - "Enable CCTV to load camera intersections": "Activez CCTV pour charger les intersections de caméras", + "Enable CCTV to load camera intersections": "Activez CCTV pour charger les carrefours équipés de caméras", "Enable CCTV to start camera-linked intelligence summaries.": "Activez CCTV pour lancer les résumés de renseignement liés aux caméras.", "No summary available.": "Aucun résumé disponible.", "loading frames": "chargement des images", @@ -1648,7 +1648,7 @@ "REGIONAL NEWS UNAVAILABLE": "ACTUS RÉGIONALES INDISPONIBLES", "RESOLVING REGION": "RÉSOLUTION DE LA RÉGION", "REGION UNAVAILABLE": "RÉGION INDISPONIBLE", - "NO RECENT LOCATION MATCHES": "AUCUNE CORRESPONDANCE RÉCENTE AU LIEU", + "NO RECENT LOCATION MATCHES": "AUCUNE ACTU RÉCENTE POUR CE LIEU", "{domain} · {age}": "{domain} · {age}", "{value} KM/H": "{value} KM/H", "CLOUD {pct}%": "NUAGES {pct} %", @@ -1685,7 +1685,7 @@ "LIVE TRACK": "PISTE EN DIRECT", "COMMERCIAL": "COMMERCIAL", "{aircraftClass} · {emergency} · {feedState}": "{aircraftClass} · {emergency} · {feedState}", - "{aircraftClass} · {feedState} · COURSE ALIGNED": "{aircraftClass} · {feedState} · CAP ALIGNÉ", + "{aircraftClass} · {feedState} · COURSE ALIGNED": "{aircraftClass} · {feedState} · ALIGNÉ SUR LA ROUTE", "ARROW · ESTIMATED DIRECTION": "FLÈCHE · DIRECTION ESTIMÉE", "ROUTE DATA UNAVAILABLE": "DONNÉES DE ROUTE INDISPONIBLES", "DEST {bearing}": "DEST {bearing}", @@ -1710,7 +1710,7 @@ "Expand briefing panel": "Développer le panneau de briefing", "OBSERVED / MAPPED PINGS": "PINGS OBSERVÉS / CARTOGRAPHIÉS", "SOURCE-BACKED EVENTS · NO SYNTHETIC NEWS": "ÉVÉNEMENTS SOURCÉS · AUCUNE ACTU SYNTHÉTIQUE", - "LATEST LOCATION-MATCHED REPORTING": "DERNIERS REPORTAGES SELON LE LIEU", + "LATEST LOCATION-MATCHED REPORTING": "DERNIÈRES ACTUS LIÉES AU LIEU", "{source} · LOCATION QUERY · RECENT": "{source} · REQUÊTE DE LIEU · RÉCENT", "LOCAL INFO": "INFOS LOCALES", "PLACE / CONDITIONS / POSITION": "LIEU / CONDITIONS / POSITION", @@ -1734,7 +1734,7 @@ "Restore real aircraft": "Restaurer l'aéronef réel", "Reclassify as TR-3B": "Reclasser en TR-3B", "TRACK ACQUIRED": "PISTE ACQUISE", - "{label} · COURSE {heading}°": "{label} · CAP {heading}°", + "{label} · COURSE {heading}°": "{label} · ROUTE {heading}°", "Context could not restore every layer; try again": "Le contexte n'a pas pu restaurer toutes les couches ; réessayez", "No selected data layers": "Aucune couche de données sélectionnée", "{count} data layers could not be cleared": { @@ -1843,7 +1843,7 @@ "Utilities": "Outils", "Other layers": "Autres couches", "{state} · {source}": "{state} · {source}", - "UNCERTAIN · {source} · lifecycle state requires reconciliation": "INCERTAIN · {source} · état du cycle de vie à réconcilier", + "UNCERTAIN · {source} · lifecycle state requires reconciliation": "INCERTAIN · {source} · état à rétablir", "{state} · {source} · {detail} · retry {seconds}s": "{state} · {source} · {detail} · nouvel essai {seconds} s", "{state} · {source} · {detail}": "{state} · {source} · {detail}", "never": "jamais", @@ -1960,10 +1960,10 @@ "Close compact Radio controls": "Fermer les commandes Radio compactes", "Open compact Radio controls": "Ouvrir les commandes Radio compactes", "SYNC": "SYNCHRO", - "RECONCILE": "RÉCONCILIER", + "RECONCILE": "RÉTABLIR", "DISABLE": "DÉSACTIVER", "ENABLE": "ACTIVER", - "Reconcile Radio — lifecycle uncertain": "Réconcilier la Radio — cycle de vie incertain", + "Reconcile Radio — lifecycle uncertain": "Rétablir la Radio — état incertain", "Disable Radio": "Désactiver la Radio", "Enable Radio": "Activer la Radio", "{category} ({count})": "{category} ({count})", @@ -2001,18 +2001,18 @@ " · muted during voice interaction": " · coupé pendant l'interaction vocale", " · restoring volume after voice": " · rétablissement du volume après la voix", " · static indicates no broadcaster audio": " · le grésillement indique l'absence de flux du diffuseur", - " · tuning static until broadcaster starts": " · grésillement de syntonisation jusqu'au démarrage du diffuseur", + " · tuning static until broadcaster starts": " · grésillement pendant le réglage, jusqu'au début de la diffusion", "Station unavailable after directory refresh — choose another channel": "Station indisponible après l'actualisation de l'annuaire — choisissez un autre canal", "Radio is enabling…": "Activation de la Radio…", "Radio is disabling…": "Désactivation de la Radio…", - "Radio lifecycle is uncertain — use Enable or Disable to reconcile": "Cycle de vie de la Radio incertain — utilisez Activer ou Désactiver pour réconcilier", - "Clear reading": "Effacer la lecture", + "Radio lifecycle is uncertain — use Enable or Disable to reconcile": "État de la Radio incertain — utilisez Activer ou Désactiver pour le rétablir", + "Clear reading": "Effacer le relevé", "Earlier observation": "Observation précédente", - "Observed history": "Historique observé", + "Observed history": "Historique des observations", "Later observation": "Observation suivante", "Latest": "Dernière", "Newest frame per product": "Image la plus récente par produit", - "Replay observed history": "Rejouer l'historique observé", + "Replay observed history": "Rejouer l'historique des observations", "Imagery on Esri · Google 3D returns when cleared": "Sur Esri · EFFACER rétablit Google 3D", "Point the camera at the ground to use the view": "Orientez la caméra vers le sol pour utiliser la vue", "That point cannot anchor a box": "Ce point ne peut pas ancrer une zone", @@ -2093,7 +2093,7 @@ "Download {slot} as a PNG": "Télécharger {slot} en PNG", "Download the image as a PNG": "Télécharger l'image en PNG", "Box {width} × {height} km": "Zone de {width} × {height} km", - "Acquired {time}": "Acquise le {time}", + "Acquired {time}": "Acquisition : {time}", "Coverage {coverage}": "Couverture {coverage}", "START HERE · {reason}": "COMMENCER ICI · {reason}", "Daily overview shows little detail in a box this small": "La vue quotidienne montre peu de détails dans une zone aussi petite", @@ -2321,7 +2321,7 @@ "Voice model tier — applies next session": "Niveau du modèle vocal — à la prochaine session", "STD": "STD", "Estimated session cost": "Coût estimé de la session", - "Voice control — activate to toggle voice; hold Space to speak": "Commande vocale — activez pour basculer la voix ; maintenez Space pour parler", + "Voice control — activate to toggle voice; hold Space to speak": "Commande vocale — activer/désactiver ; maintenez Space pour parler", "ON/OFF": "ON/OFF", "VOICE STANDBY": "VOIX EN VEILLE", "VOICE CONTROL": "COMMANDE VOCALE", @@ -2475,9 +2475,9 @@ "other": "{n} bus" }, "{n} bike share": { - "one": "{n} station de vélos en libre-service", - "many": "{n} de stations de vélos en libre-service", - "other": "{n} stations de vélos en libre-service" + "one": "{n} station de vélos partagés", + "many": "{n} de stations de vélos partagés", + "other": "{n} stations de vélos partagés" }, "{n} flock cameras": { "one": "{n} caméra LAPI", @@ -2528,7 +2528,7 @@ "{label} error": "{label} : erreur", "counted the first {returned} loaded records": "{returned} premiers enregistrements chargés comptés", "counted {returned} of {total} loaded records": "{returned} enregistrements chargés comptés sur {total}", - "{layers} just turned on — still loading, counts will rise": "{layers} : vient d'être activé — chargement en cours, les décomptes vont augmenter", + "{layers} just turned on — still loading, counts will rise": "{layers} : activation récente — chargement en cours, les décomptes vont augmenter", "Contacts panel snapshot at {time} — the same retained cohort supplies the card, speech, and follow-up memory": "Instantané du panneau Contacts ({time}) — le même groupe retenu alimente la fiche, la voix et la mémoire de suivi", "Contacts panel snapshot — the same retained cohort supplies the card, speech, and follow-up memory": "Instantané du panneau Contacts — le même groupe retenu alimente la fiche, la voix et la mémoire de suivi", "Fly below {km} km to a covered region": "Descendez sous {km} km au-dessus d'une région couverte", @@ -2544,7 +2544,7 @@ "unavailable": "indisponible", "Not answered: {layers}": "Sans réponse : {layers}", "Toggle voice control": "Activer/désactiver la commande vocale", - "Activate to toggle voice": "Activez pour basculer la voix", + "Activate to toggle voice": "Activer/désactiver la commande vocale", "Voice active": "Voix active", "Voice could not be started.": "Impossible de démarrer la voix.", "the active voice session": "la session vocale active", @@ -2701,7 +2701,7 @@ "GROUND SPEED · KTS": "VITESSE SOL · ND", "ALTITUDE · FT": "ALTITUDE · FT", "FIRST PERSON": "PREMIÈRE PERSONNE", - "LIVE TRACK · COURSE ALIGNED": "PISTE EN DIRECT · CAP ALIGNÉ", + "LIVE TRACK · COURSE ALIGNED": "PISTE EN DIRECT · ALIGNÉ SUR LA ROUTE", "Cockpit vision style": "Style de vision du cockpit", "Previous cockpit vision style": "Style de vision précédent du cockpit", "Previous vision style": "Style de vision précédent", @@ -2757,7 +2757,7 @@ "RADIO": "RADIO", "Cockpit compact Radio controls": "Commandes Radio compactes du cockpit", "Previous filtered radio station": "Station radio filtrée précédente", - "Play selected radio station": "Lire la station radio sélectionnée", + "Play selected radio station": "Écouter la station radio sélectionnée", "Next filtered radio station": "Station radio filtrée suivante", "VOLUME": "VOLUME", "Cockpit Radio volume": "Volume de la Radio du cockpit", @@ -2810,7 +2810,7 @@ "SEARCH NEARBY SITES": "CHERCHER SITES PROCHES", "Reclassify tracked contact as TR-3B": "Reclasser le contact suivi en TR-3B", "CONTACTS CONTEXT OFF": "CONTEXTE CONTACTS DÉSACTIVÉ", - "SELECT CONTACTS TO LOAD OBSERVED / MAPPED PROXIMITY": "CHOISIR CONTACTS POUR CHARGER LA PROXIMITÉ OBSERVÉE / CARTOGRAPHIÉE", + "SELECT CONTACTS TO LOAD OBSERVED / MAPPED PROXIMITY": "CHOISIR CONTACTS POUR VOIR LES CONTACTS PROCHES OBSERVÉS / CARTOGRAPHIÉS", "Available Space Missions": "Missions spatiales disponibles", "AVAILABLE MISSIONS": "MISSIONS DISPONIBLES", "SELECT A MISSION TO INSPECT": "CHOISIR UNE MISSION À EXAMINER", @@ -2821,13 +2821,13 @@ "Filter stations by station tag": "Filtrer les stations par tag de station", "All": "Tous", "Enable Radio, then choose a globe marker or use next.": "Activez la Radio, puis choisissez un marqueur sur le globe ou utilisez SUIV.", - "DRAG TO TUNE": "GLISSER POUR SYNTONISER", + "DRAG TO TUNE": "GLISSER POUR RÉGLER", "Tune available internet radio stations": "Écouter les stations de radio Internet disponibles", - "ALL · DRAG THE NEEDLE": "TOUTES · GLISSEZ L'AIGUILLE", + "ALL · DRAG THE NEEDLE": "TOUTES · DÉPLACER L'AIGUILLE", "SNAPS TO AVAILABLE STATIONS": "SE CALE SUR LES STATIONS DISPONIBLES", "Radio playback": "Lecture radio", "Previous filtered station": "Station filtrée précédente", - "Play selected station": "Lire la station sélectionnée", + "Play selected station": "Écouter la station sélectionnée", "Next filtered station": "Station filtrée suivante", "STOP": "ARRÊT", "Stop radio playback": "Arrêter la lecture radio", @@ -2887,7 +2887,7 @@ "World-overlay fade distance outside the keyhole as a percentage of its radius": "Distance de fondu de la surcouche hors de l'ouverture, en pourcentage de son rayon", "Outside": "Extérieur", "Detection opacity outside the keyhole": "Opacité de la détection hors de l'ouverture", - "World-overlay label and card opacity beyond the fade distance": "Opacité des étiquettes et cartes de la surcouche au-delà de la distance de fondu", + "World-overlay label and card opacity beyond the fade distance": "Opacité des étiquettes et fiches de la surcouche au-delà de la distance de fondu", "PARAMETERS": "PARAMÈTRES", "3D aircraft — flat icons zoomed out, 3D models up close": "Aéronefs 3D — icônes plates de loin, modèles 3D de près", "Models": "Modèles", @@ -2931,7 +2931,7 @@ "Initializing photorealistic world...": "Initialisation du monde photoréaliste…", "DATA LAYERS": "COUCHES", "ACQUIRING FRAME": "ACQUISITION DE L'IMAGE", - "NEAREST": "PROCHE", + "NEAREST": "LA PLUS PROCHE", "CCTV camera": "Caméra CCTV", "CALIBRATION": "CALIBRATION", "Drag the camera in the world: rings rotate, arrows move, handles set range/FOV": "Faites glisser la caméra dans le monde : les anneaux pivotent, les flèches déplacent, les poignées règlent portée/FOV", @@ -2979,7 +2979,7 @@ "GROUND STATION · PROVIDER SETTINGS": "STATION AU SOL · PARAMÈTRES DES FOURNISSEURS", "Close key setup": "Fermer la configuration des clés", "Power up the globe": "Allumer le globe", - "The globe already flies keyless. Every key below switches on another real feed — paste one and it's saved into this app's local configuration, then the server restarts itself. Server-side keys stay on this machine; Google Maps and Cesium ion run in the browser and must be provider-restricted. Keys you configured elsewhere are shown but never touched.": "Le globe fonctionne déjà sans clé. Chaque clé ci-dessous active un flux réel de plus : collez-en une et elle est enregistrée dans la configuration locale de l'application, puis le serveur redémarre. Les clés côté serveur restent sur cette machine ; Google Maps et Cesium ion s'exécutent dans le navigateur et doivent être restreintes au fournisseur. Les clés configurées ailleurs sont affichées mais jamais modifiées.", + "The globe already flies keyless. Every key below switches on another real feed — paste one and it's saved into this app's local configuration, then the server restarts itself. Server-side keys stay on this machine; Google Maps and Cesium ion run in the browser and must be provider-restricted. Keys you configured elsewhere are shown but never touched.": "Le globe fonctionne déjà sans clé. Chaque clé ci-dessous active un flux réel de plus : collez-en une et elle est enregistrée dans la configuration locale de l'application, puis le serveur redémarre. Les clés côté serveur restent sur cette machine ; les clés Google Maps et Cesium ion sont utilisées dans le navigateur et doivent être restreintes chez leur fournisseur. Les clés configurées ailleurs sont affichées mais jamais modifiées.", "SAVE KEYS": "ENREGISTRER LES CLÉS", "ESC to close": "ESC pour fermer", "The Google Maps key buys the photorealistic planet — everything else stacks on top.": "La clé Google Maps offre la planète photoréaliste, tout le reste s'y superpose.", diff --git a/src/scenes/recipes.js b/src/scenes/recipes.js index ba3940a57..0bb1d9e94 100644 --- a/src/scenes/recipes.js +++ b/src/scenes/recipes.js @@ -1069,17 +1069,29 @@ export function displaySceneTitle(scene) { export function displayShotTitle(title, shot = undefined, scene = undefined) { const text = typeof title === 'string' ? title : ''; const fromRecipe = shot - ? Boolean(shot.sourcePackId) || recipeDeclaresShotTitle(scene?.id, text) + ? Boolean(shot.sourcePackId) || recipeDeclaresShotTitle(scene, text) : true; if (fromRecipe && BUILTIN_SHOT_TITLES.has(text)) return t(text); const numbered = /^Shot ([1-9]\d*)$/.exec(text); return numbered ? t('Shot {n}', { n: numbered[1] }) : text; } -/** Whether the built-in recipe behind a scene declares a shot with this title. */ -function recipeDeclaresShotTitle(sceneId, title) { - const recipe = sceneId ? getSceneRecipeById(sceneId) : null; - return Boolean( - recipe?.cameraPath?.some((keyframe) => keyframe?.title === title), +/** + * Whether the built-in recipe behind a scene, or a shot pack applied to it, + * declares a shot with this title. A pack's legacy bootstrap rebuilds the + * first shots of an upgraded scene without a sourcePackId, so the pack's + * required titles vouch for them. + */ +function recipeDeclaresShotTitle(scene, title) { + const recipe = scene?.id ? getSceneRecipeById(scene.id) : null; + if (recipe?.cameraPath?.some((keyframe) => keyframe?.title === title)) + return true; + const packs = Array.isArray(scene?.appliedShotPacks) + ? scene.appliedShotPacks + : []; + return packs.some((entry) => + Boolean( + getSceneAppendRecipeById(entry?.id)?.requiredShotTitles?.includes(title), + ), ); } diff --git a/src/scenes/sceneTitleDisplay.test.mjs b/src/scenes/sceneTitleDisplay.test.mjs index 604e01c49..fae4abbd6 100644 --- a/src/scenes/sceneTitleDisplay.test.mjs +++ b/src/scenes/sceneTitleDisplay.test.mjs @@ -4,6 +4,7 @@ import test from 'node:test'; import assert from 'node:assert/strict'; import { + SCENE_APPEND_RECIPES, SCENE_RECIPES, displaySceneTitle, displayShotTitle, @@ -87,6 +88,28 @@ test('a built-in shot title localizes only for a recipe-sourced shot', () => { } }); +test('a shot rebuilt by an applied pack bootstrap localizes its built-in title', () => { + const pack = SCENE_APPEND_RECIPES.find( + (recipe) => recipe.requiredShotTitles?.length, + ); + if (!pack) return; + setLocale('ru'); + const title = pack.requiredShotTitles[0]; + const shot = { id: 'shot-bootstrap', title }; + // The legacy bootstrap rebuilds these shots without a sourcePackId; the + // scene's applied-pack marker vouches for them. + assert.equal( + displayShotTitle(title, shot, { + id: 'scene-upgraded', + appliedShotPacks: [{ id: pack.id, version: pack.version }], + }), + displayShotTitle(title), + ); + assert.notEqual(displayShotTitle(title), title); + // The same title in a scene without that pack stays as typed. + assert.equal(displayShotTitle(title, shot, { id: 'scene-user' }), title); +}); + test('the Scene status line shows an operator-typed shot title as typed', async () => { const { createScenePlaybackAdapter } = await import('./playbackAdapter.js'); const statuses = [];