start with externalizing strings

This commit is contained in:
Philipp Hofer 2025-04-08 19:16:45 +02:00
parent 808525dee5
commit c445a8ed1e
5 changed files with 396 additions and 31 deletions

333
Cargo.lock generated
View File

@ -17,6 +17,15 @@ version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627"
[[package]]
name = "aho-corasick"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
dependencies = [
"memchr",
]
[[package]]
name = "allocator-api2"
version = "0.2.21"
@ -38,6 +47,12 @@ dependencies = [
"libc",
]
[[package]]
name = "arc-swap"
version = "1.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457"
[[package]]
name = "async-trait"
version = "0.1.88"
@ -133,6 +148,15 @@ dependencies = [
"windows-targets 0.52.6",
]
[[package]]
name = "base62"
version = "2.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "10e52a7bcb1d6beebee21fb5053af9e3cbb7a7ed1a4909e534040e676437ab1f"
dependencies = [
"rustversion",
]
[[package]]
name = "base64"
version = "0.22.1"
@ -145,6 +169,12 @@ version = "1.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "89e25b6adfb930f02d1981565a6e5d9c547ac15a96606256d3b59040e5cd4ca3"
[[package]]
name = "bitflags"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bitflags"
version = "2.9.0"
@ -163,6 +193,16 @@ dependencies = [
"generic-array",
]
[[package]]
name = "bstr"
version = "1.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "234113d19d0d7d613b40e86fb654acf958910802bcceab913a4f9e7cda03b1a4"
dependencies = [
"memchr",
"serde",
]
[[package]]
name = "bumpalo"
version = "3.17.0"
@ -267,6 +307,25 @@ version = "2.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5"
[[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"
@ -570,6 +629,36 @@ version = "0.31.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f"
[[package]]
name = "glob"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2"
[[package]]
name = "globset"
version = "0.4.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "54a1028dfc5f5df5da8a56a73e6c153c9a9708ec57232470703592a3f18e49f5"
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.15.2"
@ -873,6 +962,22 @@ dependencies = [
"icu_properties",
]
[[package]]
name = "ignore"
version = "0.4.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6d89fd380afde86567dfba715db065673989d6253f42b88179abd3eae47bda4b"
dependencies = [
"crossbeam-deque",
"globset",
"log",
"memchr",
"regex-automata",
"same-file",
"walkdir",
"winapi-util",
]
[[package]]
name = "indexmap"
version = "2.9.0"
@ -883,6 +988,15 @@ dependencies = [
"hashbrown",
]
[[package]]
name = "itertools"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57"
dependencies = [
"either",
]
[[package]]
name = "itoa"
version = "1.0.15"
@ -1032,6 +1146,15 @@ dependencies = [
"windows-sys 0.52.0",
]
[[package]]
name = "normpath"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c8911957c4b1549ac0dc74e30db9c8b0e66ddcd6d7acc33098f4c63a64a6d7ed"
dependencies = [
"windows-sys 0.59.0",
]
[[package]]
name = "num-bigint-dig"
version = "0.8.4"
@ -1270,9 +1393,38 @@ version = "0.5.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2f103c6d277498fbceb16e84d317e2a400f160f46904d5f5410848c829511a3"
dependencies = [
"bitflags",
"bitflags 2.9.0",
]
[[package]]
name = "regex"
version = "1.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191"
dependencies = [
"aho-corasick",
"memchr",
"regex-automata",
"regex-syntax",
]
[[package]]
name = "regex-automata"
version = "0.4.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
[[package]]
name = "ring"
version = "0.17.14"
@ -1307,6 +1459,60 @@ dependencies = [
"zeroize",
]
[[package]]
name = "rust-i18n"
version = "3.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2b6307cde881492032919adf26e254981604a6657b339ae23cce8358e9ee203"
dependencies = [
"globwalk",
"once_cell",
"regex",
"rust-i18n-macro",
"rust-i18n-support",
"smallvec",
]
[[package]]
name = "rust-i18n-macro"
version = "3.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1c0dc724669fe2ddbbec5ed9daea8147a9030de87ebb46fdc7bb9315701d9912"
dependencies = [
"glob",
"once_cell",
"proc-macro2",
"quote",
"rust-i18n-support",
"serde",
"serde_json",
"serde_yaml",
"syn",
]
[[package]]
name = "rust-i18n-support"
version = "3.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b47501de04282525d0192c4b4133f9e3574e1fab3542ddc7bb109ff773dc108b"
dependencies = [
"arc-swap",
"base62",
"globwalk",
"itertools",
"lazy_static",
"normpath",
"once_cell",
"proc-macro2",
"regex",
"serde",
"serde_json",
"serde_yaml",
"siphasher",
"toml",
"triomphe",
]
[[package]]
name = "rustc-demangle"
version = "0.1.24"
@ -1319,7 +1525,7 @@ version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d97817398dd4bb2e6da002002db259209759911da105da92bec29ccb12cf58bf"
dependencies = [
"bitflags",
"bitflags 2.9.0",
"errno",
"libc",
"linux-raw-sys",
@ -1378,6 +1584,15 @@ version = "1.0.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
[[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 = "scopeguard"
version = "1.2.0"
@ -1426,6 +1641,15 @@ dependencies = [
"serde",
]
[[package]]
name = "serde_spanned"
version = "0.6.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1"
dependencies = [
"serde",
]
[[package]]
name = "serde_urlencoded"
version = "0.7.1"
@ -1438,6 +1662,19 @@ dependencies = [
"serde",
]
[[package]]
name = "serde_yaml"
version = "0.9.34+deprecated"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47"
dependencies = [
"indexmap",
"itoa",
"ryu",
"serde",
"unsafe-libyaml",
]
[[package]]
name = "sha1"
version = "0.10.6"
@ -1476,6 +1713,12 @@ dependencies = [
"rand_core",
]
[[package]]
name = "siphasher"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d"
[[package]]
name = "slab"
version = "0.4.9"
@ -1620,7 +1863,7 @@ checksum = "4560278f0e00ce64938540546f59f590d60beee33fffbd3b9cd47851e5fff233"
dependencies = [
"atoi",
"base64",
"bitflags",
"bitflags 2.9.0",
"byteorder",
"bytes",
"chrono",
@ -1663,7 +1906,7 @@ checksum = "c5b98a57f363ed6764d5b3a12bfedf62f07aa16e1856a7ddc2a0bb190a959613"
dependencies = [
"atoi",
"base64",
"bitflags",
"bitflags 2.9.0",
"byteorder",
"chrono",
"crc",
@ -1731,6 +1974,7 @@ dependencies = [
"chrono",
"dotenv",
"maud",
"rust-i18n",
"serde",
"sqlx",
"tokio",
@ -1910,6 +2154,40 @@ dependencies = [
"tokio",
]
[[package]]
name = "toml"
version = "0.8.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cd87a5cdd6ffab733b2f74bc4fd7ee5fff6634124999ac278c35fc78c6120148"
dependencies = [
"serde",
"serde_spanned",
"toml_datetime",
"toml_edit",
]
[[package]]
name = "toml_datetime"
version = "0.6.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41"
dependencies = [
"serde",
]
[[package]]
name = "toml_edit"
version = "0.22.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "17b4795ff5edd201c7cd6dca065ae59972ce77d1b80fa0a84d94950ece7d1474"
dependencies = [
"indexmap",
"serde",
"serde_spanned",
"toml_datetime",
"winnow",
]
[[package]]
name = "tower"
version = "0.5.2"
@ -2037,6 +2315,17 @@ dependencies = [
"once_cell",
]
[[package]]
name = "triomphe"
version = "0.1.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ef8f7726da4807b58ea5c96fdc122f80702030edc33b35aff9190a51148ccc85"
dependencies = [
"arc-swap",
"serde",
"stable_deref_trait",
]
[[package]]
name = "typenum"
version = "1.18.0"
@ -2070,6 +2359,12 @@ version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e70f2a8b45122e719eb623c01822704c4e0907e7e426a05927e1a1cfff5b75d0"
[[package]]
name = "unsafe-libyaml"
version = "0.2.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861"
[[package]]
name = "untrusted"
version = "0.9.0"
@ -2111,6 +2406,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.0+wasi-snapshot-preview1"
@ -2209,6 +2514,15 @@ dependencies = [
"wasite",
]
[[package]]
name = "winapi-util"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb"
dependencies = [
"windows-sys 0.59.0",
]
[[package]]
name = "windows-core"
version = "0.61.0"
@ -2416,13 +2730,22 @@ version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
[[package]]
name = "winnow"
version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "63d3fcd9bba44b03821e7d699eeee959f3126dcc4aa8e4ae18ec617c2a5cea10"
dependencies = [
"memchr",
]
[[package]]
name = "wit-bindgen-rt"
version = "0.39.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1"
dependencies = [
"bitflags",
"bitflags 2.9.0",
]
[[package]]

View File

@ -13,3 +13,4 @@ sqlx = { version = "0.8", features = ["sqlite", "runtime-tokio-rustls", "macros"
tokio = { version = "1.44", features = ["macros", "rt-multi-thread"] }
tower-sessions = "0.14"
tracing = "0.1"
rust-i18n = "3"

16
locales/de-AT.yml Normal file
View File

@ -0,0 +1,16 @@
_version: 1
app_name: "Stationslauf-App"
stations: "Stationen"
stations_expl_without_first_word: "sind festgelegte Orte mit spezifischen Aufgaben."
station_warning_not_assigned_route: "Noch keiner Route zugeordnet" # should be short -> tooltip
station_confirm_deletion: "Bist du sicher, dass die Station gelöscht werden soll? Das kann _NICHT_ mehr rückgängig gemacht werden."
station_hint_create_first: "Es gibt noch keine Stationen. Das kannst du hier ändern ⤵️"
station_new: "Neue Station"
station_name: "Stationsname"
station_create_succ: "Station %{name} erfolgreich erstellt"
station_create_err_duplicate_name: "Station %{name} konnte _NICHT_ erstellt werden, da es bereits eine Station mit diesem Namen gibt (%{err})!"
station_delete_succ: "Station ${name} erfolgreich gelöscht"
station_delete_err_nonexisting: "Station mit ID ${id} konnte nicht gelöscht werden, da sie nicht existiert"
station_delete_err_already_used: "Station ${name} konnte nicht gelöscht werden, da sie bereits verwendet wird (${err})"
routes: "Routen"
teams: "Teams"

View File

@ -1,6 +1,12 @@
#[macro_use]
extern crate rust_i18n;
i18n!("locales", fallback = "de-AT");
use axum::{body::Body, response::Response, routing::get, Router};
use maud::{html, Markup};
use partials::page;
use rust_i18n::t;
use sqlx::SqlitePool;
use std::sync::Arc;
use tokio::net::TcpListener;
@ -44,6 +50,19 @@ macro_rules! succ {
};
}
#[macro_export]
macro_rules! suc {
($session:expr, $message:expr) => {
$session.insert("succ", &$message).await.unwrap()
};
}
#[macro_export]
macro_rules! er {
($session:expr, $message:expr) => {
$session.insert("err", &$message).await.unwrap()
};
}
const PICO_CSS: &str = include_str!("../assets/pico.classless.min.css");
const MY_CSS: &str = include_str!("../assets/style.css");
const LEAFLET_CSS: &str = include_str!("../assets/leaflet.css");
@ -87,22 +106,22 @@ async fn serve_marker_png() -> Response<Body> {
async fn index(session: Session) -> Markup {
let content = html! {
h1 { "Stationslauf-App" }
h1 { (t!("app_name")) }
nav {
ul {
li {
a role="button" href="/station" {
"Stationen"
(t!("stations"))
}
}
li {
a role="button" href="/route" {
"Routen"
(t!("routes"))
}
}
li {
a role="button" href="/team" {
"Teams"
(t!("teams"))
}
}
}

View File

@ -1,4 +1,4 @@
use crate::{err, partials::page, station::Station, succ};
use crate::{er, err, partials::page, station::Station, suc, succ};
use axum::{
extract::State,
response::{IntoResponse, Redirect},
@ -22,11 +22,14 @@ async fn create(
Form(form): Form<CreateForm>,
) -> impl IntoResponse {
match Station::create(&db, &form.name).await {
Ok(()) => succ!(session, "Station '{}' erfolgreich erstellt!", form.name),
Err(e) => err!(
Ok(()) => suc!(session, t!("station_create_succ", name = form.name)),
Err(e) => er!(
session,
"Station '{}' konnte _NICHT_ erstellt werden, da es bereits eine Station mit diesem Namen gibt ({e})!",
form.name
t!(
"station_create_err_duplicate_name",
name = form.name,
err = e
)
),
}
@ -39,20 +42,20 @@ async fn delete(
axum::extract::Path(id): axum::extract::Path<i64>,
) -> impl IntoResponse {
let Some(station) = Station::find_by_id(&db, id).await else {
err!(
session,
"Station mit ID {id} konnte nicht gelöscht werden, da sie nicht existiert"
);
er!(session, t!("station_delete_err_nonexisting", id = id));
return Redirect::to("/station");
};
match station.delete(&db).await {
Ok(()) => succ!(session, "Station '{}' erfolgreich gelöscht!", station.name),
Err(e) => err!(
Ok(()) => suc!(session, t!("station_delete_succ", name = station.name)),
Err(e) => er!(
session,
"Station '{}' kann nicht gelöscht werden, da sie bereits verwendet wird. ({e})",
station.name
t!(
"station_delete_err_already_used",
name = station.name,
err = e
)
),
}
@ -413,17 +416,20 @@ async fn index(State(db): State<Arc<SqlitePool>>, session: Session) -> Markup {
let content = html! {
h1 {
a href="/" { "↩️" }
"Stationen"
(t!("stations"))
}
article {
em { "Stationen " }
"sind festgelegte Orte mit spezifischen Aufgaben."
em {
(t!("stations"))
" "
}
(t!("stations_expl_without_first_word"))
}
ol {
@for station in &stations {
li {
@if station.routes(&db).await.is_empty() {
em data-tooltip="Noch keiner Route zugeordnet" {
em data-tooltip=(t!("station_warning_not_assigned_route")) {
"⚠️"
}
}
@ -431,7 +437,7 @@ async fn index(State(db): State<Arc<SqlitePool>>, session: Session) -> Markup {
(station.name)
}
a href=(format!("/station/{}/delete", station.id))
onclick="return confirm('Bist du sicher, dass die Station gelöscht werden soll? Das kann _NICHT_ mehr rückgängig gemacht werden.');" {
onclick=(format!("return confirm('{}');", t!("station_confirm_deletion"))) {
"🗑️"
}
}
@ -439,14 +445,14 @@ async fn index(State(db): State<Arc<SqlitePool>>, session: Session) -> Markup {
}
@if stations.is_empty() {
article class="warning" {
"Es gibt noch keine Stationen. Das kannst du hier ändern ⤵️"
(t!("station_hint_create_first"))
}
}
h2 { "Neue Station" }
h2 { (t!("station_new")) }
form action="/station" method="post" {
fieldset role="team" {
input type="text" name="name" placeholder="Stationsname" required;
input type="submit" value="Neue Station";
input type="text" name="name" placeholder=(t!("station_name")) required;
input type="submit" value=(t!("station_new"));
}
}
};