app

Local-first trade for farms and co-ops
git clone https://radroots.dev/git/app.git
Log | Files | Refs | README | LICENSE

commit fa39edacd059ec42ddc3be91ecd28cc61a8b3c37
parent e860e5033560e1dbf03430a7b31d4d7fcdc6c796
Author: triesap <tyson@radroots.org>
Date:   Mon,  2 Feb 2026 14:16:13 +0000

app: add i18n scaffolding

- add i18n context and translate macro
- wire i18n context into app shell
- add mf2-i18n dependencies for app
- cover i18n fallback with tests

Diffstat:
MCargo.toml | 2++
Mapp/Cargo.toml | 3+++
Mapp/src/app.rs | 2++
Aapp/src/i18n.rs | 68++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mapp/src/lib.rs | 2++
5 files changed, 77 insertions(+), 0 deletions(-)

diff --git a/Cargo.toml b/Cargo.toml @@ -111,6 +111,8 @@ radroots-sql-core = { path = "refs/crates/sql-core" } radroots-tangle-db = { path = "refs/crates/tangle-db" } radroots-tangle-db-schema = { path = "refs/crates/tangle-db-schema" } radroots-tangle-events = { path = "refs/crates/tangle-events" } +mf2-i18n-core = { path = "refs/mf2-i18n/crates/mf2-i18n-core" } +mf2-i18n-embedded = { path = "refs/mf2-i18n/crates/mf2-i18n-embedded" } [profile.release] codegen-units = 1 diff --git a/app/Cargo.toml b/app/Cargo.toml @@ -20,6 +20,9 @@ web-sys.workspace = true gloo-timers = { workspace = true, features = ["futures"] } radroots-app-core = { path = "../crates/core" } radroots-app-ui-components = { path = "../crates/ui-components" } +radroots-app-lib = { path = "../crates/app-lib" } +mf2-i18n-core = { path = "../refs/mf2-i18n/crates/mf2-i18n-core" } +mf2-i18n-embedded = { path = "../refs/mf2-i18n/crates/mf2-i18n-embedded" } radroots-log = { path = "../refs/crates/log", default-features = false } radroots-nostr = { workspace = true } tracing-wasm = "0.2" diff --git a/app/src/app.rs b/app/src/app.rs @@ -33,6 +33,7 @@ use crate::{ app_init_total_add, app_init_total_unknown, app_context, + app_i18n_init, app_log_buffer_flush_deferred, app_log_debug_emit, app_log_error_emit, @@ -1435,6 +1436,7 @@ fn AppShell() -> impl IntoView { provide_context(init_error); provide_context(init_state); provide_context(setup_required); + provide_context(app_i18n_init()); Effect::new(move || { let navigate = navigate.clone(); spawn_local(async move { diff --git a/app/src/i18n.rs b/app/src/i18n.rs @@ -0,0 +1,68 @@ +#![forbid(unsafe_code)] + +use leptos::prelude::{use_context, LocalStorage, RwSignal, StoredValue, WithUntracked, WithValue}; + +use mf2_i18n_core::Args; +use mf2_i18n_embedded::EmbeddedRuntime; +use radroots_app_lib::get_locale; + +#[derive(Clone, Copy)] +pub struct RadrootsAppI18nContext { + pub locale: RwSignal<String, LocalStorage>, + pub runtime: StoredValue<Option<EmbeddedRuntime>>, +} + +pub fn app_i18n_init() -> RadrootsAppI18nContext { + let locale = get_locale(&["en"]); + let locale = RwSignal::new_local(locale); + let runtime = StoredValue::new(None::<EmbeddedRuntime>); + RadrootsAppI18nContext { locale, runtime } +} + +pub fn app_i18n() -> Option<RadrootsAppI18nContext> { + use_context::<RadrootsAppI18nContext>() +} + +pub fn translate(key: &str) -> String { + let Some(ctx) = app_i18n() else { + return key.to_string(); + }; + let locale = ctx.locale.with_untracked(|value| value.clone()); + ctx.runtime.with_value(|runtime: &Option<EmbeddedRuntime>| { + if let Some(runtime) = runtime.as_ref() { + let args = Args::new(); + runtime + .format(&locale, key, &args) + .unwrap_or_else(|_| key.to_string()) + } else { + key.to_string() + } + }) +} + +#[macro_export] +macro_rules! t { + ($key:literal) => { + $crate::i18n::translate($key) + }; +} + +#[cfg(test)] +mod tests { + use super::{app_i18n, app_i18n_init, translate}; + use leptos::prelude::{provide_context, Owner}; + + #[test] + fn translate_falls_back_without_context() { + assert_eq!(translate("hello"), "hello"); + } + + #[test] + fn translate_reads_context() { + let owner = Owner::new(); + owner.set(); + provide_context(app_i18n_init()); + assert!(app_i18n().is_some()); + assert_eq!(translate("hello"), "hello"); + } +} diff --git a/app/src/lib.rs b/app/src/lib.rs @@ -7,6 +7,7 @@ mod config; mod data; mod health; mod init; +mod i18n; mod keystore; mod logging; mod logs; @@ -198,3 +199,4 @@ pub use init::{ RadrootsAppInitState, APP_INIT_STORAGE_KEY, }; +pub use i18n::{app_i18n, app_i18n_init, RadrootsAppI18nContext};