Merge pull request 'Add locales' (#16) from add-locales into main

Reviewed-on: #16
This commit is contained in:
loube 2026-01-30 18:24:49 +01:00
commit 9a7c06b81d
22 changed files with 519 additions and 103 deletions

View File

@ -1,4 +1,5 @@
database_path = ""
locale = "fr"
[listener]
port = 8000

290
Cargo.lock generated
View File

@ -99,6 +99,15 @@ dependencies = [
"windows-sys 0.61.2",
]
[[package]]
name = "arc-swap"
version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "51d03449bb8ca2cc2ef70869af31463d1ae5ccc8fa3e334b307203fbf815207e"
dependencies = [
"rustversion",
]
[[package]]
name = "arrayvec"
version = "0.7.6"
@ -287,6 +296,12 @@ dependencies = [
"tracing",
]
[[package]]
name = "base62"
version = "2.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1adf9755786e27479693dedd3271691a92b5e242ab139cacb9fb8e7fb5381111"
[[package]]
name = "base64"
version = "0.22.1"
@ -322,6 +337,12 @@ dependencies = [
"serde",
]
[[package]]
name = "bitflags"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bitflags"
version = "2.10.0"
@ -364,6 +385,7 @@ dependencies = [
"dirs",
"log",
"pretty_env_logger",
"rust-i18n",
"sea-orm",
"sea-orm-migration",
"serde",
@ -371,7 +393,7 @@ dependencies = [
"snafu",
"static-serve",
"tokio",
"toml",
"toml 0.9.11+spec-1.1.0",
"xdg",
]
@ -398,6 +420,16 @@ dependencies = [
"syn 2.0.114",
]
[[package]]
name = "bstr"
version = "1.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "63044e1ae8e69f3b5a92c736ca6269b8d12fa7efe39bf34ddb06d102cf0e2cab"
dependencies = [
"memchr",
"serde",
]
[[package]]
name = "bumpalo"
version = "3.19.1"
@ -583,6 +615,25 @@ dependencies = [
"cfg-if",
]
[[package]]
name = "crossbeam-deque"
version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51"
dependencies = [
"crossbeam-epoch",
"crossbeam-utils",
]
[[package]]
name = "crossbeam-epoch"
version = "0.9.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e"
dependencies = [
"crossbeam-utils",
]
[[package]]
name = "crossbeam-queue"
version = "0.3.12"
@ -1018,6 +1069,30 @@ version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280"
[[package]]
name = "globset"
version = "0.4.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "52dfc19153a48bde0cbd630453615c8151bce3a5adfac7a0aebfbf0a1e1f57e3"
dependencies = [
"aho-corasick",
"bstr",
"log",
"regex-automata",
"regex-syntax",
]
[[package]]
name = "globwalk"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "93e3af942408868f6934a7b85134a3230832b9977cf66125df2f9edcfce4ddcc"
dependencies = [
"bitflags 1.3.2",
"ignore",
"walkdir",
]
[[package]]
name = "hashbrown"
version = "0.12.3"
@ -1324,6 +1399,22 @@ dependencies = [
"icu_properties",
]
[[package]]
name = "ignore"
version = "0.4.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3d782a365a015e0f5c04902246139249abf769125006fbe7649e2ee88169b4a"
dependencies = [
"crossbeam-deque",
"globset",
"log",
"memchr",
"regex-automata",
"same-file",
"walkdir",
"winapi-util",
]
[[package]]
name = "indexmap"
version = "1.9.3"
@ -1384,6 +1475,15 @@ version = "1.70.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695"
[[package]]
name = "itertools"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57"
dependencies = [
"either",
]
[[package]]
name = "itertools"
version = "0.14.0"
@ -1446,7 +1546,7 @@ version = "0.1.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d0b95e02c851351f877147b7deea7b1afb1df71b63aa5f8270716e0c5720616"
dependencies = [
"bitflags",
"bitflags 2.10.0",
"libc",
"redox_syscall 0.7.0",
]
@ -1551,6 +1651,15 @@ dependencies = [
"windows-sys 0.61.2",
]
[[package]]
name = "normpath"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bf23ab2b905654b4cb177e30b629937b3868311d4e1cba859f899c041046e69b"
dependencies = [
"windows-sys 0.61.2",
]
[[package]]
name = "num-bigint"
version = "0.4.6"
@ -1806,7 +1915,7 @@ version = "3.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "219cb19e96be00ab2e37d6e299658a0cfa83e52429179969b0f0121b4ac46983"
dependencies = [
"toml_edit",
"toml_edit 0.23.10+spec-1.0.0",
]
[[package]]
@ -1930,7 +2039,7 @@ version = "0.5.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d"
dependencies = [
"bitflags",
"bitflags 2.10.0",
]
[[package]]
@ -1939,7 +2048,7 @@ version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49f3fe0889e69e2ae9e41f4d6c4c0181701d00e4697b356fb1f74173a5e0ee27"
dependencies = [
"bitflags",
"bitflags 2.10.0",
]
[[package]]
@ -2060,6 +2169,60 @@ dependencies = [
"zeroize",
]
[[package]]
name = "rust-i18n"
version = "3.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fda2551fdfaf6cc5ee283adc15e157047b92ae6535cf80f6d4962d05717dc332"
dependencies = [
"globwalk",
"once_cell",
"regex",
"rust-i18n-macro",
"rust-i18n-support",
"smallvec",
]
[[package]]
name = "rust-i18n-macro"
version = "3.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22baf7d7f56656d23ebe24f6bb57a5d40d2bce2a5f1c503e692b5b2fa450f965"
dependencies = [
"glob",
"once_cell",
"proc-macro2",
"quote",
"rust-i18n-support",
"serde",
"serde_json",
"serde_yaml",
"syn 2.0.114",
]
[[package]]
name = "rust-i18n-support"
version = "3.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "940ed4f52bba4c0152056d771e563b7133ad9607d4384af016a134b58d758f19"
dependencies = [
"arc-swap",
"base62",
"globwalk",
"itertools 0.11.0",
"lazy_static",
"normpath",
"once_cell",
"proc-macro2",
"regex",
"serde",
"serde_json",
"serde_yaml",
"siphasher",
"toml 0.8.23",
"triomphe",
]
[[package]]
name = "rust_decimal"
version = "1.40.0"
@ -2103,6 +2266,15 @@ version = "1.0.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a50f4cf475b65d88e057964e0e9bb1f0aa9bbb2036dc65c64596b42932536984"
[[package]]
name = "same-file"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
dependencies = [
"winapi-util",
]
[[package]]
name = "schemars"
version = "0.9.0"
@ -2158,7 +2330,7 @@ dependencies = [
"chrono",
"derive_more",
"futures-util",
"itertools",
"itertools 0.14.0",
"log",
"ouroboros",
"pgvector",
@ -2362,6 +2534,15 @@ dependencies = [
"serde_core",
]
[[package]]
name = "serde_spanned"
version = "0.6.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3"
dependencies = [
"serde",
]
[[package]]
name = "serde_spanned"
version = "1.0.4"
@ -2414,6 +2595,19 @@ dependencies = [
"syn 2.0.114",
]
[[package]]
name = "serde_yaml"
version = "0.9.34+deprecated"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47"
dependencies = [
"indexmap 2.13.0",
"itoa",
"ryu",
"serde",
"unsafe-libyaml",
]
[[package]]
name = "sha1"
version = "0.10.6"
@ -2473,6 +2667,12 @@ version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e"
[[package]]
name = "siphasher"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b2aa850e253778c88a04c3d7323b043aeda9d3e30d5971937c1855769763678e"
[[package]]
name = "slab"
version = "0.4.11"
@ -2635,7 +2835,7 @@ checksum = "aa003f0038df784eb8fecbbac13affe3da23b45194bd57dba231c8f48199c526"
dependencies = [
"atoi",
"base64",
"bitflags",
"bitflags 2.10.0",
"byteorder",
"bytes",
"chrono",
@ -2681,7 +2881,7 @@ checksum = "db58fcd5a53cf07c184b154801ff91347e4c30d17a3562a635ff028ad5deda46"
dependencies = [
"atoi",
"base64",
"bitflags",
"bitflags 2.10.0",
"byteorder",
"chrono",
"crc",
@ -2985,6 +3185,18 @@ dependencies = [
"tokio",
]
[[package]]
name = "toml"
version = "0.8.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362"
dependencies = [
"serde",
"serde_spanned 0.6.9",
"toml_datetime 0.6.11",
"toml_edit 0.22.27",
]
[[package]]
name = "toml"
version = "0.9.11+spec-1.1.0"
@ -2993,13 +3205,22 @@ checksum = "f3afc9a848309fe1aaffaed6e1546a7a14de1f935dc9d89d32afd9a44bab7c46"
dependencies = [
"indexmap 2.13.0",
"serde_core",
"serde_spanned",
"toml_datetime",
"serde_spanned 1.0.4",
"toml_datetime 0.7.5+spec-1.1.0",
"toml_parser",
"toml_writer",
"winnow",
]
[[package]]
name = "toml_datetime"
version = "0.6.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c"
dependencies = [
"serde",
]
[[package]]
name = "toml_datetime"
version = "0.7.5+spec-1.1.0"
@ -3009,6 +3230,20 @@ dependencies = [
"serde_core",
]
[[package]]
name = "toml_edit"
version = "0.22.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a"
dependencies = [
"indexmap 2.13.0",
"serde",
"serde_spanned 0.6.9",
"toml_datetime 0.6.11",
"toml_write",
"winnow",
]
[[package]]
name = "toml_edit"
version = "0.23.10+spec-1.0.0"
@ -3016,7 +3251,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "84c8b9f757e028cee9fa244aea147aab2a9ec09d5325a9b01e0a49730c2b5269"
dependencies = [
"indexmap 2.13.0",
"toml_datetime",
"toml_datetime 0.7.5+spec-1.1.0",
"toml_parser",
"winnow",
]
@ -3030,6 +3265,12 @@ dependencies = [
"winnow",
]
[[package]]
name = "toml_write"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801"
[[package]]
name = "toml_writer"
version = "1.0.6+spec-1.1.0"
@ -3111,6 +3352,17 @@ dependencies = [
"tracing-core",
]
[[package]]
name = "triomphe"
version = "0.1.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd69c5aa8f924c7519d6372789a74eac5b94fb0f8fcf0d4a97eb0bfc3e785f39"
dependencies = [
"arc-swap",
"serde",
"stable_deref_trait",
]
[[package]]
name = "typenum"
version = "1.19.0"
@ -3156,6 +3408,12 @@ version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
[[package]]
name = "unsafe-libyaml"
version = "0.2.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861"
[[package]]
name = "url"
version = "2.5.8"
@ -3203,6 +3461,16 @@ version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
[[package]]
name = "walkdir"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b"
dependencies = [
"same-file",
"winapi-util",
]
[[package]]
name = "wasi"
version = "0.11.1+wasi-snapshot-preview1"

View File

@ -32,3 +32,4 @@ pretty_env_logger = "0.5.0"
log = "0.4.29"
serde_with = "3.16.1"
csv = "1.4.0"
rust-i18n = "3.1.5"

67
locales/en.yml Normal file
View File

@ -0,0 +1,67 @@
_version: 1
name: "BookForge"
common:
download: Download
create: Create
edit: Edit
delete: Delete
next: Next
previous: Prev
actions: Actions
search: Search
reset: Reset
show: Show
close: Close
confirmation: Confirmation
are_you_sure: Are you sure?
nav:
toggle: Toggle navigation
books: Books
users: Users
theme:
light: Light
dark: Dark
user:
attributes:
name: Name
owner_books: Owner books
borrowed_books: Borrowed books
index:
title: All Users
button: Add User
edit:
title: Edit
button: Edit user
new:
title: New User
button: Create User
book:
attributes:
title: Title
authors: Author(s)
description: Description
owner: Owner
current_holder: Current Holder
comment: Comment
index:
title: All Books
new:
title: New Book
button: Create Book
button_short: Add book
edit:
title: Edit Book
button: Edit Book
show:
book_details: Book Details
user_details: User Details
more_informations: More Informations
footer:
message: Made with Love & Fuck a Fascist!
error:
error_404:
title: Oops ! This page does not exist
subtitle: 404 NOT FOUND
button: Back to home
generic:
title: Oops! An error occurred

67
locales/fr.yml Normal file
View File

@ -0,0 +1,67 @@
_version: 1
name: "BookForge"
common:
download: Télécharger
create: Créer
edit: Modifier
delete: Supprimer
next: Suivant
previous: Précédent
actions: Actions
search: Filtrer
reset: Réini.
show: Voir
close: Fermer
confirmation: Confirmation
are_you_sure: Êtes-vous sûr ?
nav:
toggle: Basculer la navigation
books: Livres
users: Utilisateurs
theme:
light: Light
dark: Dark
user:
attributes:
name: Nom
owner_books: Livres possédés
borrowed_books: Livres empruntés
index:
title: Tous les utilisateurs
button: Ajouter un utilisateur
edit:
title: Modifier
button: Modifier l'utilisateur
new:
title: Nouvel utilisateur
button: Créer l'utilisateur
book:
attributes:
title: Titre
authors: Auteur.ice.(s)
description: Description
owner: Propriétaire
current_holder: Détenteur.ice actuel.le
comment: Commentaire
index:
title: Tous les livres
new:
title: Nouveau livre
button: Créer le livre
button_short: Ajouter un livre
edit:
title: Modifier le livre
button: Modifier le livre
show:
book_details: Détails du livre
user_details: Détails de l'utilisateur
more_informations: Plus d'informations
footer:
message: Fait avec amour & Nique les fachos !
error:
error_404:
title: Oups ! Cette page n'existe pas
subtitle: 404 NOT FOUND
button: Retour à l'accueil
generic:
title: Oups ! Une erreur s'est produite

View File

@ -13,7 +13,13 @@ mod models;
mod routes;
pub mod state;
#[macro_use]
extern crate rust_i18n;
i18n!("locales", fallback = "en", minify_key = true);
pub fn build_app(state: AppState) -> Router {
rust_i18n::set_locale(&state.config.locale);
embed_assets!("assets", compress = true);
Router::new()

View File

@ -273,13 +273,13 @@ pub async fn download_csv(
let mut wtr = Writer::from_writer(vec![]);
wtr.write_record(&[
"id",
"ID",
"Title",
"Author(s)",
"description",
"owner_id",
"current_holder_id",
"comment",
"Description",
"Owner",
"Current Holder",
"Comment",
])
.context(CSVSnafu)?;

View File

@ -32,6 +32,7 @@ pub enum ConfigError {
pub struct AppConfig {
#[serde(default = "AppConfig::default_sqlite_path")]
pub database_path: Utf8PathBuf,
pub locale: String,
pub listener: Listener,
}
@ -39,6 +40,7 @@ impl Default for AppConfig {
fn default() -> Self {
AppConfig {
database_path: Self::default_sqlite_path(),
locale: Self::default_locale(),
listener: Listener::default(),
}
}
@ -92,6 +94,10 @@ impl AppConfig {
Self::config_path().join("BookForge.toml")
}
fn default_locale() -> String {
"en".to_string()
}
pub fn default_sqlite_path() -> Utf8PathBuf {
Self::config_path().join("db.sqlite")
}

View File

@ -1,8 +1,8 @@
{% extends "base.html" %}
{% block main %}
<div class="mt-4 text-center">
<h1>Oops ! This page does not exist</h1>
<h2 class="fst-italic">404 NOT FOUND</h2>
<a href="/" class="mt-3 btn btn-info">Back to home</a>
<h1>{{ t!("error.error_404.title") }}</h1>
<h2 class="fst-italic">{{ t!("error.error_404.subtitle") }}</h2>
<a href="/" class="mt-3 btn btn-info">{{ t!("error.error_404.button") }}</a>
</div>
{% endblock %}

View File

@ -2,7 +2,7 @@
<head>
<meta charset="utf-8">
<title>{% block title %}Book Forge{% endblock %}</title>
<title>{% block title %}{{ t!("name") }}{% endblock %}</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="/assets/css/bootstrap.css">
<link rel="stylesheet" href="/assets/css/main.css">

View File

@ -3,22 +3,22 @@
{% import "components/cards.html" as cards %}
{% block main %}
{{ typography::heading("Editer") }}
{{ typography::heading(t!("book.edit.title")) }}
{% call cards::card() %}
<form method="post" action="/books/{{ book.id }}">
<div class="mb-3">
<label class="form-label" for="title">Name</label>
<label class="form-label" for="title">{{ t!("book.attributes.title") }}</label>
<input type="text" name="title" class="form-control" value="{{ book.title }}" required>
</div>
<div class="mb-3">
<label for="authors" class="form-label">Author(s)</label>
<label for="authors" class="form-label">{{ t!("book.attributes.authors") }}</label>
<input type="text" name="authors" class="form-control" value="{{ book.authors }}" required>
</div>
<div class="mb-3">
<label for="owner_id" class="form-label">Owner</label>
<label for="owner_id" class="form-label">{{ t!("book.attributes.owner") }}</label>
<select name="owner_id" class="form-control" required>
{% for user in users %}
{% if book.owner_id == user.id %}
@ -31,7 +31,7 @@
</div>
<div class="mb-3">
<label for="current_holder_id" class="form-label">Current Holder</label>
<label for="current_holder_id" class="form-label">{{ t!("book.attributes.current_holder") }}</label>
<select class="form-control" name="current_holder_id">
<option></option>
{% match book.current_holder_id %}
@ -52,7 +52,7 @@
</div>
<div class="mb-3">
<label for="description" class="form-label">Description</label>
<label for="description" class="form-label">{{ t!("book.attributes.description") }}</label>
{% match book.description %}
{% when Some with (description) %}
<textarea name="description" class="form-control">{{ description }}</textarea>
@ -62,7 +62,7 @@
</div>
<div class="mb-3">
<label class="form-label" for="comment">Comment</label>
<label class="form-label" for="comment">{{ t!("book.attributes.comment") }}</label>
{% match book.comment %}
{% when Some with (comment) %}
<textarea name="comment" class="form-control">{{ comment }}</textarea>
@ -72,7 +72,7 @@
</div>
<div class="mt-4 text-center">
<input type="submit" value="Edit book" class="btn btn-success">
<input type="submit" value='{{ t!("book.edit.button") }}' class="btn btn-success">
</div>
</form>
{% endcall %}

View File

@ -4,27 +4,27 @@
{% import "components/inputs.html" as form_helpers %}
{% block main %}
{{ typography::heading("New book") }}
{{ typography::heading(t!("book.new.title")) }}
{% call cards::card() %}
<form method="post" action="/books">
{{ form_helpers::input("title", "Name", is_required = true, placeholder = "Ex: La Petite Dernière") }}
{{ form_helpers::input("authors", "Author(s)", is_required = true, placeholder = "Ex: Fatima Daas") }}
{{ form_helpers::input("title", t!("book.attributes.title"), is_required = true, placeholder = "Ex: La Petite Dernière") }}
{{ form_helpers::input("authors", t!("book.attributes.authors"), is_required = true, placeholder = "Ex: Fatima Daas") }}
{% call(option) form_helpers::select("owner_id", "Owner", users, is_required = true) %}
{% call(option) form_helpers::select("owner_id", t!("book.attributes.owner"), users, is_required = true) %}
<option value="{{ option.id }}">{{ option.name }}</option>
{% endcall %}
{% call(option) form_helpers::select("current_holder_id", "Current Holder", users, is_required = false) %}
{% call(option) form_helpers::select("current_holder_id", t!("book.attributes.current_holder"), users, is_required = false) %}
<option value="{{ option.id }}">{{ option.name }}</option>
{% endcall %}
{{ form_helpers::textarea("description", "Description", rows = 5, is_required = false, placeholder = "Ex: Je mappelle Fatima Daas. Je suis la mazoziya, la petite dernière. Celle à laquelle on ne sest pas préparé. Française dorigine algérienne.") }}
{{ form_helpers::textarea("comment", "Comment", rows = 3, is_required = false, placeholder = "Ex: I recommend it, it's great!") }}
{{ form_helpers::textarea("description", t!("book.attributes.description"), rows = 5, is_required = false, placeholder = "Ex: Je mappelle Fatima Daas. Je suis la mazoziya, la petite dernière. Celle à laquelle on ne sest pas préparé. Française dorigine algérienne.") }}
{{ form_helpers::textarea("comment", t!("book.attributes.comment"), rows = 3, is_required = false, placeholder = "Ex: I recommend it, it's great!") }}
<div class="mt-4 text-center">
<input type="submit" value="Create book" class="btn btn-success">
<input type="submit" value='{{ t!("book.new.button") }}' class="btn btn-success">
</div>
</form>
{% endcall %}

View File

@ -9,34 +9,34 @@
{% call cards::card() %}
<div class="mt-4">
<h5 class="mt-4 fw-bold">Book details</h5>
{{ fields::field("Name", book.title) }}
{{ fields::field("Authors", book.authors) }}
<h5 class="mt-4 fw-bold">{{ t!("book.show.book_details") }}</h5>
{{ fields::field(t!("book.attributes.title"), book.title) }}
{{ fields::field(t!("book.attributes.authors"), book.authors) }}
{% match book.description %}
{% when Some with (description) %}
{{ fields::field("Description", description) }}
{{ fields::field(t!("book.attributes.description"), description) }}
{% when None %}
{{ fields::field("Description", "-") }}
{{ fields::field(t!("book.attributes.description"), "-") }}
{% endmatch %}
<h5 class="mt-50px fw-bold">User Details</h5>
{{ fields::field("Owner", owner.name) }}
<h5 class="mt-50px fw-bold">{{ t!("book.show.user_details") }}</h5>
{{ fields::field(t!("book.attributes.owner"), owner.name) }}
{% match current_holder %}
{% when Some with (current_holder) %}
{{ fields::field("Current Holder", current_holder.name) }}
{{ fields::field(t!("book.attributes.current_holder"), current_holder.name) }}
{% when None %}
{{ fields::field("Current Holder", "-") }}
{{ fields::field(t!("book.attributes.current_holder"), "-") }}
{% endmatch %}
<h5 class="mt-50px fw-bold">More Informations</h5>
<h5 class="mt-50px fw-bold">{{ t!("book.show.more_informations") }}</h5>
{% match book.comment %}
{% when Some with (comment) %}
{{ fields::field("Comment", comment) }}
{{ fields::field(t!("book.attributes.comment"), comment) }}
{% when None %}
{{ fields::field("Comment", "-") }}
{{ fields::field(t!("book.attributes.comment"), "-") }}
{% endmatch %}
</div>
{% endcall %}

View File

@ -14,15 +14,15 @@
{% macro crud_dropdown_button(book, label, sub_path, show = true) %}
<div class="dropdown">
<button class="btn btn-secondary dropdown-toggle" type="button" data-bs-toggle="dropdown" aria-expanded="false">
Actions
{{ label }}
</button>
<ul class="dropdown-menu">
{% if show %}
<li><a class="dropdown-item" href="/{{ sub_path }}/{{ book.id }}">Show</a></li>
<li><a class="dropdown-item" href="/{{ sub_path }}/{{ book.id }}">{{ t!("common.show") }}</a></li>
{% endif %}
<li><a class="dropdown-item" href="/{{ sub_path }}/{{ book.id }}/edit">Edit</a></li>
<li><a class="dropdown-item" href="/{{ sub_path }}/{{ book.id }}/edit">{{ t!("common.edit") }}</a></li>
<li>
<a class="dropdown-item" href="#" data-bs-toggle="modal" data-bs-target="#deleteUserModal{{ book.id }}">Delete</a>
<a class="dropdown-item" href="#" data-bs-toggle="modal" data-bs-target="#deleteUserModal{{ book.id }}">{{ t!("common.delete") }}</a>
</li>
</ul>
</div>
@ -32,16 +32,16 @@
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h1 class="modal-title fs-5" id="exampleModalLabel">Confirmation</h1>
<h1 class="modal-title fs-5" id="exampleModalLabel">{{ t!("common.confirmation") }}</h1>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<p>Are you sure ?</p>
<p>{{ t!("common.are_you_sure") }}</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">{{ t!("common.close") }}</button>
<form method="post" action="/{{ sub_path }}/{{ book.id }}/delete" class="m-0">
<input class="btn btn-danger" type="submit" value="Delete">
<input class="btn btn-danger" type="submit" value='{{ t!("common.delete") }}'>
</form>
</div>
</div>

View File

@ -21,7 +21,7 @@
</h1>
<div>
{{ dropdown::crud_dropdown_button(book, "Actions", "books", show) }}
{{ dropdown::crud_dropdown_button(book, t!("common.actions"), "books", show) }}
</div>
</div>
{% endmacro %}

View File

@ -1,12 +1,12 @@
{% extends "base.html" %}
{% block main %}
<div class="mt-4 text-center">
<h1>Oops ! An Error occured</h1>
<h1>{{ t!("error.generic.title") }}</h1>
<div class="alert alert-danger">
{% for error in state.errors %}
<p>{{ error }}</p>
{% endfor %}
</div>
<a href="/" class="mt-3 btn btn-info">Back to home</a>
<a href="/" class="mt-3 btn btn-info">{{ t!("error.error_404.button") }}</a>
</div>
{% endblock %}

View File

@ -4,9 +4,9 @@
{% import "components/cards.html" as cards %}
{% block main %}
{% call typography::heading("All books") %}
{% call typography::heading(t!("book.index.title")) %}
<a href="/books/download_csv?{{ base_query }}" class="btn btn-info">
<i class="fa fa-download me-2" aria-hidden="true"></i> Download (csv)
<i class="fa fa-download me-2" aria-hidden="true"></i> {{ t!("common.download") }} (csv)
</a>
{% endcall %}
@ -14,7 +14,7 @@
<form method="get">
<div class="row">
<div class="col-md-3">
<label for="title" class="form-label">Name</label>
<label for="title" class="form-label">{{ t!("book.attributes.title") }}</label>
{% match query.title %}
{% when Some with (value) %}
<input type="text" name="title" value="{{ value }}" class="form-control" placeholder="Ex: La liberte ou rien">
@ -24,7 +24,7 @@
</div>
<div class="col-md-3">
<label for="authors" class="form-label">Authors</label>
<label for="authors" class="form-label">{{ t!("book.attributes.authors") }}</label>
{% match query.authors %}
{% when Some with (value) %}
@ -35,7 +35,7 @@
</div>
<div class="col-md-2">
<label for="owner_id" class="form-label">Owner</label>
<label for="owner_id" class="form-label">{{ t!("book.attributes.owner") }}</label>
<select name="owner_id" class="form-select">
<option></option>
@ -57,7 +57,7 @@
</div>
<div class="col-md-2">
<label for="current_holder_id" class="form-label">Current Holder</label>
<label for="current_holder_id" class="form-label">{{ t!("book.attributes.current_holder") }}</label>
<select name="current_holder_id" class="form-select">
<option></option>
@ -79,11 +79,11 @@
</div>
<div class="col-md-1 d-flex align-items-end">
<input type="submit" value="Search" class="btn btn-info w-100">
<input type="submit" value='{{ t!("common.search") }}' class="btn btn-info w-100">
</div>
<div class="col-md-1 d-flex align-items-end">
<a href="/" class="btn btn-light">Reset</a>
<a href="/" class="btn btn-light">{{ t!("common.reset") }}</a>
</div>
</div>
</form>
@ -94,11 +94,11 @@
<thead>
<tr>
<th scope="col">#</th>
<th scope="col">Name</th>
<th scope="col">Author(s)</th>
<th scope="col">Owner</th>
<th scope="col">Current Holder</th>
<th scope="col">Actions</th>
<th scope="col">{{ t!("book.attributes.title") }}</th>
<th scope="col">{{ t!("book.attributes.authors") }}</th>
<th scope="col">{{ t!("book.attributes.owner") }}</th>
<th scope="col">{{ t!("book.attributes.current_holder") }}</th>
<th scope="col">{{ t!("common.actions") }}</th>
</tr>
</thead>
<tbody>
@ -117,21 +117,21 @@
{% endmatch %}
</td>
<td>
{{ dropdown::crud_dropdown_button(book_user.book, "Actions", "books") }}
{{ dropdown::crud_dropdown_button(book_user.book, t!("common.actions"), "books") }}
</td>
</tr>
{% endfor %}
</tbody>
</table>
{% if total_page > 1 %}
<div class="d-flex justify-content-center mt-1">
<nav aria-label="Page navigation">
<ul class="pagination">
<li class="page-item {% if current_page <= 1 %}disabled{% endif %}">
<a class="page-link" href="/?{{ base_query }}page={% if current_page > 1 %}{{ current_page - 1 }}{% else %}1{% endif %}">Prev</a>
<a class="page-link" href="/?{{ base_query }}page={% if current_page > 1 %}{{ current_page - 1 }}{% else %}1{% endif %}">{{ t!("common.prev") }}</a>
</li>
{% for page in 1..(total_page + 1) %}
{% if page >= current_page - 1 && page <= current_page + 1 %}
<li class="page-item {% if page == current_page %}active{% endif %}">
@ -141,7 +141,7 @@
{% endfor %}
<li class="page-item {% if current_page == total_page %}disabled{% endif %}">
<a class="page-link" href="/?{{ base_query }}page={{ current_page + 1 }}">Next</a>
<a class="page-link" href="/?{{ base_query }}page={{ current_page + 1 }}">{{ t!("common.next") }}</a>
</li>
</ul>
</nav>

View File

@ -1,5 +1,5 @@
<div class="container text-center mt-4">
<div class="d-flex flex-column flex-sm-row gap-0 gap-sm-3 justify-content-center">
<p>Made with Love & Fuck a Fascist !</p>
<p>{{ t!("footer.message") }}</p>
</div>
</div>

View File

@ -1,31 +1,31 @@
<nav class="navbar navbar-expand-lg bg-body-tertiary fixed-top shadow">
<div class="container">
<a class="navbar-brand" href="/">
BookForge
{{ t!("name") }}
</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav"
aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
aria-controls="navbarNav" aria-expanded="false" aria-label="{{ t!("nav.toggle") }}">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav me-auto">
<li class="nav-item">
<a class="nav-link" href="/">Books</a>
<a class="nav-link" href="/">{{ t!("nav.books") }}</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/users">Users</a>
<a class="nav-link" href="/users">{{ t!("nav.users") }}</a>
</li>
</ul>
<div class="d-flex align-items-center gap-2 py-3">
<select id="changeTheme" class="form-select">
<option value="light">light</option>
<option value="dark">dark</option>
<option value="light">{{ t!("theme.light") }}</option>
<option value="dark">{{ t!("theme.dark") }}</option>
</select>
<a class="btn btn-success text-white" href="/books/new">
Add&nbsp;book
<a class="btn btn-success text-white text-nowrap" href="/books/new">
{{ t!("book.new.button_short") }}
</a>
</div>
</div>

View File

@ -4,17 +4,17 @@
o{% import "components/inputs.html" as form_helpers %}
{% block main %}
{{ typography::heading("New User") }}
{{ typography::heading(t!("user.edit.title")) }}
{% call cards::card() %}
<form action="/users/{{ user.id }}" method="post">
<div class="row align-items-end">
<div class="col-md-10">
{{ form_helpers::input("name", "Name", value = user.name, is_required = true, placeholder = "Ex: Kropotkine", margin_bottom = false) }}
{{ form_helpers::input("name", t!("user.attributes.name"), value = user.name, is_required = true, placeholder = "Ex: Kropotkine", margin_bottom = false) }}
</div>
<div class="col-md-2">
<input type="submit" value="Edit user" class="btn btn-success">
<input type="submit" value='{{ t!("user.edit.button") }}' class="btn btn-success">
</div>
</div>
</form>

View File

@ -5,15 +5,15 @@
{% import "components/inputs.html" as form_helpers %}
{% block main %}
{% call typography::heading("All users") %}
<a class="btn-success btn" href="/users/new">Add User</a>
{% call typography::heading(t!("user.index.title")) %}
<a class="btn-success btn" href="/users/new">{{ t!("user.index.button") }}</a>
{% endcall %}
{% call cards::card() %}
<form method="get">
<div class="row">
<div class="col-md-3">
<label for="name" class="form-label">Name</label>
<label for="name" class="form-label">{{ t!("user.attributes.name") }}</label>
{% match query.name %}
{% when Some with (value) %}
<input type="text" name="name" value="{{ value }}" class="form-control" placeholder="Ex: Koprotkine">
@ -23,11 +23,11 @@
</div>
<div class="col-md-1 d-flex align-items-end">
<input type="submit" value="Search" class="btn btn-info w-100">
<input type="submit" value='{{ t!("common.search") }}' class="btn btn-info w-100">
</div>
<div class="col-md-1 d-flex align-items-end">
<a href="/users" class="btn btn-light">Reset</a>
<a href="/users" class="btn btn-light">{{ t!("common.reset") }}</a>
</div>
</div>
</form>
@ -38,10 +38,10 @@
<thead>
<tr>
<th scope="col">#</th>
<th scope="col">Name</th>
<th scope="col">Owner book</th>
<th scope="col">Borrowed book</th>
<th scope="col">Actions</th>
<th scope="col">{{ t!("user.attributes.name") }}</th>
<th scope="col">{{ t!("user.attributes.owner_books") }}</th>
<th scope="col">{{ t!("user.attributes.borrowed_books") }}</th>
<th scope="col">{{ t!("common.actions") }}</th>
</tr>
</thead>
<tbody>
@ -52,7 +52,7 @@
<td>{{ user_information.owner_book_number }}</td>
<td>{{ user_information.borrowed_book_number }}</td>
<td>
{{ dropdown::crud_dropdown_button(user_information.user, "Actions", "users", show = false) }}
{{ dropdown::crud_dropdown_button(user_information.user, t!("common.actions"), "users", show = false) }}
</td>
</tr>
{% endfor %}

View File

@ -4,16 +4,16 @@
o{% import "components/inputs.html" as form_helpers %}
{% block main %}
{{ typography::heading("New User") }}
{{ typography::heading(t!("user.new.title")) }}
{% call cards::card() %}
<form action="/users" method="post">
<div class="row align-items-end">
<div class="col-md-10">
{{ form_helpers::input("name", "Name", is_required = true, placeholder = "Ex: Kropotkine", margin_bottom = false) }}
{{ form_helpers::input("name", t!("user.attributes.name"), is_required = true, placeholder = "Ex: Kropotkine", margin_bottom = false) }}
</div>
<div class="col-md-2">
<input type="submit" value="Create user" class="btn btn-success">
<input type="submit" value='{{ t!("user.new.button") }}' class="btn btn-success">
</div>
</div>
</form>