app

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

commit d46b0c64370ec4e101c6a90c3a73e79e22e2baa7
parent 5081dd397512af7213a4a05f40ebab509d0b04c7
Author: triesap <tyson@radroots.org>
Date:   Mon,  2 Feb 2026 15:20:41 +0000

app: localize shell and home status

- add home and nav translation keys with aria labels
- map init, health, reset, and notification statuses
- translate home dashboard headings and buttons
- regenerate mf2-i18n build artifacts

Diffstat:
Mapp/i18n/build/i18n.catalog.json | 707++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
Mapp/i18n/build/id_map.json | 69+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
Mapp/i18n/build/id_map_hash | 2+-
Mapp/i18n/build/manifest.json | 4++--
Mapp/i18n/build/packs/en.mf2pack | 0
Mapp/i18n/locales/en/messages.mf2 | 134++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
Mapp/src/app.rs | 146+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------
7 files changed, 1023 insertions(+), 39 deletions(-)

diff --git a/app/i18n/build/i18n.catalog.json b/app/i18n/build/i18n.catalog.json @@ -3,5 +3,710 @@ "project": "radroots-app", "generated_at": "2026-02-02T00:00:00Z", "default_locale": "en", - "messages": [] + "messages": [ + { + "key": "app.common.missing", + "id": 3520194192, + "args": [], + "features": { + "select": false, + "plural_cardinal": false, + "plural_ordinal": false, + "formatters": [] + } + }, + { + "key": "app.common.unknown", + "id": 1588621956, + "args": [], + "features": { + "select": false, + "plural_cardinal": false, + "plural_ordinal": false, + "formatters": [] + } + }, + { + "key": "app.home.health.aria", + "id": 3172671582, + "args": [], + "features": { + "select": false, + "plural_cardinal": false, + "plural_ordinal": false, + "formatters": [] + } + }, + { + "key": "app.home.health.button.checking", + "id": 3465392175, + "args": [], + "features": { + "select": false, + "plural_cardinal": false, + "plural_ordinal": false, + "formatters": [] + } + }, + { + "key": "app.home.health.button.run", + "id": 1230595839, + "args": [], + "features": { + "select": false, + "plural_cardinal": false, + "plural_ordinal": false, + "formatters": [] + } + }, + { + "key": "app.home.health.item.active_key", + "id": 3589833151, + "args": [], + "features": { + "select": false, + "plural_cardinal": false, + "plural_ordinal": false, + "formatters": [] + } + }, + { + "key": "app.home.health.item.bootstrap_state", + "id": 737539659, + "args": [], + "features": { + "select": false, + "plural_cardinal": false, + "plural_ordinal": false, + "formatters": [] + } + }, + { + "key": "app.home.health.item.datastore_roundtrip", + "id": 1765955433, + "args": [], + "features": { + "select": false, + "plural_cardinal": false, + "plural_ordinal": false, + "formatters": [] + } + }, + { + "key": "app.home.health.item.key_maps", + "id": 1556244806, + "args": [], + "features": { + "select": false, + "plural_cardinal": false, + "plural_ordinal": false, + "formatters": [] + } + }, + { + "key": "app.home.health.item.keystore", + "id": 4253927470, + "args": [], + "features": { + "select": false, + "plural_cardinal": false, + "plural_ordinal": false, + "formatters": [] + } + }, + { + "key": "app.home.health.item.notifications", + "id": 3066505377, + "args": [], + "features": { + "select": false, + "plural_cardinal": false, + "plural_ordinal": false, + "formatters": [] + } + }, + { + "key": "app.home.health.item.state_active_key", + "id": 1422267473, + "args": [], + "features": { + "select": false, + "plural_cardinal": false, + "plural_ordinal": false, + "formatters": [] + } + }, + { + "key": "app.home.health.item.tangle", + "id": 4017335715, + "args": [], + "features": { + "select": false, + "plural_cardinal": false, + "plural_ordinal": false, + "formatters": [] + } + }, + { + "key": "app.home.health.message.mismatch", + "id": 2731529562, + "args": [], + "features": { + "select": false, + "plural_cardinal": false, + "plural_ordinal": false, + "formatters": [] + } + }, + { + "key": "app.home.health.message.missing", + "id": 2655522886, + "args": [], + "features": { + "select": false, + "plural_cardinal": false, + "plural_ordinal": false, + "formatters": [] + } + }, + { + "key": "app.home.health.message.unavailable", + "id": 645848060, + "args": [], + "features": { + "select": false, + "plural_cardinal": false, + "plural_ordinal": false, + "formatters": [] + } + }, + { + "key": "app.home.health.message.uninitialized", + "id": 3760340840, + "args": [], + "features": { + "select": false, + "plural_cardinal": false, + "plural_ordinal": false, + "formatters": [] + } + }, + { + "key": "app.home.health.status.error", + "id": 1466113983, + "args": [], + "features": { + "select": false, + "plural_cardinal": false, + "plural_ordinal": false, + "formatters": [] + } + }, + { + "key": "app.home.health.status.ok", + "id": 828208412, + "args": [], + "features": { + "select": false, + "plural_cardinal": false, + "plural_ordinal": false, + "formatters": [] + } + }, + { + "key": "app.home.health.status.skipped", + "id": 3849381953, + "args": [], + "features": { + "select": false, + "plural_cardinal": false, + "plural_ordinal": false, + "formatters": [] + } + }, + { + "key": "app.home.health.title", + "id": 1957618139, + "args": [], + "features": { + "select": false, + "plural_cardinal": false, + "plural_ordinal": false, + "formatters": [] + } + }, + { + "key": "app.home.notifications.aria", + "id": 1715867969, + "args": [], + "features": { + "select": false, + "plural_cardinal": false, + "plural_ordinal": false, + "formatters": [] + } + }, + { + "key": "app.home.notifications.button.request", + "id": 3959491343, + "args": [], + "features": { + "select": false, + "plural_cardinal": false, + "plural_ordinal": false, + "formatters": [] + } + }, + { + "key": "app.home.notifications.button.requesting", + "id": 3188736353, + "args": [], + "features": { + "select": false, + "plural_cardinal": false, + "plural_ordinal": false, + "formatters": [] + } + }, + { + "key": "app.home.notifications.status.default", + "id": 3760721877, + "args": [], + "features": { + "select": false, + "plural_cardinal": false, + "plural_ordinal": false, + "formatters": [] + } + }, + { + "key": "app.home.notifications.status.denied", + "id": 4076317564, + "args": [], + "features": { + "select": false, + "plural_cardinal": false, + "plural_ordinal": false, + "formatters": [] + } + }, + { + "key": "app.home.notifications.status.granted", + "id": 1582331756, + "args": [], + "features": { + "select": false, + "plural_cardinal": false, + "plural_ordinal": false, + "formatters": [] + } + }, + { + "key": "app.home.notifications.status.unavailable", + "id": 3118891586, + "args": [], + "features": { + "select": false, + "plural_cardinal": false, + "plural_ordinal": false, + "formatters": [] + } + }, + { + "key": "app.home.notifications.title", + "id": 907277970, + "args": [], + "features": { + "select": false, + "plural_cardinal": false, + "plural_ordinal": false, + "formatters": [] + } + }, + { + "key": "app.home.reset.aria", + "id": 3577090589, + "args": [], + "features": { + "select": false, + "plural_cardinal": false, + "plural_ordinal": false, + "formatters": [] + } + }, + { + "key": "app.home.reset.button", + "id": 3119733506, + "args": [], + "features": { + "select": false, + "plural_cardinal": false, + "plural_ordinal": false, + "formatters": [] + } + }, + { + "key": "app.home.reset.status.done", + "id": 3550097324, + "args": [], + "features": { + "select": false, + "plural_cardinal": false, + "plural_ordinal": false, + "formatters": [] + } + }, + { + "key": "app.home.reset.status.idle", + "id": 4013677279, + "args": [], + "features": { + "select": false, + "plural_cardinal": false, + "plural_ordinal": false, + "formatters": [] + } + }, + { + "key": "app.home.reset.status.missing_backends", + "id": 3398209588, + "args": [], + "features": { + "select": false, + "plural_cardinal": false, + "plural_ordinal": false, + "formatters": [] + } + }, + { + "key": "app.home.reset.status.resetting", + "id": 3968037487, + "args": [], + "features": { + "select": false, + "plural_cardinal": false, + "plural_ordinal": false, + "formatters": [] + } + }, + { + "key": "app.home.status.aria", + "id": 3526893198, + "args": [], + "features": { + "select": false, + "plural_cardinal": false, + "plural_ordinal": false, + "formatters": [] + } + }, + { + "key": "app.home.title", + "id": 3182100107, + "args": [], + "features": { + "select": false, + "plural_cardinal": false, + "plural_ordinal": false, + "formatters": [] + } + }, + { + "key": "app.init.stage.database", + "id": 3687544718, + "args": [], + "features": { + "select": false, + "plural_cardinal": false, + "plural_ordinal": false, + "formatters": [] + } + }, + { + "key": "app.init.stage.download_geo", + "id": 3254824087, + "args": [], + "features": { + "select": false, + "plural_cardinal": false, + "plural_ordinal": false, + "formatters": [] + } + }, + { + "key": "app.init.stage.download_sql", + "id": 333951479, + "args": [], + "features": { + "select": false, + "plural_cardinal": false, + "plural_ordinal": false, + "formatters": [] + } + }, + { + "key": "app.init.stage.error", + "id": 388972410, + "args": [], + "features": { + "select": false, + "plural_cardinal": false, + "plural_ordinal": false, + "formatters": [] + } + }, + { + "key": "app.init.stage.geocoder", + "id": 192352243, + "args": [], + "features": { + "select": false, + "plural_cardinal": false, + "plural_ordinal": false, + "formatters": [] + } + }, + { + "key": "app.init.stage.idle", + "id": 1446931460, + "args": [], + "features": { + "select": false, + "plural_cardinal": false, + "plural_ordinal": false, + "formatters": [] + } + }, + { + "key": "app.init.stage.ready", + "id": 4200712024, + "args": [], + "features": { + "select": false, + "plural_cardinal": false, + "plural_ordinal": false, + "formatters": [] + } + }, + { + "key": "app.init.stage.storage", + "id": 1242977634, + "args": [], + "features": { + "select": false, + "plural_cardinal": false, + "plural_ordinal": false, + "formatters": [] + } + }, + { + "key": "app.nav.home", + "id": 2360822558, + "args": [], + "features": { + "select": false, + "plural_cardinal": false, + "plural_ordinal": false, + "formatters": [] + } + }, + { + "key": "app.nav.logs", + "id": 952998791, + "args": [], + "features": { + "select": false, + "plural_cardinal": false, + "plural_ordinal": false, + "formatters": [] + } + }, + { + "key": "app.nav.primary_aria", + "id": 3495744422, + "args": [], + "features": { + "select": false, + "plural_cardinal": false, + "plural_ordinal": false, + "formatters": [] + } + }, + { + "key": "app.nav.settings", + "id": 326721352, + "args": [], + "features": { + "select": false, + "plural_cardinal": false, + "plural_ordinal": false, + "formatters": [] + } + }, + { + "key": "app.nav.setup", + "id": 2066290074, + "args": [], + "features": { + "select": false, + "plural_cardinal": false, + "plural_ordinal": false, + "formatters": [] + } + }, + { + "key": "app.nav.ui", + "id": 2416341108, + "args": [], + "features": { + "select": false, + "plural_cardinal": false, + "plural_ordinal": false, + "formatters": [] + } + }, + { + "key": "app.not_found", + "id": 3182331848, + "args": [], + "features": { + "select": false, + "plural_cardinal": false, + "plural_ordinal": false, + "formatters": [] + } + }, + { + "key": "error.app.init.assets", + "id": 172100683, + "args": [], + "features": { + "select": false, + "plural_cardinal": false, + "plural_ordinal": false, + "formatters": [] + } + }, + { + "key": "error.app.init.config", + "id": 3737196850, + "args": [], + "features": { + "select": false, + "plural_cardinal": false, + "plural_ordinal": false, + "formatters": [] + } + }, + { + "key": "error.app.init.datastore", + "id": 1124445179, + "args": [], + "features": { + "select": false, + "plural_cardinal": false, + "plural_ordinal": false, + "formatters": [] + } + }, + { + "key": "error.app.init.idb", + "id": 3385056441, + "args": [], + "features": { + "select": false, + "plural_cardinal": false, + "plural_ordinal": false, + "formatters": [] + } + }, + { + "key": "error.app.init.keystore", + "id": 2043630525, + "args": [], + "features": { + "select": false, + "plural_cardinal": false, + "plural_ordinal": false, + "formatters": [] + } + }, + { + "key": "error.app.state.already_exists", + "id": 4003177973, + "args": [], + "features": { + "select": false, + "plural_cardinal": false, + "plural_ordinal": false, + "formatters": [] + } + }, + { + "key": "error.app.state.checksum_invalid", + "id": 56309183, + "args": [], + "features": { + "select": false, + "plural_cardinal": false, + "plural_ordinal": false, + "formatters": [] + } + }, + { + "key": "error.app.state.corrupt", + "id": 2251393452, + "args": [], + "features": { + "select": false, + "plural_cardinal": false, + "plural_ordinal": false, + "formatters": [] + } + }, + { + "key": "error.app.state.missing", + "id": 2404167425, + "args": [], + "features": { + "select": false, + "plural_cardinal": false, + "plural_ordinal": false, + "formatters": [] + } + }, + { + "key": "error.app.state.schema_unsupported", + "id": 3991204251, + "args": [], + "features": { + "select": false, + "plural_cardinal": false, + "plural_ordinal": false, + "formatters": [] + } + }, + { + "key": "error.client.notifications.read_failure", + "id": 350195904, + "args": [], + "features": { + "select": false, + "plural_cardinal": false, + "plural_ordinal": false, + "formatters": [] + } + }, + { + "key": "error.client.notifications.unavailable", + "id": 4206009627, + "args": [], + "features": { + "select": false, + "plural_cardinal": false, + "plural_ordinal": false, + "formatters": [] + } + } + ] } \ No newline at end of file diff --git a/app/i18n/build/id_map.json b/app/i18n/build/id_map.json @@ -1 +1,66 @@ -{} -\ No newline at end of file +{ + "app.common.missing": 3520194192, + "app.common.unknown": 1588621956, + "app.home.health.aria": 3172671582, + "app.home.health.button.checking": 3465392175, + "app.home.health.button.run": 1230595839, + "app.home.health.item.active_key": 3589833151, + "app.home.health.item.bootstrap_state": 737539659, + "app.home.health.item.datastore_roundtrip": 1765955433, + "app.home.health.item.key_maps": 1556244806, + "app.home.health.item.keystore": 4253927470, + "app.home.health.item.notifications": 3066505377, + "app.home.health.item.state_active_key": 1422267473, + "app.home.health.item.tangle": 4017335715, + "app.home.health.message.mismatch": 2731529562, + "app.home.health.message.missing": 2655522886, + "app.home.health.message.unavailable": 645848060, + "app.home.health.message.uninitialized": 3760340840, + "app.home.health.status.error": 1466113983, + "app.home.health.status.ok": 828208412, + "app.home.health.status.skipped": 3849381953, + "app.home.health.title": 1957618139, + "app.home.notifications.aria": 1715867969, + "app.home.notifications.button.request": 3959491343, + "app.home.notifications.button.requesting": 3188736353, + "app.home.notifications.status.default": 3760721877, + "app.home.notifications.status.denied": 4076317564, + "app.home.notifications.status.granted": 1582331756, + "app.home.notifications.status.unavailable": 3118891586, + "app.home.notifications.title": 907277970, + "app.home.reset.aria": 3577090589, + "app.home.reset.button": 3119733506, + "app.home.reset.status.done": 3550097324, + "app.home.reset.status.idle": 4013677279, + "app.home.reset.status.missing_backends": 3398209588, + "app.home.reset.status.resetting": 3968037487, + "app.home.status.aria": 3526893198, + "app.home.title": 3182100107, + "app.init.stage.database": 3687544718, + "app.init.stage.download_geo": 3254824087, + "app.init.stage.download_sql": 333951479, + "app.init.stage.error": 388972410, + "app.init.stage.geocoder": 192352243, + "app.init.stage.idle": 1446931460, + "app.init.stage.ready": 4200712024, + "app.init.stage.storage": 1242977634, + "app.nav.home": 2360822558, + "app.nav.logs": 952998791, + "app.nav.primary_aria": 3495744422, + "app.nav.settings": 326721352, + "app.nav.setup": 2066290074, + "app.nav.ui": 2416341108, + "app.not_found": 3182331848, + "error.app.init.assets": 172100683, + "error.app.init.config": 3737196850, + "error.app.init.datastore": 1124445179, + "error.app.init.idb": 3385056441, + "error.app.init.keystore": 2043630525, + "error.app.state.already_exists": 4003177973, + "error.app.state.checksum_invalid": 56309183, + "error.app.state.corrupt": 2251393452, + "error.app.state.missing": 2404167425, + "error.app.state.schema_unsupported": 3991204251, + "error.client.notifications.read_failure": 350195904, + "error.client.notifications.unavailable": 4206009627 +} +\ No newline at end of file diff --git a/app/i18n/build/id_map_hash b/app/i18n/build/id_map_hash @@ -1 +1 @@ -sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 +sha256:6f697fe6b69bf95389c0703cd551d61fecee1f5607ad76520d10390781027b82 diff --git a/app/i18n/build/manifest.json b/app/i18n/build/manifest.json @@ -1 +1 @@ -{"schema":1,"release_id":"dev","generated_at":"2026-02-02T00:00:00Z","default_locale":"en","supported_locales":["en"],"id_map_hash":"sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855","mf2_packs":{"en":{"kind":"base","url":"packs/en.mf2pack","hash":"sha256:f8f3c3a662af2cb807c03b5f908735a19c5ee8f414e2cf0002cfcad7c25de6f0","size":136,"content_encoding":"identity","pack_schema":0}}} -\ No newline at end of file +{"schema":1,"release_id":"dev","generated_at":"2026-02-02T00:00:00Z","default_locale":"en","supported_locales":["en"],"id_map_hash":"sha256:6f697fe6b69bf95389c0703cd551d61fecee1f5607ad76520d10390781027b82","mf2_packs":{"en":{"kind":"base","url":"packs/en.mf2pack","hash":"sha256:8c7a60157aafe3fc9a5acb3725cf5a748eea3a252839eb1e4aa87ddb8ad86f31","size":3182,"content_encoding":"identity","pack_schema":0}}} +\ No newline at end of file diff --git a/app/i18n/build/packs/en.mf2pack b/app/i18n/build/packs/en.mf2pack Binary files differ. diff --git a/app/i18n/locales/en/messages.mf2 b/app/i18n/locales/en/messages.mf2 @@ -1 +1,133 @@ -# radroots app messages +# common +app.common.missing = missing + +app.common.unknown = unknown + +# nav +app.nav.primary_aria = primary + +app.nav.home = home + +app.nav.logs = logs + +app.nav.ui = ui + +app.nav.settings = settings + +app.nav.setup = setup + +# not found +app.not_found = not found + +# init stages +app.init.stage.idle = idle + +app.init.stage.storage = storage + +app.init.stage.download_sql = download sql + +app.init.stage.download_geo = download geo + +app.init.stage.database = database + +app.init.stage.geocoder = geocoder + +app.init.stage.ready = ready + +app.init.stage.error = error + +# home +app.home.title = app + +app.home.status.aria = status + +app.home.reset.aria = reset + +app.home.reset.button = reset + +app.home.reset.status.idle = idle + +app.home.reset.status.resetting = resetting + +app.home.reset.status.missing_backends = backends unavailable + +app.home.reset.status.done = reset complete + +app.home.notifications.aria = notifications + +app.home.notifications.title = notifications + +app.home.notifications.button.request = request + +app.home.notifications.button.requesting = requesting + +app.home.notifications.status.granted = granted + +app.home.notifications.status.denied = denied + +app.home.notifications.status.default = default + +app.home.notifications.status.unavailable = unavailable + +app.home.health.aria = health checks + +app.home.health.title = health checks + +app.home.health.button.checking = checking + +app.home.health.button.run = run checks + +app.home.health.item.key_maps = key maps + +app.home.health.item.bootstrap_state = bootstrap state + +app.home.health.item.state_active_key = active key state + +app.home.health.item.notifications = notifications + +app.home.health.item.tangle = tangle + +app.home.health.item.datastore_roundtrip = datastore roundtrip + +app.home.health.item.keystore = keystore + +app.home.health.item.active_key = active key + +app.home.health.status.ok = ok + +app.home.health.status.error = error + +app.home.health.status.skipped = skipped + +app.home.health.message.missing = missing + +app.home.health.message.mismatch = mismatch + +app.home.health.message.uninitialized = uninitialized + +app.home.health.message.unavailable = unavailable + +# errors +error.app.init.idb = storage unavailable + +error.app.init.datastore = datastore unavailable + +error.app.init.keystore = keystore unavailable + +error.app.init.config = configuration error + +error.app.init.assets = assets unavailable + +error.app.state.missing = setup required + +error.app.state.corrupt = stored data is corrupt + +error.app.state.checksum_invalid = stored data checksum invalid + +error.app.state.schema_unsupported = stored data schema unsupported + +error.app.state.already_exists = setup already completed + +error.client.notifications.unavailable = notifications unavailable + +error.client.notifications.read_failure = notifications permission read failed diff --git a/app/src/app.rs b/app/src/app.rs @@ -20,6 +20,7 @@ use radroots_app_ui_components::{ RadrootsAppUiButtonLayoutPair, }; +use crate::t; use crate::{ app_init_assets, app_init_backends, @@ -78,10 +79,81 @@ fn health_status_color(status: RadrootsAppHealthCheckStatus) -> &'static str { } } +fn init_stage_label(stage: RadrootsAppInitStage) -> String { + match stage { + RadrootsAppInitStage::Idle => t!("app.init.stage.idle"), + RadrootsAppInitStage::Storage => t!("app.init.stage.storage"), + RadrootsAppInitStage::DownloadSql => t!("app.init.stage.download_sql"), + RadrootsAppInitStage::DownloadGeo => t!("app.init.stage.download_geo"), + RadrootsAppInitStage::Database => t!("app.init.stage.database"), + RadrootsAppInitStage::Geocoder => t!("app.init.stage.geocoder"), + RadrootsAppInitStage::Ready => t!("app.init.stage.ready"), + RadrootsAppInitStage::Error => t!("app.init.stage.error"), + } +} + +fn health_status_label(status: RadrootsAppHealthCheckStatus) -> String { + match status { + RadrootsAppHealthCheckStatus::Ok => t!("app.home.health.status.ok"), + RadrootsAppHealthCheckStatus::Error => t!("app.home.health.status.error"), + RadrootsAppHealthCheckStatus::Skipped => t!("app.home.health.status.skipped"), + } +} + +fn health_message_label(message: &str) -> String { + match message { + "missing" => t!("app.home.health.message.missing"), + "mismatch" => t!("app.home.health.message.mismatch"), + "uninitialized" => t!("app.home.health.message.uninitialized"), + "unavailable" => t!("app.home.health.message.unavailable"), + _ => message.to_string(), + } +} + fn health_result_label(result: &RadrootsAppHealthCheckResult) -> String { + let status = health_status_label(result.status); match result.message.as_deref() { - Some(message) => format!("{}: {}", result.status.as_str(), message), - None => result.status.as_str().to_string(), + Some(message) => format!("{}: {}", status, health_message_label(message)), + None => status, + } +} + +fn error_label(key: &str) -> Option<String> { + let label = match key { + "error.app.init.idb" => t!("error.app.init.idb"), + "error.app.init.datastore" => t!("error.app.init.datastore"), + "error.app.init.keystore" => t!("error.app.init.keystore"), + "error.app.init.config" => t!("error.app.init.config"), + "error.app.init.assets" => t!("error.app.init.assets"), + "error.app.state.missing" => t!("error.app.state.missing"), + "error.app.state.corrupt" => t!("error.app.state.corrupt"), + "error.app.state.checksum_invalid" => t!("error.app.state.checksum_invalid"), + "error.app.state.schema_unsupported" => t!("error.app.state.schema_unsupported"), + "error.app.state.already_exists" => t!("error.app.state.already_exists"), + "error.client.notifications.unavailable" => t!("error.client.notifications.unavailable"), + "error.client.notifications.read_failure" => t!("error.client.notifications.read_failure"), + _ => return None, + }; + Some(label) +} + +fn reset_status_label(value: &str) -> String { + match value { + "reset_idle" => t!("app.home.reset.status.idle"), + "resetting" => t!("app.home.reset.status.resetting"), + "reset_missing_backends" => t!("app.home.reset.status.missing_backends"), + "reset_done" => t!("app.home.reset.status.done"), + _ => error_label(value).unwrap_or_else(|| value.to_string()), + } +} + +fn notifications_status_label(value: &str) -> String { + match value { + "granted" => t!("app.home.notifications.status.granted"), + "denied" => t!("app.home.notifications.status.denied"), + "default" => t!("app.home.notifications.status.default"), + "unavailable" => t!("app.home.notifications.status.unavailable"), + _ => error_label(value).unwrap_or_else(|| value.to_string()), } } @@ -105,7 +177,7 @@ enum RadrootsAppSetupFarmerChoice { fn active_key_label(value: Option<String>) -> String { let Some(value) = value else { - return "missing".to_string(); + return t!("app.common.missing"); }; if value.len() <= 12 { return value; @@ -1145,7 +1217,9 @@ fn HomePage() -> impl IntoView { let reset_label = move || { reset_status .get() - .unwrap_or_else(|| "reset_idle".to_string()) + .as_deref() + .map(reset_status_label) + .unwrap_or_else(|| t!("app.home.reset.status.idle")) }; let health_disabled = move || { backends.with(|value| value.is_none()) @@ -1158,21 +1232,23 @@ fn HomePage() -> impl IntoView { let notifications_label = move || { notifications_status .get() - .unwrap_or_else(|| "unknown".to_string()) + .as_deref() + .map(notifications_status_label) + .unwrap_or_else(|| t!("app.common.unknown")) }; let notifications_button_label = move || { if notifications_requesting.get() { - "requesting" + t!("app.home.notifications.button.requesting") } else { - "request" + t!("app.home.notifications.button.request") } }; view! { <main id="app-home" class="app-page app-page-scroll"> <header id="app-home-header"> - <h1 id="app-home-title">"app"</h1> + <h1 id="app-home-title">{t!("app.home.title")}</h1> </header> - <section id="app-home-status" aria-label="Status"> + <section id="app-home-status" aria-label=t!("app.home.status.aria")> <div id="app-home-status-row" style="margin-top: 8px; display: flex; align-items: center; gap: 8px;"> <span style=move || format!( @@ -1180,10 +1256,10 @@ fn HomePage() -> impl IntoView { status_color() ) ></span> - <span>{move || init_state.get().stage.as_str()}</span> + <span>{move || init_stage_label(init_state.get().stage)}</span> </div> </section> - <section id="app-home-reset" aria-label="Reset"> + <section id="app-home-reset" aria-label=t!("app.home.reset.aria")> <div id="app-home-reset-row" style="margin-top: 12px; display: flex; align-items: center; gap: 8px;"> <button on:click=move |_| { @@ -1244,14 +1320,14 @@ fn HomePage() -> impl IntoView { } disabled=reset_disabled > - "reset" + {t!("app.home.reset.button")} </button> <span>{reset_label}</span> </div> </section> - <section id="app-home-notifications" aria-label="Notifications" style="margin-top: 16px;"> + <section id="app-home-notifications" aria-label=t!("app.home.notifications.aria") style="margin-top: 16px;"> <header id="app-home-notifications-header"> - <h2 id="app-home-notifications-title" style="font-weight: 600;">"notifications"</h2> + <h2 id="app-home-notifications-title" style="font-weight: 600;">{t!("app.home.notifications.title")}</h2> </header> <div id="app-home-notifications-actions" style="margin-top: 8px; display: flex; align-items: center; gap: 8px;"> <button @@ -1307,9 +1383,9 @@ fn HomePage() -> impl IntoView { <span>{notifications_label}</span> </div> </section> - <section id="app-home-health" aria-label="Health checks" style="margin-top: 16px;"> + <section id="app-home-health" aria-label=t!("app.home.health.aria") style="margin-top: 16px;"> <header id="app-home-health-header"> - <h2 id="app-home-health-title" style="font-weight: 600;">"health checks"</h2> + <h2 id="app-home-health-title" style="font-weight: 600;">{t!("app.home.health.title")}</h2> </header> <div id="app-home-health-actions" style="margin-top: 8px; display: flex; align-items: center; gap: 8px;"> <button @@ -1332,7 +1408,13 @@ fn HomePage() -> impl IntoView { } disabled=health_disabled > - {move || if health_running.get() { "checking" } else { "run checks" }} + {move || { + if health_running.get() { + t!("app.home.health.button.checking") + } else { + t!("app.home.health.button.run") + } + }} </button> </div> <ul id="app-home-health-list" style="margin-top: 8px; display: grid; gap: 6px;"> @@ -1343,7 +1425,7 @@ fn HomePage() -> impl IntoView { health_status_color(health_report.get().key_maps.status) ) ></span> - <span>"key_maps"</span> + <span>{t!("app.home.health.item.key_maps")}</span> <span>{move || health_result_label(&health_report.get().key_maps)}</span> </li> <li id="app-home-health-bootstrap-state" style="display: flex; align-items: center; gap: 8px;"> @@ -1353,7 +1435,7 @@ fn HomePage() -> impl IntoView { health_status_color(health_report.get().bootstrap_state.status) ) ></span> - <span>"bootstrap_state"</span> + <span>{t!("app.home.health.item.bootstrap_state")}</span> <span>{move || health_result_label(&health_report.get().bootstrap_state)}</span> </li> <li id="app-home-health-active-key-state" style="display: flex; align-items: center; gap: 8px;"> @@ -1363,7 +1445,7 @@ fn HomePage() -> impl IntoView { health_status_color(health_report.get().state_active_key.status) ) ></span> - <span>"state_active_key"</span> + <span>{t!("app.home.health.item.state_active_key")}</span> <span>{move || health_result_label(&health_report.get().state_active_key)}</span> </li> <li id="app-home-health-notifications" style="display: flex; align-items: center; gap: 8px;"> @@ -1373,7 +1455,7 @@ fn HomePage() -> impl IntoView { health_status_color(health_report.get().notifications.status) ) ></span> - <span>"notifications"</span> + <span>{t!("app.home.health.item.notifications")}</span> <span>{move || health_result_label(&health_report.get().notifications)}</span> </li> <li id="app-home-health-tangle" style="display: flex; align-items: center; gap: 8px;"> @@ -1383,7 +1465,7 @@ fn HomePage() -> impl IntoView { health_status_color(health_report.get().tangle.status) ) ></span> - <span>"tangle"</span> + <span>{t!("app.home.health.item.tangle")}</span> <span>{move || health_result_label(&health_report.get().tangle)}</span> </li> <li id="app-home-health-datastore" style="display: flex; align-items: center; gap: 8px;"> @@ -1393,7 +1475,7 @@ fn HomePage() -> impl IntoView { health_status_color(health_report.get().datastore_roundtrip.status) ) ></span> - <span>"datastore_roundtrip"</span> + <span>{t!("app.home.health.item.datastore_roundtrip")}</span> <span>{move || health_result_label(&health_report.get().datastore_roundtrip)}</span> </li> <li id="app-home-health-keystore" style="display: flex; align-items: center; gap: 8px;"> @@ -1403,11 +1485,11 @@ fn HomePage() -> impl IntoView { health_status_color(health_report.get().keystore.status) ) ></span> - <span>"keystore"</span> + <span>{t!("app.home.health.item.keystore")}</span> <span>{move || health_result_label(&health_report.get().keystore)}</span> </li> <li id="app-home-health-active-key" style="display: flex; align-items: center; gap: 8px;"> - <span>"active_key"</span> + <span>{t!("app.home.health.item.active_key")}</span> <span>{move || active_key_label(active_key.get())}</span> </li> </ul> @@ -1546,17 +1628,17 @@ fn AppShell() -> impl IntoView { fallback=|| view! { <SetupPage /> } > <div id="app-shell"> - <nav id="app-nav" aria-label="Primary" style="display:flex;gap:12px;margin-bottom:12px;"> - <A href="/" exact=true>"home"</A> - <A href="/logs">"logs"</A> - <A href="/ui">"ui"</A> - <A href="/settings">"settings"</A> - <A href="/setup">"setup"</A> + <nav id="app-nav" aria-label=t!("app.nav.primary_aria") style="display:flex;gap:12px;margin-bottom:12px;"> + <A href="/" exact=true>{t!("app.nav.home")}</A> + <A href="/logs">{t!("app.nav.logs")}</A> + <A href="/ui">{t!("app.nav.ui")}</A> + <A href="/settings">{t!("app.nav.settings")}</A> + <A href="/setup">{t!("app.nav.setup")}</A> </nav> <Routes fallback=|| view! { <main id="app-not-found" class="app-page app-page-fixed"> - <p id="app-not-found-label">"not_found"</p> + <p id="app-not-found-label">{t!("app.not_found")}</p> </main> } >