Compare commits

...

5 Commits

81 changed files with 20881 additions and 3035 deletions

4
.gitignore vendored
View File

@ -8,4 +8,6 @@ species_list.txt
push.sh
config/*.conf
config/*.conf
.vscode/

View File

@ -9,5 +9,6 @@
- Extracts BirdNET bird contacts into SQL database
- Add birdnet_stream icecast audio streaming and live spectrogram service https://birdnet/spectro
- Add /today/species and /today/{date}/species/{id} endpoints
- Add records deletion button
- Add records deletion button and /records/delete endpoint as well as bulk deletion (select all button on /today/species/{id} endpoint)
- Add systemd status page /status
- Add i18n for webapp (not species name), en|fr only for the moment

View File

@ -22,6 +22,9 @@ BirdNET-stream webapp is written in PHP Symfony. The i18n files are stored in th
Any help is welcome to translate the webapp into your language.
Add your language code into [./www/bin/translate.sh](./www/bin/translate.sh) and run it to update the translation files.
Then, edit generated files in [./www/translations](./www/translations).
## Filing a pull request

View File

@ -5,7 +5,7 @@ This guide allow you to install BirdNET-stream step by step on your debian based
For a one-liner installation, you can use the following command:
```bash
curl -sL https://raw.githubusercontent.com/birdnet-stream/birdnet-stream/master/install.sh | bash
curl -sL https://raw.githubusercontent.com/UncleSamulus/BirdNET-stream/main/install.sh | bash
```
## Requirements
@ -34,7 +34,7 @@ sudo apt-get install ffmpeg
### Clone BirdNET-stream repository
```bash
git clone --recurse-submodules https://forge.chapril.org/UncleSamulus/BirdNET-stream.git
git clone --recurse-submodules https://github.com/UncleSamulus/BirdNET-stream.git
```
### Setup python virtualenv and packages

3
TODO
View File

@ -1,2 +1,5 @@
- Fix clean script
- Fix service manager
- Add docker support
- Species i18n
- File purge policy

View File

@ -82,7 +82,11 @@ analyze_chunk() {
# Perform audio chunk analysis on all recorded chunks
analyze_chunks() {
for chunk_name in $(get_chunk_list); do
analyze_chunk $chunk_name
if [[ -f "${CHUNK_FOLDER}/out/$chunk_name.d/model.out.csv" ]]; then
debug "Skipping $chunk_name, as it has already been analyzed"
else
analyze_chunk $chunk_name
fi
chunk_path="${CHUNK_FOLDER}/in/$chunk_name"
mv $chunk_path "${CHUNK_FOLDER}/out/$chunk_name"
done

View File

@ -3,6 +3,16 @@
## Clean up var folder from useless files
##
set -e
# set -x
DEBUG=${DEBUG:-1}
debug() {
if [[ $DEBUG -eq 1 ]]; then
echo "$1"
fi
}
config_filepath="./config/analyzer.conf"
if [ -f "$config_filepath" ]; then
@ -20,51 +30,61 @@ wav2dir_name() {
# Clean out folder from empty audio
clean() {
rm -rf $(junk)
for item in $(junk); do
debug "Removing: $item"
rm -rf "$CHUNK_FOLDER/out/$item"
done
empty_audios=$(find "$CHUNK_FOLDER/in" -type f -size 0)
for item in $empty_audios; do
rm -rf "$item"
done
}
# Check if string contains string
mem() {
string=$2
substring=$1
if [[ "$string" == *"$substring"* ]]; then
echo "true"
else
echo "false"
fi
dryclean() {
debug "Dry run mode"
debug "Script will remove the following files:"
for item in $(junk); do
debug "$item"
done
empty_audios=$(find "$CHUNK_FOLDER/in" -type f -size 0)
for item in $empty_audios; do
echo "$item"
done
}
# Get list of junk files
junk() {
# Get all empty files from treatement folder
find "${CHUNK_FOLDER}/out" -type f -name '*.wav' -size 0
junk=$(find "${CHUNK_FOLDER}/out" -type f -name '*.wav' -size 0)
for file in $junk; do
folder=$(wav2dir_name "$file")
if [[ -d $folder ]]; then
junk="$junk $folder"
fi
done
# Get all empty files from record folder
junk=$(find "${CHUNK_FOLDER}/in" -type f -name '*.wav' -exec basename {} \; ! -size 0)
# Get all empty treatment directories
junk="$junk $(find ${CHUNK_FOLDER}/out -type d -empty)"
# Get all empty record directories
treatement_folder=$(find -wholename "${CHUNK_FOLDER}/out/*" -type d ! -empty)
if [[ ! -z ${treatement_folder} ]]; then
for folder in $treatement_folder; do
echo $folder
if [[ ! $(mem $folder $junk) = "true" ]] && $(no_bird_in_model_output $folder); then
junk="$junk $folder"
fi
done
fi
junk="$junk $(find ${CHUNK_FOLDER}/out/* -type d -empty)"
# Get all no birdcontact directories
treatement_folders=$(find ${CHUNK_FOLDER}/out/* -type d ! -empty)
for folder in $treatement_folders; do
folder_basename=$(basename "$folder")
if [[ $(no_bird_in_model_output $folder_basename) = "true" ]]; then
# Add model output file to junk list
junk="$junk $folder_basename/model.out.csv"
junk="$junk $folder_basename"
fi
done
echo "$junk"
}
no_bird_in_model_output() {
folder=$1
output="${folder}/model.out.csv"
lines=$(wc -l < "$output")
output="$CHUNK_FOLDER/out/$folder/model.out.csv"
if [[ -f $output ]]; then
lines=$(wc -l <"$output")
else
lines=0
fi
if [[ $lines -eq 1 ]]; then
echo "true"
else
@ -72,8 +92,8 @@ no_bird_in_model_output() {
fi
}
main() {
if [[ $1 = "dry" ]]; then
dryclean
else
clean
}
main
fi

1
daemon/database/migrations/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
.

View File

@ -0,0 +1,34 @@
#! /usr/bin/bash
DEBUG=${DEBUG:-1}
debug() {
if [ "$DEBUG" -eq 1 ]; then
echo "$1"
fi
}
# Load config file
config_filepath="./config/analyzer.conf"
if [ -f "$config_filepath" ]; then
source "$config_filepath"
else
echo "Config file not found: $config_filepath"
exit 1
fi
# Check if database location is specified
if [ -z "$DATABASE" ]; then
echo "DATABASE location not specified"
echo "Defaults to ./var/db.sqlite"
DATABASE="./var/db.sqlite"
fi
if [[ ! -f "$DATABASE" ]]; then
echo "Database file not found: $DATABASE"
exit 1
fi
source ./daemon/database/scripts/database.sh
sqlite3 "$DATABASE" "ALTER TABLE observation ADD COLUMN verified BOOLEAN CHECK (verified IN (0, 1)) DEFAULT 0;"

View File

@ -25,6 +25,7 @@ CREATE TABLE IF NOT EXISTS observation (
`date` TEXT NOT NULL,
`notes` TEXT,
`confidence` REAL NOT NULL,
`verified` BOOLEAN NOT NULL CHECK (`verified` IN (0, 1)) DEFAULT 0,
FOREIGN KEY(taxon_id) REFERENCES taxon(taxon_id),
FOREIGN KEY(location_id) REFERENCES location(location_id)
);
);

36
docker-compose.yml Normal file
View File

@ -0,0 +1,36 @@
version: '3.8'
networks:
birdnet_network:
services:
# database:
# container_name: birdnet_database
# image:
php:
container_name: birdnet_php
image: php:8.1-fpm
ports:
- "${PHP_FPM_PORT:-9001}:9000"
nginx:
container_name: birdnet_nginx
build:
context: ./docker/
environment:
SERVER_NAME: ${SERVER_NAME:-birdnet.local}
PHP_FPM_PORT: ${PHP_FPM_PORT:-9001}
restart: unless-stopped
volumes:
- ./www:/var/www/birdnet/
- ./www/nginx.conf:/etc/nginx/conf.d/birdnet.conf
ports:
- "81:80"
dependends_on:
- php
birdnet:
container_name: birdnet_analyzer
image:

20
docker/all/Dockerfile Normal file
View File

@ -0,0 +1,20 @@
# All in One BirdNET docker image
FROM debian:bullseye
ENV REPOSITORY=${REPOSITORY:-https://github.com/UncleSamulus/BirdNET-stream.git}
# DEBUG defaults to 1 for descriptive DEBUG logs, 0 for error logs only
ENV DEBUG=${DEBUG:-1}
RUN useradd birdnet
WORKDIR /home/birdnet
# Upgrade system
RUN apt-get update && apt-get upgrade -y
# Install curl
RUN apt-get install -y \
curl \
sudo
RUN curl -sL https://raw.githubusercontent.com/UncleSamulus/BirdNET-stream/master/install.sh | bash
USER birdnet

27
docker/all/README.md Normal file
View File

@ -0,0 +1,27 @@
# All in One Docker Container for BirdNET-stream application
## Requirements
- docker
## Quick start
```bash
git clone https://github.com/UncleSamulus/BirdNET-stream.git
cd ./BirdNET-stream/docker/all
docker build -t "birdnet_all:latest" .
```
If `docker` command does not work because of unsufficient permissions, you could add your user to `docker` group:
```bash
sudo usermod -aG docker $USER
```
Then logout, reconnect and try again.
Then, docker container should be run this way:
```bash
docker run -it birdnet_all --restart unless-stopped
```

View File

@ -0,0 +1,16 @@
# Recording container for BirdNET-stream
# Reference: https://leimao.github.io/blog/Docker-Container-Audio/
FROM debian:bullseye
ENV DEBIAN_FRONTEND noninteractive
# Install packages dependencies
RUN apt-get update && \
apt-get install apt-utils \
&& apt-get install -y --no-install-recommends \
libasound2 \
alsa-utils \
libsndfile1-dev \
&& apt-get clean

0
docker/www/Dockerfile Normal file
View File

View File

@ -1,19 +1,12 @@
#! /usr/bin/env bash
# Standard Installation Script for BirdNET-stream for Debian Based Linux distros
# set -x
set -e
DEBUG=${DEBUG:-0}
# Standard Installation Script for BirdNET-stream for Debian Based Linux distros
REQUIREMENTS="git ffmpeg python3-pip python3-dev"
REPOSITORY="https://github.com/UncleSamulus/BirdNET-stream.git"
# Update system
update() {
sudo apt-get update
sudo apt-get upgrade -y
}
REPOSITORY=${REPOSITORY:-https://github.com/UncleSamulus/BirdNET-stream.git}
debug() {
if [ $DEBUG -eq 1 ]; then
@ -32,7 +25,7 @@ install_requirements() {
done
if [ -n "$missing_requirements" ]; then
debug "Installing missing requirements: $missing_requirements"
sudo apt-get install $missing_requirements
sudo apt-get install -y $missing_requirements
fi
}

25
utils/fix_permissions.sh Normal file
View File

@ -0,0 +1,25 @@
#! /usr/bin/env bash
set -e
DEBUG=${DEBUG:-1}
debug() {
if [ $DEBUG -eq 1 ]; then
echo "$1"
fi
}
config_filepath="./config/analyzer.conf"
if [ -f "$config_filepath" ]; then
source "$config_filepath"
else
echo "Config file not found: $config_filepath"
exit 1
fi
GROUP=birdnet
sudo chown -R $USER:$GROUP $CHUNK_FOLDER
sudo chmod -R 775 $CHUNK_FOLDER

View File

@ -31,3 +31,7 @@ DATABASE_URL="postgresql://app:!ChangeMe!@127.0.0.1:5432/app?serverVersion=14&ch
### records folder
RECORDS_DIR=%kernel.project_dir%/../var/chunks # adapt to your needs
###
###> symfony/mailer ###
# MAILER_DSN=null://null
###< symfony/mailer ###

27
www/.env.local.example Normal file
View File

@ -0,0 +1,27 @@
# In all environments, the following files are loaded if they exist,
# the latter taking precedence over the former:
#
# * .env contains default values for the environment variables needed by the app
# * .env.local uncommitted file with local overrides
# * .env.$APP_ENV committed environment-specific defaults
# * .env.$APP_ENV.local uncommitted environment-specific overrides
#
# Real environment variables win over .env files.
#
# Run "composer dump-env prod" to compile .env files for production use (requires symfony/flex >=1.2).
# https://symfony.com/doc/current/best_practices.html#use-environment-variables-for-infrastructure-configuration
###> symfony/framework-bundle ###
APP_ENV=prod
APP_SECRET=8bd3643031a08d0cd34e6fd3f680fb22
###< symfony/framework-bundle ###
###> symfony/doctrine-bundle ###
DATABASE_DEFAULT_URL=sqlite:///%kernel.project_dir%/./var/db-default.sqlite
DATABASE_OBSERVATIONS_URL=sqlite:///%kernel.project_dir%/../var/db.sqlite
###< doctrine/doctrine-bundle ###
###> records folder and disk
RECORDS_DISK=/dev/sda1
RECORDS_DIR=%kernel.project_dir%/../var/chunks
###< records folder and disk

View File

@ -1 +1,3 @@
- i18n switch
- i18n switch (to keep on same page)
- /records pagination
- /records ajax bird contacts retrieval

View File

@ -13,11 +13,12 @@ import './styles/menu.css';
import './bootstrap';
import feather from 'feather-icons';
import { version } from 'core-js';
feather.replace();
/** Update css variables --{header, footer}-height
* by querying elements real height */
(function() {
(function () {
let css_root = document.querySelector(':root');
let header = document.getElementsByTagName('header')[0];
let header_height = header.clientHeight;
@ -25,7 +26,15 @@ feather.replace();
let footer = document.getElementsByTagName('footer')[0];
let footer_height = footer.clientHeight;
css_root.style.setProperty('--footer-height', footer_height + 'px');
})();
/** Add git version in web interface
*/
(function () {
let version_span = document.querySelector('.version-number');
version_span.textContent = `${VERSION}`;
console.debug("Version: " + VERSION);
})();
try {

View File

@ -1,25 +1,11 @@
import { Controller } from '@hotwired/stimulus';
import { delete_record } from '../utils/delete';
/* stimulusFetch: 'lazy' */
export default class extends Controller {
static targets = ['filename']
delete() {
let filename = this.filenameTarget.value;
let url = `/records/delete/${filename}`;
fetch(url, {
method: 'POST'
})
.then(response => {
if (response.ok) {
window.location.reload();
} else {
console.log(response);
}
})
.catch(error => {
console.log(error);
}
);
delete_record(filename);
}
}

View File

@ -0,0 +1,45 @@
const axios = require('axios').default;
import { Controller } from '@hotwired/stimulus';
import { delete_record } from '../utils/delete';
/* stimulusFetch: 'lazy' */
export default class extends Controller {
static targets = ['current'];
mark_as_verified() {
let selected = this.currentTarget.value;
let url = `/records/manage/verify/${selected}`;
axios.post(url)
.then(function (response) {
console.log(response);
}
).catch(function (error) {
console.error(error);
}
);
}
select_all() {
let selected = document.querySelectorAll(".select-record");
selected.forEach(function (item) {
item.checked = true;
});
}
delete_selected() {
let selected = document.querySelectorAll(".select-record:checked");
if (selected.length > 0) {
// confirm delete
if (confirm("Are you sure you want to delete these records?")) {
// delete
for (let file of selected) {
delete_record(file.value);
}
}
}
}
}

View File

@ -65,7 +65,9 @@ a {
z-index: 1;
}
.button, input[type=submit], input[type=button] {
.button,
input[type=submit],
input[type=button] {
background-color: #f1f1f1;
color: black;
border-radius: 5px;
@ -97,7 +99,8 @@ a {
word-wrap: break-word;
}
.button:hover, input:hover {
.button:hover,
input:hover {
background-color: #F3F4F6;
text-decoration: none;
transition-duration: 0.1s;
@ -111,30 +114,30 @@ a {
}
.button-main:focus:not(:focus-visible):not(.focus-visible) {
box-shadow: none;
outline: none;
box-shadow: none;
outline: none;
}
.button-main:hover {
background-color: #2c974b;
padding: 6px 16px;
background-color: #2c974b;
padding: 6px 16px;
}
.button-main:focus {
box-shadow: rgba(46, 164, 79, .4) 0 0 0 3px;
outline: none;
box-shadow: rgba(46, 164, 79, .4) 0 0 0 3px;
outline: none;
}
.button-main:disabled {
background-color: #94d3a2;
border-color: rgba(27, 31, 35, .1);
color: rgba(255, 255, 255, .8);
cursor: default;
background-color: #94d3a2;
border-color: rgba(27, 31, 35, .1);
color: rgba(255, 255, 255, .8);
cursor: default;
}
.button-main:active {
background-color: #298e46;
box-shadow: rgba(20, 70, 32, .2) 0 1px 0 inset;
background-color: #298e46;
box-shadow: rgba(20, 70, 32, .2) 0 1px 0 inset;
}
.button-main:active {
@ -143,7 +146,8 @@ a {
transition: none 0s;
}
.button:focus, input:focus {
.button:focus,
input:focus {
outline: 1px transparent;
border: solid blue 1px;
}
@ -179,7 +183,7 @@ header img.logo {
}
main {
min-height: calc(100vh - (var(--header-height, 4em) + var(--footer-height, 4em)) );
min-height: calc(100vh - (var(--header-height, 4em) + var(--footer-height, 4em)));
padding: 5em;
z-index: 0;
}
@ -201,11 +205,11 @@ footer a:hover {
font-style: italic;
}
li, td {
align-items: center;
li {
list-style: none;
}
/* .dropdown-button:hover {
.dropdown-toggle:hover {
background-color: #900;
color: white
}
@ -213,22 +217,58 @@ li, td {
.dropdown:hover .dropdown-content {
display: block;
}
*/
.dropdown-content {
display: none;
position: absolute;
min-width: 160px;
box-shadow: 0px 8px 16px 0px rgba(0, 0, 0, 0.2);
z-index: 1;
width: fit-content;
}
canvas {
display: block;
height: 100%;
width: 100%;
}
.language-selector {
position: absolute;
top: 0;
right: 0;
}
.language-selector .toggle {
position: absolute;
top: 0;
right: 0;
opacity: 0;
width: 5em;
height: 1em;
}
.touch {
cursor: pointer;
}
.touch::after {
content: "+";
}
.slide {
clear: both;
width: 100%;
height: 0px;
overflow: hidden;
text-align: center;
transition: height .4s ease;
box-shadow: 0px 8px 16px 0px rgba(0, 0, 0, 0.2);
}
.toggle:checked + .slide {
height: fit-content;
}
.scientific-name {
font-style: italic;
}
@ -294,4 +334,4 @@ canvas {
main {
padding: 1em;
}
}
}

View File

@ -0,0 +1,25 @@
const axios = require('axios').default;
function delete_record(filename) {
let url = `/records/delete/${filename}`;
axios.post(url)
.then(function(response) {
console.log(response);
})
.catch(function(error) {
console.error(error);
});
}
function delete_records(filenames) {
let url = "/records/delete/all";
axios.post(url)
.then(function(response) {
console.log(response);
})
.catch(function(error) {
console.error(error);
});
}
export { delete_record, delete_records };

View File

@ -1,6 +1,8 @@
#! /usr/bin/env bash
LANGUAGES="fr es"
# Extract and update translation files
php bin/console translation:extract --dump-messages fr
php bin/console translation:extract --force fr
for LANGUAGE in $LANGUAGES; do
php bin/console translation:extract --dump-messages $LANGUAGE
php bin/console translation:extract --force $LANGUAGE
done

View File

@ -14,15 +14,21 @@
"symfony/console": "6.1.*",
"symfony/dotenv": "6.1.*",
"symfony/flex": "^2",
"symfony/form": "6.1.*",
"symfony/framework-bundle": "6.1.*",
"symfony/mailer": "6.1.*",
"symfony/proxy-manager-bridge": "6.1.*",
"symfony/runtime": "6.1.*",
"symfony/security-bundle": "6.1.*",
"symfony/translation": "6.1.*",
"symfony/twig-bundle": "6.1.*",
"symfony/validator": "6.1.*",
"symfony/webpack-encore-bundle": "^1.15",
"symfony/yaml": "6.1.*",
"symfonycasts/verify-email-bundle": "^1.11",
"twig/extra-bundle": "^2.12|^3.0",
"twig/intl-extra": "^3.4",
"twig/string-extra": "^3.4",
"twig/twig": "^2.12|^3.0"
},
"config": {
@ -78,6 +84,8 @@
}
},
"require-dev": {
"symfony/maker-bundle": "^1.45"
"symfony/maker-bundle": "^1.45",
"symfony/stopwatch": "6.1.*",
"symfony/web-profiler-bundle": "6.1.*"
}
}

1460
www/composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -9,4 +9,7 @@ return [
Symfony\Bundle\MakerBundle\MakerBundle::class => ['dev' => true],
Doctrine\Bundle\DoctrineBundle\DoctrineBundle::class => ['all' => true],
Doctrine\Bundle\MigrationsBundle\DoctrineMigrationsBundle::class => ['all' => true],
Symfony\Bundle\SecurityBundle\SecurityBundle::class => ['all' => true],
SymfonyCasts\Bundle\VerifyEmail\SymfonyCastsVerifyEmailBundle::class => ['all' => true],
Symfony\Bundle\WebProfilerBundle\WebProfilerBundle::class => ['dev' => true, 'test' => true],
];

View File

@ -1,6 +1,14 @@
doctrine:
dbal:
url: '%env(resolve:DATABASE_URL)%'
default_connection: default
connections:
default:
dbname: birdnet_default_db
url: '%env(resolve:DATABASE_DEFAULT_URL)%'
observations:
dbname: birdnet_observations_db
url: '%env(resolve:DATABASE_OBSERVATIONS_URL)%'
wrapper_class: App\AppBundle\ConnectionObservations
# IMPORTANT: You MUST configure your server version,
# either here or in the DATABASE_URL env var (see .env file)

View File

@ -0,0 +1,3 @@
framework:
mailer:
dsn: '%env(MAILER_DSN)%'

View File

@ -0,0 +1,43 @@
security:
# https://symfony.com/doc/current/security.html#registering-the-user-hashing-passwords
password_hashers:
Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface: 'auto'
# https://symfony.com/doc/current/security.html#loading-the-user-the-user-provider
providers:
# used to reload user from session & other features (e.g. switch_user)
app_user_provider:
entity:
class: App\Entity\User
property: username
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
main:
lazy: true
provider: app_user_provider
# activate different ways to authenticate
# https://symfony.com/doc/current/security.html#the-firewall
# https://symfony.com/doc/current/security/impersonating_user.html
# switch_user: true
# Easy way to control access for large sections of your site
# Note: Only the *first* access control that matches will be used
access_control:
# - { path: ^/admin, roles: ROLE_ADMIN }
# - { path: ^/profile, roles: ROLE_USER }
when@test:
security:
password_hashers:
# By default, password hashers are resource intensive and take time. This is
# important to generate secure password hashes. In tests however, secure hashes
# are not important, waste resources and increase test times. The following
# reduces the work factor to the lowest possible values.
Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface:
algorithm: auto
cost: 4 # Lowest possible value for bcrypt
time_cost: 3 # Lowest possible value for argon
memory_cost: 10 # Lowest possible value for argon

View File

@ -0,0 +1,13 @@
framework:
validation:
email_validation_mode: html5
# Enables validator auto-mapping support.
# For instance, basic validation constraints will be inferred from Doctrine's metadata.
#auto_mapping:
# App\Entity\: []
when@test:
framework:
validation:
not_compromised_password: false

View File

@ -0,0 +1,17 @@
when@dev:
web_profiler:
toolbar: true
intercept_redirects: false
framework:
profiler:
only_exceptions: false
collect_serializer_data: true
when@test:
web_profiler:
toolbar: false
intercept_redirects: false
framework:
profiler: { collect: false }

View File

@ -0,0 +1,8 @@
when@dev:
web_profiler_wdt:
resource: '@WebProfilerBundle/Resources/config/routing/wdt.xml'
prefix: /_wdt
web_profiler_profiler:
resource: '@WebProfilerBundle/Resources/config/routing/profiler.xml'
prefix: /_profiler

View File

@ -0,0 +1,4 @@
<?php // dev.decrypt.private on Thu, 18 Aug 2022 11:24:08 +0000
// SYMFONY_DECRYPTION_SECRET=Wa3L9epQCNyArZ9uGqChJro5t3n97TKgwVyoqmHHZnCtzcDIHxxI6HD6Vd1HRW46d+I4Pc3aPKh6NMIJ309TeQ==
return "Y\xAD\xCB\xF5\xEAP\x08\xDC\x80\xAD\x9Fn\x1A\xA0\xA1\x26\xBA9\xB7y\xFD\xED2\xA0\xC1\x5C\xA8\xAAa\xC7fp\xAD\xCD\xC0\xC8\x1F\x1CH\xE8p\xFAU\xDDGEn\x3Aw\xE28\x3D\xCD\xDA\x3C\xA8z4\xC2\x09\xDFOSy";

View File

@ -0,0 +1,3 @@
<?php // dev.encrypt.public on Thu, 18 Aug 2022 11:24:08 +0000
return "\xAD\xCD\xC0\xC8\x1F\x1CH\xE8p\xFAU\xDDGEn\x3Aw\xE28\x3D\xCD\xDA\x3C\xA8z4\xC2\x09\xDFOSy";

View File

@ -0,0 +1,3 @@
<?php // prod.encrypt.public on Thu, 18 Aug 2022 11:25:20 +0000
return "\xFBm\xD4\xAF\xF1b\x88\x93\x96\xC3\x9DJSJ\x0A\xDB\xA0\xF6\xBCp\x98\x96\xC7\xA6\xD2\x82\x28\x28y3\xCF\x11";

View File

@ -4,7 +4,11 @@
# Put parameters here that don't need to change on each machine where the app is deployed
# https://symfony.com/doc/current/best_practices.html#use-parameters-for-application-configuration
parameters:
app.records_disk: '%env(string:RECORDS_DISK)%'
app.records_dir: '%env(resolve:RECORDS_DIR)%'
app.supported_locales: 'en|fr'
app.default_locale: 'en'
services:
# default configuration for services in *this* file
@ -21,5 +25,7 @@ services:
- '../src/Entity/'
- '../src/Kernel.php'
App\AppBundle\ConnectionObservations: '@doctrine.dbal.observations_connection'
# add more service definitions when explicit configuration is needed
# please note that last definitions always *replace* previous ones

View File

@ -6,3 +6,9 @@ services:
ports:
- "5432"
###< doctrine/doctrine-bundle ###
###> symfony/mailer ###
mailer:
image: schickling/mailcatcher
ports: [1025, 1080]
###< symfony/mailer ###

View File

@ -0,0 +1,33 @@
<?php
declare(strict_types=1);
namespace DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Auto-generated Migration: Please modify to your needs!
*/
final class Version20220818163807 extends AbstractMigration
{
public function getDescription(): string
{
return '';
}
public function up(Schema $schema): void
{
// this up() migration is auto-generated, please modify it to your needs
$this->addSql('CREATE TABLE user (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, username VARCHAR(180) NOT NULL, roles CLOB NOT NULL --(DC2Type:json)
, password VARCHAR(255) NOT NULL)');
$this->addSql('CREATE UNIQUE INDEX UNIQ_8D93D649F85E0677 ON user (username)');
}
public function down(Schema $schema): void
{
// this down() migration is auto-generated, please modify it to your needs
$this->addSql('DROP TABLE user');
}
}

View File

@ -0,0 +1,44 @@
<?php
declare(strict_types=1);
namespace DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
/**
* Auto-generated Migration: Please modify to your needs!
*/
final class Version20220818165607 extends AbstractMigration
{
public function getDescription(): string
{
return '';
}
public function up(Schema $schema): void
{
// this up() migration is auto-generated, please modify it to your needs
$this->addSql('CREATE TEMPORARY TABLE __temp__user AS SELECT id, username, roles, password FROM user');
$this->addSql('DROP TABLE user');
$this->addSql('CREATE TABLE user (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, username VARCHAR(180) NOT NULL, roles CLOB NOT NULL --(DC2Type:json)
, password VARCHAR(255) NOT NULL, email VARCHAR(255) NOT NULL)');
$this->addSql('INSERT INTO user (id, username, roles, password) SELECT id, username, roles, password FROM __temp__user');
$this->addSql('DROP TABLE __temp__user');
$this->addSql('CREATE UNIQUE INDEX UNIQ_8D93D649F85E0677 ON user (username)');
$this->addSql('CREATE UNIQUE INDEX UNIQ_8D93D649E7927C74 ON user (email)');
}
public function down(Schema $schema): void
{
// this down() migration is auto-generated, please modify it to your needs
$this->addSql('CREATE TEMPORARY TABLE __temp__user AS SELECT id, username, roles, password FROM user');
$this->addSql('DROP TABLE user');
$this->addSql('CREATE TABLE user (id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, username VARCHAR(180) NOT NULL, roles CLOB NOT NULL --(DC2Type:json)
, password VARCHAR(255) NOT NULL)');
$this->addSql('INSERT INTO user (id, username, roles, password) SELECT id, username, roles, password FROM __temp__user');
$this->addSql('DROP TABLE __temp__user');
$this->addSql('CREATE UNIQUE INDEX UNIQ_8D93D649F85E0677 ON user (username)');
}
}

13378
www/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -4,6 +4,7 @@
"@symfony/stimulus-bridge": "^3.2.0",
"@symfony/webpack-encore": "^3.0.0",
"core-js": "^3.23.0",
"git-revision-webpack-plugin": "^5.0.0",
"regenerator-runtime": "^0.13.9",
"webpack-notifier": "^1.15.0"
},
@ -16,6 +17,7 @@
"build": "encore production --progress"
},
"dependencies": {
"axios": "^0.27.2",
"feather-icons": "^4.29.0"
}
}

View File

@ -0,0 +1,10 @@
<?php
namespace App\AppBundle;
use Doctrine\DBAL\Connection;
class ConnectionObservations extends Connection
{
}
// Reference: https://stackoverflow.com/questions/46235336/autowire-specific-dbal-connection-when-using-multiple-of-them

View File

@ -4,23 +4,24 @@ namespace App\Controller;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Doctrine\DBAL\Connection;
use App\Entity\User;
class AuthController extends AbstractController
{
private Connection $connection;
{
/**
* @Route("/auth", name="auth")
* @Route("/{_locale<%app.supported_locales%>}/auth", name="auth_i18n")
*/
public function index(Connection $connection)
public function index()
{
return $this->redirectToRoute("login");
}
/**
* @Route("/auth/login", name="login")
* @Route("/{_locale<%app.supported_locales%>}/auth/login", name="login_i18n")
*/
public function login()
{
@ -28,4 +29,17 @@ class AuthController extends AbstractController
]);
}
/**
* @Route("/auth/register", name="register")
* @Route("/{_locale<%app.supported_locales%>}/auth/register", name="register_i18n")
*/
public function register(UserPasswordHasherInterface $passwordHasher)
{
$user = new User();
$plaintextPassword = "";
$hashedPassword = $passwordHasher->hashPassword($user, $plaintextPassword);
$user->setPassword($hashedPassword);
}
}

View File

@ -0,0 +1,35 @@
<?php
namespace App\Controller;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
class DisksController extends AbstractController
{
/**
* @Route("/disks/", name="disks_index")
* @Route("{_locale}/disks/", name="disks_index_i18n")
*/
public function index() {
return $this->render('disks/index.html.twig', [
"disk"=>$this->get_disk_usage()
]);
}
private function get_disk_usage()
{
$usage = [];
$disk = $this->getParameter('app.records_disk');
$cmd = "df -h | grep $disk | awk '{print $5}'";
$output = shell_exec($cmd);
$usage["device"] = $disk;
$usage["usage"] = $output;
$cmd = "df -h | grep $disk | awk '{print $4}'";
$output = shell_exec($cmd);
$usage["available"] = $output;
return $usage;
}
}

View File

@ -5,18 +5,23 @@ use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Doctrine\DBAL\Connection;
use App\AppBundle\ConnectionObservations;
class HomeController extends AbstractController
{
private Connection $connection;
private ConnectionObservations $connection;
/**
* @Route("/", name="home")
*/
public function index(Connection $connection)
public function __construct(ConnectionObservations $connection)
{
$this->connection = $connection;
}
/**
* @Route("", name="home")
* @Route("/{_locale<%app.supported_locales%>}/", name="home_i18n")
*/
public function index()
{
return $this->render('index.html.twig', [
"stats" => $this->get_stats(),
"charts" => $this->last_chart_generated(),
@ -25,6 +30,7 @@ class HomeController extends AbstractController
/**
* @Route("/about", name="about")
* @Route("/{_locale<%app.supported_locales%>}/about", name="about_i18n")
*/
public function about()
{

View File

@ -12,6 +12,7 @@ class LogsController extends AbstractController
private $allowed_services = "recording analyzis miner plotter";
/**
* @Route("/logs/{service}", name="logs")
* @Route("/{_locale<%app.supported_locales%>}/logs/{service}", name="logs_i18n")
*/
public function logs($service = "all")
{

View File

@ -6,16 +6,22 @@ use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Doctrine\DBAL\Connection;
use App\AppBundle\ConnectionObservations;
class RecordsController extends AbstractController
{
private Connection $connection;
private ConnectionObservations $connection;
public function __construct(ConnectionObservations $connection)
{
$this->connection = $connection;
}
/**
* @Route("/records/{date}", name="records_index")
* @Route("/records/{date}", name="records", defaults={"date" = "now"})
* @Route("/{_locale<%app.supported_locales%>}/records/{date}", name="records_i18n")
*/
public function records_index($date = "now")
public function records_index($date="now")
{
if ($date == "now") {
$date = date("Y-m-d");
@ -29,13 +35,40 @@ class RecordsController extends AbstractController
}
/**
* @Route("/records/delete/{basename}", name="record_delete")
* @Route("/records/delete/all", name="record_selection_delete")
*/
public function delete_record(Connection $connection, $basename)
public function delete_all(Request $request)
{
$this->connection = $connection;
$this->remove_record_by_basename($basename);
return $this->redirectToRoute('records_index');
$records = $request->request->filenames;
foreach($records as $record) {
$alright = $this->remove_record_by_basename($record);
if ($alright) {
return new Response("Selected records deleted.", Response::HTTP_OK);
}
}
}
/**
* @Route("/records/delete/{record}", name="record_delete")
*/
public function record_delete($record)
{
$alright = $this->remove_record_by_basename($record);
if ($alright) {
return new Response("Record $record deleted.", Response::HTTP_OK);
} else {
return new Response("Record deletion failed", Response::HTTP_INTERNAL_SERVER_ERROR);
}
}
/**
* @Route("/records/best", name="records_best")
* @Route("/{_locale<%app.supported_locales%>}/records/best", name="records_best_i18n")
*/
public function best_records()
{
$this->render('records/best.html.twig', [
]);
}
private function list_records()
@ -83,6 +116,9 @@ class RecordsController extends AbstractController
rmdir($model_out_dir);
/** Remove database entry associated with this filename */
$this->remove_observations_from_record($basename);
return true;
} else {
return false;
}
}

View File

@ -0,0 +1,90 @@
<?php
namespace App\Controller;
use App\Entity\User;
use App\Form\RegistrationFormType;
use App\Security\EmailVerifier;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bridge\Twig\Mime\TemplatedEmail;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Mime\Address;
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Contracts\Translation\TranslatorInterface;
use SymfonyCasts\Bundle\VerifyEmail\Exception\VerifyEmailExceptionInterface;
class RegistrationController extends AbstractController
{
private EmailVerifier $emailVerifier;
public function __construct(EmailVerifier $emailVerifier)
{
$this->emailVerifier = $emailVerifier;
}
/**
* @Route("/auth/register", name="register")
* @Route("/{_locale<%app.supported_locales%>}/auth/register", name="register_i18n")
*/
public function register(Request $request, UserPasswordHasherInterface $userPasswordHasher, EntityManagerInterface $entityManager): Response
{
$user = new User();
$form = $this->createForm(RegistrationFormType::class, $user);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
// encode the plain password
$user->setPassword(
$userPasswordHasher->hashPassword(
$user,
$form->get('plainPassword')->getData()
)
);
$entityManager->persist($user);
$entityManager->flush();
// generate a signed url and email it to the user
$this->emailVerifier->sendEmailConfirmation('app_verify_email', $user,
(new TemplatedEmail())
->from(new Address('mailer@1ib.re', 'BirdNET-stream Mail Bot'))
->to($user->getEmail())
->subject('Please Confirm your Email')
->htmlTemplate('registration/confirmation_email.html.twig')
);
// do anything else you need here, like send an email
return $this->redirectToRoute('login_i18n');
}
return $this->render('registration/register.html.twig', [
'registrationForm' => $form->createView(),
]);
}
/**
* @Route("/auth/verify/email", name="verify_email")
* @Route("/{_locale<%app.supported_locales%>}/auth/verify/email", name="verify_email_i18n")
*/
public function verifyUserEmail(Request $request, TranslatorInterface $translator): Response
{
$this->denyAccessUnlessGranted('IS_AUTHENTICATED_FULLY');
// validate email confirmation link, sets User::isVerified=true and persists
try {
$this->emailVerifier->handleEmailConfirmation($request, $this->getUser());
} catch (VerifyEmailExceptionInterface $exception) {
$this->addFlash('verify_email_error', $translator->trans($exception->getReason(), [], 'VerifyEmailBundle'));
return $this->redirectToRoute('register_i18n');
}
// @TODO Change the redirect on success and handle or remove the flash message in your templates
$this->addFlash('success', 'Your email address has been verified.');
return $this->redirectToRoute('login_i18n');
}
}

View File

@ -14,7 +14,8 @@ class ServicesController extends AbstractController
/**
* @Route("/services/status", name="service_status")
* @Route("/services/status", name="services_status")
* @Route("/{_locale<%app.supported_locales%>}/services/status", name="services_status_i18n")
*/
public function service_status() {
$status = array_map(function($service) {
@ -29,7 +30,8 @@ class ServicesController extends AbstractController
}
/**
* @Route("/services/manage/{action}/{service}", name="service_manager")
* @Route("/services/manage/{action}/{service}", name="services_manager")
* @Route("/{_locale<%app.supported_locales%>}/services/manage/{action}/{service}", name="service_manager_i18n")
*/
public function service_manage($action, $service="all")
{

View File

@ -10,8 +10,9 @@ class SpectroController extends AbstractController
{
/**
* @Route("/spectro", name="spectro")
* @Route("/{_locale<%app.supported_locales%>}/spectro", name="spectro_i18n")
*/
public function about()
public function spectro()
{
return $this->render('spectro/index.html.twig', [

View File

@ -0,0 +1,27 @@
<?php
namespace App\Controller;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use App\AppBundle\ConnectionObservations;
class StatsController extends AbstractController
{
private ConnectionObservations $connection;
public function __construct(ConnectionObservations $connection)
{
$this->connection = $connection;
}
/**
* @Route("/stats", name="stats")
* @Route("/{_locale<%app.supported_locales%>}/stats", name="stats_i18n")
*/
public function index()
{
return $this->render("stats/index.html.twig");
}
}

View File

@ -5,25 +5,30 @@ use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Doctrine\DBAL\Connection;
use App\AppBundle\ConnectionObservations;
class TodayController extends AbstractController
{
{ private ConnectionObservations $connection;
private Connection $connection;
public function __construct(ConnectionObservations $connection)
{
$this->connection = $connection;
}
/**
* @Route("/today", name="today")
* @Route("/{_locale<%app.supported_locales%>}/today", name="today_i18n")
*/
public function today(Connection $connection)
public function today(ConnectionObservations $connection)
{
return $this->redirectToRoute("today_species");
return $this->redirectToRoute("today_species_i18n");
}
/**
* @Route("/today/species", name="today_species")
* @Route("/{_locale<%app.supported_locales%>}/today/species", name="today_species_i18n")
*/
public function today_species_page(Connection $connection)
public function today_species_page(ConnectionObservations $connection)
{
$this->connection = $connection;
$date = date('Y-m-d');
@ -35,10 +40,10 @@ class TodayController extends AbstractController
/**
* @Route("/today/species/{id}", name="today_species_id")
* @Route("/{_locale<%app.supported_locales%>}/today/species/{id}", name="today_species_id_i18n")
*/
public function today_species_by_id(Connection $connection, $id)
public function today_species_by_id($id)
{
$this->connection = $connection;
$date = date('Y-m-d');
return $this->render('today/species.html.twig', [
"date" => $date,
@ -49,18 +54,19 @@ class TodayController extends AbstractController
/**
* @Route("/today/{date}", name="today_date")
* @Route("/{_locale<%app.supported_locales%>}/today/{date}", name="today_date_i18n")
*/
public function today_date(Connection $connection, $date)
public function today_date($date="2022-08-13")
{
return $this->redirectToRoute('today_species_date', array('date' => $date));
}
/**
* @Route("/today/{date}/species", name="today_species_date")
* @Route("/{_locale<%app.supported_locales%>}/today/{date}/species", name="today_species_date_i18n")
*/
public function today_species_by_date(Connection $connection, $date)
public function today_species_by_date($date="2022-08-13")
{
$this->connection = $connection;
return $this->render('today/index.html.twig', [
"date" => $date,
"results" => $this->recorded_species_by_date($date)
@ -69,10 +75,10 @@ class TodayController extends AbstractController
/**
* @Route("/today/{date}/species/{id}", name="today_species_id_and_date")
* @Route("/{_locale<%app.supported_locales%>}/today/{date}/species/{id}", name="today_species_id_and_date_i18n")
*/
public function today_species_by_id_and_date(Connection $connection, $date, $id)
public function today_species_by_id_and_date($id, $date="2022-08-13")
{
$this->connection = $connection;
return $this->render('today/species.html.twig', [
"date" => $date,
"results" => $this->recorded_species_by_id_and_date($id, $date)
@ -141,4 +147,4 @@ class TodayController extends AbstractController
$result = $stmt->executeQuery();
return $result->fetchAllAssociative();
}
}
}

131
www/src/Entity/User.php Normal file
View File

@ -0,0 +1,131 @@
<?php
namespace App\Entity;
use App\Repository\UserRepository;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface;
use Symfony\Component\Security\Core\User\UserInterface;
#[ORM\Entity(repositoryClass: UserRepository::class)]
#[UniqueEntity(fields: ['username'], message: 'There is already an account with this username')]
class User implements UserInterface, PasswordAuthenticatedUserInterface
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column]
private ?int $id = null;
#[ORM\Column(length: 180, unique: true)]
private ?string $username = null;
#[ORM\Column]
private array $roles = [];
/**
* @var string The hashed password
*/
#[ORM\Column]
private ?string $password = null;
#[ORM\Column(length: 255, unique: true)]
private ?string $email = null;
#[ORM\Column(type: 'boolean')]
private $isVerified = false;
public function getId(): ?int
{
return $this->id;
}
public function getUsername(): ?string
{
return $this->username;
}
public function setUsername(string $username): self
{
$this->username = $username;
return $this;
}
/**
* A visual identifier that represents this user.
*
* @see UserInterface
*/
public function getUserIdentifier(): string
{
return (string) $this->username;
}
/**
* @see UserInterface
*/
public function getRoles(): array
{
$roles = $this->roles;
// guarantee every user at least has ROLE_USER
$roles[] = 'ROLE_USER';
return array_unique($roles);
}
public function setRoles(array $roles): self
{
$this->roles = $roles;
return $this;
}
/**
* @see PasswordAuthenticatedUserInterface
*/
public function getPassword(): string
{
return $this->password;
}
public function setPassword(string $password): self
{
$this->password = $password;
return $this;
}
/**
* @see UserInterface
*/
public function eraseCredentials()
{
// If you store any temporary, sensitive data on the user, clear it here
// $this->plainPassword = null;
}
public function getEmail(): ?string
{
return $this->email;
}
public function setEmail(string $email): self
{
$this->email = $email;
return $this;
}
public function isVerified(): bool
{
return $this->isVerified;
}
public function setIsVerified(bool $isVerified): self
{
$this->isVerified = $isVerified;
return $this;
}
}

View File

@ -0,0 +1,55 @@
<?php
namespace App\Form;
use App\Entity\User;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
use Symfony\Component\Form\Extension\Core\Type\PasswordType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Validator\Constraints\IsTrue;
use Symfony\Component\Validator\Constraints\Length;
use Symfony\Component\Validator\Constraints\NotBlank;
class RegistrationFormType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder
->add('username')
->add('agreeTerms', CheckboxType::class, [
'mapped' => false,
'constraints' => [
new IsTrue([
'message' => 'You should agree to our terms.',
]),
],
])
->add('plainPassword', PasswordType::class, [
// instead of being set onto the object directly,
// this is read and encoded in the controller
'mapped' => false,
'attr' => ['autocomplete' => 'new-password'],
'constraints' => [
new NotBlank([
'message' => 'Please enter a password',
]),
new Length([
'min' => 6,
'minMessage' => 'Your password should be at least {{ limit }} characters',
// max length allowed by Symfony for security reasons
'max' => 4096,
]),
],
])
;
}
public function configureOptions(OptionsResolver $resolver): void
{
$resolver->setDefaults([
'data_class' => User::class,
]);
}
}

View File

@ -0,0 +1,83 @@
<?php
namespace App\Repository;
use App\Entity\User;
use Doctrine\Bundle\DoctrineBundle\Repository\ServiceEntityRepository;
use Doctrine\Persistence\ManagerRegistry;
use Symfony\Component\Security\Core\Exception\UnsupportedUserException;
use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface;
use Symfony\Component\Security\Core\User\PasswordUpgraderInterface;
/**
* @extends ServiceEntityRepository<User>
*
* @method User|null find($id, $lockMode = null, $lockVersion = null)
* @method User|null findOneBy(array $criteria, array $orderBy = null)
* @method User[] findAll()
* @method User[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
*/
class UserRepository extends ServiceEntityRepository implements PasswordUpgraderInterface
{
public function __construct(ManagerRegistry $registry)
{
parent::__construct($registry, User::class);
}
public function add(User $entity, bool $flush = false): void
{
$this->getEntityManager()->persist($entity);
if ($flush) {
$this->getEntityManager()->flush();
}
}
public function remove(User $entity, bool $flush = false): void
{
$this->getEntityManager()->remove($entity);
if ($flush) {
$this->getEntityManager()->flush();
}
}
/**
* Used to upgrade (rehash) the user's password automatically over time.
*/
public function upgradePassword(PasswordAuthenticatedUserInterface $user, string $newHashedPassword): void
{
if (!$user instanceof User) {
throw new UnsupportedUserException(sprintf('Instances of "%s" are not supported.', \get_class($user)));
}
$user->setPassword($newHashedPassword);
$this->add($user, true);
}
// /**
// * @return User[] Returns an array of User objects
// */
// public function findByExampleField($value): array
// {
// return $this->createQueryBuilder('u')
// ->andWhere('u.exampleField = :val')
// ->setParameter('val', $value)
// ->orderBy('u.id', 'ASC')
// ->setMaxResults(10)
// ->getQuery()
// ->getResult()
// ;
// }
// public function findOneBySomeField($value): ?User
// {
// return $this->createQueryBuilder('u')
// ->andWhere('u.exampleField = :val')
// ->setParameter('val', $value)
// ->getQuery()
// ->getOneOrNullResult()
// ;
// }
}

View File

@ -0,0 +1,52 @@
<?php
namespace App\Security;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bridge\Twig\Mime\TemplatedEmail;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Mailer\MailerInterface;
use Symfony\Component\Security\Core\User\UserInterface;
use SymfonyCasts\Bundle\VerifyEmail\Exception\VerifyEmailExceptionInterface;
use SymfonyCasts\Bundle\VerifyEmail\VerifyEmailHelperInterface;
class EmailVerifier
{
public function __construct(
private VerifyEmailHelperInterface $verifyEmailHelper,
private MailerInterface $mailer,
private EntityManagerInterface $entityManager
) {
}
public function sendEmailConfirmation(string $verifyEmailRouteName, UserInterface $user, TemplatedEmail $email): void
{
$signatureComponents = $this->verifyEmailHelper->generateSignature(
$verifyEmailRouteName,
$user->getId(),
$user->getEmail()
);
$context = $email->getContext();
$context['signedUrl'] = $signatureComponents->getSignedUrl();
$context['expiresAtMessageKey'] = $signatureComponents->getExpirationMessageKey();
$context['expiresAtMessageData'] = $signatureComponents->getExpirationMessageData();
$email->context($context);
$this->mailer->send($email);
}
/**
* @throws VerifyEmailExceptionInterface
*/
public function handleEmailConfirmation(Request $request, UserInterface $user): void
{
$this->verifyEmailHelper->validateEmailConfirmation($request->getUri(), $user->getId(), $user->getEmail());
$user->setIsVerified(true);
$this->entityManager->persist($user);
$this->entityManager->flush();
}
}

View File

@ -90,6 +90,18 @@
"src/Kernel.php"
]
},
"symfony/mailer": {
"version": "6.1",
"recipe": {
"repo": "github.com/symfony/recipes",
"branch": "main",
"version": "4.3",
"ref": "97a61eabb351d7f6cb7702039bcfe07fe9d7e03c"
},
"files": [
"config/packages/mailer.yaml"
]
},
"symfony/maker-bundle": {
"version": "1.45",
"recipe": {
@ -112,6 +124,18 @@
"config/routes.yaml"
]
},
"symfony/security-bundle": {
"version": "6.1",
"recipe": {
"repo": "github.com/symfony/recipes",
"branch": "main",
"version": "6.0",
"ref": "8a5b112826f7d3d5b07027f93786ae11a1c7de48"
},
"files": [
"config/packages/security.yaml"
]
},
"symfony/translation": {
"version": "6.1",
"recipe": {
@ -138,6 +162,31 @@
"templates/base.html.twig"
]
},
"symfony/validator": {
"version": "6.1",
"recipe": {
"repo": "github.com/symfony/recipes",
"branch": "main",
"version": "5.3",
"ref": "c32cfd98f714894c4f128bb99aa2530c1227603c"
},
"files": [
"config/packages/validator.yaml"
]
},
"symfony/web-profiler-bundle": {
"version": "6.1",
"recipe": {
"repo": "github.com/symfony/recipes",
"branch": "main",
"version": "6.1",
"ref": "e42b3f0177df239add25373083a564e5ead4e13a"
},
"files": [
"config/packages/web_profiler.yaml",
"config/routes/web_profiler.yaml"
]
},
"symfony/webpack-encore-bundle": {
"version": "1.15",
"recipe": {
@ -157,6 +206,9 @@
"webpack.config.js"
]
},
"symfonycasts/verify-email-bundle": {
"version": "v1.11.0"
},
"twig/extra-bundle": {
"version": "v3.4.0"
}

View File

@ -1,5 +1,5 @@
<!DOCTYPE html>
<html lang="en">
<html lang="{{ app.request.locale }}">
<head>
<meta charset="UTF-8">
<title>
@ -27,6 +27,7 @@
</div>
</a>
</header>
{% include "utils/locale-switcher.html.twig" %}
<main>
{% block content %}
<p>Welcome to BirdNET-stream !</p>

View File

@ -0,0 +1,14 @@
{% block content %}
<h2>{{ "Disk usage"|trans }}</h2>
<div class="disk">
<span class="disk-device">
{{ disk.device }}
</span>
<span>
{{ disk.usage }}
</span>
<span>
{{ disk.available }}
</span>
</div>
{% endblock %}

View File

@ -9,4 +9,7 @@
</span>
<a href="https://samuel.ortion.fr/">Samuel ORTION</a>
</p>
<p class="version">
version: <span class="version-number">v</span>
</p>
</footer>

View File

@ -8,58 +8,43 @@
<ul>
{% include 'utils/nav-item.html.twig' with {
route: 'home',
url: '/',
name: 'Home'|trans
text: 'Home'|trans
} %}
{% include 'utils/nav-item.html.twig' with {
route: 'about',
url: '/about',
name: 'About'|trans
text: 'About'|trans
} %}
{% include 'utils/nav-item.html.twig' with {
route: 'today',
url: '/today',
name: "Today's Detections"|trans
text: "Today's Detections"|trans
} %}
{% include 'utils/nav-item.html.twig' with {
route: 'spectro',
url: '/spectro',
name: 'Live Spectrogram'|trans
text: 'Live Spectrogram'|trans
} %}
{% include 'utils/nav-item.html.twig' with {
route: 'stats',
url: '/stats',
name: 'Statistics'|trans
text: 'Statistics'|trans
} %}
<li class="dropdown">
{% include 'utils/nav-item.html.twig' with {
route: 'records',
url: '/records',
name: 'Recordings'|trans
text: 'Recordings'|trans
} %}
<ul class="dropdown-content">
{% include 'utils/nav-item.html.twig' with {
route: 'bests',
url: '/records/best',
name: 'Best Recordings'|trans
route: 'records_best',
text: 'Best Recordings'|trans
} %}
</ul>
</li>
{% include 'utils/nav-item.html.twig' with {
route: 'bests',
url: '/records/best',
name: 'Best Recordings'|trans
} %}
{% include 'utils/nav-item.html.twig' with {
route: 'logs',
url: '/logs',
name: 'View Logs'|trans
text: 'View Logs'|trans
} %}
{% include 'utils/nav-item.html.twig' with {
route: 'status',
url: '/services/status',
name: 'Status'|trans
route: 'services_status',
text: 'Status'|trans
} %}
</ul>
</div>

View File

@ -0,0 +1 @@
<p>{{ "Not implemented yet"|trans </p>

View File

@ -0,0 +1,11 @@
<h1>Hi! Please confirm your email!</h1>
<p>
Please confirm your email address by clicking the following link: <br><br>
<a href="{{ signedUrl }}">Confirm my Email</a>.
This link will expire in {{ expiresAtMessageKey|trans(expiresAtMessageData, 'VerifyEmailBundle') }}.
</p>
<p>
Cheers!
</p>

View File

@ -0,0 +1,21 @@
{% extends 'base.html.twig' %}
{% block title %}Register{% endblock %}
{% block body %}
{% for flash_error in app.flashes('verify_email_error') %}
<div class="alert alert-danger" role="alert">{{ flash_error }}</div>
{% endfor %}
<h1>Register</h1>
{{ form_start(registrationForm) }}
{{ form_row(registrationForm.username) }}
{{ form_row(registrationForm.plainPassword, {
label: 'Password'
}) }}
{{ form_row(registrationForm.agreeTerms) }}
<button type="submit" class="btn">Register</button>
{{ form_end(registrationForm) }}
{% endblock %}

View File

@ -0,0 +1,3 @@
{% block content %}
{{ "Not implemented yet"|trans }}
{% endblock %}

View File

@ -1,31 +1,38 @@
{% extends "base.html.twig" %}
{% extends 'base.html.twig' %}
{% block content %}
<h2>
{% set today = "now" | date("Y-m-d") %}
{% if today == date %}
{{ "Today's detected species" | trans }}
{% else %}
{{ "Detected species on" | trans }}
{{ date | format_datetime("full", "none") }}
{% endif %}
</h2>
{% set endpoint = "today" %}
{% include "utils/calendar.html.twig" %}
{# Display a list of records if any, else, print message #}
{% if results is defined and results | length > 0 %}
<ul>
{% for sp in results %}
<li class="species">
<a href="./species/{{ sp['taxon_id'] }}">
<span class="scientific-name">{{ sp["scientific_name"] }}
(</span>
<span class="common-name">{{ sp["common_name"] }}</span>)
</a>
</li>
{% endfor %}
</ul>
{% else %}
<p>{{ "No species detected this day" | trans }}</p>
{% endif %}
<h2>
{% set today = 'now'|date('Y-m-d') %}
{% if today == date %}
{{ "Today's detected species"|trans }}
{% else %}
{{ 'Detected species on'|trans }}
{{ date|format_datetime('full', 'none') }}
{% endif %}
</h2>
{% set endpoint = 'today' %}
{% include 'utils/calendar.html.twig' %}
{# Display a list of records if any, else, print message #}
{% if results is defined and (results|length) > 0 %}
<ul>
{% for sp in results %}
<li class="species">
<span class="scientific-name">
{{ sp['scientific_name'] }}
(
</span>
<span class="common-name">{{ sp['common_name'] }}</span>)
<span class="link">
<a href="./species/{{ sp['taxon_id'] }}">
<i data-feather="link"></i>
</a>
</span>
</li>
{% endfor %}
</ul>
{% else %}
<p>
{{ 'No species detected this day'|trans }}
</p>
{% endif %}
{% endblock %}

View File

@ -1,8 +1,10 @@
<div class="manager">
<button class="button" id="select-all" title="{{ "Select all" | trans }}">
<div class="manager" {{ stimulus_controller('manage-records') }}>
<button class="button" id="select-all" title="{{ "Select all" | trans }}" {{ stimulus_action('manage-records', 'select_all') }}>
<i data-feather="check-square"></i>
</button>
<button class="delete button" title="{{ "Delete selected" | trans }}">
<button class="delete button" title="{{ "Delete selected" | trans }}" {{ stimulus_action('manage-records', 'delete_selected') }}>
<i data-feather="trash-2"></i>
</button>
</div>

View File

@ -47,20 +47,31 @@
</thead>
<tbody>
{% for record in records %}
<tr>
<tr {{ stimulus_controller('manage-records') }}>
<td>
<input title="{{ "Select this record" | trans }}" type="checkbox" name="select-file" value="{{ record.audio_file }}">
<input title="{{ "Select this record" | trans }}" type="checkbox" class="select-record" value="{{ record.audio_file }}" {{ stimulus_target('manage-records', 'current') }}>
</td>
<td>
<a title="{{ "Download audio file" | trans }}" href="/media/records/{{ record.audio_file }}">
{{ record.audio_file }}
</a>
</td>
<td>{{ record.date | date("H:m") }}</td>
<td>{{ record.confidence }}</td>
<td>
{% include "records/player.html.twig" with { "filename": record.audio_file } only %}
{% include "records/delete_button.html.twig" with { "filename": record.audio_file } only %} </td>
<div class="controlls container row">
{% include "records/player.html.twig" with { "filename": record.audio_file } only %}
{% include "records/delete_button.html.twig" with { "filename": record.audio_file } only %}
<a class="button" title="{{ "Download audio file" | trans }}" href="/media/records/{{ record.audio_file }}">
<i data-feather="download"></i>
</a>
</div>
</td>
<td>
<button {{ stimulus_action('manage-records', 'mark_as_verified') }} class="button" title="{{ "Mark this record as verified"|trans }}">
<i data-feather="eye"></i>
</button>
</td>
</tr>
{% endfor %}
</tbody>
@ -70,5 +81,4 @@
{% endif %}
</div>
{% endif %}
{% endblock %}

View File

@ -0,0 +1,25 @@
{% set current = app.request.get('_route')|replace({"_i18n": ""})~"_i18n" %}
{% macro lang_url(route, locale, name) %}
<a class="language-item"
href="{{
path(
route, app.request.attributes.get('_route_params')|merge({'_locale': locale})
)
}}">{{ name }}
</a>
{% endmacro %}
<li class="language-selector">
<label for="toggle" class="touch"
role="button">
{{ app.request.locale|locale_name(app.request.locale)|u.title }}
</label>
<input type="checkbox" name="dropdown-checkbox" class="toggle">
<ul class="slide">
<li>
{{ _self.lang_url(current, "en", "English") }}
</li>
<li>
{{ _self.lang_url(current, "fr", "Français") }}
</li>
</ul>
</li>

View File

@ -1,6 +1,6 @@
<li>
<a class="{{ app.request.get('_route') matches '{' ~ route ~ '_*}' ? 'active' }}"
href="{{ url }}">
{{ name }}
href="{{ path(route ~ "_i18n", { _locale: app.request.locale }) }}">
{{ text }}
</a>
</li>

View File

@ -0,0 +1,250 @@
<?xml version="1.0" encoding="utf-8"?>
<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" version="1.2">
<file source-language="en" target-language="es" datatype="plaintext" original="file.ext">
<header>
<tool tool-id="symfony" tool-name="Symfony"/>
</header>
<body>
<trans-unit id="39aF.j_" resname="Frequency charts">
<source>Frequency charts</source>
<target state="translated">Gráfico de frecuencia </target>
</trans-unit>
<trans-unit id="SJtk1Aa" resname="No charts available">
<source>No charts available</source>
<target state="translated">No grafico disponible</target>
</trans-unit>
<trans-unit id="OnhpU4i" resname="Home">
<source>Home</source>
<target state="translated">Pagina principal</target>
</trans-unit>
<trans-unit id="Tvyg0Qx" resname="About">
<source>About</source>
<target state="translated">A proposito</target>
</trans-unit>
<trans-unit id="Lc4yjyd" resname="Today's Detections">
<source>Today's Detections</source>
<target state="translated">Las detecciones de hoy</target>
</trans-unit>
<trans-unit id="11.I7BE" resname="Live Spectrogram">
<source>Live Spectrogram</source>
<target state="translated">espectrograma en vivo</target>
</trans-unit>
<trans-unit id="plYQfpn" resname="Statistics">
<source>Statistics</source>
<target state="translated">estadisticas</target>
</trans-unit>
<trans-unit id="YMVtZqE" resname="Recordings">
<source>Recordings</source>
<target state="translated">Grabaciones</target>
</trans-unit>
<trans-unit id="GiWvoLV" resname="Best Recordings">
<source>Best Recordings</source>
<target state="translated">mejores grabaciones </target>
</trans-unit>
<trans-unit id="4IfwRtw" resname="View Logs">
<source>View Logs</source>
<target state="translated">Ver registros</target>
</trans-unit>
<trans-unit id="kg5BPH1" resname="Status">
<source>Status</source>
<target state="translated">estatuto</target>
</trans-unit>
<trans-unit id="yQxt8UA" resname="Not implemented yet">
<source>Not implemented yet</source>
<target state="translated">Aun no se ha implementado</target>
</trans-unit>
<trans-unit id="mcQKtAW" resname="Date">
<source>Date</source>
<target state="translated">Fecha</target>
</trans-unit>
<trans-unit id="zFIH2Rd" resname="Enter a date in this format YYYY-MM-DD">
<source>Enter a date in this format YYYY-MM-DD</source>
<target state="translated">introducir una fecha en este formato YYYY-MM-DD</target>
</trans-unit>
<trans-unit id="bMhRm5F" resname="Go">
<source>Go</source>
<target>__Go</target>
</trans-unit>
<trans-unit id="6iEA3Im" resname="Logs">
<source>Logs</source>
<target>__Logs</target>
</trans-unit>
<trans-unit id="yIMayeg" resname="No logs available">
<source>No logs available</source>
<target>__No logs available</target>
</trans-unit>
<trans-unit id="VAw_dLX" resname="BirdNET-stream is a realtime soundscape analyzis software powered by BirdNET AI.">
<source>BirdNET-stream is a realtime soundscape analyzis software powered by BirdNET AI.</source>
<target>__BirdNET-stream is a realtime soundscape analyzis software powered by BirdNET AI.</target>
</trans-unit>
<trans-unit id="vvz1r3A" resname="It aims to be able to run on any computer with a microphone.">
<source>It aims to be able to run on any computer with a microphone.</source>
<target>__It aims to be able to run on any computer with a microphone.</target>
</trans-unit>
<trans-unit id="2VCCou5" resname="Author">
<source>Author</source>
<target>__Author</target>
</trans-unit>
<trans-unit id="1zd9FJ_" resname="This project is made with &amp;hearts; by Samuel ORTION.">
<source>This project is made with &amp;hearts; by Samuel ORTION.</source>
<target><![CDATA[__This project is made with &hearts; by Samuel ORTION.]]></target>
</trans-unit>
<trans-unit id="wBHWCXv" resname="License">
<source>License</source>
<target>__License</target>
</trans-unit>
<trans-unit id="6AM7sRb" resname="BirdNET-stream is licensed under the">
<source>BirdNET-stream is licensed under the</source>
<target>__BirdNET-stream is licensed under the</target>
</trans-unit>
<trans-unit id="nNkktM9" resname="BirdNET-Analyzer, on which this project relies, is licensed under">
<source>BirdNET-Analyzer, on which this project relies, is licensed under</source>
<target>__BirdNET-Analyzer, on which this project relies, is licensed under</target>
</trans-unit>
<trans-unit id="_RN0U0G" resname="Spectrogram">
<source>Spectrogram</source>
<target>__Spectrogram</target>
</trans-unit>
<trans-unit id="cOE9kUb" resname="Launch Live Spectrogram">
<source>Launch Live Spectrogram</source>
<target>__Launch Live Spectrogram</target>
</trans-unit>
<trans-unit id="VF2TvYg" resname="No audio sources yet">
<source>No audio sources yet</source>
<target>__No audio sources yet</target>
</trans-unit>
<trans-unit id="b3yIXEw" resname="Quick Stats">
<source>Quick Stats</source>
<target>__Quick Stats</target>
</trans-unit>
<trans-unit id="EVOzKuV" resname="Most recorded species">
<source>Most recorded species</source>
<target>__Most recorded species</target>
</trans-unit>
<trans-unit id="BpW1Y6z" resname="with">
<source>with</source>
<target>__with</target>
</trans-unit>
<trans-unit id="qlr0CE8" resname="contacts">
<source>contacts</source>
<target>__contacts</target>
</trans-unit>
<trans-unit id="pHx6Sb8" resname="No species in database.">
<source>No species in database.</source>
<target>__No species in database.</target>
</trans-unit>
<trans-unit id="kbbkF9." resname="Last detected species">
<source>Last detected species</source>
<target>__Last detected species</target>
</trans-unit>
<trans-unit id="UfL722." resname="AI confidence">
<source>AI confidence</source>
<target>__AI confidence</target>
</trans-unit>
<trans-unit id="4PT3Z6y" resname="today">
<source>today</source>
<target>__today</target>
</trans-unit>
<trans-unit id="uNMehSc" resname="on">
<source>on</source>
<target>__on</target>
</trans-unit>
<trans-unit id="UXnWgGD" resname="No species in database">
<source>No species in database</source>
<target>__No species in database</target>
</trans-unit>
<trans-unit id="O.a8OVk" resname="Today's contacts for">
<source>Today's contacts for</source>
<target>__Today's contacts for</target>
</trans-unit>
<trans-unit id="1XvGb5I" resname="Contacts on">
<source>Contacts on</source>
<target>__Contacts on</target>
</trans-unit>
<trans-unit id="EMIrz0x" resname="for">
<source>for</source>
<target>__for</target>
</trans-unit>
<trans-unit id="tKc2V0D" resname="Stats">
<source>Stats</source>
<target>__Stats</target>
</trans-unit>
<trans-unit id="j93Wzbi" resname="Contact count:">
<source>Contact count:</source>
<target>__Contact count:</target>
</trans-unit>
<trans-unit id="ALgyC6W" resname="Max confidence">
<source>Max confidence</source>
<target>__Max confidence</target>
</trans-unit>
<trans-unit id="WTXVn0G" resname="Contact records">
<source>Contact records</source>
<target>__Contact records</target>
</trans-unit>
<trans-unit id="lxuXQjW" resname="Filename">
<source>Filename</source>
<target>__Filename</target>
</trans-unit>
<trans-unit id="M7k0ds9" resname="Time">
<source>Time</source>
<target>__Time</target>
</trans-unit>
<trans-unit id="ZCLj549" resname="Confidence">
<source>Confidence</source>
<target>__Confidence</target>
</trans-unit>
<trans-unit id="vBuIkH0" resname="Audio">
<source>Audio</source>
<target>__Audio</target>
</trans-unit>
<trans-unit id="I3eaEC5" resname="Select this record">
<source>Select this record</source>
<target>__Select this record</target>
</trans-unit>
<trans-unit id="NBh3Jvf" resname="Download audio file">
<source>Download audio file</source>
<target>__Download audio file</target>
</trans-unit>
<trans-unit id="gp0zWYd" resname="No records this day for this species">
<source>No records this day for this species</source>
<target>__No records this day for this species</target>
</trans-unit>
<trans-unit id="H8mjh2V" resname="Select all">
<source>Select all</source>
<target>__Select all</target>
</trans-unit>
<trans-unit id="0qtdRv7" resname="Delete selected">
<source>Delete selected</source>
<target>__Delete selected</target>
</trans-unit>
<trans-unit id="XdPmSCB" resname="Today's detected species">
<source>Today's detected species</source>
<target>__Today's detected species</target>
</trans-unit>
<trans-unit id="ujkYinn" resname="Detected species on">
<source>Detected species on</source>
<target>__Detected species on</target>
</trans-unit>
<trans-unit id="tZEkkkO" resname="No species detected this day">
<source>No species detected this day</source>
<target>__No species detected this day</target>
</trans-unit>
<trans-unit id="AmHJKRh" resname="Services status">
<source>Services status</source>
<target>__Services status</target>
</trans-unit>
<trans-unit id="hb1Ij3K" resname="No status available">
<source>No status available</source>
<target>__No status available</target>
</trans-unit>
<trans-unit id="OzJoKh6" resname="Welcome to BirdNET-stream !">
<source>Welcome to BirdNET-stream !</source>
<target>__Welcome to BirdNET-stream !</target>
</trans-unit>
<trans-unit id="T_YMEAK" resname="Disk usage">
<source>Disk usage</source>
<target>__Disk usage</target>
</trans-unit>
</body>
</file>
</xliff>

View File

@ -23,7 +23,7 @@
</trans-unit>
<trans-unit id="1zd9FJ_" resname="This project is made with &amp;hearts; by Samuel ORTION.">
<source>This project is made with &amp;hearts; by Samuel ORTION.</source>
<target><![CDATA[Ce projet est fait avec &hearts; par Samuel ORTION]]></target>
<target><![CDATA[Ce projet est fait avec &hearts; par Samuel ORTION.]]></target>
</trans-unit>
<trans-unit id="wBHWCXv" resname="License">
<source>License</source>
@ -37,6 +37,214 @@
<source>BirdNET-Analyzer, on which this project relies, is licensed under</source>
<target>BirdNET-Analyzer, sur qui ce projet repose, est sous licence</target>
</trans-unit>
<trans-unit id="39aF.j_" resname="Frequency charts">
<source>Frequency charts</source>
<target>Graphe de fréquence</target>
</trans-unit>
<trans-unit id="SJtk1Aa" resname="No charts available">
<source>No charts available</source>
<target>Pas de graphe disponible</target>
</trans-unit>
<trans-unit id="OnhpU4i" resname="Home">
<source>Home</source>
<target>Accueil</target>
</trans-unit>
<trans-unit id="Lc4yjyd" resname="Today's Detections">
<source>Today's Detections</source>
<target>Détection aujourd'hui</target>
</trans-unit>
<trans-unit id="11.I7BE" resname="Live Spectrogram">
<source>Live Spectrogram</source>
<target>Spectrogramme en temps réel</target>
</trans-unit>
<trans-unit id="plYQfpn" resname="Statistics">
<source>Statistics</source>
<target>Statistiques</target>
</trans-unit>
<trans-unit id="YMVtZqE" resname="Recordings">
<source>Recordings</source>
<target>Enregistrements</target>
</trans-unit>
<trans-unit id="GiWvoLV" resname="Best Recordings">
<source>Best Recordings</source>
<target>Meilleurs Enregistrements</target>
</trans-unit>
<trans-unit id="4IfwRtw" resname="View Logs">
<source>View Logs</source>
<target>Voir les logs</target>
</trans-unit>
<trans-unit id="kg5BPH1" resname="Status">
<source>Status</source>
<target>Status</target>
</trans-unit>
<trans-unit id="mcQKtAW" resname="Date">
<source>Date</source>
<target>Date</target>
</trans-unit>
<trans-unit id="zFIH2Rd" resname="Enter a date in this format YYYY-MM-DD">
<source>Enter a date in this format YYYY-MM-DD</source>
<target>Entrez une date au format YYYY-MM-DD</target>
</trans-unit>
<trans-unit id="bMhRm5F" resname="Go">
<source>Go</source>
<target>Go</target>
</trans-unit>
<trans-unit id="6iEA3Im" resname="Logs">
<source>Logs</source>
<target>Logs</target>
</trans-unit>
<trans-unit id="yIMayeg" resname="No logs available">
<source>No logs available</source>
<target>Pas de logs disponible</target>
</trans-unit>
<trans-unit id="_RN0U0G" resname="Spectrogram">
<source>Spectrogram</source>
<target>Spectrogramme</target>
</trans-unit>
<trans-unit id="cOE9kUb" resname="Launch Live Spectrogram">
<source>Launch Live Spectrogram</source>
<target>Lancer le Spectrogramme en temps réel</target>
</trans-unit>
<trans-unit id="VF2TvYg" resname="No audio sources yet">
<source>No audio sources yet</source>
<target>Pas de source audio pour le moment</target>
</trans-unit>
<trans-unit id="b3yIXEw" resname="Quick Stats">
<source>Quick Stats</source>
<target>Statistiques rapides</target>
</trans-unit>
<trans-unit id="EVOzKuV" resname="Most recorded species">
<source>Most recorded species</source>
<target>Espèce la plus souvent enregistrée</target>
</trans-unit>
<trans-unit id="BpW1Y6z" resname="with">
<source>with</source>
<target>avec</target>
</trans-unit>
<trans-unit id="qlr0CE8" resname="contacts">
<source>contacts</source>
<target>contacts</target>
</trans-unit>
<trans-unit id="pHx6Sb8" resname="No species in database.">
<source>No species in database.</source>
<target>Aucune espèce dans la base de donnée.</target>
</trans-unit>
<trans-unit id="kbbkF9." resname="Last detected species">
<source>Last detected species</source>
<target>Dernière espèce détectée</target>
</trans-unit>
<trans-unit id="UfL722." resname="AI confidence">
<source>AI confidence</source>
<target>Niveau de confiance</target>
</trans-unit>
<trans-unit id="4PT3Z6y" resname="today">
<source>today</source>
<target>aujourd'hui</target>
</trans-unit>
<trans-unit id="uNMehSc" resname="on">
<source>on</source>
<target>le</target>
</trans-unit>
<trans-unit id="UXnWgGD" resname="No species in database">
<source>No species in database</source>
<target>Aucune espèce dans la base de données</target>
</trans-unit>
<trans-unit id="O.a8OVk" resname="Today's contacts for">
<source>Today's contacts for</source>
<target>Contacts d'aujourd'hui pour</target>
</trans-unit>
<trans-unit id="1XvGb5I" resname="Contacts on">
<source>Contacts on</source>
<target>Contacts le</target>
</trans-unit>
<trans-unit id="EMIrz0x" resname="for">
<source>for</source>
<target>pour</target>
</trans-unit>
<trans-unit id="tKc2V0D" resname="Stats">
<source>Stats</source>
<target>Stats</target>
</trans-unit>
<trans-unit id="j93Wzbi" resname="Contact count:">
<source>Contact count:</source>
<target>Nombre de contact:</target>
</trans-unit>
<trans-unit id="ALgyC6W" resname="Max confidence">
<source>Max confidence</source>
<target>Niveau de confiance maximal</target>
</trans-unit>
<trans-unit id="WTXVn0G" resname="Contact records">
<source>Contact records</source>
<target>Enregistrement du contact</target>
</trans-unit>
<trans-unit id="lxuXQjW" resname="Filename">
<source>Filename</source>
<target>Fichier</target>
</trans-unit>
<trans-unit id="M7k0ds9" resname="Time">
<source>Time</source>
<target>Heure</target>
</trans-unit>
<trans-unit id="ZCLj549" resname="Confidence">
<source>Confidence</source>
<target>Confiance</target>
</trans-unit>
<trans-unit id="vBuIkH0" resname="Audio">
<source>Audio</source>
<target>Audio</target>
</trans-unit>
<trans-unit id="I3eaEC5" resname="Select this record">
<source>Select this record</source>
<target>Sélectionner cet enregistrement</target>
</trans-unit>
<trans-unit id="NBh3Jvf" resname="Download audio file">
<source>Download audio file</source>
<target>Télécharger le fichier audio</target>
</trans-unit>
<trans-unit id="gp0zWYd" resname="No records this day for this species">
<source>No records this day for this species</source>
<target>Pas d'entrement ce jour pour cette espèce</target>
</trans-unit>
<trans-unit id="H8mjh2V" resname="Select all">
<source>Select all</source>
<target>Séléctionner tout</target>
</trans-unit>
<trans-unit id="0qtdRv7" resname="Delete selected">
<source>Delete selected</source>
<target>Supprimer la sélection</target>
</trans-unit>
<trans-unit id="XdPmSCB" resname="Today's detected species">
<source>Today's detected species</source>
<target>Espèces détectées aujourd'hui</target>
</trans-unit>
<trans-unit id="ujkYinn" resname="Detected species on">
<source>Detected species on</source>
<target>Espèces détectées le</target>
</trans-unit>
<trans-unit id="tZEkkkO" resname="No species detected this day">
<source>No species detected this day</source>
<target>Aucune espèce détectée ce jour</target>
</trans-unit>
<trans-unit id="AmHJKRh" resname="Services status">
<source>Services status</source>
<target>Status des services</target>
</trans-unit>
<trans-unit id="hb1Ij3K" resname="No status available">
<source>No status available</source>
<target>Aucun status disponible</target>
</trans-unit>
<trans-unit id="OzJoKh6" resname="Welcome to BirdNET-stream !">
<source>Welcome to BirdNET-stream !</source>
<target>Bienvenue sur BirdNET-stream !</target>
</trans-unit>
<trans-unit id="yQxt8UA" resname="Not implemented yet">
<source>Not implemented yet</source>
<target>__Not implemented yet</target>
</trans-unit>
<trans-unit id="T_YMEAK" resname="Disk usage">
<source>Disk usage</source>
<target>__Disk usage</target>
</trans-unit>
</body>
</file>
</xliff>

View File

@ -0,0 +1,86 @@
<?xml version="1.0" encoding="utf-8"?>
<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" version="1.2">
<file source-language="en" target-language="es" datatype="plaintext" original="file.ext">
<header>
<tool tool-id="symfony" tool-name="Symfony"/>
</header>
<body>
<trans-unit id="baI_ZxO" resname="An authentication exception occurred.">
<source>An authentication exception occurred.</source>
<target>Ocurrió un error de autenticación.</target>
</trans-unit>
<trans-unit id="OETylMq" resname="Authentication credentials could not be found.">
<source>Authentication credentials could not be found.</source>
<target>No se encontraron las credenciales de autenticación.</target>
</trans-unit>
<trans-unit id="3RJINQ0" resname="Authentication request could not be processed due to a system problem.">
<source>Authentication request could not be processed due to a system problem.</source>
<target>La solicitud de autenticación no se pudo procesar debido a un problema del sistema.</target>
</trans-unit>
<trans-unit id="qr0aiUo" resname="Invalid credentials.">
<source>Invalid credentials.</source>
<target>Credenciales no válidas.</target>
</trans-unit>
<trans-unit id="zrJWK0F" resname="Cookie has already been used by someone else.">
<source>Cookie has already been used by someone else.</source>
<target>La cookie ya ha sido usada por otra persona.</target>
</trans-unit>
<trans-unit id="blC0fXX" resname="Not privileged to request the resource.">
<source>Not privileged to request the resource.</source>
<target>No tiene privilegios para solicitar el recurso.</target>
</trans-unit>
<trans-unit id="dLzMRPR" resname="Invalid CSRF token.">
<source>Invalid CSRF token.</source>
<target>Token CSRF no válido.</target>
</trans-unit>
<trans-unit id="PhhlLem" resname="No authentication provider found to support the authentication token.">
<source>No authentication provider found to support the authentication token.</source>
<target>No se encontró un proveedor de autenticación que soporte el token de autenticación.</target>
</trans-unit>
<trans-unit id="v_RS21A" resname="No session available, it either timed out or cookies are not enabled.">
<source>No session available, it either timed out or cookies are not enabled.</source>
<target>No hay ninguna sesión disponible, ha expirado o las cookies no están habilitados.</target>
</trans-unit>
<trans-unit id="EYCKpDH" resname="No token could be found.">
<source>No token could be found.</source>
<target>No se encontró ningún token.</target>
</trans-unit>
<trans-unit id="z3cOUZo" resname="Username could not be found.">
<source>Username could not be found.</source>
<target>No se encontró el nombre de usuario.</target>
</trans-unit>
<trans-unit id="By5eLYM" resname="Account has expired.">
<source>Account has expired.</source>
<target>La cuenta ha expirado.</target>
</trans-unit>
<trans-unit id="YfZhiuA" resname="Credentials have expired.">
<source>Credentials have expired.</source>
<target>Las credenciales han expirado.</target>
</trans-unit>
<trans-unit id="NrSSfLs" resname="Account is disabled.">
<source>Account is disabled.</source>
<target>La cuenta está deshabilitada.</target>
</trans-unit>
<trans-unit id="O5ZyxHr" resname="Account is locked.">
<source>Account is locked.</source>
<target>La cuenta está bloqueada.</target>
</trans-unit>
<trans-unit id="gd.MOnZ" resname="Too many failed login attempts, please try again later.">
<source>Too many failed login attempts, please try again later.</source>
<target>Demasiados intentos fallidos de inicio de sesión, inténtelo de nuevo más tarde.</target>
</trans-unit>
<trans-unit id="l9VYRj0" resname="Invalid or expired login link.">
<source>Invalid or expired login link.</source>
<target>Enlace de inicio de sesión inválido o expirado.</target>
</trans-unit>
<trans-unit id="9qGC3hG" resname="Too many failed login attempts, please try again in %minutes% minute.">
<source>Too many failed login attempts, please try again in %minutes% minute.</source>
<target>Demasiados intentos fallidos de inicio de sesión, inténtelo de nuevo en %minutes% minuto.</target>
</trans-unit>
<trans-unit id="AJR3lMs" resname="Too many failed login attempts, please try again in %minutes% minutes.">
<source>Too many failed login attempts, please try again in %minutes% minutes.</source>
<target>Demasiados intentos fallidos de inicio de sesión, inténtelo de nuevo en %minutes% minutos.</target>
</trans-unit>
</body>
</file>
</xliff>

View File

@ -0,0 +1,86 @@
<?xml version="1.0" encoding="utf-8"?>
<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" version="1.2">
<file source-language="en" target-language="fr" datatype="plaintext" original="file.ext">
<header>
<tool tool-id="symfony" tool-name="Symfony"/>
</header>
<body>
<trans-unit id="baI_ZxO" resname="An authentication exception occurred.">
<source>An authentication exception occurred.</source>
<target>Une exception d'authentification s'est produite.</target>
</trans-unit>
<trans-unit id="OETylMq" resname="Authentication credentials could not be found.">
<source>Authentication credentials could not be found.</source>
<target>Les identifiants d'authentification n'ont pas pu être trouvés.</target>
</trans-unit>
<trans-unit id="3RJINQ0" resname="Authentication request could not be processed due to a system problem.">
<source>Authentication request could not be processed due to a system problem.</source>
<target>La requête d'authentification n'a pas pu être executée à cause d'un problème système.</target>
</trans-unit>
<trans-unit id="qr0aiUo" resname="Invalid credentials.">
<source>Invalid credentials.</source>
<target>Identifiants invalides.</target>
</trans-unit>
<trans-unit id="zrJWK0F" resname="Cookie has already been used by someone else.">
<source>Cookie has already been used by someone else.</source>
<target>Le cookie a déjà été utilisé par quelqu'un d'autre.</target>
</trans-unit>
<trans-unit id="blC0fXX" resname="Not privileged to request the resource.">
<source>Not privileged to request the resource.</source>
<target>Privilèges insuffisants pour accéder à la ressource.</target>
</trans-unit>
<trans-unit id="dLzMRPR" resname="Invalid CSRF token.">
<source>Invalid CSRF token.</source>
<target>Jeton CSRF invalide.</target>
</trans-unit>
<trans-unit id="PhhlLem" resname="No authentication provider found to support the authentication token.">
<source>No authentication provider found to support the authentication token.</source>
<target>Aucun fournisseur d'authentification n'a été trouvé pour supporter le jeton d'authentification.</target>
</trans-unit>
<trans-unit id="v_RS21A" resname="No session available, it either timed out or cookies are not enabled.">
<source>No session available, it either timed out or cookies are not enabled.</source>
<target>Aucune session disponible, celle-ci a expiré ou les cookies ne sont pas activés.</target>
</trans-unit>
<trans-unit id="EYCKpDH" resname="No token could be found.">
<source>No token could be found.</source>
<target>Aucun jeton n'a pu être trouvé.</target>
</trans-unit>
<trans-unit id="z3cOUZo" resname="Username could not be found.">
<source>Username could not be found.</source>
<target>Le nom d'utilisateur n'a pas pu être trouvé.</target>
</trans-unit>
<trans-unit id="By5eLYM" resname="Account has expired.">
<source>Account has expired.</source>
<target>Le compte a expiré.</target>
</trans-unit>
<trans-unit id="YfZhiuA" resname="Credentials have expired.">
<source>Credentials have expired.</source>
<target>Les identifiants ont expiré.</target>
</trans-unit>
<trans-unit id="NrSSfLs" resname="Account is disabled.">
<source>Account is disabled.</source>
<target>Le compte est désactivé.</target>
</trans-unit>
<trans-unit id="O5ZyxHr" resname="Account is locked.">
<source>Account is locked.</source>
<target>Le compte est bloqué.</target>
</trans-unit>
<trans-unit id="gd.MOnZ" resname="Too many failed login attempts, please try again later.">
<source>Too many failed login attempts, please try again later.</source>
<target>Plusieurs tentatives de connexion ont échoué, veuillez réessayer plus tard.</target>
</trans-unit>
<trans-unit id="l9VYRj0" resname="Invalid or expired login link.">
<source>Invalid or expired login link.</source>
<target>Lien de connexion invalide ou expiré.</target>
</trans-unit>
<trans-unit id="9qGC3hG" resname="Too many failed login attempts, please try again in %minutes% minute.">
<source>Too many failed login attempts, please try again in %minutes% minute.</source>
<target>Plusieurs tentatives de connexion ont échoué, veuillez réessayer dans %minutes% minute.</target>
</trans-unit>
<trans-unit id="AJR3lMs" resname="Too many failed login attempts, please try again in %minutes% minutes.">
<source>Too many failed login attempts, please try again in %minutes% minutes.</source>
<target>Plusieurs tentatives de connexion ont échoué, veuillez réessayer dans %minutes% minutes.</target>
</trans-unit>
</body>
</file>
</xliff>

View File

@ -0,0 +1,542 @@
<?xml version="1.0" encoding="utf-8"?>
<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" version="1.2">
<file source-language="en" target-language="es" datatype="plaintext" original="file.ext">
<header>
<tool tool-id="symfony" tool-name="Symfony"/>
</header>
<body>
<trans-unit id="zh5oKD9" resname="This value should be false.">
<source>This value should be false.</source>
<target>Este valor debería ser falso.</target>
</trans-unit>
<trans-unit id="NN2_iru" resname="This value should be true.">
<source>This value should be true.</source>
<target>Este valor debería ser verdadero.</target>
</trans-unit>
<trans-unit id="OjM_kpf" resname="This value should be of type {{ type }}.">
<source>This value should be of type {{ type }}.</source>
<target>Este valor debería ser de tipo {{ type }}.</target>
</trans-unit>
<trans-unit id="f0P5pBD" resname="This value should be blank.">
<source>This value should be blank.</source>
<target>Este valor debería estar vacío.</target>
</trans-unit>
<trans-unit id="ih.4TRN" resname="The value you selected is not a valid choice.">
<source>The value you selected is not a valid choice.</source>
<target>El valor seleccionado no es una opción válida.</target>
</trans-unit>
<trans-unit id="u81CkP8" resname="You must select at least {{ limit }} choice.|You must select at least {{ limit }} choices.">
<source>You must select at least {{ limit }} choice.|You must select at least {{ limit }} choices.</source>
<target>Debe seleccionar al menos {{ limit }} opción.|Debe seleccionar al menos {{ limit }} opciones.</target>
</trans-unit>
<trans-unit id="nIvOQ_o" resname="You must select at most {{ limit }} choice.|You must select at most {{ limit }} choices.">
<source>You must select at most {{ limit }} choice.|You must select at most {{ limit }} choices.</source>
<target>Debe seleccionar como máximo {{ limit }} opción.|Debe seleccionar como máximo {{ limit }} opciones.</target>
</trans-unit>
<trans-unit id="87zjiHi" resname="One or more of the given values is invalid.">
<source>One or more of the given values is invalid.</source>
<target>Uno o más de los valores indicados no son válidos.</target>
</trans-unit>
<trans-unit id="3NeQftv" resname="This field was not expected.">
<source>This field was not expected.</source>
<target>Este campo no se esperaba.</target>
</trans-unit>
<trans-unit id="SwMV4zp" resname="This field is missing.">
<source>This field is missing.</source>
<target>Este campo está desaparecido.</target>
</trans-unit>
<trans-unit id="LO2vFKN" resname="This value is not a valid date.">
<source>This value is not a valid date.</source>
<target>Este valor no es una fecha válida.</target>
</trans-unit>
<trans-unit id="86dU_nv" resname="This value is not a valid datetime.">
<source>This value is not a valid datetime.</source>
<target>Este valor no es una fecha y hora válidas.</target>
</trans-unit>
<trans-unit id="PSvNXdi" resname="This value is not a valid email address.">
<source>This value is not a valid email address.</source>
<target>Este valor no es una dirección de email válida.</target>
</trans-unit>
<trans-unit id="3KeHbZy" resname="The file could not be found.">
<source>The file could not be found.</source>
<target>No se pudo encontrar el archivo.</target>
</trans-unit>
<trans-unit id="KtJhQZo" resname="The file is not readable.">
<source>The file is not readable.</source>
<target>No se puede leer el archivo.</target>
</trans-unit>
<trans-unit id="JocOVM2" resname="The file is too large ({{ size }} {{ suffix }}). Allowed maximum size is {{ limit }} {{ suffix }}.">
<source>The file is too large ({{ size }} {{ suffix }}). Allowed maximum size is {{ limit }} {{ suffix }}.</source>
<target>El archivo es demasiado grande ({{ size }} {{ suffix }}). El tamaño máximo permitido es {{ limit }} {{ suffix }}.</target>
</trans-unit>
<trans-unit id="YW21SPH" resname="The mime type of the file is invalid ({{ type }}). Allowed mime types are {{ types }}.">
<source>The mime type of the file is invalid ({{ type }}). Allowed mime types are {{ types }}.</source>
<target>El tipo mime del archivo no es válido ({{ type }}). Los tipos mime válidos son {{ types }}.</target>
</trans-unit>
<trans-unit id="NubOmrs" resname="This value should be {{ limit }} or less.">
<source>This value should be {{ limit }} or less.</source>
<target>Este valor debería ser {{ limit }} o menos.</target>
</trans-unit>
<trans-unit id="HX7TOFm" resname="This value is too long. It should have {{ limit }} character or less.|This value is too long. It should have {{ limit }} characters or less.">
<source>This value is too long. It should have {{ limit }} character or less.|This value is too long. It should have {{ limit }} characters or less.</source>
<target>Este valor es demasiado largo. Debería tener {{ limit }} carácter o menos.|Este valor es demasiado largo. Debería tener {{ limit }} caracteres o menos.</target>
</trans-unit>
<trans-unit id="qgR8M_U" resname="This value should be {{ limit }} or more.">
<source>This value should be {{ limit }} or more.</source>
<target>Este valor debería ser {{ limit }} o más.</target>
</trans-unit>
<trans-unit id="ekfrU.c" resname="This value is too short. It should have {{ limit }} character or more.|This value is too short. It should have {{ limit }} characters or more.">
<source>This value is too short. It should have {{ limit }} character or more.|This value is too short. It should have {{ limit }} characters or more.</source>
<target>Este valor es demasiado corto. Debería tener {{ limit }} carácter o más.|Este valor es demasiado corto. Debería tener {{ limit }} caracteres o más.</target>
</trans-unit>
<trans-unit id="1KV4L.t" resname="This value should not be blank.">
<source>This value should not be blank.</source>
<target>Este valor no debería estar vacío.</target>
</trans-unit>
<trans-unit id="2G4Vepm" resname="This value should not be null.">
<source>This value should not be null.</source>
<target>Este valor no debería ser nulo.</target>
</trans-unit>
<trans-unit id="yDc3m6E" resname="This value should be null.">
<source>This value should be null.</source>
<target>Este valor debería ser nulo.</target>
</trans-unit>
<trans-unit id="zKzWejA" resname="This value is not valid.">
<source>This value is not valid.</source>
<target>Este valor no es válido.</target>
</trans-unit>
<trans-unit id="HSuBZpQ" resname="This value is not a valid time.">
<source>This value is not a valid time.</source>
<target>Este valor no es una hora válida.</target>
</trans-unit>
<trans-unit id="snWc_QT" resname="This value is not a valid URL.">
<source>This value is not a valid URL.</source>
<target>Este valor no es una URL válida.</target>
</trans-unit>
<trans-unit id="jpaLsb2" resname="The two values should be equal.">
<source>The two values should be equal.</source>
<target>Los dos valores deberían ser iguales.</target>
</trans-unit>
<trans-unit id="fIlB1B_" resname="The file is too large. Allowed maximum size is {{ limit }} {{ suffix }}.">
<source>The file is too large. Allowed maximum size is {{ limit }} {{ suffix }}.</source>
<target>El archivo es demasiado grande. El tamaño máximo permitido es {{ limit }} {{ suffix }}.</target>
</trans-unit>
<trans-unit id="tW7o0t9" resname="The file is too large.">
<source>The file is too large.</source>
<target>El archivo es demasiado grande.</target>
</trans-unit>
<trans-unit id=".exF5Ww" resname="The file could not be uploaded.">
<source>The file could not be uploaded.</source>
<target>No se pudo subir el archivo.</target>
</trans-unit>
<trans-unit id="d7sS5yw" resname="This value should be a valid number.">
<source>This value should be a valid number.</source>
<target>Este valor debería ser un número válido.</target>
</trans-unit>
<trans-unit id="BS2Ez6i" resname="This file is not a valid image.">
<source>This file is not a valid image.</source>
<target>El archivo no es una imagen válida.</target>
</trans-unit>
<trans-unit id="ydcT9kU" resname="This is not a valid IP address.">
<source>This is not a valid IP address.</source>
<target>Esto no es una dirección IP válida.</target>
</trans-unit>
<trans-unit id="lDOGNFX" resname="This value is not a valid language.">
<source>This value is not a valid language.</source>
<target>Este valor no es un idioma válido.</target>
</trans-unit>
<trans-unit id="y9IdYkA" resname="This value is not a valid locale.">
<source>This value is not a valid locale.</source>
<target>Este valor no es una localización válida.</target>
</trans-unit>
<trans-unit id="1YC0pOd" resname="This value is not a valid country.">
<source>This value is not a valid country.</source>
<target>Este valor no es un país válido.</target>
</trans-unit>
<trans-unit id="B5ebaMp" resname="This value is already used.">
<source>This value is already used.</source>
<target>Este valor ya se ha utilizado.</target>
</trans-unit>
<trans-unit id="L6097a6" resname="The size of the image could not be detected.">
<source>The size of the image could not be detected.</source>
<target>No se pudo determinar el tamaño de la imagen.</target>
</trans-unit>
<trans-unit id="zVtJJEa" resname="The image width is too big ({{ width }}px). Allowed maximum width is {{ max_width }}px.">
<source>The image width is too big ({{ width }}px). Allowed maximum width is {{ max_width }}px.</source>
<target>El ancho de la imagen es demasiado grande ({{ width }}px). El ancho máximo permitido es de {{ max_width }}px.</target>
</trans-unit>
<trans-unit id="s8LFQGC" resname="The image width is too small ({{ width }}px). Minimum width expected is {{ min_width }}px.">
<source>The image width is too small ({{ width }}px). Minimum width expected is {{ min_width }}px.</source>
<target>El ancho de la imagen es demasiado pequeño ({{ width }}px). El ancho mínimo requerido es {{ min_width }}px.</target>
</trans-unit>
<trans-unit id="Z.NgqFj" resname="The image height is too big ({{ height }}px). Allowed maximum height is {{ max_height }}px.">
<source>The image height is too big ({{ height }}px). Allowed maximum height is {{ max_height }}px.</source>
<target>La altura de la imagen es demasiado grande ({{ height }}px). La altura máxima permitida es de {{ max_height }}px.</target>
</trans-unit>
<trans-unit id="AW1lWVM" resname="The image height is too small ({{ height }}px). Minimum height expected is {{ min_height }}px.">
<source>The image height is too small ({{ height }}px). Minimum height expected is {{ min_height }}px.</source>
<target>La altura de la imagen es demasiado pequeña ({{ height }}px). La altura mínima requerida es de {{ min_height }}px.</target>
</trans-unit>
<trans-unit id="PSdMNab" resname="This value should be the user's current password.">
<source>This value should be the user's current password.</source>
<target>Este valor debería ser la contraseña actual del usuario.</target>
</trans-unit>
<trans-unit id="gYImVyV" resname="This value should have exactly {{ limit }} character.|This value should have exactly {{ limit }} characters.">
<source>This value should have exactly {{ limit }} character.|This value should have exactly {{ limit }} characters.</source>
<target>Este valor debería tener exactamente {{ limit }} carácter.|Este valor debería tener exactamente {{ limit }} caracteres.</target>
</trans-unit>
<trans-unit id="xJ2Bcr_" resname="The file was only partially uploaded.">
<source>The file was only partially uploaded.</source>
<target>El archivo fue sólo subido parcialmente.</target>
</trans-unit>
<trans-unit id="HzkJDtF" resname="No file was uploaded.">
<source>No file was uploaded.</source>
<target>Ningún archivo fue subido.</target>
</trans-unit>
<trans-unit id="mHfEaB3" resname="No temporary folder was configured in php.ini.">
<source>No temporary folder was configured in php.ini.</source>
<target>Ninguna carpeta temporal fue configurada en php.ini o la carpeta configurada no existe.</target>
</trans-unit>
<trans-unit id="y9K3BGb" resname="Cannot write temporary file to disk.">
<source>Cannot write temporary file to disk.</source>
<target>No se pudo escribir el archivo temporal en el disco.</target>
</trans-unit>
<trans-unit id="kx3yHIM" resname="A PHP extension caused the upload to fail.">
<source>A PHP extension caused the upload to fail.</source>
<target>Una extensión de PHP hizo que la subida fallara.</target>
</trans-unit>
<trans-unit id="gTJYRl6" resname="This collection should contain {{ limit }} element or more.|This collection should contain {{ limit }} elements or more.">
<source>This collection should contain {{ limit }} element or more.|This collection should contain {{ limit }} elements or more.</source>
<target>Esta colección debe contener {{ limit }} elemento o más.|Esta colección debe contener {{ limit }} elementos o más.</target>
</trans-unit>
<trans-unit id="FFn3lVn" resname="This collection should contain {{ limit }} element or less.|This collection should contain {{ limit }} elements or less.">
<source>This collection should contain {{ limit }} element or less.|This collection should contain {{ limit }} elements or less.</source>
<target>Esta colección debe contener {{ limit }} elemento o menos.|Esta colección debe contener {{ limit }} elementos o menos.</target>
</trans-unit>
<trans-unit id="bSdilZv" resname="This collection should contain exactly {{ limit }} element.|This collection should contain exactly {{ limit }} elements.">
<source>This collection should contain exactly {{ limit }} element.|This collection should contain exactly {{ limit }} elements.</source>
<target>Esta colección debe contener exactamente {{ limit }} elemento.|Esta colección debe contener exactamente {{ limit }} elementos.</target>
</trans-unit>
<trans-unit id="MAzmID7" resname="Invalid card number.">
<source>Invalid card number.</source>
<target>Número de tarjeta inválido.</target>
</trans-unit>
<trans-unit id="c3REGK3" resname="Unsupported card type or invalid card number.">
<source>Unsupported card type or invalid card number.</source>
<target>Tipo de tarjeta no soportado o número de tarjeta inválido.</target>
</trans-unit>
<trans-unit id="XSVzcbV" resname="This is not a valid International Bank Account Number (IBAN).">
<source>This is not a valid International Bank Account Number (IBAN).</source>
<target>Esto no es un International Bank Account Number (IBAN) válido.</target>
</trans-unit>
<trans-unit id="yHirwNr" resname="This value is not a valid ISBN-10.">
<source>This value is not a valid ISBN-10.</source>
<target>Este valor no es un ISBN-10 válido.</target>
</trans-unit>
<trans-unit id="c_q0_ua" resname="This value is not a valid ISBN-13.">
<source>This value is not a valid ISBN-13.</source>
<target>Este valor no es un ISBN-13 válido.</target>
</trans-unit>
<trans-unit id="M4FlD6n" resname="This value is neither a valid ISBN-10 nor a valid ISBN-13.">
<source>This value is neither a valid ISBN-10 nor a valid ISBN-13.</source>
<target>Este valor no es ni un ISBN-10 válido ni un ISBN-13 válido.</target>
</trans-unit>
<trans-unit id="cuct0Ow" resname="This value is not a valid ISSN.">
<source>This value is not a valid ISSN.</source>
<target>Este valor no es un ISSN válido.</target>
</trans-unit>
<trans-unit id="JBLs1a1" resname="This value is not a valid currency.">
<source>This value is not a valid currency.</source>
<target>Este valor no es una divisa válida.</target>
</trans-unit>
<trans-unit id="c.WxzFW" resname="This value should be equal to {{ compared_value }}.">
<source>This value should be equal to {{ compared_value }}.</source>
<target>Este valor debería ser igual que {{ compared_value }}.</target>
</trans-unit>
<trans-unit id="_jdjkwq" resname="This value should be greater than {{ compared_value }}.">
<source>This value should be greater than {{ compared_value }}.</source>
<target>Este valor debería ser mayor que {{ compared_value }}.</target>
</trans-unit>
<trans-unit id="o8A8a0H" resname="This value should be greater than or equal to {{ compared_value }}.">
<source>This value should be greater than or equal to {{ compared_value }}.</source>
<target>Este valor debería ser mayor o igual que {{ compared_value }}.</target>
</trans-unit>
<trans-unit id="bOF1fpm" resname="This value should be identical to {{ compared_value_type }} {{ compared_value }}.">
<source>This value should be identical to {{ compared_value_type }} {{ compared_value }}.</source>
<target>Este valor debería ser idéntico a {{ compared_value_type }} {{ compared_value }}.</target>
</trans-unit>
<trans-unit id="jG0QFKw" resname="This value should be less than {{ compared_value }}.">
<source>This value should be less than {{ compared_value }}.</source>
<target>Este valor debería ser menor que {{ compared_value }}.</target>
</trans-unit>
<trans-unit id="9lWrKmm" resname="This value should be less than or equal to {{ compared_value }}.">
<source>This value should be less than or equal to {{ compared_value }}.</source>
<target>Este valor debería ser menor o igual que {{ compared_value }}.</target>
</trans-unit>
<trans-unit id="ftTwGs." resname="This value should not be equal to {{ compared_value }}.">
<source>This value should not be equal to {{ compared_value }}.</source>
<target>Este valor debería ser distinto de {{ compared_value }}.</target>
</trans-unit>
<trans-unit id="Bi22JLt" resname="This value should not be identical to {{ compared_value_type }} {{ compared_value }}.">
<source>This value should not be identical to {{ compared_value_type }} {{ compared_value }}.</source>
<target>Este valor no debería ser idéntico a {{ compared_value_type }} {{ compared_value }}.</target>
</trans-unit>
<trans-unit id="VczCWzQ" resname="The image ratio is too big ({{ ratio }}). Allowed maximum ratio is {{ max_ratio }}.">
<source>The image ratio is too big ({{ ratio }}). Allowed maximum ratio is {{ max_ratio }}.</source>
<target>La proporción de la imagen es demasiado grande ({{ ratio }}). La máxima proporción permitida es {{ max_ratio }}.</target>
</trans-unit>
<trans-unit id="v57PXhq" resname="The image ratio is too small ({{ ratio }}). Minimum ratio expected is {{ min_ratio }}.">
<source>The image ratio is too small ({{ ratio }}). Minimum ratio expected is {{ min_ratio }}.</source>
<target>La proporción de la imagen es demasiado pequeña ({{ ratio }}). La mínima proporción permitida es {{ min_ratio }}.</target>
</trans-unit>
<trans-unit id="rpajj.a" resname="The image is square ({{ width }}x{{ height }}px). Square images are not allowed.">
<source>The image is square ({{ width }}x{{ height }}px). Square images are not allowed.</source>
<target>La imagen es cuadrada ({{ width }}x{{ height }}px). Las imágenes cuadradas no están permitidas.</target>
</trans-unit>
<trans-unit id="G_lu2qW" resname="The image is landscape oriented ({{ width }}x{{ height }}px). Landscape oriented images are not allowed.">
<source>The image is landscape oriented ({{ width }}x{{ height }}px). Landscape oriented images are not allowed.</source>
<target>La imagen está orientada horizontalmente ({{ width }}x{{ height }}px). Las imágenes orientadas horizontalmente no están permitidas.</target>
</trans-unit>
<trans-unit id="sFyGx4B" resname="The image is portrait oriented ({{ width }}x{{ height }}px). Portrait oriented images are not allowed.">
<source>The image is portrait oriented ({{ width }}x{{ height }}px). Portrait oriented images are not allowed.</source>
<target>La imagen está orientada verticalmente ({{ width }}x{{ height }}px). Las imágenes orientadas verticalmente no están permitidas.</target>
</trans-unit>
<trans-unit id="jZgqcpL" resname="An empty file is not allowed.">
<source>An empty file is not allowed.</source>
<target>No está permitido un archivo vacío.</target>
</trans-unit>
<trans-unit id="bcfVezI" resname="The host could not be resolved.">
<source>The host could not be resolved.</source>
<target>No se puede resolver el host.</target>
</trans-unit>
<trans-unit id="NtzKvgt" resname="This value does not match the expected {{ charset }} charset.">
<source>This value does not match the expected {{ charset }} charset.</source>
<target>La codificación de caracteres para este valor debería ser {{ charset }}.</target>
</trans-unit>
<trans-unit id="Wi2y9.N" resname="This is not a valid Business Identifier Code (BIC).">
<source>This is not a valid Business Identifier Code (BIC).</source>
<target>No es un Código de Identificación Bancaria (BIC) válido.</target>
</trans-unit>
<trans-unit id="VKDowX6" resname="Error">
<source>Error</source>
<target>Error</target>
</trans-unit>
<trans-unit id="8zqt0Ik" resname="This is not a valid UUID.">
<source>This is not a valid UUID.</source>
<target>Este valor no es un UUID válido.</target>
</trans-unit>
<trans-unit id="ru.4wkH" resname="This value should be a multiple of {{ compared_value }}.">
<source>This value should be a multiple of {{ compared_value }}.</source>
<target>Este valor debería ser múltiplo de {{ compared_value }}.</target>
</trans-unit>
<trans-unit id="M3vyK6s" resname="This Business Identifier Code (BIC) is not associated with IBAN {{ iban }}.">
<source>This Business Identifier Code (BIC) is not associated with IBAN {{ iban }}.</source>
<target>Este Código de Identificación Bancaria (BIC) no está asociado con el IBAN {{ iban }}.</target>
</trans-unit>
<trans-unit id="2v2xpAh" resname="This value should be valid JSON.">
<source>This value should be valid JSON.</source>
<target>Este valor debería ser un JSON válido.</target>
</trans-unit>
<trans-unit id="9CWVEGq" resname="This collection should contain only unique elements.">
<source>This collection should contain only unique elements.</source>
<target>Esta colección debería tener exclusivamente elementos únicos.</target>
</trans-unit>
<trans-unit id="WdvZfq." resname="This value should be positive.">
<source>This value should be positive.</source>
<target>Este valor debería ser positivo.</target>
</trans-unit>
<trans-unit id="ubHMK2q" resname="This value should be either positive or zero.">
<source>This value should be either positive or zero.</source>
<target>Este valor debería ser positivo o igual a cero.</target>
</trans-unit>
<trans-unit id="IwNTzo_" resname="This value should be negative.">
<source>This value should be negative.</source>
<target>Este valor debería ser negativo.</target>
</trans-unit>
<trans-unit id="0GfwMfP" resname="This value should be either negative or zero.">
<source>This value should be either negative or zero.</source>
<target>Este valor debería ser negativo o igual a cero.</target>
</trans-unit>
<trans-unit id="fs3qQZR" resname="This value is not a valid timezone.">
<source>This value is not a valid timezone.</source>
<target>Este valor no es una zona horaria válida.</target>
</trans-unit>
<trans-unit id="40dnsod" resname="This password has been leaked in a data breach, it must not be used. Please use another password.">
<source>This password has been leaked in a data breach, it must not be used. Please use another password.</source>
<target>Esta contraseña no se puede utilizar porque está incluida en un listado de contraseñas públicas obtenido gracias a fallos de seguridad de otros sitios y aplicaciones. Por favor utilice otra contraseña.</target>
</trans-unit>
<trans-unit id="VvxxWas" resname="This value should be between {{ min }} and {{ max }}.">
<source>This value should be between {{ min }} and {{ max }}.</source>
<target>Este valor debería estar entre {{ min }} y {{ max }}.</target>
</trans-unit>
<trans-unit id="7g313cV" resname="This value is not a valid hostname.">
<source>This value is not a valid hostname.</source>
<target>Este valor no es un nombre de host válido.</target>
</trans-unit>
<trans-unit id="xwtBimR" resname="The number of elements in this collection should be a multiple of {{ compared_value }}.">
<source>The number of elements in this collection should be a multiple of {{ compared_value }}.</source>
<target>El número de elementos en esta colección debería ser múltiplo de {{ compared_value }}.</target>
</trans-unit>
<trans-unit id="FDRZr.s" resname="This value should satisfy at least one of the following constraints:">
<source>This value should satisfy at least one of the following constraints:</source>
<target>Este valor debería satisfacer al menos una de las siguientes restricciones:</target>
</trans-unit>
<trans-unit id="WwY0IGI" resname="Each element of this collection should satisfy its own set of constraints.">
<source>Each element of this collection should satisfy its own set of constraints.</source>
<target>Cada elemento de esta colección debería satisfacer su propio conjunto de restricciones.</target>
</trans-unit>
<trans-unit id="lrbaa8g" resname="This value is not a valid International Securities Identification Number (ISIN).">
<source>This value is not a valid International Securities Identification Number (ISIN).</source>
<target>Este valor no es un número de identificación internacional de valores (ISIN) válido.</target>
</trans-unit>
<trans-unit id="6akW5mb" resname="This value should be a valid expression.">
<source>This value should be a valid expression.</source>
<target>Este valor debería ser una expresión válida.</target>
</trans-unit>
<trans-unit id="CQ0wfFa" resname="This value is not a valid CSS color.">
<source>This value is not a valid CSS color.</source>
<target>Este valor no es un color CSS válido.</target>
</trans-unit>
<trans-unit id="GAaG0KN" resname="This value is not a valid CIDR notation.">
<source>This value is not a valid CIDR notation.</source>
<target>Este valor no es una notación CIDR válida.</target>
</trans-unit>
<trans-unit id="ddJy.XP" resname="The value of the netmask should be between {{ min }} and {{ max }}.">
<source>The value of the netmask should be between {{ min }} and {{ max }}.</source>
<target>El valor de la máscara de red debería estar entre {{ min }} y {{ max }}.</target>
</trans-unit>
<trans-unit id=".SEaaBa" resname="This form should not contain extra fields.">
<source>This form should not contain extra fields.</source>
<target>Este formulario no debería contener campos adicionales.</target>
</trans-unit>
<trans-unit id="WPnLAh9" resname="The uploaded file was too large. Please try to upload a smaller file.">
<source>The uploaded file was too large. Please try to upload a smaller file.</source>
<target>El archivo subido es demasiado grande. Por favor, suba un archivo más pequeño.</target>
</trans-unit>
<trans-unit id="fvxWW3V" resname="The CSRF token is invalid. Please try to resubmit the form.">
<source>The CSRF token is invalid. Please try to resubmit the form.</source>
<target>El token CSRF no es válido. Por favor, pruebe a enviar nuevamente el formulario.</target>
</trans-unit>
<trans-unit id="eo1zeih" resname="This value is not a valid HTML5 color.">
<source>This value is not a valid HTML5 color.</source>
<target>Este valor no es un color HTML5 válido.</target>
</trans-unit>
<trans-unit id="dN.ZVc0" resname="Please enter a valid birthdate.">
<source>Please enter a valid birthdate.</source>
<target>Por favor, ingrese una fecha de cumpleaños válida.</target>
</trans-unit>
<trans-unit id="OPymPfZ" resname="The selected choice is invalid.">
<source>The selected choice is invalid.</source>
<target>La opción seleccionada no es válida.</target>
</trans-unit>
<trans-unit id="GUH6TYE" resname="The collection is invalid.">
<source>The collection is invalid.</source>
<target>La colección no es válida.</target>
</trans-unit>
<trans-unit id="14il0ha" resname="Please select a valid color.">
<source>Please select a valid color.</source>
<target>Por favor, seleccione un color válido.</target>
</trans-unit>
<trans-unit id="4AJRUCC" resname="Please select a valid country.">
<source>Please select a valid country.</source>
<target>Por favor, seleccione un país válido.</target>
</trans-unit>
<trans-unit id="xAI5mrh" resname="Please select a valid currency.">
<source>Please select a valid currency.</source>
<target>Por favor, seleccione una moneda válida.</target>
</trans-unit>
<trans-unit id="c6v9ASZ" resname="Please choose a valid date interval.">
<source>Please choose a valid date interval.</source>
<target>Por favor, elija un intervalo de fechas válido.</target>
</trans-unit>
<trans-unit id="2BR60Jg" resname="Please enter a valid date and time.">
<source>Please enter a valid date and time.</source>
<target>Por favor, ingrese una fecha y hora válidas.</target>
</trans-unit>
<trans-unit id="ofLBhv0" resname="Please enter a valid date.">
<source>Please enter a valid date.</source>
<target>Por favor, ingrese una fecha valida.</target>
</trans-unit>
<trans-unit id="Xsmm6Mc" resname="Please select a valid file.">
<source>Please select a valid file.</source>
<target>Por favor, seleccione un archivo válido.</target>
</trans-unit>
<trans-unit id="Fka0cZD" resname="The hidden field is invalid.">
<source>The hidden field is invalid.</source>
<target>El campo oculto no es válido.</target>
</trans-unit>
<trans-unit id="69ux5Ml" resname="Please enter an integer.">
<source>Please enter an integer.</source>
<target>Por favor, ingrese un número entero.</target>
</trans-unit>
<trans-unit id="TQbat_9" resname="Please select a valid language.">
<source>Please select a valid language.</source>
<target>Por favor, seleccione un idioma válido.</target>
</trans-unit>
<trans-unit id="vpa7l8w" resname="Please select a valid locale.">
<source>Please select a valid locale.</source>
<target>Por favor, seleccione una configuración regional válida.</target>
</trans-unit>
<trans-unit id="Gz8pWER" resname="Please enter a valid money amount.">
<source>Please enter a valid money amount.</source>
<target>Por favor, ingrese una cantidad de dinero válida.</target>
</trans-unit>
<trans-unit id="PRxiOhB" resname="Please enter a number.">
<source>Please enter a number.</source>
<target>Por favor, ingrese un número.</target>
</trans-unit>
<trans-unit id="seAdKJo" resname="The password is invalid.">
<source>The password is invalid.</source>
<target>La contraseña no es válida.</target>
</trans-unit>
<trans-unit id="A5k0Q20" resname="Please enter a percentage value.">
<source>Please enter a percentage value.</source>
<target>Por favor, ingrese un valor porcentual.</target>
</trans-unit>
<trans-unit id="rH6V.WA" resname="The values do not match.">
<source>The values do not match.</source>
<target>Los valores no coinciden.</target>
</trans-unit>
<trans-unit id="Eopl1Sm" resname="Please enter a valid time.">
<source>Please enter a valid time.</source>
<target>Por favor, ingrese una hora válida.</target>
</trans-unit>
<trans-unit id="ll14Sm9" resname="Please select a valid timezone.">
<source>Please select a valid timezone.</source>
<target>Por favor, seleccione una zona horaria válida.</target>
</trans-unit>
<trans-unit id="1xP1kmd" resname="Please enter a valid URL.">
<source>Please enter a valid URL.</source>
<target>Por favor, ingrese una URL válida.</target>
</trans-unit>
<trans-unit id="iMmAsrC" resname="Please enter a valid search term.">
<source>Please enter a valid search term.</source>
<target>Por favor, ingrese un término de búsqueda válido.</target>
</trans-unit>
<trans-unit id="GRMTXRX" resname="Please provide a valid phone number.">
<source>Please provide a valid phone number.</source>
<target>Por favor, proporcione un número de teléfono válido.</target>
</trans-unit>
<trans-unit id="WkYuMDd" resname="The checkbox has an invalid value.">
<source>The checkbox has an invalid value.</source>
<target>La casilla de verificación tiene un valor inválido.</target>
</trans-unit>
<trans-unit id="lY5Mzyl" resname="Please enter a valid email address.">
<source>Please enter a valid email address.</source>
<target>Por favor, ingrese una dirección de correo electrónico válida.</target>
</trans-unit>
<trans-unit id="TzSC4.o" resname="Please select a valid option.">
<source>Please select a valid option.</source>
<target>Por favor, seleccione una opción válida.</target>
</trans-unit>
<trans-unit id="qGRcTk7" resname="Please select a valid range.">
<source>Please select a valid range.</source>
<target>Por favor, seleccione un rango válido.</target>
</trans-unit>
<trans-unit id="PU.w6nC" resname="Please enter a valid week.">
<source>Please enter a valid week.</source>
<target>Por favor, ingrese una semana válida.</target>
</trans-unit>
</body>
</file>
</xliff>

View File

@ -0,0 +1,542 @@
<?xml version="1.0" encoding="utf-8"?>
<xliff xmlns="urn:oasis:names:tc:xliff:document:1.2" version="1.2">
<file source-language="en" target-language="fr" datatype="plaintext" original="file.ext">
<header>
<tool tool-id="symfony" tool-name="Symfony"/>
</header>
<body>
<trans-unit id="zh5oKD9" resname="This value should be false.">
<source>This value should be false.</source>
<target>Cette valeur doit être fausse.</target>
</trans-unit>
<trans-unit id="NN2_iru" resname="This value should be true.">
<source>This value should be true.</source>
<target>Cette valeur doit être vraie.</target>
</trans-unit>
<trans-unit id="OjM_kpf" resname="This value should be of type {{ type }}.">
<source>This value should be of type {{ type }}.</source>
<target>Cette valeur doit être de type {{ type }}.</target>
</trans-unit>
<trans-unit id="f0P5pBD" resname="This value should be blank.">
<source>This value should be blank.</source>
<target>Cette valeur doit être vide.</target>
</trans-unit>
<trans-unit id="ih.4TRN" resname="The value you selected is not a valid choice.">
<source>The value you selected is not a valid choice.</source>
<target>Cette valeur doit être l'un des choix proposés.</target>
</trans-unit>
<trans-unit id="u81CkP8" resname="You must select at least {{ limit }} choice.|You must select at least {{ limit }} choices.">
<source>You must select at least {{ limit }} choice.|You must select at least {{ limit }} choices.</source>
<target>Vous devez sélectionner au moins {{ limit }} choix.|Vous devez sélectionner au moins {{ limit }} choix.</target>
</trans-unit>
<trans-unit id="nIvOQ_o" resname="You must select at most {{ limit }} choice.|You must select at most {{ limit }} choices.">
<source>You must select at most {{ limit }} choice.|You must select at most {{ limit }} choices.</source>
<target>Vous devez sélectionner au maximum {{ limit }} choix.|Vous devez sélectionner au maximum {{ limit }} choix.</target>
</trans-unit>
<trans-unit id="87zjiHi" resname="One or more of the given values is invalid.">
<source>One or more of the given values is invalid.</source>
<target>Une ou plusieurs des valeurs soumises sont invalides.</target>
</trans-unit>
<trans-unit id="3NeQftv" resname="This field was not expected.">
<source>This field was not expected.</source>
<target>Ce champ n'a pas été prévu.</target>
</trans-unit>
<trans-unit id="SwMV4zp" resname="This field is missing.">
<source>This field is missing.</source>
<target>Ce champ est manquant.</target>
</trans-unit>
<trans-unit id="LO2vFKN" resname="This value is not a valid date.">
<source>This value is not a valid date.</source>
<target>Cette valeur n'est pas une date valide.</target>
</trans-unit>
<trans-unit id="86dU_nv" resname="This value is not a valid datetime.">
<source>This value is not a valid datetime.</source>
<target>Cette valeur n'est pas une date valide.</target>
</trans-unit>
<trans-unit id="PSvNXdi" resname="This value is not a valid email address.">
<source>This value is not a valid email address.</source>
<target>Cette valeur n'est pas une adresse email valide.</target>
</trans-unit>
<trans-unit id="3KeHbZy" resname="The file could not be found.">
<source>The file could not be found.</source>
<target>Le fichier n'a pas été trouvé.</target>
</trans-unit>
<trans-unit id="KtJhQZo" resname="The file is not readable.">
<source>The file is not readable.</source>
<target>Le fichier n'est pas lisible.</target>
</trans-unit>
<trans-unit id="JocOVM2" resname="The file is too large ({{ size }} {{ suffix }}). Allowed maximum size is {{ limit }} {{ suffix }}.">
<source>The file is too large ({{ size }} {{ suffix }}). Allowed maximum size is {{ limit }} {{ suffix }}.</source>
<target>Le fichier est trop volumineux ({{ size }} {{ suffix }}). Sa taille ne doit pas dépasser {{ limit }} {{ suffix }}.</target>
</trans-unit>
<trans-unit id="YW21SPH" resname="The mime type of the file is invalid ({{ type }}). Allowed mime types are {{ types }}.">
<source>The mime type of the file is invalid ({{ type }}). Allowed mime types are {{ types }}.</source>
<target>Le type du fichier est invalide ({{ type }}). Les types autorisés sont {{ types }}.</target>
</trans-unit>
<trans-unit id="NubOmrs" resname="This value should be {{ limit }} or less.">
<source>This value should be {{ limit }} or less.</source>
<target>Cette valeur doit être inférieure ou égale à {{ limit }}.</target>
</trans-unit>
<trans-unit id="HX7TOFm" resname="This value is too long. It should have {{ limit }} character or less.|This value is too long. It should have {{ limit }} characters or less.">
<source>This value is too long. It should have {{ limit }} character or less.|This value is too long. It should have {{ limit }} characters or less.</source>
<target>Cette chaîne est trop longue. Elle doit avoir au maximum {{ limit }} caractère.|Cette chaîne est trop longue. Elle doit avoir au maximum {{ limit }} caractères.</target>
</trans-unit>
<trans-unit id="qgR8M_U" resname="This value should be {{ limit }} or more.">
<source>This value should be {{ limit }} or more.</source>
<target>Cette valeur doit être supérieure ou égale à {{ limit }}.</target>
</trans-unit>
<trans-unit id="ekfrU.c" resname="This value is too short. It should have {{ limit }} character or more.|This value is too short. It should have {{ limit }} characters or more.">
<source>This value is too short. It should have {{ limit }} character or more.|This value is too short. It should have {{ limit }} characters or more.</source>
<target>Cette chaîne est trop courte. Elle doit avoir au minimum {{ limit }} caractère.|Cette chaîne est trop courte. Elle doit avoir au minimum {{ limit }} caractères.</target>
</trans-unit>
<trans-unit id="1KV4L.t" resname="This value should not be blank.">
<source>This value should not be blank.</source>
<target>Cette valeur ne doit pas être vide.</target>
</trans-unit>
<trans-unit id="2G4Vepm" resname="This value should not be null.">
<source>This value should not be null.</source>
<target>Cette valeur ne doit pas être nulle.</target>
</trans-unit>
<trans-unit id="yDc3m6E" resname="This value should be null.">
<source>This value should be null.</source>
<target>Cette valeur doit être nulle.</target>
</trans-unit>
<trans-unit id="zKzWejA" resname="This value is not valid.">
<source>This value is not valid.</source>
<target>Cette valeur n'est pas valide.</target>
</trans-unit>
<trans-unit id="HSuBZpQ" resname="This value is not a valid time.">
<source>This value is not a valid time.</source>
<target>Cette valeur n'est pas une heure valide.</target>
</trans-unit>
<trans-unit id="snWc_QT" resname="This value is not a valid URL.">
<source>This value is not a valid URL.</source>
<target>Cette valeur n'est pas une URL valide.</target>
</trans-unit>
<trans-unit id="jpaLsb2" resname="The two values should be equal.">
<source>The two values should be equal.</source>
<target>Les deux valeurs doivent être identiques.</target>
</trans-unit>
<trans-unit id="fIlB1B_" resname="The file is too large. Allowed maximum size is {{ limit }} {{ suffix }}.">
<source>The file is too large. Allowed maximum size is {{ limit }} {{ suffix }}.</source>
<target>Le fichier est trop volumineux. Sa taille ne doit pas dépasser {{ limit }} {{ suffix }}.</target>
</trans-unit>
<trans-unit id="tW7o0t9" resname="The file is too large.">
<source>The file is too large.</source>
<target>Le fichier est trop volumineux.</target>
</trans-unit>
<trans-unit id=".exF5Ww" resname="The file could not be uploaded.">
<source>The file could not be uploaded.</source>
<target>Le téléchargement de ce fichier est impossible.</target>
</trans-unit>
<trans-unit id="d7sS5yw" resname="This value should be a valid number.">
<source>This value should be a valid number.</source>
<target>Cette valeur doit être un nombre.</target>
</trans-unit>
<trans-unit id="BS2Ez6i" resname="This file is not a valid image.">
<source>This file is not a valid image.</source>
<target>Ce fichier n'est pas une image valide.</target>
</trans-unit>
<trans-unit id="ydcT9kU" resname="This is not a valid IP address.">
<source>This is not a valid IP address.</source>
<target>Cette adresse IP n'est pas valide.</target>
</trans-unit>
<trans-unit id="lDOGNFX" resname="This value is not a valid language.">
<source>This value is not a valid language.</source>
<target>Cette langue n'est pas valide.</target>
</trans-unit>
<trans-unit id="y9IdYkA" resname="This value is not a valid locale.">
<source>This value is not a valid locale.</source>
<target>Ce paramètre régional n'est pas valide.</target>
</trans-unit>
<trans-unit id="1YC0pOd" resname="This value is not a valid country.">
<source>This value is not a valid country.</source>
<target>Ce pays n'est pas valide.</target>
</trans-unit>
<trans-unit id="B5ebaMp" resname="This value is already used.">
<source>This value is already used.</source>
<target>Cette valeur est déjà utilisée.</target>
</trans-unit>
<trans-unit id="L6097a6" resname="The size of the image could not be detected.">
<source>The size of the image could not be detected.</source>
<target>La taille de l'image n'a pas pu être détectée.</target>
</trans-unit>
<trans-unit id="zVtJJEa" resname="The image width is too big ({{ width }}px). Allowed maximum width is {{ max_width }}px.">
<source>The image width is too big ({{ width }}px). Allowed maximum width is {{ max_width }}px.</source>
<target>La largeur de l'image est trop grande ({{ width }}px). La largeur maximale autorisée est de {{ max_width }}px.</target>
</trans-unit>
<trans-unit id="s8LFQGC" resname="The image width is too small ({{ width }}px). Minimum width expected is {{ min_width }}px.">
<source>The image width is too small ({{ width }}px). Minimum width expected is {{ min_width }}px.</source>
<target>La largeur de l'image est trop petite ({{ width }}px). La largeur minimale attendue est de {{ min_width }}px.</target>
</trans-unit>
<trans-unit id="Z.NgqFj" resname="The image height is too big ({{ height }}px). Allowed maximum height is {{ max_height }}px.">
<source>The image height is too big ({{ height }}px). Allowed maximum height is {{ max_height }}px.</source>
<target>La hauteur de l'image est trop grande ({{ height }}px). La hauteur maximale autorisée est de {{ max_height }}px.</target>
</trans-unit>
<trans-unit id="AW1lWVM" resname="The image height is too small ({{ height }}px). Minimum height expected is {{ min_height }}px.">
<source>The image height is too small ({{ height }}px). Minimum height expected is {{ min_height }}px.</source>
<target>La hauteur de l'image est trop petite ({{ height }}px). La hauteur minimale attendue est de {{ min_height }}px.</target>
</trans-unit>
<trans-unit id="PSdMNab" resname="This value should be the user's current password.">
<source>This value should be the user's current password.</source>
<target>Cette valeur doit être le mot de passe actuel de l'utilisateur.</target>
</trans-unit>
<trans-unit id="gYImVyV" resname="This value should have exactly {{ limit }} character.|This value should have exactly {{ limit }} characters.">
<source>This value should have exactly {{ limit }} character.|This value should have exactly {{ limit }} characters.</source>
<target>Cette chaîne doit avoir exactement {{ limit }} caractère.|Cette chaîne doit avoir exactement {{ limit }} caractères.</target>
</trans-unit>
<trans-unit id="xJ2Bcr_" resname="The file was only partially uploaded.">
<source>The file was only partially uploaded.</source>
<target>Le fichier a été partiellement transféré.</target>
</trans-unit>
<trans-unit id="HzkJDtF" resname="No file was uploaded.">
<source>No file was uploaded.</source>
<target>Aucun fichier n'a été transféré.</target>
</trans-unit>
<trans-unit id="mHfEaB3" resname="No temporary folder was configured in php.ini.">
<source>No temporary folder was configured in php.ini.</source>
<target>Aucun répertoire temporaire n'a été configuré dans le php.ini, ou le répertoire configuré n'existe pas.</target>
</trans-unit>
<trans-unit id="y9K3BGb" resname="Cannot write temporary file to disk.">
<source>Cannot write temporary file to disk.</source>
<target>Impossible d'écrire le fichier temporaire sur le disque.</target>
</trans-unit>
<trans-unit id="kx3yHIM" resname="A PHP extension caused the upload to fail.">
<source>A PHP extension caused the upload to fail.</source>
<target>Une extension PHP a empêché le transfert du fichier.</target>
</trans-unit>
<trans-unit id="gTJYRl6" resname="This collection should contain {{ limit }} element or more.|This collection should contain {{ limit }} elements or more.">
<source>This collection should contain {{ limit }} element or more.|This collection should contain {{ limit }} elements or more.</source>
<target>Cette collection doit contenir {{ limit }} élément ou plus.|Cette collection doit contenir {{ limit }} éléments ou plus.</target>
</trans-unit>
<trans-unit id="FFn3lVn" resname="This collection should contain {{ limit }} element or less.|This collection should contain {{ limit }} elements or less.">
<source>This collection should contain {{ limit }} element or less.|This collection should contain {{ limit }} elements or less.</source>
<target>Cette collection doit contenir {{ limit }} élément ou moins.|Cette collection doit contenir {{ limit }} éléments ou moins.</target>
</trans-unit>
<trans-unit id="bSdilZv" resname="This collection should contain exactly {{ limit }} element.|This collection should contain exactly {{ limit }} elements.">
<source>This collection should contain exactly {{ limit }} element.|This collection should contain exactly {{ limit }} elements.</source>
<target>Cette collection doit contenir exactement {{ limit }} élément.|Cette collection doit contenir exactement {{ limit }} éléments.</target>
</trans-unit>
<trans-unit id="MAzmID7" resname="Invalid card number.">
<source>Invalid card number.</source>
<target>Numéro de carte invalide.</target>
</trans-unit>
<trans-unit id="c3REGK3" resname="Unsupported card type or invalid card number.">
<source>Unsupported card type or invalid card number.</source>
<target>Type de carte non supporté ou numéro invalide.</target>
</trans-unit>
<trans-unit id="XSVzcbV" resname="This is not a valid International Bank Account Number (IBAN).">
<source>This is not a valid International Bank Account Number (IBAN).</source>
<target>Le numéro IBAN (International Bank Account Number) saisi n'est pas valide.</target>
</trans-unit>
<trans-unit id="yHirwNr" resname="This value is not a valid ISBN-10.">
<source>This value is not a valid ISBN-10.</source>
<target>Cette valeur n'est pas un code ISBN-10 valide.</target>
</trans-unit>
<trans-unit id="c_q0_ua" resname="This value is not a valid ISBN-13.">
<source>This value is not a valid ISBN-13.</source>
<target>Cette valeur n'est pas un code ISBN-13 valide.</target>
</trans-unit>
<trans-unit id="M4FlD6n" resname="This value is neither a valid ISBN-10 nor a valid ISBN-13.">
<source>This value is neither a valid ISBN-10 nor a valid ISBN-13.</source>
<target>Cette valeur n'est ni un code ISBN-10, ni un code ISBN-13 valide.</target>
</trans-unit>
<trans-unit id="cuct0Ow" resname="This value is not a valid ISSN.">
<source>This value is not a valid ISSN.</source>
<target>Cette valeur n'est pas un code ISSN valide.</target>
</trans-unit>
<trans-unit id="JBLs1a1" resname="This value is not a valid currency.">
<source>This value is not a valid currency.</source>
<target>Cette valeur n'est pas une devise valide.</target>
</trans-unit>
<trans-unit id="c.WxzFW" resname="This value should be equal to {{ compared_value }}.">
<source>This value should be equal to {{ compared_value }}.</source>
<target>Cette valeur doit être égale à {{ compared_value }}.</target>
</trans-unit>
<trans-unit id="_jdjkwq" resname="This value should be greater than {{ compared_value }}.">
<source>This value should be greater than {{ compared_value }}.</source>
<target>Cette valeur doit être supérieure à {{ compared_value }}.</target>
</trans-unit>
<trans-unit id="o8A8a0H" resname="This value should be greater than or equal to {{ compared_value }}.">
<source>This value should be greater than or equal to {{ compared_value }}.</source>
<target>Cette valeur doit être supérieure ou égale à {{ compared_value }}.</target>
</trans-unit>
<trans-unit id="bOF1fpm" resname="This value should be identical to {{ compared_value_type }} {{ compared_value }}.">
<source>This value should be identical to {{ compared_value_type }} {{ compared_value }}.</source>
<target>Cette valeur doit être identique à {{ compared_value_type }} {{ compared_value }}.</target>
</trans-unit>
<trans-unit id="jG0QFKw" resname="This value should be less than {{ compared_value }}.">
<source>This value should be less than {{ compared_value }}.</source>
<target>Cette valeur doit être inférieure à {{ compared_value }}.</target>
</trans-unit>
<trans-unit id="9lWrKmm" resname="This value should be less than or equal to {{ compared_value }}.">
<source>This value should be less than or equal to {{ compared_value }}.</source>
<target>Cette valeur doit être inférieure ou égale à {{ compared_value }}.</target>
</trans-unit>
<trans-unit id="ftTwGs." resname="This value should not be equal to {{ compared_value }}.">
<source>This value should not be equal to {{ compared_value }}.</source>
<target>Cette valeur ne doit pas être égale à {{ compared_value }}.</target>
</trans-unit>
<trans-unit id="Bi22JLt" resname="This value should not be identical to {{ compared_value_type }} {{ compared_value }}.">
<source>This value should not be identical to {{ compared_value_type }} {{ compared_value }}.</source>
<target>Cette valeur ne doit pas être identique à {{ compared_value_type }} {{ compared_value }}.</target>
</trans-unit>
<trans-unit id="VczCWzQ" resname="The image ratio is too big ({{ ratio }}). Allowed maximum ratio is {{ max_ratio }}.">
<source>The image ratio is too big ({{ ratio }}). Allowed maximum ratio is {{ max_ratio }}.</source>
<target>Le rapport largeur/hauteur de l'image est trop grand ({{ ratio }}). Le rapport maximal autorisé est {{ max_ratio }}.</target>
</trans-unit>
<trans-unit id="v57PXhq" resname="The image ratio is too small ({{ ratio }}). Minimum ratio expected is {{ min_ratio }}.">
<source>The image ratio is too small ({{ ratio }}). Minimum ratio expected is {{ min_ratio }}.</source>
<target>Le rapport largeur/hauteur de l'image est trop petit ({{ ratio }}). Le rapport minimal attendu est {{ min_ratio }}.</target>
</trans-unit>
<trans-unit id="rpajj.a" resname="The image is square ({{ width }}x{{ height }}px). Square images are not allowed.">
<source>The image is square ({{ width }}x{{ height }}px). Square images are not allowed.</source>
<target>L'image est carrée ({{ width }}x{{ height }}px). Les images carrées ne sont pas autorisées.</target>
</trans-unit>
<trans-unit id="G_lu2qW" resname="The image is landscape oriented ({{ width }}x{{ height }}px). Landscape oriented images are not allowed.">
<source>The image is landscape oriented ({{ width }}x{{ height }}px). Landscape oriented images are not allowed.</source>
<target>L'image est au format paysage ({{ width }}x{{ height }}px). Les images au format paysage ne sont pas autorisées.</target>
</trans-unit>
<trans-unit id="sFyGx4B" resname="The image is portrait oriented ({{ width }}x{{ height }}px). Portrait oriented images are not allowed.">
<source>The image is portrait oriented ({{ width }}x{{ height }}px). Portrait oriented images are not allowed.</source>
<target>L'image est au format portrait ({{ width }}x{{ height }}px). Les images au format portrait ne sont pas autorisées.</target>
</trans-unit>
<trans-unit id="jZgqcpL" resname="An empty file is not allowed.">
<source>An empty file is not allowed.</source>
<target>Un fichier vide n'est pas autorisé.</target>
</trans-unit>
<trans-unit id="bcfVezI" resname="The host could not be resolved.">
<source>The host could not be resolved.</source>
<target>Le nom de domaine n'a pas pu être résolu.</target>
</trans-unit>
<trans-unit id="NtzKvgt" resname="This value does not match the expected {{ charset }} charset.">
<source>This value does not match the expected {{ charset }} charset.</source>
<target>Cette valeur ne correspond pas au jeu de caractères {{ charset }} attendu.</target>
</trans-unit>
<trans-unit id="Wi2y9.N" resname="This is not a valid Business Identifier Code (BIC).">
<source>This is not a valid Business Identifier Code (BIC).</source>
<target>Ce n'est pas un code universel d'identification des banques (BIC) valide.</target>
</trans-unit>
<trans-unit id="VKDowX6" resname="Error">
<source>Error</source>
<target>Erreur</target>
</trans-unit>
<trans-unit id="8zqt0Ik" resname="This is not a valid UUID.">
<source>This is not a valid UUID.</source>
<target>Ceci n'est pas un UUID valide.</target>
</trans-unit>
<trans-unit id="ru.4wkH" resname="This value should be a multiple of {{ compared_value }}.">
<source>This value should be a multiple of {{ compared_value }}.</source>
<target>Cette valeur doit être un multiple de {{ compared_value }}.</target>
</trans-unit>
<trans-unit id="M3vyK6s" resname="This Business Identifier Code (BIC) is not associated with IBAN {{ iban }}.">
<source>This Business Identifier Code (BIC) is not associated with IBAN {{ iban }}.</source>
<target>Ce code d'identification d'entreprise (BIC) n'est pas associé à l'IBAN {{ iban }}.</target>
</trans-unit>
<trans-unit id="2v2xpAh" resname="This value should be valid JSON.">
<source>This value should be valid JSON.</source>
<target>Cette valeur doit être un JSON valide.</target>
</trans-unit>
<trans-unit id="9CWVEGq" resname="This collection should contain only unique elements.">
<source>This collection should contain only unique elements.</source>
<target>Cette collection ne doit pas comporter de doublons.</target>
</trans-unit>
<trans-unit id="WdvZfq." resname="This value should be positive.">
<source>This value should be positive.</source>
<target>Cette valeur doit être strictement positive.</target>
</trans-unit>
<trans-unit id="ubHMK2q" resname="This value should be either positive or zero.">
<source>This value should be either positive or zero.</source>
<target>Cette valeur doit être supérieure ou égale à zéro.</target>
</trans-unit>
<trans-unit id="IwNTzo_" resname="This value should be negative.">
<source>This value should be negative.</source>
<target>Cette valeur doit être strictement négative.</target>
</trans-unit>
<trans-unit id="0GfwMfP" resname="This value should be either negative or zero.">
<source>This value should be either negative or zero.</source>
<target>Cette valeur doit être inférieure ou égale à zéro.</target>
</trans-unit>
<trans-unit id="fs3qQZR" resname="This value is not a valid timezone.">
<source>This value is not a valid timezone.</source>
<target>Cette valeur n'est pas un fuseau horaire valide.</target>
</trans-unit>
<trans-unit id="40dnsod" resname="This password has been leaked in a data breach, it must not be used. Please use another password.">
<source>This password has been leaked in a data breach, it must not be used. Please use another password.</source>
<target>Ce mot de passe a été divulgué lors d'une fuite de données, il ne doit plus être utilisé. Veuillez utiliser un autre mot de passe.</target>
</trans-unit>
<trans-unit id="VvxxWas" resname="This value should be between {{ min }} and {{ max }}.">
<source>This value should be between {{ min }} and {{ max }}.</source>
<target>Cette valeur doit être comprise entre {{ min }} et {{ max }}.</target>
</trans-unit>
<trans-unit id="7g313cV" resname="This value is not a valid hostname.">
<source>This value is not a valid hostname.</source>
<target>Cette valeur n'est pas un nom d'hôte valide.</target>
</trans-unit>
<trans-unit id="xwtBimR" resname="The number of elements in this collection should be a multiple of {{ compared_value }}.">
<source>The number of elements in this collection should be a multiple of {{ compared_value }}.</source>
<target>Le nombre d'éléments de cette collection doit être un multiple de {{ compared_value }}.</target>
</trans-unit>
<trans-unit id="FDRZr.s" resname="This value should satisfy at least one of the following constraints:">
<source>This value should satisfy at least one of the following constraints:</source>
<target>Cette valeur doit satisfaire à au moins une des contraintes suivantes :</target>
</trans-unit>
<trans-unit id="WwY0IGI" resname="Each element of this collection should satisfy its own set of constraints.">
<source>Each element of this collection should satisfy its own set of constraints.</source>
<target>Chaque élément de cette collection doit satisfaire à son propre jeu de contraintes.</target>
</trans-unit>
<trans-unit id="lrbaa8g" resname="This value is not a valid International Securities Identification Number (ISIN).">
<source>This value is not a valid International Securities Identification Number (ISIN).</source>
<target>Cette valeur n'est pas un code international de sécurité valide (ISIN).</target>
</trans-unit>
<trans-unit id="6akW5mb" resname="This value should be a valid expression.">
<source>This value should be a valid expression.</source>
<target>Cette valeur doit être une expression valide.</target>
</trans-unit>
<trans-unit id="CQ0wfFa" resname="This value is not a valid CSS color.">
<source>This value is not a valid CSS color.</source>
<target>Cette valeur n'est pas une couleur CSS valide.</target>
</trans-unit>
<trans-unit id="GAaG0KN" resname="This value is not a valid CIDR notation.">
<source>This value is not a valid CIDR notation.</source>
<target>Cette valeur n'est pas une notation CIDR valide.</target>
</trans-unit>
<trans-unit id="ddJy.XP" resname="The value of the netmask should be between {{ min }} and {{ max }}.">
<source>The value of the netmask should be between {{ min }} and {{ max }}.</source>
<target>La valeur du masque de réseau doit être comprise entre {{ min }} et {{ max }}.</target>
</trans-unit>
<trans-unit id=".SEaaBa" resname="This form should not contain extra fields.">
<source>This form should not contain extra fields.</source>
<target>Ce formulaire ne doit pas contenir de champs supplémentaires.</target>
</trans-unit>
<trans-unit id="WPnLAh9" resname="The uploaded file was too large. Please try to upload a smaller file.">
<source>The uploaded file was too large. Please try to upload a smaller file.</source>
<target>Le fichier téléchargé est trop volumineux. Merci d'essayer d'envoyer un fichier plus petit.</target>
</trans-unit>
<trans-unit id="fvxWW3V" resname="The CSRF token is invalid. Please try to resubmit the form.">
<source>The CSRF token is invalid. Please try to resubmit the form.</source>
<target>Le jeton CSRF est invalide. Veuillez renvoyer le formulaire.</target>
</trans-unit>
<trans-unit id="eo1zeih" resname="This value is not a valid HTML5 color.">
<source>This value is not a valid HTML5 color.</source>
<target>Cette valeur n'est pas une couleur HTML5 valide.</target>
</trans-unit>
<trans-unit id="dN.ZVc0" resname="Please enter a valid birthdate.">
<source>Please enter a valid birthdate.</source>
<target>Veuillez entrer une date de naissance valide.</target>
</trans-unit>
<trans-unit id="OPymPfZ" resname="The selected choice is invalid.">
<source>The selected choice is invalid.</source>
<target>Le choix sélectionné est invalide.</target>
</trans-unit>
<trans-unit id="GUH6TYE" resname="The collection is invalid.">
<source>The collection is invalid.</source>
<target>La collection est invalide.</target>
</trans-unit>
<trans-unit id="14il0ha" resname="Please select a valid color.">
<source>Please select a valid color.</source>
<target>Veuillez sélectionner une couleur valide.</target>
</trans-unit>
<trans-unit id="4AJRUCC" resname="Please select a valid country.">
<source>Please select a valid country.</source>
<target>Veuillez sélectionner un pays valide.</target>
</trans-unit>
<trans-unit id="xAI5mrh" resname="Please select a valid currency.">
<source>Please select a valid currency.</source>
<target>Veuillez sélectionner une devise valide.</target>
</trans-unit>
<trans-unit id="c6v9ASZ" resname="Please choose a valid date interval.">
<source>Please choose a valid date interval.</source>
<target>Veuillez choisir un intervalle de dates valide.</target>
</trans-unit>
<trans-unit id="2BR60Jg" resname="Please enter a valid date and time.">
<source>Please enter a valid date and time.</source>
<target>Veuillez saisir une date et une heure valides.</target>
</trans-unit>
<trans-unit id="ofLBhv0" resname="Please enter a valid date.">
<source>Please enter a valid date.</source>
<target>Veuillez entrer une date valide.</target>
</trans-unit>
<trans-unit id="Xsmm6Mc" resname="Please select a valid file.">
<source>Please select a valid file.</source>
<target>Veuillez sélectionner un fichier valide.</target>
</trans-unit>
<trans-unit id="Fka0cZD" resname="The hidden field is invalid.">
<source>The hidden field is invalid.</source>
<target>Le champ masqué n'est pas valide.</target>
</trans-unit>
<trans-unit id="69ux5Ml" resname="Please enter an integer.">
<source>Please enter an integer.</source>
<target>Veuillez saisir un entier.</target>
</trans-unit>
<trans-unit id="TQbat_9" resname="Please select a valid language.">
<source>Please select a valid language.</source>
<target>Veuillez sélectionner une langue valide.</target>
</trans-unit>
<trans-unit id="vpa7l8w" resname="Please select a valid locale.">
<source>Please select a valid locale.</source>
<target>Veuillez sélectionner une langue valide.</target>
</trans-unit>
<trans-unit id="Gz8pWER" resname="Please enter a valid money amount.">
<source>Please enter a valid money amount.</source>
<target>Veuillez saisir un montant valide.</target>
</trans-unit>
<trans-unit id="PRxiOhB" resname="Please enter a number.">
<source>Please enter a number.</source>
<target>Veuillez saisir un nombre.</target>
</trans-unit>
<trans-unit id="seAdKJo" resname="The password is invalid.">
<source>The password is invalid.</source>
<target>Le mot de passe est invalide.</target>
</trans-unit>
<trans-unit id="A5k0Q20" resname="Please enter a percentage value.">
<source>Please enter a percentage value.</source>
<target>Veuillez saisir un pourcentage valide.</target>
</trans-unit>
<trans-unit id="rH6V.WA" resname="The values do not match.">
<source>The values do not match.</source>
<target>Les valeurs ne correspondent pas.</target>
</trans-unit>
<trans-unit id="Eopl1Sm" resname="Please enter a valid time.">
<source>Please enter a valid time.</source>
<target>Veuillez saisir une heure valide.</target>
</trans-unit>
<trans-unit id="ll14Sm9" resname="Please select a valid timezone.">
<source>Please select a valid timezone.</source>
<target>Veuillez sélectionner un fuseau horaire valide.</target>
</trans-unit>
<trans-unit id="1xP1kmd" resname="Please enter a valid URL.">
<source>Please enter a valid URL.</source>
<target>Veuillez saisir une URL valide.</target>
</trans-unit>
<trans-unit id="iMmAsrC" resname="Please enter a valid search term.">
<source>Please enter a valid search term.</source>
<target>Veuillez saisir un terme de recherche valide.</target>
</trans-unit>
<trans-unit id="GRMTXRX" resname="Please provide a valid phone number.">
<source>Please provide a valid phone number.</source>
<target>Veuillez fournir un numéro de téléphone valide.</target>
</trans-unit>
<trans-unit id="WkYuMDd" resname="The checkbox has an invalid value.">
<source>The checkbox has an invalid value.</source>
<target>La case à cocher a une valeur non valide.</target>
</trans-unit>
<trans-unit id="lY5Mzyl" resname="Please enter a valid email address.">
<source>Please enter a valid email address.</source>
<target>Veuillez saisir une adresse email valide.</target>
</trans-unit>
<trans-unit id="TzSC4.o" resname="Please select a valid option.">
<source>Please select a valid option.</source>
<target>Veuillez sélectionner une option valide.</target>
</trans-unit>
<trans-unit id="qGRcTk7" resname="Please select a valid range.">
<source>Please select a valid range.</source>
<target>Veuillez sélectionner une plage valide.</target>
</trans-unit>
<trans-unit id="PU.w6nC" resname="Please enter a valid week.">
<source>Please enter a valid week.</source>
<target>Veuillez entrer une semaine valide.</target>
</trans-unit>
</body>
</file>
</xliff>

View File

@ -1,4 +1,7 @@
const Encore = require('@symfony/webpack-encore');
const { DefinePlugin } = require('webpack');
const { GitRevisionPlugin } = require('git-revision-webpack-plugin');
const gitRevisionPlugin = new GitRevisionPlugin({lightweightTags: true});
// Manually configure the runtime environment if not already configured yet by the "encore" command.
// It's useful when you use tools that rely on webpack.config.js file.
@ -73,6 +76,14 @@ Encore
// uncomment if you're having problems with a jQuery plugin
//.autoProvidejQuery()
;
.addPlugin(
new DefinePlugin(
{
VERSION: JSON.stringify(gitRevisionPlugin.version()),
}
)
)
;
module.exports = Encore.getWebpackConfig();

File diff suppressed because it is too large Load Diff