Fiabilise les médias et la navigation mobile

This commit is contained in:
Alexandre NOEL 2026-09-08 22:28:01 +02:00
commit 5909f55aba
12 changed files with 266 additions and 19 deletions

View file

@ -2,6 +2,15 @@
Toutes les modifications importantes du projet sont consignées ici. Le format s’inspire de [Keep a Changelog](https://keepachangelog.com/fr/1.1.0/) et le projet suit le [versionnage sémantique](https://semver.org/lang/fr/). Toutes les modifications importantes du projet sont consignées ici. Le format s’inspire de [Keep a Changelog](https://keepachangelog.com/fr/1.1.0/) et le projet suit le [versionnage sémantique](https://semver.org/lang/fr/).
## [1.0.0-rc.17] — 2026-09-08
### Interface mobile et médias publics
- remplacement du défilement horizontal du menu principal par un menu repliable sur téléphone et tablette ;
- maintien d’une navigation complète et accessible au clavier comme aux lecteurs d’écran ;
- correction des permissions des nouveaux dossiers de photos publiques afin qu’ils restent lisibles par le serveur web ;
- conservation des permissions strictes des photographies et documents privés.
## [1.0.0-rc.16] — 2026-09-08 ## [1.0.0-rc.16] — 2026-09-08
### Mise à jour autonome ### Mise à jour autonome

View file

@ -73,3 +73,7 @@ Intégration de la modification directement dans les lignes du catalogue pour re
## `1.0.0-rc.16` — Mise à jour autonome ## `1.0.0-rc.16` — Mise à jour autonome
Création d’un parcours de mise à jour commun à l’interface et à la ligne de commande, avec releases Forgejo, sauvegarde obligatoire, contrôles d’intégrité et retour arrière complet. Création d’un parcours de mise à jour commun à l’interface et à la ligne de commande, avec releases Forgejo, sauvegarde obligatoire, contrôles d’intégrité et retour arrière complet.
## `1.0.0-rc.17` — Utilisation mobile
Remplacement de la barre de navigation horizontale par un menu mobile repliable et fiabilisation des permissions appliquées aux nouvelles photos publiques.

View file

@ -200,7 +200,7 @@ L’option `--apply` ne doit être utilisée qu’après lecture des rapports. L
## État du projet ## État du projet
La candidate actuelle est `1.0.0-rc.3`. La branche 1.0 est gelée fonctionnellement : seuls les défauts bloquants découverts pendant les validations terrain sont corrigés avant `1.0.0`. La candidate actuelle est `1.0.0-rc.17`. La branche 1.0 est gelée fonctionnellement : seuls les défauts bloquants découverts pendant les validations terrain sont corrigés avant `1.0.0`.
Le contrôle de stabilisation est disponible dans [docs/AUDIT-1.0.md](docs/AUDIT-1.0.md). Le contrôle de stabilisation est disponible dans [docs/AUDIT-1.0.md](docs/AUDIT-1.0.md).

View file

@ -1 +1 @@
1.0.0-rc.16 1.0.0-rc.17

View file

@ -36,6 +36,7 @@ trait AnimalPhotoActions
$base = $base ?: 'photo'; $base = $base ?: 'photo';
$dir = __DIR__ . '/../../public/media/animals/' . $id; $dir = __DIR__ . '/../../public/media/animals/' . $id;
try { try {
PrivateMediaService::ensure($dir, true);
$stored = ImageService::storeUploaded($tmp, $dir, $base, 'photo'); $stored = ImageService::storeUploaded($tmp, $dir, $base, 'photo');
$filename = $stored['filename']; $filename = $stored['filename'];
$mime = $stored['mime']; $mime = $stored['mime'];

View file

@ -191,10 +191,7 @@ final class AnimalsController
private static function ensureAnimalMediaDir(int $animalId): void private static function ensureAnimalMediaDir(int $animalId): void
{ {
$dir = self::animalMediaDir($animalId); PrivateMediaService::ensure(self::animalMediaDir($animalId), true);
if (!is_dir($dir)) {
mkdir($dir, 0775, true);
}
} }
private static function safeFilename(string $name): string private static function safeFilename(string $name): string

View file

@ -28,6 +28,19 @@ final class PrivateMediaService
throw new RuntimeException(t('service.image.directory_failed')); throw new RuntimeException(t('service.image.directory_failed'));
} }
@chmod($dir, $mode); @chmod($dir, $mode);
if ($public) {
self::hardenPublicParents($dir);
}
}
private static function hardenPublicParents(string $dir): void
{
$mediaRoot = dirname(__DIR__, 2) . '/public/media';
foreach ([$mediaRoot, $mediaRoot . '/animals', $dir] as $publicDir) {
if (is_dir($publicDir)) {
@chmod($publicDir, 0755);
}
}
} }
public static function moveVisibility(int $animalId, string $filename, bool $toPublic): void public static function moveVisibility(int $animalId, string $filename, bool $toPublic): void

