feat: enrichir l'édition du site public
This commit is contained in:
parent
6e3beea211
commit
1df26401a3
17 changed files with 115 additions and 15 deletions
|
|
@ -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.5] — 2026-09-07
|
||||||
|
|
||||||
|
### Site public et navigation
|
||||||
|
|
||||||
|
- ajout d’une bibliothèque d’images intégrée à l’éditeur du site public ;
|
||||||
|
- retrait des réseaux Mastodon et Bluesky des réglages et de la page de contact ;
|
||||||
|
- correction des filtres statistiques afin que « Toutes les fiches » inclue aussi les animaux archivés ;
|
||||||
|
- remplacement des derniers libellés « Dashboard » visibles en français par « Tableau de bord ».
|
||||||
|
|
||||||
## [1.0.0-rc.4] — 2026-09-07
|
## [1.0.0-rc.4] — 2026-09-07
|
||||||
|
|
||||||
### Correctifs et site public
|
### Correctifs et site public
|
||||||
|
|
|
||||||
|
|
@ -25,3 +25,7 @@ Séparation des ressources visuelles et des routes, correction de l’installati
|
||||||
## `1.0.0-rc.4` — Retours de terrain
|
## `1.0.0-rc.4` — Retours de terrain
|
||||||
|
|
||||||
Correction des déplacements et des médias publics, amélioration du site public, ajout de filtres depuis les statistiques et extension des références biologiques à toutes les espèces configurées.
|
Correction des déplacements et des médias publics, amélioration du site public, ajout de filtres depuis les statistiques et extension des références biologiques à toutes les espèces configurées.
|
||||||
|
|
||||||
|
## `1.0.0-rc.5` — Édition du site public
|
||||||
|
|
||||||
|
Ajout d’une bibliothèque d’images pour les contenus publics, simplification des réseaux sociaux et correction de la navigation depuis les statistiques vers les fiches archivées.
|
||||||
|
|
|
||||||
2
VERSION
2
VERSION
|
|
@ -1 +1 @@
|
||||||
1.0.0-rc.4
|
1.0.0-rc.5
|
||||||
|
|
|
||||||
|
|
@ -47,7 +47,10 @@ trait AnimalBrowseActions
|
||||||
];
|
];
|
||||||
$orderBy = $allowed[$sort] ?? 'a.updated_at';
|
$orderBy = $allowed[$sort] ?? 'a.updated_at';
|
||||||
|
|
||||||
$where = ['a.deleted_at IS NULL', 'a.archived_at IS NULL'];
|
$where = ['a.deleted_at IS NULL'];
|
||||||
|
if ($status !== '') {
|
||||||
|
$where[] = 'a.archived_at IS NULL';
|
||||||
|
}
|
||||||
$params = [];
|
$params = [];
|
||||||
if (in_array($availability, ['available', 'not_available', 'unknown'], true)) {
|
if (in_array($availability, ['available', 'not_available', 'unknown'], true)) {
|
||||||
$where[] = 'a.adoption_availability=:availability';
|
$where[] = 'a.adoption_availability=:availability';
|
||||||
|
|
|
||||||
|
|
@ -274,7 +274,7 @@ final class DashboardController
|
||||||
$agendaDue = PermissionService::can('agenda') ? AgendaService::due() : [];
|
$agendaDue = PermissionService::can('agenda') ? AgendaService::due() : [];
|
||||||
|
|
||||||
render('dashboard.php', [
|
render('dashboard.php', [
|
||||||
'title' => 'Dashboard',
|
'title' => t('nav.dashboard'),
|
||||||
'stats' => $stats,
|
'stats' => $stats,
|
||||||
'shelterRoomCounts' => $shelterRoomCounts,
|
'shelterRoomCounts' => $shelterRoomCounts,
|
||||||
'quality' => $quality,
|
'quality' => $quality,
|
||||||
|
|
|
||||||
|
|
@ -666,13 +666,41 @@ final class SettingsController
|
||||||
public static function publicSite(): void
|
public static function publicSite(): void
|
||||||
{
|
{
|
||||||
self::admin();
|
self::admin();
|
||||||
|
$images = [];
|
||||||
|
$mediaDirectory = dirname(__DIR__, 2) . '/public/media/site';
|
||||||
|
foreach (glob($mediaDirectory . '/*.{jpg,jpeg,png,webp,gif}', GLOB_BRACE) ?: [] as $path) {
|
||||||
|
$filename = basename($path);
|
||||||
|
$images[] = ['filename' => $filename, 'url' => '/media/site/' . rawurlencode($filename)];
|
||||||
|
}
|
||||||
|
usort($images, static fn (array $left, array $right): int => strcmp($right['filename'], $left['filename']));
|
||||||
render('settings_public_site.php', [
|
render('settings_public_site.php', [
|
||||||
'title' => t('public_site.settings_title'),
|
'title' => t('public_site.settings_title'),
|
||||||
'settings' => AppSettings::all(),
|
'settings' => AppSettings::all(),
|
||||||
'saved' => isset($_GET['saved']),
|
'saved' => isset($_GET['saved']),
|
||||||
'error' => (string) ($_GET['error'] ?? ''),
|
'error' => (string) ($_GET['error'] ?? ''),
|
||||||
|
'images' => $images,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static function uploadPublicSiteImage(): void
|
||||||
|
{
|
||||||
|
self::admin();
|
||||||
|
self::postOnly();
|
||||||
|
try {
|
||||||
|
$upload = $_FILES['public_site_image'] ?? null;
|
||||||
|
if (!is_array($upload) || (int) ($upload['error'] ?? UPLOAD_ERR_NO_FILE) !== UPLOAD_ERR_OK) {
|
||||||
|
throw new RuntimeException(t('public_site.image_invalid'));
|
||||||
|
}
|
||||||
|
$directory = dirname(__DIR__, 2) . '/public/media/site';
|
||||||
|
$filename = ImageService::storeUploaded((string) $upload['tmp_name'], $directory, 'site', 'photo');
|
||||||
|
@chmod($directory, 0755);
|
||||||
|
AuditService::log('public_site_image_uploaded', '/settings/public-site/image', 'Image du site public ajoutée');
|
||||||
|
header('Location: /settings/public-site?image=' . rawurlencode($filename));
|
||||||
|
} catch (Throwable $e) {
|
||||||
|
header('Location: /settings/public-site?error=' . rawurlencode(SecurityService::publicError($e)));
|
||||||
|
}
|
||||||
|
exit();
|
||||||
|
}
|
||||||
public static function health(): void
|
public static function health(): void
|
||||||
{
|
{
|
||||||
self::admin();
|
self::admin();
|
||||||
|
|
@ -770,7 +798,7 @@ final class SettingsController
|
||||||
exit();
|
exit();
|
||||||
}
|
}
|
||||||
$values['public_site_custom_css'] = $customCss;
|
$values['public_site_custom_css'] = $customCss;
|
||||||
foreach (['public_site_donation_url', 'public_site_facebook_url', 'public_site_instagram_url', 'public_site_mastodon_url', 'public_site_youtube_url', 'public_site_tiktok_url', 'public_site_bluesky_url'] as $key) {
|
foreach (['public_site_donation_url', 'public_site_facebook_url', 'public_site_instagram_url', 'public_site_youtube_url', 'public_site_tiktok_url'] as $key) {
|
||||||
$url = trim((string) ($_POST[$key] ?? ''));
|
$url = trim((string) ($_POST[$key] ?? ''));
|
||||||
$scheme = strtolower((string) parse_url($url, PHP_URL_SCHEME));
|
$scheme = strtolower((string) parse_url($url, PHP_URL_SCHEME));
|
||||||
if ($url !== '' && (!filter_var($url, FILTER_VALIDATE_URL) || $scheme !== 'https')) {
|
if ($url !== '' && (!filter_var($url, FILTER_VALIDATE_URL) || $scheme !== 'https')) {
|
||||||
|
|
|
||||||
|
|
@ -350,6 +350,9 @@ switch ($path) {
|
||||||
case '/settings/public-site/save':
|
case '/settings/public-site/save':
|
||||||
SettingsController::savePublicSite();
|
SettingsController::savePublicSite();
|
||||||
break;
|
break;
|
||||||
|
case '/settings/public-site/image':
|
||||||
|
SettingsController::uploadPublicSiteImage();
|
||||||
|
break;
|
||||||
case '/settings/privacy':
|
case '/settings/privacy':
|
||||||
PrivacyController::index();
|
PrivacyController::index();
|
||||||
break;
|
break;
|
||||||
|
|
|
||||||
|
|
@ -37,10 +37,8 @@ final class AppSettings
|
||||||
'public_site_contact_text' => 'Une question sur une adoption ou sur l’association ? Contactez-nous.',
|
'public_site_contact_text' => 'Une question sur une adoption ou sur l’association ? Contactez-nous.',
|
||||||
'public_site_facebook_url' => '',
|
'public_site_facebook_url' => '',
|
||||||
'public_site_instagram_url' => '',
|
'public_site_instagram_url' => '',
|
||||||
'public_site_mastodon_url' => '',
|
|
||||||
'public_site_youtube_url' => '',
|
'public_site_youtube_url' => '',
|
||||||
'public_site_tiktok_url' => '',
|
'public_site_tiktok_url' => '',
|
||||||
'public_site_bluesky_url' => '',
|
|
||||||
'notification_email_enabled' => '0',
|
'notification_email_enabled' => '0',
|
||||||
'notification_email_frequency' => 'weekly',
|
'notification_email_frequency' => 'weekly',
|
||||||
'notification_email_recipient' => '',
|
'notification_email_recipient' => '',
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ final class PermissionService
|
||||||
'lecture' => 'Lecture seule',
|
'lecture' => 'Lecture seule',
|
||||||
];
|
];
|
||||||
public const MODULES = [
|
public const MODULES = [
|
||||||
'dashboard' => ['Dashboard', 'Priorités et situation quotidienne'],
|
'dashboard' => ['Tableau de bord', 'Priorités et situation quotidienne'],
|
||||||
'animals' => ['Animaux', 'Fiches, portées, liens, médias et adoptions'],
|
'animals' => ['Animaux', 'Fiches, portées, liens, médias et adoptions'],
|
||||||
'medical' => ['Médical', 'Notes, traitements, vaccinations et décès'],
|
'medical' => ['Médical', 'Notes, traitements, vaccinations et décès'],
|
||||||
'care' => ['Tournées', 'Passages tablette et configuration des box'],
|
'care' => ['Tournées', 'Passages tablette et configuration des box'],
|
||||||
|
|
|
||||||
|
|
@ -46,6 +46,14 @@ final class PublicContentFormatter
|
||||||
|
|
||||||
private static function inline(string $text): string
|
private static function inline(string $text): string
|
||||||
{
|
{
|
||||||
|
$images = [];
|
||||||
|
$text = preg_replace_callback('/!\[([^\]]*)\]\((\/media\/site\/[A-Za-z0-9._%-]+)\)/u', static function (array $match) use (&$images): string {
|
||||||
|
$token = "\x1B" . count($images) . "\x1B";
|
||||||
|
$images[] = '<img class="public-content-image" src="' .
|
||||||
|
htmlspecialchars($match[2], ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8') . '" alt="' .
|
||||||
|
htmlspecialchars($match[1], ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8') . '" loading="lazy">';
|
||||||
|
return $token;
|
||||||
|
}, $text) ?? $text;
|
||||||
$links = [];
|
$links = [];
|
||||||
$text = preg_replace_callback('/\[([^\]]+)\]\((https:\/\/[^\s)]+)\)/u', static function (array $match) use (&$links): string {
|
$text = preg_replace_callback('/\[([^\]]+)\]\((https:\/\/[^\s)]+)\)/u', static function (array $match) use (&$links): string {
|
||||||
if (!filter_var($match[2], FILTER_VALIDATE_URL)) {
|
if (!filter_var($match[2], FILTER_VALIDATE_URL)) {
|
||||||
|
|
@ -62,6 +70,9 @@ final class PublicContentFormatter
|
||||||
foreach ($links as $index => $link) {
|
foreach ($links as $index => $link) {
|
||||||
$text = str_replace("\x1A" . $index . "\x1A", $link, $text);
|
$text = str_replace("\x1A" . $index . "\x1A", $link, $text);
|
||||||
}
|
}
|
||||||
|
foreach ($images as $index => $image) {
|
||||||
|
$text = str_replace("\x1B" . $index . "\x1B", $image, $text);
|
||||||
|
}
|
||||||
return $text;
|
return $text;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -31,10 +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.4</span>
|
<span class="badge bg-blue-lt mb-2">1.0.0-rc.5</span>
|
||||||
<h3>Retours de terrain avant la 1.0</h3>
|
<h3>Édition du site public</h3>
|
||||||
<p class="text-muted">Photos publiques et déplacements fiabilisés, statistiques navigables,
|
<p class="text-muted">Bibliothèque d’images, réseaux sociaux simplifiés et navigation complète
|
||||||
références biologiques extensibles et site public personnalisable.</p>
|
depuis les statistiques, y compris vers les fiches archivées.</p>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ $obfuscate = static fn (string$value): string => implode('', array_reverse(mb_st
|
||||||
class="contact-obfuscated"><?= h($obfuscate($settings['association_email'])) ?></span></div>
|
class="contact-obfuscated"><?= h($obfuscate($settings['association_email'])) ?></span></div>
|
||||||
<?php endif; ?></div>
|
<?php endif; ?></div>
|
||||||
<div class="chips" style="margin-top:25px">
|
<div class="chips" style="margin-top:25px">
|
||||||
<?php foreach (['facebook' => 'Facebook','instagram' => 'Instagram','mastodon' => 'Mastodon','youtube' => 'YouTube','tiktok' => 'TikTok','bluesky' => 'Bluesky'] as $network => $label):
|
<?php foreach (['facebook' => 'Facebook','instagram' => 'Instagram','youtube' => 'YouTube','tiktok' => 'TikTok'] as $network => $label):
|
||||||
$url = trim((string)($settings['public_site_' . $network . '_url'] ?? ''));
|
$url = trim((string)($settings['public_site_' . $network . '_url'] ?? ''));
|
||||||
if ($url === '') continue; ?>
|
if ($url === '') continue; ?>
|
||||||
<a class="button secondary" href="<?= h($url) ?>" rel="noopener noreferrer"><?= h($label) ?></a>
|
<a class="button secondary" href="<?= h($url) ?>" rel="noopener noreferrer"><?= h($label) ?></a>
|
||||||
|
|
|
||||||
|
|
@ -216,6 +216,14 @@ if (!function_exists('public_age')) {
|
||||||
min-height: 65vh
|
min-height: 65vh
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.public-content-image {
|
||||||
|
display: block;
|
||||||
|
max-width: 100%;
|
||||||
|
height: auto;
|
||||||
|
margin: 1rem 0;
|
||||||
|
border-radius: var(--radius)
|
||||||
|
}
|
||||||
|
|
||||||
.section {
|
.section {
|
||||||
padding: 52px 0
|
padding: 52px 0
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,25 @@
|
||||||
</div>
|
</div>
|
||||||
<?php if ($saved): ?><div class="alert alert-success">✅ <?= h(t('public_site.saved')) ?></div>
|
<?php if ($saved): ?><div class="alert alert-success">✅ <?= h(t('public_site.saved')) ?></div>
|
||||||
<?php endif; ?><?php if ($error): ?><div class="alert alert-danger">⚠️ <?= h($error) ?></div><?php endif; ?>
|
<?php endif; ?><?php if ($error): ?><div class="alert alert-danger">⚠️ <?= h($error) ?></div><?php endif; ?>
|
||||||
|
<div class="card mb-3">
|
||||||
|
<div class="card-header"><h3 class="card-title">🖼️ <?= h(t('public_site.image_library')) ?></h3></div>
|
||||||
|
<div class="card-body">
|
||||||
|
<form method="post" action="/settings/public-site/image" enctype="multipart/form-data" class="row g-2 align-items-end mb-3">
|
||||||
|
<input type="hidden" name="csrf" value="<?= h(Auth::csrf()) ?>">
|
||||||
|
<div class="col-md"><label class="form-label"><?= h(t('public_site.image_file')) ?></label><input class="form-control" type="file" name="public_site_image" accept="image/jpeg,image/png,image/webp" required></div>
|
||||||
|
<div class="col-md-auto"><button class="btn btn-outline-primary"><?= h(t('public_site.image_upload')) ?></button></div>
|
||||||
|
</form>
|
||||||
|
<div class="form-hint mb-3"><?= h(t('public_site.image_help')) ?></div>
|
||||||
|
<?php if (!$images): ?><div class="text-muted"><?= h(t('public_site.image_none')) ?></div><?php else: ?>
|
||||||
|
<div class="row g-3">
|
||||||
|
<?php foreach ($images as $image): ?><div class="col-6 col-md-3 col-xl-2">
|
||||||
|
<div class="card h-100"><img src="<?= h($image['url']) ?>" alt="" class="card-img-top" style="height:110px;object-fit:cover">
|
||||||
|
<div class="card-body p-2"><button type="button" class="btn btn-sm btn-outline-secondary w-100 public-image-insert" data-markup="<?= h('') ?>"><?= h(t('public_site.image_insert')) ?></button></div>
|
||||||
|
</div>
|
||||||
|
</div><?php endforeach; ?>
|
||||||
|
</div><?php endif; ?>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<form method="post" action="/settings/public-site/save"><input type="hidden" name="csrf" value="<?= h(Auth::csrf()) ?>">
|
<form method="post" action="/settings/public-site/save"><input type="hidden" name="csrf" value="<?= h(Auth::csrf()) ?>">
|
||||||
<div class="card mb-3">
|
<div class="card mb-3">
|
||||||
<div class="card-status-top bg-green"></div>
|
<div class="card-status-top bg-green"></div>
|
||||||
|
|
@ -118,7 +137,7 @@
|
||||||
name="public_site_facebook_url" value="<?= h($settings['public_site_facebook_url']) ?>"></div>
|
name="public_site_facebook_url" value="<?= h($settings['public_site_facebook_url']) ?>"></div>
|
||||||
<div class="col-md-6"><label class="form-label">Instagram</label><input class="form-control" type="url"
|
<div class="col-md-6"><label class="form-label">Instagram</label><input class="form-control" type="url"
|
||||||
name="public_site_instagram_url" value="<?= h($settings['public_site_instagram_url']) ?>"></div>
|
name="public_site_instagram_url" value="<?= h($settings['public_site_instagram_url']) ?>"></div>
|
||||||
<?php foreach (['mastodon' => 'Mastodon','youtube' => 'YouTube','tiktok' => 'TikTok','bluesky' => 'Bluesky'] as $network => $label): ?>
|
<?php foreach (['youtube' => 'YouTube','tiktok' => 'TikTok'] as $network => $label): ?>
|
||||||
<div class="col-md-6"><label class="form-label"><?= h($label) ?></label><input class="form-control"
|
<div class="col-md-6"><label class="form-label"><?= h($label) ?></label><input class="form-control"
|
||||||
type="url" name="public_site_<?= h($network) ?>_url"
|
type="url" name="public_site_<?= h($network) ?>_url"
|
||||||
value="<?= h($settings['public_site_' . $network . '_url']) ?>" placeholder="https://..."></div>
|
value="<?= h($settings['public_site_' . $network . '_url']) ?>" placeholder="https://..."></div>
|
||||||
|
|
@ -142,7 +161,9 @@
|
||||||
<div class="text-end mb-4"><button class="btn btn-primary"><?= h(t('common.save')) ?></button></div>
|
<div class="text-end mb-4"><button class="btn btn-primary"><?= h(t('common.save')) ?></button></div>
|
||||||
</form>
|
</form>
|
||||||
<script>
|
<script>
|
||||||
|
let activePublicEditor = null;
|
||||||
document.querySelectorAll('.public-rich-text').forEach((textarea) => {
|
document.querySelectorAll('.public-rich-text').forEach((textarea) => {
|
||||||
|
textarea.addEventListener('focus', () => { activePublicEditor = textarea; });
|
||||||
const toolbar = document.createElement('div');
|
const toolbar = document.createElement('div');
|
||||||
toolbar.className = 'btn-list mb-2';
|
toolbar.className = 'btn-list mb-2';
|
||||||
const actions = [['B', '**', '**'], ['I', '*', '*'], ['Titre', '## ', ''], ['Liste', '- ', ''], ['Lien', '[', '](https://)']];
|
const actions = [['B', '**', '**'], ['I', '*', '*'], ['Titre', '## ', ''], ['Liste', '- ', ''], ['Lien', '[', '](https://)']];
|
||||||
|
|
@ -162,4 +183,13 @@ document.querySelectorAll('.public-rich-text').forEach((textarea) => {
|
||||||
});
|
});
|
||||||
textarea.before(toolbar);
|
textarea.before(toolbar);
|
||||||
});
|
});
|
||||||
|
document.querySelectorAll('.public-image-insert').forEach((button) => {
|
||||||
|
button.addEventListener('click', () => {
|
||||||
|
const textarea = activePublicEditor || document.querySelector('.public-rich-text');
|
||||||
|
if (!textarea) return;
|
||||||
|
const start = textarea.selectionStart;
|
||||||
|
textarea.setRangeText(button.dataset.markup + '\n', start, textarea.selectionEnd, 'end');
|
||||||
|
textarea.focus();
|
||||||
|
});
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
|
||||||
|
|
@ -120,4 +120,5 @@ return [
|
||||||
'accounting.document_save_failed'=>'The document could not be saved.',
|
'accounting.document_save_failed'=>'The document could not be saved.',
|
||||||
'search.title'=>'Global search','search.placeholder'=>'Animal, contact, appointment or invoice…','search.action'=>'Search','search.hint'=>'Enter at least two characters.','search.none'=>'No accessible result.','search.open'=>'Open global search','search.shortcut'=>'Ctrl K','search.group.animals'=>'Animals','search.group.directory'=>'Directory','search.group.agenda'=>'Calendar','search.group.accounting'=>'Accounting','search.loading'=>'Searching…','search.all_results'=>'Show all results','search.error'=>'Search is temporarily unavailable.',
|
'search.title'=>'Global search','search.placeholder'=>'Animal, contact, appointment or invoice…','search.action'=>'Search','search.hint'=>'Enter at least two characters.','search.none'=>'No accessible result.','search.open'=>'Open global search','search.shortcut'=>'Ctrl K','search.group.animals'=>'Animals','search.group.directory'=>'Directory','search.group.agenda'=>'Calendar','search.group.accounting'=>'Accounting','search.loading'=>'Searching…','search.all_results'=>'Show all results','search.error'=>'Search is temporarily unavailable.',
|
||||||
'public_site.settings_heading'=>'Public website','public_site.theme_custom'=>'Custom','public_site.site_name'=>'Website name','public_site.custom_css'=>'Custom CSS','public_site.custom_css_help'=>'Used by the custom theme. Imports and external resources are blocked.','public_site.invalid_css'=>'The stylesheet contains a forbidden instruction.','public_site.routing_management_help'=>'Management stays at / and the public website at /site.','public_site.publish_animal'=>'Publish this animal on the public website','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.','lab_reference.add_species'=>'Add a species to lab reports','lab_reference.copy_from'=>'Copy reference ranges from','lab_reference.target_species'=>'New species','lab_reference.copy_help'=>'Ranges are copied as a starting point and remain editable.','lab_reference.invalid_species'=>'Choose two different valid species.','lab_reference.nothing_copied'=>'This species already has these reference ranges.','statistics.hair_types'=>'Hair types','animals.filtered_by'=>'Filter: :value','service.icad.home_unavailable'=>'The I-CAD website is unavailable','service.icad.search_unavailable'=>'I-CAD search is unavailable','service.icad.record_unavailable'=>'The I-CAD record is unavailable',
|
'public_site.settings_heading'=>'Public website','public_site.theme_custom'=>'Custom','public_site.site_name'=>'Website name','public_site.custom_css'=>'Custom CSS','public_site.custom_css_help'=>'Used by the custom theme. Imports and external resources are blocked.','public_site.invalid_css'=>'The stylesheet contains a forbidden instruction.','public_site.routing_management_help'=>'Management stays at / and the public website at /site.','public_site.publish_animal'=>'Publish this animal on the public website','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.','lab_reference.add_species'=>'Add a species to lab reports','lab_reference.copy_from'=>'Copy reference ranges from','lab_reference.target_species'=>'New species','lab_reference.copy_help'=>'Ranges are copied as a starting point and remain editable.','lab_reference.invalid_species'=>'Choose two different valid species.','lab_reference.nothing_copied'=>'This species already has these reference ranges.','statistics.hair_types'=>'Hair types','animals.filtered_by'=>'Filter: :value','service.icad.home_unavailable'=>'The I-CAD website is unavailable','service.icad.search_unavailable'=>'I-CAD search is unavailable','service.icad.record_unavailable'=>'The I-CAD record is unavailable',
|
||||||
|
'public_site.image_library'=>'Image library','public_site.image_file'=>'Image to add','public_site.image_upload'=>'Upload image','public_site.image_help'=>'Upload an image, place the cursor in a text field, then choose “Insert”. Accepted formats: JPEG, PNG and WebP.','public_site.image_none'=>'No image uploaded.','public_site.image_insert'=>'Insert','public_site.image_invalid'=>'Choose a valid image.',
|
||||||
];
|
];
|
||||||
|
|
|
||||||
|
|
@ -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'=>'Dashboard','nav.animals'=>'Animaux','nav.directory'=>'Annuaire','nav.care_round'=>'Tournée','nav.statistics'=>'Statistiques','nav.administrative'=>'Administratif','nav.grants'=>'Subventions',
|
'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',
|
||||||
|
|
@ -51,7 +51,7 @@ return [
|
||||||
'settings.saved'=>'Les paramètres ont été enregistrés.','settings.association'=>'Association','settings.association_name'=>'Nom de l’association','settings.rna'=>'Numéro RNA','settings.siret'=>'SIRET','settings.address'=>'Adresse','settings.postcode'=>'Code postal','settings.city'=>'Ville','settings.phone'=>'Téléphone','settings.email'=>'E-mail','settings.association_logo'=>'Logo de l’association','settings.logo_help'=>'PNG, JPEG ou WebP, 5 Mo maximum. Il pourra être intégré aux documents officiels.','settings.delete_logo'=>'Supprimer le logo actuel','settings.logo_alt'=>'Logo de :name','settings.current_preview'=>'Aperçu actuel','settings.no_logo'=>'Aucun logo enregistré','settings.operation'=>'Fonctionnement','settings.quarantine_duration'=>'Durée de quarantaine','settings.days'=>'jours','settings.backups_kept'=>'Sauvegardes conservées',
|
'settings.saved'=>'Les paramètres ont été enregistrés.','settings.association'=>'Association','settings.association_name'=>'Nom de l’association','settings.rna'=>'Numéro RNA','settings.siret'=>'SIRET','settings.address'=>'Adresse','settings.postcode'=>'Code postal','settings.city'=>'Ville','settings.phone'=>'Téléphone','settings.email'=>'E-mail','settings.association_logo'=>'Logo de l’association','settings.logo_help'=>'PNG, JPEG ou WebP, 5 Mo maximum. Il pourra être intégré aux documents officiels.','settings.delete_logo'=>'Supprimer le logo actuel','settings.logo_alt'=>'Logo de :name','settings.current_preview'=>'Aperçu actuel','settings.no_logo'=>'Aucun logo enregistré','settings.operation'=>'Fonctionnement','settings.quarantine_duration'=>'Durée de quarantaine','settings.days'=>'jours','settings.backups_kept'=>'Sauvegardes conservées',
|
||||||
'settings.users_title'=>'Paramètres — Utilisateurs','settings.users_intro'=>'Comptes, rôles et accès à l’application.','settings.configure_permissions'=>'Configurer les permissions des rôles','settings.add_user'=>'Ajouter un utilisateur','settings.display_name'=>'Nom affiché','settings.username'=>'Identifiant','settings.role'=>'Rôle','settings.initial_password'=>'Mot de passe initial','settings.new_password'=>'Nouveau mot de passe','settings.unchanged'=>'Inchangé','settings.active'=>'Actif','settings.create'=>'Créer','settings.never_logged'=>'Jamais connecté','settings.seen'=>'Vu :date','settings.invalid_data'=>'Données invalides','settings.keep_admin'=>'Vous ne pouvez pas retirer vos propres droits administrateur.','settings.password_short'=>'Mot de passe trop court','settings.password_required'=>'Mot de passe de 10 caractères minimum requis',
|
'settings.users_title'=>'Paramètres — Utilisateurs','settings.users_intro'=>'Comptes, rôles et accès à l’application.','settings.configure_permissions'=>'Configurer les permissions des rôles','settings.add_user'=>'Ajouter un utilisateur','settings.display_name'=>'Nom affiché','settings.username'=>'Identifiant','settings.role'=>'Rôle','settings.initial_password'=>'Mot de passe initial','settings.new_password'=>'Nouveau mot de passe','settings.unchanged'=>'Inchangé','settings.active'=>'Actif','settings.create'=>'Créer','settings.never_logged'=>'Jamais connecté','settings.seen'=>'Vu :date','settings.invalid_data'=>'Données invalides','settings.keep_admin'=>'Vous ne pouvez pas retirer vos propres droits administrateur.','settings.password_short'=>'Mot de passe trop court','settings.password_required'=>'Mot de passe de 10 caractères minimum requis',
|
||||||
'settings.permissions_title'=>'Paramètres — Rôles et permissions','settings.permissions_saved'=>'Les permissions des rôles ont été enregistrées.','settings.permissions_intro'=>'Les droits s’appliquent à tous les utilisateurs appartenant au rôle concerné.','settings.safeguard'=>'Garde-fou :','settings.safeguard_help'=>'Les administrateurs conservent toujours tous les droits. Eux seuls peuvent gérer les utilisateurs, cette matrice, les paramètres, les sauvegardes et le journal d’audit. L’accès médical nécessite également de pouvoir consulter les fiches animales.','settings.module'=>'Module','settings.view'=>'Voir','settings.edit'=>'Modifier','settings.permission_view'=>'Voir :module — :role','settings.permission_edit'=>'Modifier :module — :role','settings.edit_implies_view'=>'Autoriser la modification active automatiquement la consultation du module.','settings.save_permissions'=>'Enregistrer les permissions',
|
'settings.permissions_title'=>'Paramètres — Rôles et permissions','settings.permissions_saved'=>'Les permissions des rôles ont été enregistrées.','settings.permissions_intro'=>'Les droits s’appliquent à tous les utilisateurs appartenant au rôle concerné.','settings.safeguard'=>'Garde-fou :','settings.safeguard_help'=>'Les administrateurs conservent toujours tous les droits. Eux seuls peuvent gérer les utilisateurs, cette matrice, les paramètres, les sauvegardes et le journal d’audit. L’accès médical nécessite également de pouvoir consulter les fiches animales.','settings.module'=>'Module','settings.view'=>'Voir','settings.edit'=>'Modifier','settings.permission_view'=>'Voir :module — :role','settings.permission_edit'=>'Modifier :module — :role','settings.edit_implies_view'=>'Autoriser la modification active automatiquement la consultation du module.','settings.save_permissions'=>'Enregistrer les permissions',
|
||||||
'permission.role.admin'=>'Administrateur','permission.role.responsable'=>'Responsable','permission.role.benevole'=>'Bénévole','permission.role.lecture'=>'Lecture seule','permission.module.dashboard'=>'Dashboard','permission.module.animals'=>'Animaux','permission.module.medical'=>'Médical','permission.module.care'=>'Tournées','permission.module.directory'=>'Annuaire','permission.module.statistics'=>'Statistiques','permission.module.administrative'=>'Administratif','permission.module.grants'=>'Subventions','permission.module_help.dashboard'=>'Priorités et situation quotidienne','permission.module_help.animals'=>'Fiches, portées, liens, médias et adoptions','permission.module_help.medical'=>'Notes, traitements, vaccinations et décès','permission.module_help.care'=>'Passages tablette et configuration des box','permission.module_help.directory'=>'Personnes, structures et coordonnées','permission.module_help.statistics'=>'Analyses annuelles et tendances','permission.module_help.administrative'=>'Registre, documents et exports','permission.module_help.grants'=>'Bilans, demandes et justificatifs',
|
'permission.role.admin'=>'Administrateur','permission.role.responsable'=>'Responsable','permission.role.benevole'=>'Bénévole','permission.role.lecture'=>'Lecture seule','permission.module.dashboard'=>'Tableau de bord','permission.module.animals'=>'Animaux','permission.module.medical'=>'Médical','permission.module.care'=>'Tournées','permission.module.directory'=>'Annuaire','permission.module.statistics'=>'Statistiques','permission.module.administrative'=>'Administratif','permission.module.grants'=>'Subventions','permission.module_help.dashboard'=>'Priorités et situation quotidienne','permission.module_help.animals'=>'Fiches, portées, liens, médias et adoptions','permission.module_help.medical'=>'Notes, traitements, vaccinations et décès','permission.module_help.care'=>'Passages tablette et configuration des box','permission.module_help.directory'=>'Personnes, structures et coordonnées','permission.module_help.statistics'=>'Analyses annuelles et tendances','permission.module_help.administrative'=>'Registre, documents et exports','permission.module_help.grants'=>'Bilans, demandes et justificatifs',
|
||||||
'settings.species_title'=>'Paramètres — Espèces','settings.species_heading'=>'Espèces animales','settings.species_intro'=>'Référentiel commun aux fiches, imports, registres et statistiques. Une espèce utilisée peut être désactivée sans perdre ses animaux.','settings.species_saved'=>'Le référentiel des espèces a été enregistré.','settings.add_species'=>'Ajouter une espèce','settings.name'=>'Nom','settings.species_example'=>'Ex. Tortue d’Hermann','settings.code'=>'Code','settings.category'=>'Catégorie','settings.icon'=>'Icône','settings.color'=>'Couleur','settings.order'=>'Ordre','settings.active_f'=>'Active','settings.stable_code'=>'Code stable','settings.animal_count'=>':count animal(aux)','common.save_short'=>'Sauver','species.category.animaux_compagnie'=>'Animaux de compagnie','species.category.nac'=>'NAC','species.category.animal_ferme'=>'Animaux de ferme','species.category.faune_sauvage'=>'Faune sauvage','species.category.autre'=>'Autre',
|
'settings.species_title'=>'Paramètres — Espèces','settings.species_heading'=>'Espèces animales','settings.species_intro'=>'Référentiel commun aux fiches, imports, registres et statistiques. Une espèce utilisée peut être désactivée sans perdre ses animaux.','settings.species_saved'=>'Le référentiel des espèces a été enregistré.','settings.add_species'=>'Ajouter une espèce','settings.name'=>'Nom','settings.species_example'=>'Ex. Tortue d’Hermann','settings.code'=>'Code','settings.category'=>'Catégorie','settings.icon'=>'Icône','settings.color'=>'Couleur','settings.order'=>'Ordre','settings.active_f'=>'Active','settings.stable_code'=>'Code stable','settings.animal_count'=>':count animal(aux)','common.save_short'=>'Sauver','species.category.animaux_compagnie'=>'Animaux de compagnie','species.category.nac'=>'NAC','species.category.animal_ferme'=>'Animaux de ferme','species.category.faune_sauvage'=>'Faune sauvage','species.category.autre'=>'Autre',
|
||||||
'settings.association_required'=>'Le nom de l’association est obligatoire.','settings.invalid_email'=>'Adresse e-mail invalide.','settings.species_invalid'=>'Nom, code, catégorie ou couleur invalide.','settings.species_not_found'=>'Espèce introuvable.','settings.admin_only'=>'Accès réservé aux administrateurs.',
|
'settings.association_required'=>'Le nom de l’association est obligatoire.','settings.invalid_email'=>'Adresse e-mail invalide.','settings.species_invalid'=>'Nom, code, catégorie ou couleur invalide.','settings.species_not_found'=>'Espèce introuvable.','settings.admin_only'=>'Accès réservé aux administrateurs.',
|
||||||
'pricing.title'=>'Paramètres — Tarifs','pricing.heading'=>'Tarifs et coûts','pricing.intro'=>'Référentiel par structure. Les dépenses enregistrées conservent le tarif et la remise du jour.','pricing.saved'=>'Tarifs enregistrés.','pricing.adoption_fees'=>'Tarifs d’adoption','pricing.sterilized'=>'Stérilisé','pricing.unsterilized'=>'Non stérilisé','pricing.discounts'=>'Remises par structure','pricing.discounts_help'=>'Vue synthétique des réductions appliquées.','pricing.no_discount'=>'Aucune remise','pricing.configure'=>'Configurer','pricing.no_structure'=>'Ajoutez d’abord une structure éligible dans l’annuaire.','pricing.discount_for'=>'Remises · :name','pricing.future_help'=>'Les valeurs seront appliquées aux dépenses futures.','pricing.antibiotic_help'=>'Les antibiotiques restent à 0 % tant qu’aucune remise spécifique n’est accordée.','pricing.save_discounts'=>'Enregistrer les remises','pricing.add_label'=>'Ajouter un intitulé tarifaire','pricing.add_label_help'=>'Les médicaments peuvent être liés au référentiel existant. Choisissez « Antibiotique » pour neutraliser la remise.','pricing.structure'=>'Structure','pricing.label'=>'Intitulé','pricing.label_example'=>'Ex. Radiographie','pricing.catalog_price'=>'Prix catalogue','pricing.discount_group'=>'Groupe de remise','pricing.unit'=>'Unité','pricing.linked_medication'=>'Médicament lié','pricing.active_tariff'=>'Tarif actif','pricing.add_tariff'=>'Ajouter le tarif','pricing.grid'=>'Grille tarifaire','pricing.discount'=>'Remise','pricing.catalog'=>'Catalogue','pricing.estimated_cost'=>'Coût estimé','pricing.state'=>'État','pricing.per_unit'=>'par :unit','pricing.active'=>'Actif','pricing.inactive'=>'Inactif','pricing.invalid_structure'=>'Choisissez une structure éligible et complétez le tarif.','pricing.invalid_discount_structure'=>'Structure tarifaire invalide.',
|
'pricing.title'=>'Paramètres — Tarifs','pricing.heading'=>'Tarifs et coûts','pricing.intro'=>'Référentiel par structure. Les dépenses enregistrées conservent le tarif et la remise du jour.','pricing.saved'=>'Tarifs enregistrés.','pricing.adoption_fees'=>'Tarifs d’adoption','pricing.sterilized'=>'Stérilisé','pricing.unsterilized'=>'Non stérilisé','pricing.discounts'=>'Remises par structure','pricing.discounts_help'=>'Vue synthétique des réductions appliquées.','pricing.no_discount'=>'Aucune remise','pricing.configure'=>'Configurer','pricing.no_structure'=>'Ajoutez d’abord une structure éligible dans l’annuaire.','pricing.discount_for'=>'Remises · :name','pricing.future_help'=>'Les valeurs seront appliquées aux dépenses futures.','pricing.antibiotic_help'=>'Les antibiotiques restent à 0 % tant qu’aucune remise spécifique n’est accordée.','pricing.save_discounts'=>'Enregistrer les remises','pricing.add_label'=>'Ajouter un intitulé tarifaire','pricing.add_label_help'=>'Les médicaments peuvent être liés au référentiel existant. Choisissez « Antibiotique » pour neutraliser la remise.','pricing.structure'=>'Structure','pricing.label'=>'Intitulé','pricing.label_example'=>'Ex. Radiographie','pricing.catalog_price'=>'Prix catalogue','pricing.discount_group'=>'Groupe de remise','pricing.unit'=>'Unité','pricing.linked_medication'=>'Médicament lié','pricing.active_tariff'=>'Tarif actif','pricing.add_tariff'=>'Ajouter le tarif','pricing.grid'=>'Grille tarifaire','pricing.discount'=>'Remise','pricing.catalog'=>'Catalogue','pricing.estimated_cost'=>'Coût estimé','pricing.state'=>'État','pricing.per_unit'=>'par :unit','pricing.active'=>'Actif','pricing.inactive'=>'Inactif','pricing.invalid_structure'=>'Choisissez une structure éligible et complétez le tarif.','pricing.invalid_discount_structure'=>'Structure tarifaire invalide.',
|
||||||
|
|
@ -120,4 +120,5 @@ return [
|
||||||
'accounting.document_save_failed'=>'Impossible d’enregistrer le document.',
|
'accounting.document_save_failed'=>'Impossible d’enregistrer le document.',
|
||||||
'search.title'=>'Recherche globale','search.placeholder'=>'Animal, contact, rendez-vous ou facture…','search.action'=>'Rechercher','search.hint'=>'Saisissez au moins deux caractères.','search.none'=>'Aucun résultat accessible.','search.open'=>'Ouvrir la recherche globale','search.shortcut'=>'Ctrl K','search.group.animals'=>'Animaux','search.group.directory'=>'Annuaire','search.group.agenda'=>'Agenda','search.group.accounting'=>'Comptabilité','search.loading'=>'Recherche…','search.all_results'=>'Afficher tous les résultats','search.error'=>'La recherche est momentanément indisponible.',
|
'search.title'=>'Recherche globale','search.placeholder'=>'Animal, contact, rendez-vous ou facture…','search.action'=>'Rechercher','search.hint'=>'Saisissez au moins deux caractères.','search.none'=>'Aucun résultat accessible.','search.open'=>'Ouvrir la recherche globale','search.shortcut'=>'Ctrl K','search.group.animals'=>'Animaux','search.group.directory'=>'Annuaire','search.group.agenda'=>'Agenda','search.group.accounting'=>'Comptabilité','search.loading'=>'Recherche…','search.all_results'=>'Afficher tous les résultats','search.error'=>'La recherche est momentanément indisponible.',
|
||||||
'public_site.settings_heading'=>'Site public','public_site.theme_custom'=>'Personnalisé','public_site.site_name'=>'Nom du site','public_site.custom_css'=>'CSS personnalisé','public_site.custom_css_help'=>'Actif avec le thème personnalisé. Les imports et les ressources externes sont bloqués.','public_site.invalid_css'=>'La feuille de style contient une instruction interdite.','public_site.routing_management_help'=>'La gestion reste sur / et le site public sur /site.','public_site.publish_animal'=>'Publier cet animal sur le site public','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.','lab_reference.add_species'=>'Ajouter une espèce aux bilans','lab_reference.copy_from'=>'Copier les références de','lab_reference.target_species'=>'Nouvelle espèce','lab_reference.copy_help'=>'Les intervalles sont copiés comme point de départ, puis restent modifiables.','lab_reference.invalid_species'=>'Choisissez deux espèces différentes et valides.','lab_reference.nothing_copied'=>'Cette espèce possède déjà ces références.','statistics.hair_types'=>'Types de poil','animals.filtered_by'=>'Filtre : :value','service.icad.home_unavailable'=>'Le site I-CAD est indisponible','service.icad.search_unavailable'=>'La recherche I-CAD est indisponible','service.icad.record_unavailable'=>'La fiche I-CAD est indisponible',
|
'public_site.settings_heading'=>'Site public','public_site.theme_custom'=>'Personnalisé','public_site.site_name'=>'Nom du site','public_site.custom_css'=>'CSS personnalisé','public_site.custom_css_help'=>'Actif avec le thème personnalisé. Les imports et les ressources externes sont bloqués.','public_site.invalid_css'=>'La feuille de style contient une instruction interdite.','public_site.routing_management_help'=>'La gestion reste sur / et le site public sur /site.','public_site.publish_animal'=>'Publier cet animal sur le site public','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.','lab_reference.add_species'=>'Ajouter une espèce aux bilans','lab_reference.copy_from'=>'Copier les références de','lab_reference.target_species'=>'Nouvelle espèce','lab_reference.copy_help'=>'Les intervalles sont copiés comme point de départ, puis restent modifiables.','lab_reference.invalid_species'=>'Choisissez deux espèces différentes et valides.','lab_reference.nothing_copied'=>'Cette espèce possède déjà ces références.','statistics.hair_types'=>'Types de poil','animals.filtered_by'=>'Filtre : :value','service.icad.home_unavailable'=>'Le site I-CAD est indisponible','service.icad.search_unavailable'=>'La recherche I-CAD est indisponible','service.icad.record_unavailable'=>'La fiche I-CAD est indisponible',
|
||||||
|
'public_site.image_library'=>'Bibliothèque d’images','public_site.image_file'=>'Image à ajouter','public_site.image_upload'=>'Ajouter l’image','public_site.image_help'=>'Ajoutez une image, placez le curseur dans un texte puis choisissez « Insérer ». Formats acceptés : JPEG, PNG et WebP.','public_site.image_none'=>'Aucune image ajoutée.','public_site.image_insert'=>'Insérer','public_site.image_invalid'=>'Choisissez une image valide.',
|
||||||
];
|
];
|
||||||
|
|
|
||||||
|
|
@ -148,6 +148,10 @@ try {
|
||||||
$assert(str_contains($html, '<h3>Titre</h3>'));
|
$assert(str_contains($html, '<h3>Titre</h3>'));
|
||||||
$assert(str_contains($html, '<strong>Texte</strong>'));
|
$assert(str_contains($html, '<strong>Texte</strong>'));
|
||||||
$assert(!str_contains($html, '<script>'), 'HTML brut non échappé');
|
$assert(!str_contains($html, '<script>'), 'HTML brut non échappé');
|
||||||
|
$image = PublicContentFormatter::render('');
|
||||||
|
$assert(str_contains($image, '<img class="public-content-image"'));
|
||||||
|
$externalImage = PublicContentFormatter::render('');
|
||||||
|
$assert(!str_contains($externalImage, '<img'), 'Une image externe a été acceptée');
|
||||||
});
|
});
|
||||||
$test('Authentification et mot de passe haché', function () use ($db, $assert): void {
|
$test('Authentification et mot de passe haché', function () use ($db, $assert): void {
|
||||||
$assert(ini_get('session.use_strict_mode') === '1', 'Le mode strict des sessions est désactivé');
|
$assert(ini_get('session.use_strict_mode') === '1', 'Le mode strict des sessions est désactivé');
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue