i18n on the web - deepseek translations

This commit is contained in:
Priec
2026-08-14 20:35:37 +02:00
parent 2dc0d94ba0
commit 8f1bfdbe0c
91 changed files with 4679 additions and 1237 deletions

View File

@@ -55,6 +55,33 @@ impl Nav {
self.locale.lookup(key)
}
/// Like [`Self::tr`], but interpolates named `{ $name }` placeholders.
/// Values are strings; a template needing a *number* (for Fluent plural
/// selection) should have the label built in Rust instead, where the
/// `tr!` macro accepts `i64` arguments.
#[allow(dead_code)]
pub(crate) fn tr_args(&self, key: &str, args: &[(&str, String)]) -> String {
use std::{borrow::Cow, collections::HashMap};
let map: HashMap<Cow<'static, str>, fluent_templates::fluent_bundle::FluentValue<'_>> =
args.iter()
.map(|(name, value)| (Cow::Owned(name.to_string()), value.clone().into()))
.collect();
self.locale.lookup_args(key, &map)
}
/// Translates a key with one numeric argument, for Fluent plural
/// selection (`{ $count -> [one] … *[other] … }`).
#[allow(dead_code)]
pub(crate) fn tr_count(&self, key: &str, name: &str, count: &i64) -> String {
use std::{borrow::Cow, collections::HashMap};
let mut map: HashMap<
Cow<'static, str>,
fluent_templates::fluent_bundle::FluentValue<'_>,
> = HashMap::new();
map.insert(Cow::Owned(name.to_string()), (*count).into());
self.locale.lookup_args(key, &map)
}
pub(crate) fn with_authorization(
mut self,
authorization: &crate::auth::AuthorizationSnapshot,
@@ -97,22 +124,27 @@ impl Default for Nav {
#[derive(Template)]
#[template(path = "ui/alert_fragment.html")]
pub(crate) struct Alert<'a> {
/// The request's language, which the shared alert/dialog markup needs for
/// its own chrome (the dismiss aria-label, the "Back to the form" button).
locale: crate::i18n::Locale,
success: bool,
title: &'a str,
message: &'a str,
}
impl<'a> Alert<'a> {
pub(crate) fn error(title: &'a str, message: &'a str) -> Self {
pub(crate) fn error(locale: crate::i18n::Locale, title: &'a str, message: &'a str) -> Self {
Self {
locale,
success: false,
title,
message,
}
}
pub(crate) fn success(title: &'a str, message: &'a str) -> Self {
pub(crate) fn success(locale: crate::i18n::Locale, title: &'a str, message: &'a str) -> Self {
Self {
locale,
success: true,
title,
message,
@@ -124,22 +156,25 @@ impl<'a> Alert<'a> {
#[derive(Template)]
#[template(path = "ui/notice.html")]
pub(crate) struct Notice<'a> {
locale: crate::i18n::Locale,
error: bool,
message: &'a str,
login_link: bool,
}
impl<'a> Notice<'a> {
pub(crate) fn error(message: &'a str) -> Self {
pub(crate) fn error(locale: crate::i18n::Locale, message: &'a str) -> Self {
Self {
locale,
error: true,
message,
login_link: false,
}
}
pub(crate) fn login_required(message: &'a str) -> Self {
pub(crate) fn login_required(locale: crate::i18n::Locale, message: &'a str) -> Self {
Self {
locale,
error: true,
message,
login_link: true,
@@ -172,7 +207,11 @@ mod tests {
/// place that decides a failure cannot go unnoticed.
#[test]
fn a_failed_form_gets_a_dialog_and_a_successful_one_does_not() {
let failure = render(&Alert::error("Could not save", "A name is required."));
let failure = render(&Alert::error(
crate::i18n::Locale::default(),
"Could not save",
"A name is required.",
));
assert!(!failure.contains("Template error"), "{failure}");
assert!(failure.contains(r#"role="alert""#));
assert!(failure.contains(r#"role="dialog""#));
@@ -182,7 +221,11 @@ mod tests {
// A success only asks the toast stack in `ui/base.html` to show it: it
// dismisses itself, so it neither blocks the page nor stays behind in
// the layout, and it renders nothing of its own here.
let success = render(&Alert::success("Saved", "Two rows written."));
let success = render(&Alert::success(
crate::i18n::Locale::default(),
"Saved",
"Two rows written.",
));
assert!(!success.contains("Template error"), "{success}");
assert!(success.contains("$dispatch('notify'"));
assert!(success.contains(r#"data-message="Two rows written.""#));