View file

@ -1,7 +1,7 @@
<?php <?php
/** Barre de navigation et recherche globale. */ /** Barre de navigation et recherche globale. */
?> ?>
<header class="navbar navbar-expand-md d-print-none"> <header class="navbar navbar-expand-lg d-print-none">
<div class="container-xl"> <div class="container-xl">
<div class="topbar-surface d-flex align-items-center justify-content-between w-100"> <div class="topbar-surface d-flex align-items-center justify-content-between w-100">
<a class="navbar-brand d-flex flex-row align-items-center py-0" <a class="navbar-brand d-flex flex-row align-items-center py-0"
@ -16,7 +16,15 @@
</span> </span>
</a> </a>
<div class="navbar-nav flex-row order-md-last"> <button class="navbar-toggler" type="button" data-bs-toggle="collapse"
data-bs-target="#globinoursMainNavigation" aria-controls="globinoursMainNavigation"
aria-expanded="false" aria-label="<?= h(t('nav.open_menu')) ?>">
<span class="navbar-toggler-icon"></span>
</button>
<nav class="collapse navbar-collapse topbar-menu" id="globinoursMainNavigation"
aria-label="<?= h(t('nav.main')) ?>">
<div class="navbar-nav flex-row order-md-last ms-md-auto">
<span class="search-trigger-wrap"><button class="search-trigger" type="button" data-bs-toggle="modal" <span class="search-trigger-wrap"><button class="search-trigger" type="button" data-bs-toggle="modal"
data-bs-target="#globalSearchModal" data-bs-target="#globalSearchModal"
title="<?= h(t('search.open')) ?> — <?= h(t('search.shortcut')) ?>" title="<?= h(t('search.open')) ?> — <?= h(t('search.shortcut')) ?>"
@ -124,7 +132,8 @@
</div> </div>
</div> </div>
</div> </div>
</nav>
</div> </div>
</div> </div>
</header> </header>

View file

@ -31,9 +31,10 @@
<div class="col-lg-6"> <div class="col-lg-6">
<section class="card h-100"> <section class="card h-100">
<div class="card-body"> <div class="card-body">
<span class="badge bg-blue-lt mb-2">1.0.0-rc.16</span> <span class="badge bg-blue-lt mb-2">1.0.0-rc.17</span>
<h3>Mises à jour autonomes</h3> <h3>Utilisation mobile</h3>
<p class="text-muted">Installation vérifiée depuis Forgejo, sauvegarde obligatoire et retour automatique du code et des données en cas d’échec.</p> <p class="text-muted">Navigation repliable sur les petits écrans et publication fiable des nouvelles
photographies, y compris depuis l’application Android.</p>
</div> </div>
</section> </section>
</div> </div>

View file

