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 push.sh
config/*.conf config/*.conf
.vscode/

View File

@ -9,5 +9,6 @@
- Extracts BirdNET bird contacts into SQL database - Extracts BirdNET bird contacts into SQL database
- Add birdnet_stream icecast audio streaming and live spectrogram service https://birdnet/spectro - Add birdnet_stream icecast audio streaming and live spectrogram service https://birdnet/spectro
- Add /today/species and /today/{date}/species/{id} endpoints - 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 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. 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 ## 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: For a one-liner installation, you can use the following command:
```bash ```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 ## Requirements
@ -34,7 +34,7 @@ sudo apt-get install ffmpeg
### Clone BirdNET-stream repository ### Clone BirdNET-stream repository
```bash ```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 ### Setup python virtualenv and packages

3
TODO
View File

@ -1,2 +1,5 @@
- Fix clean script - Fix clean script
- Fix service manager - 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 # Perform audio chunk analysis on all recorded chunks
analyze_chunks() { analyze_chunks() {
for chunk_name in $(get_chunk_list); do 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" chunk_path="${CHUNK_FOLDER}/in/$chunk_name"
mv $chunk_path "${CHUNK_FOLDER}/out/$chunk_name" mv $chunk_path "${CHUNK_FOLDER}/out/$chunk_name"
done done

View File

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

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, `date` TEXT NOT NULL,
`notes` TEXT, `notes` TEXT,
`confidence` REAL NOT NULL, `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(taxon_id) REFERENCES taxon(taxon_id),
FOREIGN KEY(location_id) REFERENCES location(location_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 #! /usr/bin/env bash
# Standard Installation Script for BirdNET-stream for Debian Based Linux distros
# set -x # set -x
set -e set -e
DEBUG=${DEBUG:-0} DEBUG=${DEBUG:-0}
# Standard Installation Script for BirdNET-stream for Debian Based Linux distros
REQUIREMENTS="git ffmpeg python3-pip python3-dev" REQUIREMENTS="git ffmpeg python3-pip python3-dev"
REPOSITORY="https://github.com/UncleSamulus/BirdNET-stream.git" REPOSITORY=${REPOSITORY:-https://github.com/UncleSamulus/BirdNET-stream.git}
# Update system
update() {
sudo apt-get update
sudo apt-get upgrade -y
}
debug() { debug() {
if [ $DEBUG -eq 1 ]; then if [ $DEBUG -eq 1 ]; then
@ -32,7 +25,7 @@ install_requirements() {
done done
if [ -n "$missing_requirements" ]; then if [ -n "$missing_requirements" ]; then
debug "Installing missing requirements: $missing_requirements" debug "Installing missing requirements: $missing_requirements"
sudo apt-get install $missing_requirements sudo apt-get install -y $missing_requirements
fi 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 folder
RECORDS_DIR=%kernel.project_dir%/../var/chunks # adapt to your needs 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 './bootstrap';
import feather from 'feather-icons'; import feather from 'feather-icons';
import { version } from 'core-js';
feather.replace(); feather.replace();
/** Update css variables --{header, footer}-height /** Update css variables --{header, footer}-height
* by querying elements real height */ * by querying elements real height */
(function() { (function () {
let css_root = document.querySelector(':root'); let css_root = document.querySelector(':root');
let header = document.getElementsByTagName('header')[0]; let header = document.getElementsByTagName('header')[0];
let header_height = header.clientHeight; let header_height = header.clientHeight;
@ -25,7 +26,15 @@ feather.replace();
let footer = document.getElementsByTagName('footer')[0]; let footer = document.getElementsByTagName('footer')[0];
let footer_height = footer.clientHeight; let footer_height = footer.clientHeight;
css_root.style.setProperty('--footer-height', footer_height + 'px'); 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 { try {

View File

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

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; z-index: 1;
} }
.button, input[type=submit], input[type=button] { .button,
input[type=submit],
input[type=button] {
background-color: #f1f1f1; background-color: #f1f1f1;
color: black; color: black;
border-radius: 5px; border-radius: 5px;
@ -97,7 +99,8 @@ a {
word-wrap: break-word; word-wrap: break-word;
} }
.button:hover, input:hover { .button:hover,
input:hover {
background-color: #F3F4F6; background-color: #F3F4F6;
text-decoration: none; text-decoration: none;
transition-duration: 0.1s; transition-duration: 0.1s;
@ -111,30 +114,30 @@ a {
} }
.button-main:focus:not(:focus-visible):not(.focus-visible) { .button-main:focus:not(:focus-visible):not(.focus-visible) {
box-shadow: none; box-shadow: none;
outline: none; outline: none;
} }
.button-main:hover { .button-main:hover {
background-color: #2c974b; background-color: #2c974b;
padding: 6px 16px; padding: 6px 16px;
} }
.button-main:focus { .button-main:focus {
box-shadow: rgba(46, 164, 79, .4) 0 0 0 3px; box-shadow: rgba(46, 164, 79, .4) 0 0 0 3px;
outline: none; outline: none;
} }
.button-main:disabled { .button-main:disabled {
background-color: #94d3a2; background-color: #94d3a2;
border-color: rgba(27, 31, 35, .1); border-color: rgba(27, 31, 35, .1);
color: rgba(255, 255, 255, .8); color: rgba(255, 255, 255, .8);
cursor: default; cursor: default;
} }
.button-main:active { .button-main:active {
background-color: #298e46; background-color: #298e46;
box-shadow: rgba(20, 70, 32, .2) 0 1px 0 inset; box-shadow: rgba(20, 70, 32, .2) 0 1px 0 inset;
} }
.button-main:active { .button-main:active {
@ -143,7 +146,8 @@ a {
transition: none 0s; transition: none 0s;
} }
.button:focus, input:focus { .button:focus,
input:focus {
outline: 1px transparent; outline: 1px transparent;
border: solid blue 1px; border: solid blue 1px;
} }
@ -179,7 +183,7 @@ header img.logo {
} }
main { 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; padding: 5em;
z-index: 0; z-index: 0;
} }
@ -201,11 +205,11 @@ footer a:hover {
font-style: italic; font-style: italic;
} }
li, td { li {
align-items: center; list-style: none;
} }
/* .dropdown-button:hover { .dropdown-toggle:hover {
background-color: #900; background-color: #900;
color: white color: white
} }
@ -213,22 +217,58 @@ li, td {
.dropdown:hover .dropdown-content { .dropdown:hover .dropdown-content {
display: block; display: block;
} }
*/
.dropdown-content { .dropdown-content {
display: none; display: none;
position: absolute;
min-width: 160px;
box-shadow: 0px 8px 16px 0px rgba(0, 0, 0, 0.2); box-shadow: 0px 8px 16px 0px rgba(0, 0, 0, 0.2);
z-index: 1; z-index: 1;
width: fit-content;
} }
canvas { canvas {
display: block; display: block;
height: 100%; height: 100%;
width: 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 { .scientific-name {
font-style: italic; font-style: italic;
} }
@ -294,4 +334,4 @@ canvas {
main { main {
padding: 1em; 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 #! /usr/bin/env bash
LANGUAGES="fr es"
# Extract and update translation files # Extract and update translation files
php bin/console translation:extract --dump-messages fr for LANGUAGE in $LANGUAGES; do
php bin/console translation:extract --dump-messages $LANGUAGE
php bin/console translation:extract --force fr php bin/console translation:extract --force $LANGUAGE
done

View File

@ -14,15 +14,21 @@
"symfony/console": "6.1.*", "symfony/console": "6.1.*",
"symfony/dotenv": "6.1.*", "symfony/dotenv": "6.1.*",
"symfony/flex": "^2", "symfony/flex": "^2",
"symfony/form": "6.1.*",
"symfony/framework-bundle": "6.1.*", "symfony/framework-bundle": "6.1.*",
"symfony/mailer": "6.1.*",
"symfony/proxy-manager-bridge": "6.1.*", "symfony/proxy-manager-bridge": "6.1.*",
"symfony/runtime": "6.1.*", "symfony/runtime": "6.1.*",
"symfony/security-bundle": "6.1.*",
"symfony/translation": "6.1.*", "symfony/translation": "6.1.*",
"symfony/twig-bundle": "6.1.*", "symfony/twig-bundle": "6.1.*",
"symfony/validator": "6.1.*",
"symfony/webpack-encore-bundle": "^1.15", "symfony/webpack-encore-bundle": "^1.15",
"symfony/yaml": "6.1.*", "symfony/yaml": "6.1.*",
"symfonycasts/verify-email-bundle": "^1.11",
"twig/extra-bundle": "^2.12|^3.0", "twig/extra-bundle": "^2.12|^3.0",
"twig/intl-extra": "^3.4", "twig/intl-extra": "^3.4",
"twig/string-extra": "^3.4",
"twig/twig": "^2.12|^3.0" "twig/twig": "^2.12|^3.0"
}, },
"config": { "config": {
@ -78,6 +84,8 @@
} }
}, },
"require-dev": { "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], Symfony\Bundle\MakerBundle\MakerBundle::class => ['dev' => true],
Doctrine\Bundle\DoctrineBundle\DoctrineBundle::class => ['all' => true], Doctrine\Bundle\DoctrineBundle\DoctrineBundle::class => ['all' => true],
Doctrine\Bundle\MigrationsBundle\DoctrineMigrationsBundle::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: doctrine:
dbal: 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, # IMPORTANT: You MUST configure your server version,
# either here or in the DATABASE_URL env var (see .env file) # 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 # 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 # https://symfony.com/doc/current/best_practices.html#use-parameters-for-application-configuration
parameters: parameters:
app.records_disk: '%env(string:RECORDS_DISK)%'
app.records_dir: '%env(resolve:RECORDS_DIR)%' app.records_dir: '%env(resolve:RECORDS_DIR)%'
app.supported_locales: 'en|fr'
app.default_locale: 'en'
services: services:
# default configuration for services in *this* file # default configuration for services in *this* file
@ -21,5 +25,7 @@ services:
- '../src/Entity/' - '../src/Entity/'
- '../src/Kernel.php' - '../src/Kernel.php'
App\AppBundle\ConnectionObservations: '@doctrine.dbal.observations_connection'
# add more service definitions when explicit configuration is needed # add more service definitions when explicit configuration is needed
# please note that last definitions always *replace* previous ones # please note that last definitions always *replace* previous ones

View File

@ -6,3 +6,9 @@ services:
ports: ports:
- "5432" - "5432"
###< doctrine/doctrine-bundle ### ###< 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/stimulus-bridge": "^3.2.0",
"@symfony/webpack-encore": "^3.0.0", "@symfony/webpack-encore": "^3.0.0",
"core-js": "^3.23.0", "core-js": "^3.23.0",
"git-revision-webpack-plugin": "^5.0.0",
"regenerator-runtime": "^0.13.9", "regenerator-runtime": "^0.13.9",
"webpack-notifier": "^1.15.0" "webpack-notifier": "^1.15.0"
}, },
@ -16,6 +17,7 @@
"build": "encore production --progress" "build": "encore production --progress"
}, },
"dependencies": { "dependencies": {
"axios": "^0.27.2",
"feather-icons": "^4.29.0" "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\Response;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Annotation\Route; use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Doctrine\DBAL\Connection; use App\Entity\User;
class AuthController extends AbstractController class AuthController extends AbstractController
{ {
private Connection $connection;
/** /**
* @Route("/auth", name="auth") * @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"); return $this->redirectToRoute("login");
} }
/** /**
* @Route("/auth/login", name="login") * @Route("/auth/login", name="login")
* @Route("/{_locale<%app.supported_locales%>}/auth/login", name="login_i18n")
*/ */
public function login() 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\HttpFoundation\Request;
use Symfony\Component\Routing\Annotation\Route; use Symfony\Component\Routing\Annotation\Route;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Doctrine\DBAL\Connection; use App\AppBundle\ConnectionObservations;
class HomeController extends AbstractController class HomeController extends AbstractController
{ {
private Connection $connection; private ConnectionObservations $connection;
/** public function __construct(ConnectionObservations $connection)
* @Route("/", name="home")
*/
public function index(Connection $connection)
{ {
$this->connection = $connection; $this->connection = $connection;
}
/**
* @Route("", name="home")
* @Route("/{_locale<%app.supported_locales%>}/", name="home_i18n")
*/
public function index()
{
return $this->render('index.html.twig', [ return $this->render('index.html.twig', [
"stats" => $this->get_stats(), "stats" => $this->get_stats(),
"charts" => $this->last_chart_generated(), "charts" => $this->last_chart_generated(),
@ -25,6 +30,7 @@ class HomeController extends AbstractController
/** /**
* @Route("/about", name="about") * @Route("/about", name="about")
* @Route("/{_locale<%app.supported_locales%>}/about", name="about_i18n")
*/ */
public function about() public function about()
{ {

View File

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

View File

@ -6,16 +6,22 @@ use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Annotation\Route; use Symfony\Component\Routing\Annotation\Route;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Doctrine\DBAL\Connection; use App\AppBundle\ConnectionObservations;
class RecordsController extends AbstractController 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") { if ($date == "now") {
$date = date("Y-m-d"); $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; $records = $request->request->filenames;
$this->remove_record_by_basename($basename); foreach($records as $record) {
return $this->redirectToRoute('records_index'); $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() private function list_records()
@ -83,6 +116,9 @@ class RecordsController extends AbstractController
rmdir($model_out_dir); rmdir($model_out_dir);
/** Remove database entry associated with this filename */ /** Remove database entry associated with this filename */
$this->remove_observations_from_record($basename); $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() { public function service_status() {
$status = array_map(function($service) { $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") public function service_manage($action, $service="all")
{ {

View File

@ -10,8 +10,9 @@ class SpectroController extends AbstractController
{ {
/** /**
* @Route("/spectro", name="spectro") * @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', [ 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\HttpFoundation\Request;
use Symfony\Component\Routing\Annotation\Route; use Symfony\Component\Routing\Annotation\Route;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Doctrine\DBAL\Connection; use App\AppBundle\ConnectionObservations;
class TodayController extends AbstractController class TodayController extends AbstractController
{ { private ConnectionObservations $connection;
private Connection $connection; public function __construct(ConnectionObservations $connection)
{
$this->connection = $connection;
}
/** /**
* @Route("/today", name="today") * @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("/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; $this->connection = $connection;
$date = date('Y-m-d'); $date = date('Y-m-d');
@ -35,10 +40,10 @@ class TodayController extends AbstractController
/** /**
* @Route("/today/species/{id}", name="today_species_id") * @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'); $date = date('Y-m-d');
return $this->render('today/species.html.twig', [ return $this->render('today/species.html.twig', [
"date" => $date, "date" => $date,
@ -49,18 +54,19 @@ class TodayController extends AbstractController
/** /**
* @Route("/today/{date}", name="today_date") * @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)); return $this->redirectToRoute('today_species_date', array('date' => $date));
} }
/** /**
* @Route("/today/{date}/species", name="today_species_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', [ return $this->render('today/index.html.twig', [
"date" => $date, "date" => $date,
"results" => $this->recorded_species_by_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("/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', [ return $this->render('today/species.html.twig', [
"date" => $date, "date" => $date,
"results" => $this->recorded_species_by_id_and_date($id, $date) "results" => $this->recorded_species_by_id_and_date($id, $date)
@ -141,4 +147,4 @@ class TodayController extends AbstractController
$result = $stmt->executeQuery(); $result = $stmt->executeQuery();
return $result->fetchAllAssociative(); 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" "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": { "symfony/maker-bundle": {
"version": "1.45", "version": "1.45",
"recipe": { "recipe": {
@ -112,6 +124,18 @@
"config/routes.yaml" "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": { "symfony/translation": {
"version": "6.1", "version": "6.1",
"recipe": { "recipe": {
@ -138,6 +162,31 @@
"templates/base.html.twig" "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": { "symfony/webpack-encore-bundle": {
"version": "1.15", "version": "1.15",
"recipe": { "recipe": {
@ -157,6 +206,9 @@
"webpack.config.js" "webpack.config.js"
] ]
}, },
"symfonycasts/verify-email-bundle": {
"version": "v1.11.0"
},
"twig/extra-bundle": { "twig/extra-bundle": {
"version": "v3.4.0" "version": "v3.4.0"
} }

View File

@ -1,5 +1,5 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="{{ app.request.locale }}">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<title> <title>
@ -27,6 +27,7 @@
</div> </div>
</a> </a>
</header> </header>
{% include "utils/locale-switcher.html.twig" %}
<main> <main>
{% block content %} {% block content %}
<p>Welcome to BirdNET-stream !</p> <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> </span>
<a href="https://samuel.ortion.fr/">Samuel ORTION</a> <a href="https://samuel.ortion.fr/">Samuel ORTION</a>
</p> </p>
<p class="version">
version: <span class="version-number">v</span>
</p>
</footer> </footer>

View File

@ -8,58 +8,43 @@
<ul> <ul>
{% include 'utils/nav-item.html.twig' with { {% include 'utils/nav-item.html.twig' with {
route: 'home', route: 'home',
url: '/', text: 'Home'|trans
name: 'Home'|trans
} %} } %}
{% include 'utils/nav-item.html.twig' with { {% include 'utils/nav-item.html.twig' with {
route: 'about', route: 'about',
url: '/about', text: 'About'|trans
name: 'About'|trans
} %} } %}
{% include 'utils/nav-item.html.twig' with { {% include 'utils/nav-item.html.twig' with {
route: 'today', route: 'today',
url: '/today', text: "Today's Detections"|trans
name: "Today's Detections"|trans
} %} } %}
{% include 'utils/nav-item.html.twig' with { {% include 'utils/nav-item.html.twig' with {
route: 'spectro', route: 'spectro',
url: '/spectro', text: 'Live Spectrogram'|trans
name: 'Live Spectrogram'|trans
} %} } %}
{% include 'utils/nav-item.html.twig' with { {% include 'utils/nav-item.html.twig' with {
route: 'stats', route: 'stats',
url: '/stats', text: 'Statistics'|trans
name: 'Statistics'|trans
} %} } %}
<li class="dropdown"> <li class="dropdown">
{% include 'utils/nav-item.html.twig' with { {% include 'utils/nav-item.html.twig' with {
route: 'records', route: 'records',
url: '/records', text: 'Recordings'|trans
name: 'Recordings'|trans
} %} } %}
<ul class="dropdown-content"> <ul class="dropdown-content">
{% include 'utils/nav-item.html.twig' with { {% include 'utils/nav-item.html.twig' with {
route: 'bests', route: 'records_best',
url: '/records/best', text: 'Best Recordings'|trans
name: 'Best Recordings'|trans
} %} } %}
</ul> </ul>
</li> </li>
{% include 'utils/nav-item.html.twig' with {
route: 'bests',
url: '/records/best',
name: 'Best Recordings'|trans
} %}
{% include 'utils/nav-item.html.twig' with { {% include 'utils/nav-item.html.twig' with {
route: 'logs', route: 'logs',
url: '/logs', text: 'View Logs'|trans
name: 'View Logs'|trans
} %} } %}
{% include 'utils/nav-item.html.twig' with { {% include 'utils/nav-item.html.twig' with {
route: 'status', route: 'services_status',
url: '/services/status', text: 'Status'|trans
name: 'Status'|trans
} %} } %}
</ul> </ul>
</div> </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 %} {% block content %}
<h2> <h2>
{% set today = "now" | date("Y-m-d") %} {% set today = 'now'|date('Y-m-d') %}
{% if today == date %} {% if today == date %}
{{ "Today's detected species" | trans }} {{ "Today's detected species"|trans }}
{% else %} {% else %}
{{ "Detected species on" | trans }} {{ 'Detected species on'|trans }}
{{ date | format_datetime("full", "none") }} {{ date|format_datetime('full', 'none') }}
{% endif %} {% endif %}
</h2> </h2>
{% set endpoint = "today" %} {% set endpoint = 'today' %}
{% include "utils/calendar.html.twig" %} {% include 'utils/calendar.html.twig' %}
{# Display a list of records if any, else, print message #} {# Display a list of records if any, else, print message #}
{% if results is defined and results | length > 0 %} {% if results is defined and (results|length) > 0 %}
<ul> <ul>
{% for sp in results %} {% for sp in results %}
<li class="species"> <li class="species">
<a href="./species/{{ sp['taxon_id'] }}"> <span class="scientific-name">
<span class="scientific-name">{{ sp["scientific_name"] }} {{ sp['scientific_name'] }}
(</span> (
<span class="common-name">{{ sp["common_name"] }}</span>) </span>
</a> <span class="common-name">{{ sp['common_name'] }}</span>)
</li> <span class="link">
{% endfor %} <a href="./species/{{ sp['taxon_id'] }}">
</ul> <i data-feather="link"></i>
{% else %} </a>
<p>{{ "No species detected this day" | trans }}</p> </span>
{% endif %} </li>
{% endfor %}
</ul>
{% else %}
<p>
{{ 'No species detected this day'|trans }}
</p>
{% endif %}
{% endblock %} {% 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> <i data-feather="check-square"></i>
</button> </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> <i data-feather="trash-2"></i>
</button> </button>
</div> </div>

View File

@ -47,20 +47,31 @@
</thead> </thead>
<tbody> <tbody>
{% for record in records %} {% for record in records %}
<tr> <tr {{ stimulus_controller('manage-records') }}>
<td> <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>
<td> <td>
<a title="{{ "Download audio file" | trans }}" href="/media/records/{{ record.audio_file }}">
{{ record.audio_file }} {{ record.audio_file }}
</a> </a>
</td> </td>
<td>{{ record.date | date("H:m") }}</td> <td>{{ record.date | date("H:m") }}</td>
<td>{{ record.confidence }}</td> <td>{{ record.confidence }}</td>
<td> <td>
{% include "records/player.html.twig" with { "filename": record.audio_file } only %} <div class="controlls container row">
{% include "records/delete_button.html.twig" with { "filename": record.audio_file } only %} </td> {% 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> </tr>
{% endfor %} {% endfor %}
</tbody> </tbody>
@ -70,5 +81,4 @@
{% endif %} {% endif %}
</div> </div>
{% endif %} {% endif %}
{% endblock %} {% 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> <li>
<a class="{{ app.request.get('_route') matches '{' ~ route ~ '_*}' ? 'active' }}" <a class="{{ app.request.get('_route') matches '{' ~ route ~ '_*}' ? 'active' }}"
href="{{ url }}"> href="{{ path(route ~ "_i18n", { _locale: app.request.locale }) }}">
{{ name }} {{ text }}
</a> </a>
</li> </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>
<trans-unit id="1zd9FJ_" resname="This project is made with &amp;hearts; by Samuel ORTION."> <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> <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>
<trans-unit id="wBHWCXv" resname="License"> <trans-unit id="wBHWCXv" resname="License">
<source>License</source> <source>License</source>
@ -37,6 +37,214 @@
<source>BirdNET-Analyzer, on which this project relies, is licensed under</source> <source>BirdNET-Analyzer, on which this project relies, is licensed under</source>
<target>BirdNET-Analyzer, sur qui ce projet repose, est sous licence</target> <target>BirdNET-Analyzer, sur qui ce projet repose, est sous licence</target>
</trans-unit> </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> </body>
</file> </file>
</xliff> </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 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. // 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. // 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 // uncomment if you're having problems with a jQuery plugin
//.autoProvidejQuery() //.autoProvidejQuery()
;
.addPlugin(
new DefinePlugin(
{
VERSION: JSON.stringify(gitRevisionPlugin.version()),
}
)
)
;
module.exports = Encore.getWebpackConfig(); module.exports = Encore.getWebpackConfig();

File diff suppressed because it is too large Load Diff