feat(i18n): add i18n on server side

- remove the hardcoded list of locales in favor of a discovry on start time
    - server will stop on badly formatted locale .json
    - add server.* entries for serer side translation

    server side translation will be used for templating and email
    note: no json in some embded html (like in /magic), amount of work was similar
This commit is contained in:
Edouard Vanbelle
2026-06-03 13:18:05 +02:00
parent e0f59aa942
commit 044bd76738
32 changed files with 1520 additions and 149 deletions
+38
View File
@@ -869,6 +869,36 @@ pub struct AppConfig {
pub smtp: SmtpConfig,
/// Magic-link authentication configuration (TTL, external-users kill switch)
pub magic_link: MagicLinkConfig,
/// I18n configuration (default locale for server-rendered surfaces)
pub i18n: I18nConfig,
}
/// Server-side i18n knobs.
///
/// Locale discovery itself is driven by `static/locales/*.json` at boot
/// (see [`crate::common::locale::LocaleRegistry`]) — no hardcoded list,
/// no `build.rs`. This struct only carries the configurable defaults
/// around that discovery.
#[derive(Debug, Clone)]
pub struct I18nConfig {
/// Fallback locale used when:
/// - an anonymous request's `Accept-Language` matches nothing in
/// the registry,
/// - a user's `preferred_locale` is `NULL`,
/// - an OIDC `locale` claim doesn't resolve.
///
/// Must be present in `static/locales/`; the registry-build step
/// errors at startup if this is set to a locale we don't ship.
/// Defaults to `"en"`. Override via `OXICLOUD_DEFAULT_LOCALE`.
pub default_locale: String,
}
impl Default for I18nConfig {
fn default() -> Self {
Self {
default_locale: "en".to_string(),
}
}
}
impl Default for AppConfig {
@@ -891,6 +921,7 @@ impl Default for AppConfig {
nextcloud: NextcloudConfig::default(),
smtp: SmtpConfig::default(),
magic_link: MagicLinkConfig::default(),
i18n: I18nConfig::default(),
}
}
}
@@ -1445,6 +1476,13 @@ impl AppConfig {
config.magic_link.open_to_password_users = v == "true" || v == "1";
}
if let Ok(v) = env::var("OXICLOUD_DEFAULT_LOCALE") {
let trimmed = v.trim();
if !trimmed.is_empty() {
config.i18n.default_locale = trimmed.to_string();
}
}
config
}