@ -1,5 +1,218 @@
html{scroll-padding-top:1rem}img{max-width:100%}.table-responsive{overscroll-behavior-inline:contain}.nav-tabs{flex-wrap:nowrap;overflow-x:auto;overflow-y:hidden;-webkit-overflow-scrolling:touch;scrollbar-width:thin}.nav-tabs .nav-link{white-space:nowrap}.modal-dialog-scrollable{max-height:calc(100dvh - 1rem)} html {
@media(max-width:991.98px){.page>.navbar{padding:.5rem}.topbar-surface{height:auto;min-height:72px;padding:.55rem;gap:.5rem}.topbar-surface>.navbar-brand{flex:0 0 auto}.topbar-surface>.navbar-nav{min-width:0;overflow-x:auto;overscroll-behavior-inline:contain;padding-bottom:.2rem}.topbar-surface>.navbar-nav>.btn,.topbar-surface>.navbar-nav>.dropdown{flex:0 0 auto}.page-header{padding-top:.5rem}.page-body{margin-top:.75rem}.settings-section-nav{flex-wrap:nowrap!important;overflow-x:auto;padding-bottom:.35rem}.footer-surface{align-items:flex-start!important}.page-title{font-size:1.15rem}} scroll-padding-top: 1rem;
@media(max-width:767.98px){body,.page{background-attachment:scroll}.container-xl{padding-left:.75rem;padding-right:.75rem}.topbar-surface .navbar-brand img{width:42px;height:44px}.topbar-surface .navbar-brand span span:last-child{display:none}.topbar-surface>.navbar-nav{gap:.25rem}.topbar-surface>.navbar-nav>.btn{min-width:44px;height:46px;padding:.45rem;white-space:nowrap;margin-right:.25rem!important}.settings-trigger{width:46px;height:46px;margin-right:0!important}.settings-dropdown .dropdown-menu{position:fixed!important;inset:auto .75rem .75rem .75rem!important;width:auto!important;max-height:75dvh;overflow-y:auto;transform:none!important}.settings-menu-columns{grid-template-columns:1fr}.settings-menu-column{padding:.15rem .35rem}.settings-menu-column+.settings-menu-column{border-left:0;border-top:1px solid rgba(98,105,118,.16);margin-top:.65rem;padding-top:.65rem;padding-left:.35rem}.card-header,.card-body,.card-footer{padding-left:.85rem;padding-right:.85rem}.btn{min-height:44px}.btn-sm{min-height:40px}input.form-control,select.form-select,textarea.form-control{font-size:16px}.modal-dialog{margin:.5rem}.modal-content{max-height:calc(100dvh - 1rem)}.modal-body{overflow-y:auto}.footer-meta{gap:.65rem!important}.paw-footer{height:80px;flex-basis:80px}} }
@media(max-width:420px){.topbar-surface .navbar-brand span{display:none}.topbar-surface .navbar-brand img{margin-right:0!important}.footer-surface{font-size:.9rem}}
@media(display-mode:standalone){body{padding-top:env(safe-area-inset-top);padding-bottom:env(safe-area-inset-bottom)}.page>.navbar{position:sticky;top:env(safe-area-inset-top);background:#f7f3eb;z-index:1050}} img {
max-width: 100%;
}
.table-responsive {
overscroll-behavior-inline: contain;
}
.nav-tabs {
flex-wrap: nowrap;
overflow-x: auto;
overflow-y: hidden;
-webkit-overflow-scrolling: touch;
scrollbar-width: thin;
}
.nav-tabs .nav-link {
white-space: nowrap;
}
.modal-dialog-scrollable {
max-height: calc(100dvh - 1rem);
}
@media (max-width: 991.98px) {
.page > .navbar {
padding: .5rem;
}
.topbar-surface {
flex-wrap: wrap;
height: auto;
min-height: 72px;
padding: .55rem;
gap: .5rem;
}
.topbar-surface > .navbar-brand {
flex: 0 0 auto;
}
.topbar-menu {
flex-basis: 100%;
width: 100%;
}
.topbar-menu > .navbar-nav {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
width: 100%;
gap: .5rem;
padding-top: .5rem;
}
.topbar-menu .btn {
justify-content: flex-start;
width: 100%;
margin-right: 0 !important;
white-space: normal;
}
.topbar-menu .search-trigger-wrap,
.topbar-menu .settings-dropdown {
min-width: 0;
}
.topbar-menu .search-trigger,
.topbar-menu .settings-trigger {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
min-height: 44px;
margin: 0 !important;
}
.page-header {
padding-top: .5rem;
}
.page-body {
margin-top: .75rem;
}
.settings-section-nav {
flex-wrap: nowrap !important;
overflow-x: auto;
padding-bottom: .35rem;
}
.footer-surface {
align-items: flex-start !important;
}
.page-title {
font-size: 1.15rem;
}
}
@media (max-width: 767.98px) {
body,
.page {
background-attachment: scroll;
}
.container-xl {
padding-left: .75rem;
padding-right: .75rem;
}
.topbar-surface .navbar-brand img {
width: 42px;
height: 44px;
}
.topbar-surface .navbar-brand span span:last-child {
display: none;
}
.settings-dropdown .dropdown-menu {
position: fixed !important;
inset: auto .75rem .75rem .75rem !important;
width: auto !important;
max-height: 75dvh;
overflow-y: auto;
transform: none !important;
}
.settings-menu-columns {
grid-template-columns: 1fr;
}
.settings-menu-column {
padding: .15rem .35rem;
}
.settings-menu-column + .settings-menu-column {
margin-top: .65rem;
padding-top: .65rem;
padding-left: .35rem;
border-top: 1px solid rgba(98, 105, 118, .16);
border-left: 0;
}
.card-header,
.card-body,
.card-footer {
padding-left: .85rem;
padding-right: .85rem;
}
.btn {
min-height: 44px;
}
.btn-sm {
min-height: 40px;
}
input.form-control,
select.form-select,
textarea.form-control {
font-size: 16px;
}
.modal-dialog {
margin: .5rem;
}
.modal-content {
max-height: calc(100dvh - 1rem);
}
.modal-body {
overflow-y: auto;
}
.footer-meta {
gap: .65rem !important;
}
.paw-footer {
height: 80px;
flex-basis: 80px;
}
}
@media (max-width: 420px) {
.topbar-surface .navbar-brand span {
display: none;
}
.topbar-surface .navbar-brand img {
margin-right: 0 !important;
}
.footer-surface {
font-size: .9rem;
}
}
@media (display-mode: standalone) {
body {
padding-top: env(safe-area-inset-top);
padding-bottom: env(safe-area-inset-bottom);
}
.page > .navbar {
position: sticky;
top: env(safe-area-inset-top);
z-index: 1050;
background: #f7f3eb;
}
}

View file

@ -12,7 +12,7 @@ return [
'settings.public_site'=>'Public website','common.title'=>'Title','common.text'=>'Text','public_site.unavailable'=>'Website unavailable','public_site.unavailable_help'=>'The public website is not available yet.','public_site.not_found'=>'Page not found','public_site.animals'=>'Our animals','public_site.contact'=>'Contact','public_site.home'=>'Home','public_site.donation'=>'Donations','public_site.navigation'=>'Main navigation','public_site.powered_by'=>'Powered by','public_site.rna'=>'RNA registration','public_site.siret'=>'SIRET','public_site.map_title'=>'Find us','public_site.map_marker'=>'Association address','public_site.discover'=>'Meet the animals','public_site.latest_animals'=>'Looking for a family','public_site.no_animals'=>'No animal is currently published in this category.','public_site.all_species'=>'All species','public_site.sterilized'=>'Sterilised','public_site.age_unknown'=>'Age not provided','public_site.months'=>':count months','public_site.years'=>':count year(s)','public_site.years_months'=>':years year(s) and :months months','public_site.back_animals'=>'Back to animals','public_site.age'=>'Age','public_site.breed'=>'Breed','public_site.coat'=>'Coat','public_site.dogs'=>'Dog-friendly','public_site.cats'=>'Cat-friendly','public_site.children'=>'Child-friendly','public_site.clean'=>'House-trained','public_site.ask_adoption'=>'Contact us about adoption','public_site.make_donation'=>'Make a donation', 'settings.public_site'=>'Public website','common.title'=>'Title','common.text'=>'Text','public_site.unavailable'=>'Website unavailable','public_site.unavailable_help'=>'The public website is not available yet.','public_site.not_found'=>'Page not found','public_site.animals'=>'Our animals','public_site.contact'=>'Contact','public_site.home'=>'Home','public_site.donation'=>'Donations','public_site.navigation'=>'Main navigation','public_site.powered_by'=>'Powered by','public_site.rna'=>'RNA registration','public_site.siret'=>'SIRET','public_site.map_title'=>'Find us','public_site.map_marker'=>'Association address','public_site.discover'=>'Meet the animals','public_site.latest_animals'=>'Looking for a family','public_site.no_animals'=>'No animal is currently published in this category.','public_site.all_species'=>'All species','public_site.sterilized'=>'Sterilised','public_site.age_unknown'=>'Age not provided','public_site.months'=>':count months','public_site.years'=>':count year(s)','public_site.years_months'=>':years year(s) and :months months','public_site.back_animals'=>'Back to animals','public_site.age'=>'Age','public_site.breed'=>'Breed','public_site.coat'=>'Coat','public_site.dogs'=>'Dog-friendly','public_site.cats'=>'Cat-friendly','public_site.children'=>'Child-friendly','public_site.clean'=>'House-trained','public_site.ask_adoption'=>'Contact us about adoption','public_site.make_donation'=>'Make a donation',
'public_site.settings_title'=>'Settings — Public website','public_site.settings_heading'=>'Public mini-site','public_site.settings_intro'=>'A lightweight website powered only by information deliberately made public.','public_site.preview'=>'View website','public_site.saved'=>'Public website settings were saved.','public_site.activation_style'=>'Activation and appearance','public_site.enable'=>'Enable public website','public_site.enable_help'=>'When disabled, no public page or animal profile can be viewed.','public_site.theme'=>'Visual theme','public_site.theme_warm'=>'Warm','public_site.theme_nature'=>'Natural','public_site.theme_minimal'=>'Minimal','public_site.free_page'=>'Free-form page','public_site.show_free_page'=>'Show this page in the menu','public_site.donation_url'=>'Donation platform link','public_site.contact_reuse'=>'Address, phone number, email, and logo come from the association’s general settings.','public_site.invalid_url'=>'One of the entered links is invalid.','public_site.publish_animal'=>'Publish this animal on the mini-site','public_site.publish_animal_help'=>'The profile will only be visible when the animal is also available for adoption. Only public photos and the text below are used.','public_site.public_description'=>'Public description','public_site.public_description_example'=>'Personality, habits, and the kind of family they need…', 'public_site.settings_title'=>'Settings — Public website','public_site.settings_heading'=>'Public mini-site','public_site.settings_intro'=>'A lightweight website powered only by information deliberately made public.','public_site.preview'=>'View website','public_site.saved'=>'Public website settings were saved.','public_site.activation_style'=>'Activation and appearance','public_site.enable'=>'Enable public website','public_site.enable_help'=>'When disabled, no public page or animal profile can be viewed.','public_site.theme'=>'Visual theme','public_site.theme_warm'=>'Warm','public_site.theme_nature'=>'Natural','public_site.theme_minimal'=>'Minimal','public_site.free_page'=>'Free-form page','public_site.show_free_page'=>'Show this page in the menu','public_site.donation_url'=>'Donation platform link','public_site.contact_reuse'=>'Address, phone number, email, and logo come from the association’s general settings.','public_site.invalid_url'=>'One of the entered links is invalid.','public_site.publish_animal'=>'Publish this animal on the mini-site','public_site.publish_animal_help'=>'The profile will only be visible when the animal is also available for adoption. Only public photos and the text below are used.','public_site.public_description'=>'Public description','public_site.public_description_example'=>'Personality, habits, and the kind of family they need…',
'common.open'=>'Open','settings.lab_references'=>'Lab references','medical_document.menu_prescription'=>'Prescription','medical_document.menu_analysis'=>'Lab results','lab_reference.title'=>'Settings — Lab references','lab_reference.heading'=>'Lab reference ranges','lab_reference.intro'=>'Ranges by analyser and species used to prefill lab reports and interpret their charts.','lab_reference.saved'=>'Lab reference ranges were saved.','lab_reference.priority_title'=>'Local reference takes priority.','lab_reference.priority_help'=>'If your analyser or clinic prints different ranges, enter them here: they take precedence over manufacturer values. An empty bound does not trigger colour interpretation.','lab_reference.manufacturer_ranges'=>'Initial manufacturer ranges; all values remain editable.','lab_reference.code'=>'Code','lab_reference.source'=>'Source / note','lab_reference.invalid'=>'Invalid reference data: check labels, units, and bounds.', 'common.open'=>'Open','settings.lab_references'=>'Lab references','medical_document.menu_prescription'=>'Prescription','medical_document.menu_analysis'=>'Lab results','lab_reference.title'=>'Settings — Lab references','lab_reference.heading'=>'Lab reference ranges','lab_reference.intro'=>'Ranges by analyser and species used to prefill lab reports and interpret their charts.','lab_reference.saved'=>'Lab reference ranges were saved.','lab_reference.priority_title'=>'Local reference takes priority.','lab_reference.priority_help'=>'If your analyser or clinic prints different ranges, enter them here: they take precedence over manufacturer values. An empty bound does not trigger colour interpretation.','lab_reference.manufacturer_ranges'=>'Initial manufacturer ranges; all values remain editable.','lab_reference.code'=>'Code','lab_reference.source'=>'Source / note','lab_reference.invalid'=>'Invalid reference data: check labels, units, and bounds.',
'nav.dashboard'=>'Dashboard','nav.animals'=>'Animals','nav.directory'=>'Directory','nav.care_round'=>'Care rounds','nav.statistics'=>'Statistics','nav.administrative'=>'Administration','nav.grants'=>'Grants', 'nav.main'=>'Main navigation','nav.open_menu'=>'Open menu','nav.dashboard'=>'Dashboard','nav.animals'=>'Animals','nav.directory'=>'Directory','nav.care_round'=>'Care rounds','nav.statistics'=>'Statistics','nav.administrative'=>'Administration','nav.grants'=>'Grants',
'account.settings'=>'Account and settings','account.settings_heading'=>'Settings','account.logout'=>'Log out', 'account.settings'=>'Account and settings','account.settings_heading'=>'Settings','account.logout'=>'Log out',
'settings.general'=>'General','settings.users'=>'Users','settings.permissions'=>'Roles and permissions','settings.species'=>'Species','settings.pricing'=>'Pricing','settings.backups'=>'Backups','settings.audit'=>'Audit log','settings.asm3'=>'ASM3 migration','settings.data'=>'Data and demo', 'settings.general'=>'General','settings.users'=>'Users','settings.permissions'=>'Roles and permissions','settings.species'=>'Species','settings.pricing'=>'Pricing','settings.backups'=>'Backups','settings.audit'=>'Audit log','settings.asm3'=>'ASM3 migration','settings.data'=>'Data and demo',
'settings.general_title'=>'General settings','settings.general_intro'=>'Association identity and shared Globinours rules.','settings.language'=>'Application language','settings.language_help'=>'Default interface language. French is used as a fallback whenever a translation is missing.','settings.save'=>'Save settings', 'settings.general_title'=>'General settings','settings.general_intro'=>'Association identity and shared Globinours rules.','settings.language'=>'Application language','settings.language_help'=>'Default interface language. French is used as a fallback whenever a translation is missing.','settings.save'=>'Save settings',

View file

@ -12,7 +12,7 @@ return [
'settings.public_site'=>'Site public','common.title'=>'Titre','common.text'=>'Texte','public_site.unavailable'=>'Site indisponible','public_site.unavailable_help'=>'Le site public n’est pas encore disponible.','public_site.not_found'=>'Page introuvable','public_site.animals'=>'Nos animaux','public_site.contact'=>'Contact','public_site.home'=>'Accueil','public_site.donation'=>'Dons','public_site.navigation'=>'Navigation principale','public_site.powered_by'=>'Propulsé par','public_site.rna'=>'RNA','public_site.siret'=>'SIRET','public_site.map_title'=>'Nous trouver','public_site.map_marker'=>'Adresse de l’association','public_site.discover'=>'Découvrir les animaux','public_site.latest_animals'=>'Ils cherchent une famille','public_site.no_animals'=>'Aucun animal n’est actuellement publié dans cette catégorie.','public_site.all_species'=>'Toutes les espèces','public_site.sterilized'=>'Stérilisé','public_site.age_unknown'=>'Âge non renseigné','public_site.months'=>':count mois','public_site.years'=>':count an(s)','public_site.years_months'=>':years an(s) et :months mois','public_site.back_animals'=>'Retour aux animaux','public_site.age'=>'Âge','public_site.breed'=>'Race','public_site.coat'=>'Robe','public_site.dogs'=>'OK chiens','public_site.cats'=>'OK chats','public_site.children'=>'OK enfants','public_site.clean'=>'Propre','public_site.ask_adoption'=>'Nous contacter pour son adoption','public_site.make_donation'=>'Faire un don', 'settings.public_site'=>'Site public','common.title'=>'Titre','common.text'=>'Texte','public_site.unavailable'=>'Site indisponible','public_site.unavailable_help'=>'Le site public n’est pas encore disponible.','public_site.not_found'=>'Page introuvable','public_site.animals'=>'Nos animaux','public_site.contact'=>'Contact','public_site.home'=>'Accueil','public_site.donation'=>'Dons','public_site.navigation'=>'Navigation principale','public_site.powered_by'=>'Propulsé par','public_site.rna'=>'RNA','public_site.siret'=>'SIRET','public_site.map_title'=>'Nous trouver','public_site.map_marker'=>'Adresse de l’association','public_site.discover'=>'Découvrir les animaux','public_site.latest_animals'=>'Ils cherchent une famille','public_site.no_animals'=>'Aucun animal n’est actuellement publié dans cette catégorie.','public_site.all_species'=>'Toutes les espèces','public_site.sterilized'=>'Stérilisé','public_site.age_unknown'=>'Âge non renseigné','public_site.months'=>':count mois','public_site.years'=>':count an(s)','public_site.years_months'=>':years an(s) et :months mois','public_site.back_animals'=>'Retour aux animaux','public_site.age'=>'Âge','public_site.breed'=>'Race','public_site.coat'=>'Robe','public_site.dogs'=>'OK chiens','public_site.cats'=>'OK chats','public_site.children'=>'OK enfants','public_site.clean'=>'Propre','public_site.ask_adoption'=>'Nous contacter pour son adoption','public_site.make_donation'=>'Faire un don',
'public_site.settings_title'=>'Paramètres — Site public','public_site.settings_heading'=>'Mini-site public','public_site.settings_intro'=>'Un site léger alimenté uniquement par les informations volontairement publiées.','public_site.preview'=>'Voir le site','public_site.saved'=>'La configuration du site public a été enregistrée.','public_site.activation_style'=>'Activation et apparence','public_site.enable'=>'Activer le site public','public_site.enable_help'=>'Lorsqu’il est désactivé, aucune page publique ni fiche animale n’est consultable.','public_site.theme'=>'Thème visuel','public_site.theme_warm'=>'Chaleureux','public_site.theme_nature'=>'Naturel','public_site.theme_minimal'=>'Minimal','public_site.free_page'=>'Page libre','public_site.show_free_page'=>'Afficher cette page dans le menu','public_site.donation_url'=>'Lien vers la plateforme de don','public_site.contact_reuse'=>'L’adresse, le téléphone, l’e-mail et le logo viennent des paramètres généraux de l’association.','public_site.invalid_url'=>'Un des liens saisis est invalide.','public_site.publish_animal'=>'Publier cet animal sur le mini-site','public_site.publish_animal_help'=>'La fiche ne sera visible que si l’animal est également disponible à l’adoption. Seules les photos publiques et le texte ci-dessous seront utilisés.','public_site.public_description'=>'Présentation publique','public_site.public_description_example'=>'Son caractère, ses habitudes et la famille qu’il recherche…', 'public_site.settings_title'=>'Paramètres — Site public','public_site.settings_heading'=>'Mini-site public','public_site.settings_intro'=>'Un site léger alimenté uniquement par les informations volontairement publiées.','public_site.preview'=>'Voir le site','public_site.saved'=>'La configuration du site public a été enregistrée.','public_site.activation_style'=>'Activation et apparence','public_site.enable'=>'Activer le site public','public_site.enable_help'=>'Lorsqu’il est désactivé, aucune page publique ni fiche animale n’est consultable.','public_site.theme'=>'Thème visuel','public_site.theme_warm'=>'Chaleureux','public_site.theme_nature'=>'Naturel','public_site.theme_minimal'=>'Minimal','public_site.free_page'=>'Page libre','public_site.show_free_page'=>'Afficher cette page dans le menu','public_site.donation_url'=>'Lien vers la plateforme de don','public_site.contact_reuse'=>'L’adresse, le téléphone, l’e-mail et le logo viennent des paramètres généraux de l’association.','public_site.invalid_url'=>'Un des liens saisis est invalide.','public_site.publish_animal'=>'Publier cet animal sur le mini-site','public_site.publish_animal_help'=>'La fiche ne sera visible que si l’animal est également disponible à l’adoption. Seules les photos publiques et le texte ci-dessous seront utilisés.','public_site.public_description'=>'Présentation publique','public_site.public_description_example'=>'Son caractère, ses habitudes et la famille qu’il recherche…',
'common.open'=>'Ouvrir','settings.lab_references'=>'Références biologiques','medical_document.menu_prescription'=>'Ordonnance','medical_document.menu_analysis'=>'Résultats d’analyses','lab_reference.title'=>'Paramètres — Références biologiques','lab_reference.heading'=>'Références biologiques','lab_reference.intro'=>'Intervalles par analyseur et par espèce utilisés pour préremplir les analyses et interpréter leurs courbes.','lab_reference.saved'=>'Les références biologiques ont été enregistrées.','lab_reference.priority_title'=>'Référence locale prioritaire.','lab_reference.priority_help'=>'Si votre analyseur ou votre clinique imprime des bornes différentes, reportez-les ici : elles priment sur les valeurs constructeur. Une borne vide ne déclenche aucune interprétation colorée.','lab_reference.manufacturer_ranges'=>'Valeurs initiales issues des intervalles constructeur ; elles restent modifiables.','lab_reference.code'=>'Code','lab_reference.source'=>'Source / remarque','lab_reference.invalid'=>'Référentiel invalide : vérifiez les libellés, unités et bornes.', 'common.open'=>'Ouvrir','settings.lab_references'=>'Références biologiques','medical_document.menu_prescription'=>'Ordonnance','medical_document.menu_analysis'=>'Résultats d’analyses','lab_reference.title'=>'Paramètres — Références biologiques','lab_reference.heading'=>'Références biologiques','lab_reference.intro'=>'Intervalles par analyseur et par espèce utilisés pour préremplir les analyses et interpréter leurs courbes.','lab_reference.saved'=>'Les références biologiques ont été enregistrées.','lab_reference.priority_title'=>'Référence locale prioritaire.','lab_reference.priority_help'=>'Si votre analyseur ou votre clinique imprime des bornes différentes, reportez-les ici : elles priment sur les valeurs constructeur. Une borne vide ne déclenche aucune interprétation colorée.','lab_reference.manufacturer_ranges'=>'Valeurs initiales issues des intervalles constructeur ; elles restent modifiables.','lab_reference.code'=>'Code','lab_reference.source'=>'Source / remarque','lab_reference.invalid'=>'Référentiel invalide : vérifiez les libellés, unités et bornes.',
'nav.dashboard'=>'Tableau de bord','nav.animals'=>'Animaux','nav.directory'=>'Annuaire','nav.care_round'=>'Tournée','nav.statistics'=>'Statistiques','nav.administrative'=>'Administratif','nav.grants'=>'Subventions', 'nav.main'=>'Navigation principale','nav.open_menu'=>'Ouvrir le menu','nav.dashboard'=>'Tableau de bord','nav.animals'=>'Animaux','nav.directory'=>'Annuaire','nav.care_round'=>'Tournée','nav.statistics'=>'Statistiques','nav.administrative'=>'Administratif','nav.grants'=>'Subventions',
'account.settings'=>'Compte et paramètres','account.settings_heading'=>'Paramètres','account.logout'=>'Déconnexion', 'account.settings'=>'Compte et paramètres','account.settings_heading'=>'Paramètres','account.logout'=>'Déconnexion',
'settings.general'=>'Général','settings.users'=>'Utilisateurs','settings.permissions'=>'Rôles et permissions','settings.species'=>'Espèces','settings.pricing'=>'Tarifs','settings.backups'=>'Sauvegardes','settings.audit'=>'Journal d’audit','settings.asm3'=>'Migration ASM3','settings.data'=>'Données et démo', 'settings.general'=>'Général','settings.users'=>'Utilisateurs','settings.permissions'=>'Rôles et permissions','settings.species'=>'Espèces','settings.pricing'=>'Tarifs','settings.backups'=>'Sauvegardes','settings.audit'=>'Journal d’audit','settings.asm3'=>'Migration ASM3','settings.data'=>'Données et démo',
'settings.general_title'=>'Paramètres généraux','settings.general_intro'=>'Identité de l’association et règles communes de Globinours.','settings.language'=>'Langue de l’application','settings.language_help'=>'Langue générale de l’interface. Le français reste utilisé lorsqu’une traduction manque.','settings.save'=>'Enregistrer les paramètres', 'settings.general_title'=>'Paramètres généraux','settings.general_intro'=>'Identité de l’association et règles communes de Globinours.','settings.language'=>'Langue de l’application','settings.language_help'=>'Langue générale de l’interface. Le français reste utilisé lorsqu’une traduction manque.','settings.save'=>'Enregistrer les paramètres',