app

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

commit 7524cfba1ee4fc18ce30379dd0f3b9bb25fbeb1b
parent b0aafce32e9fc4c1c7fde79ef5e285580da467e5
Author: triesap <tyson@radroots.org>
Date:   Mon,  2 Feb 2026 16:07:11 +0000

app: localize logs page

- add message keys for log actions and filters
- localize status and error labels
- update support instructions strings
- regenerate i18n build artifacts

Diffstat:
Mapp/i18n/build/i18n.catalog.json | 517+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mapp/i18n/build/id_map.json | 47+++++++++++++++++++++++++++++++++++++++++++++++
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 | 95+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mapp/src/logs.rs | 122+++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------------------
7 files changed, 750 insertions(+), 37 deletions(-)

diff --git a/app/i18n/build/i18n.catalog.json b/app/i18n/build/i18n.catalog.json @@ -566,6 +566,523 @@ } }, { + "key": "app.logs.action.clear", + "id": 618410449, + "args": [], + "features": { + "select": false, + "plural_cardinal": false, + "plural_ordinal": false, + "formatters": [] + } + }, + { + "key": "app.logs.action.copy_dump", + "id": 4122246909, + "args": [], + "features": { + "select": false, + "plural_cardinal": false, + "plural_ordinal": false, + "formatters": [] + } + }, + { + "key": "app.logs.action.download_dump", + "id": 4154904867, + "args": [], + "features": { + "select": false, + "plural_cardinal": false, + "plural_ordinal": false, + "formatters": [] + } + }, + { + "key": "app.logs.action.refresh", + "id": 2946508119, + "args": [], + "features": { + "select": false, + "plural_cardinal": false, + "plural_ordinal": false, + "formatters": [] + } + }, + { + "key": "app.logs.content.aria", + "id": 495027300, + "args": [], + "features": { + "select": false, + "plural_cardinal": false, + "plural_ordinal": false, + "formatters": [] + } + }, + { + "key": "app.logs.dump.title", + "id": 1388485668, + "args": [], + "features": { + "select": false, + "plural_cardinal": false, + "plural_ordinal": false, + "formatters": [] + } + }, + { + "key": "app.logs.entries.title", + "id": 684273162, + "args": [], + "features": { + "select": false, + "plural_cardinal": false, + "plural_ordinal": false, + "formatters": [] + } + }, + { + "key": "app.logs.error.anchor_cast_failed", + "id": 2533250272, + "args": [], + "features": { + "select": false, + "plural_cardinal": false, + "plural_ordinal": false, + "formatters": [] + } + }, + { + "key": "app.logs.error.anchor_failed", + "id": 3154831416, + "args": [], + "features": { + "select": false, + "plural_cardinal": false, + "plural_ordinal": false, + "formatters": [] + } + }, + { + "key": "app.logs.error.blob_failed", + "id": 2801667274, + "args": [], + "features": { + "select": false, + "plural_cardinal": false, + "plural_ordinal": false, + "formatters": [] + } + }, + { + "key": "app.logs.error.copy_failed", + "id": 3247522700, + "args": [], + "features": { + "select": false, + "plural_cardinal": false, + "plural_ordinal": false, + "formatters": [] + } + }, + { + "key": "app.logs.error.copy_unavailable", + "id": 4141297599, + "args": [], + "features": { + "select": false, + "plural_cardinal": false, + "plural_ordinal": false, + "formatters": [] + } + }, + { + "key": "app.logs.error.document_unavailable", + "id": 1080722381, + "args": [], + "features": { + "select": false, + "plural_cardinal": false, + "plural_ordinal": false, + "formatters": [] + } + }, + { + "key": "app.logs.error.download_unavailable", + "id": 4264123149, + "args": [], + "features": { + "select": false, + "plural_cardinal": false, + "plural_ordinal": false, + "formatters": [] + } + }, + { + "key": "app.logs.error.url_failed", + "id": 2872906070, + "args": [], + "features": { + "select": false, + "plural_cardinal": false, + "plural_ordinal": false, + "formatters": [] + } + }, + { + "key": "app.logs.error.window_unavailable", + "id": 2350479185, + "args": [], + "features": { + "select": false, + "plural_cardinal": false, + "plural_ordinal": false, + "formatters": [] + } + }, + { + "key": "app.logs.filters.aria", + "id": 2416988381, + "args": [], + "features": { + "select": false, + "plural_cardinal": false, + "plural_ordinal": false, + "formatters": [] + } + }, + { + "key": "app.logs.filters.from_ms", + "id": 1695556959, + "args": [], + "features": { + "select": false, + "plural_cardinal": false, + "plural_ordinal": false, + "formatters": [] + } + }, + { + "key": "app.logs.filters.search_placeholder", + "id": 2459121539, + "args": [], + "features": { + "select": false, + "plural_cardinal": false, + "plural_ordinal": false, + "formatters": [] + } + }, + { + "key": "app.logs.filters.to_ms", + "id": 3879399801, + "args": [], + "features": { + "select": false, + "plural_cardinal": false, + "plural_ordinal": false, + "formatters": [] + } + }, + { + "key": "app.logs.level.all", + "id": 3715000598, + "args": [], + "features": { + "select": false, + "plural_cardinal": false, + "plural_ordinal": false, + "formatters": [] + } + }, + { + "key": "app.logs.level.debug", + "id": 2466649524, + "args": [], + "features": { + "select": false, + "plural_cardinal": false, + "plural_ordinal": false, + "formatters": [] + } + }, + { + "key": "app.logs.level.error", + "id": 135430264, + "args": [], + "features": { + "select": false, + "plural_cardinal": false, + "plural_ordinal": false, + "formatters": [] + } + }, + { + "key": "app.logs.level.info", + "id": 289833569, + "args": [], + "features": { + "select": false, + "plural_cardinal": false, + "plural_ordinal": false, + "formatters": [] + } + }, + { + "key": "app.logs.level.warn", + "id": 3186734388, + "args": [], + "features": { + "select": false, + "plural_cardinal": false, + "plural_ordinal": false, + "formatters": [] + } + }, + { + "key": "app.logs.pagination.aria", + "id": 887319320, + "args": [], + "features": { + "select": false, + "plural_cardinal": false, + "plural_ordinal": false, + "formatters": [] + } + }, + { + "key": "app.logs.pagination.next", + "id": 2509771422, + "args": [], + "features": { + "select": false, + "plural_cardinal": false, + "plural_ordinal": false, + "formatters": [] + } + }, + { + "key": "app.logs.pagination.prev", + "id": 3713405982, + "args": [], + "features": { + "select": false, + "plural_cardinal": false, + "plural_ordinal": false, + "formatters": [] + } + }, + { + "key": "app.logs.status.copy_ok", + "id": 4173530628, + "args": [], + "features": { + "select": false, + "plural_cardinal": false, + "plural_ordinal": false, + "formatters": [] + } + }, + { + "key": "app.logs.status.download_ok", + "id": 251408416, + "args": [], + "features": { + "select": false, + "plural_cardinal": false, + "plural_ordinal": false, + "formatters": [] + } + }, + { + "key": "app.logs.status.dump_empty", + "id": 3480269256, + "args": [], + "features": { + "select": false, + "plural_cardinal": false, + "plural_ordinal": false, + "formatters": [] + } + }, + { + "key": "app.logs.status.idle", + "id": 3608732267, + "args": [], + "features": { + "select": false, + "plural_cardinal": false, + "plural_ordinal": false, + "formatters": [] + } + }, + { + "key": "app.logs.status.loading", + "id": 2750762471, + "args": [], + "features": { + "select": false, + "plural_cardinal": false, + "plural_ordinal": false, + "formatters": [] + } + }, + { + "key": "app.logs.status.support_bundle_ready", + "id": 3166859448, + "args": [], + "features": { + "select": false, + "plural_cardinal": false, + "plural_ordinal": false, + "formatters": [] + } + }, + { + "key": "app.logs.status.support_copy_ok", + "id": 1839220955, + "args": [], + "features": { + "select": false, + "plural_cardinal": false, + "plural_ordinal": false, + "formatters": [] + } + }, + { + "key": "app.logs.summary.limit", + "id": 3457741666, + "args": [], + "features": { + "select": false, + "plural_cardinal": false, + "plural_ordinal": false, + "formatters": [] + } + }, + { + "key": "app.logs.summary.of", + "id": 2155553479, + "args": [], + "features": { + "select": false, + "plural_cardinal": false, + "plural_ordinal": false, + "formatters": [] + } + }, + { + "key": "app.logs.summary.page", + "id": 1355876668, + "args": [], + "features": { + "select": false, + "plural_cardinal": false, + "plural_ordinal": false, + "formatters": [] + } + }, + { + "key": "app.logs.summary.showing", + "id": 2923985603, + "args": [], + "features": { + "select": false, + "plural_cardinal": false, + "plural_ordinal": false, + "formatters": [] + } + }, + { + "key": "app.logs.support.button.bundle", + "id": 2889964186, + "args": [], + "features": { + "select": false, + "plural_cardinal": false, + "plural_ordinal": false, + "formatters": [] + } + }, + { + "key": "app.logs.support.button.copy_instructions", + "id": 3553383813, + "args": [], + "features": { + "select": false, + "plural_cardinal": false, + "plural_ordinal": false, + "formatters": [] + } + }, + { + "key": "app.logs.support.instructions.download", + "id": 483676330, + "args": [], + "features": { + "select": false, + "plural_cardinal": false, + "plural_ordinal": false, + "formatters": [] + } + }, + { + "key": "app.logs.support.instructions.notes", + "id": 3320932212, + "args": [], + "features": { + "select": false, + "plural_cardinal": false, + "plural_ordinal": false, + "formatters": [] + } + }, + { + "key": "app.logs.support.instructions.share", + "id": 1138860050, + "args": [], + "features": { + "select": false, + "plural_cardinal": false, + "plural_ordinal": false, + "formatters": [] + } + }, + { + "key": "app.logs.support.instructions.title", + "id": 3942736448, + "args": [], + "features": { + "select": false, + "plural_cardinal": false, + "plural_ordinal": false, + "formatters": [] + } + }, + { + "key": "app.logs.support.title", + "id": 1704291719, + "args": [], + "features": { + "select": false, + "plural_cardinal": false, + "plural_ordinal": false, + "formatters": [] + } + }, + { + "key": "app.logs.title", + "id": 2765296598, + "args": [], + "features": { + "select": false, + "plural_cardinal": false, + "plural_ordinal": false, + "formatters": [] + } + }, + { "key": "app.nav.home", "id": 2360822558, "args": [], diff --git a/app/i18n/build/id_map.json b/app/i18n/build/id_map.json @@ -50,6 +50,53 @@ "app.init.stage.idle": 1446931460, "app.init.stage.ready": 4200712024, "app.init.stage.storage": 1242977634, + "app.logs.action.clear": 618410449, + "app.logs.action.copy_dump": 4122246909, + "app.logs.action.download_dump": 4154904867, + "app.logs.action.refresh": 2946508119, + "app.logs.content.aria": 495027300, + "app.logs.dump.title": 1388485668, + "app.logs.entries.title": 684273162, + "app.logs.error.anchor_cast_failed": 2533250272, + "app.logs.error.anchor_failed": 3154831416, + "app.logs.error.blob_failed": 2801667274, + "app.logs.error.copy_failed": 3247522700, + "app.logs.error.copy_unavailable": 4141297599, + "app.logs.error.document_unavailable": 1080722381, + "app.logs.error.download_unavailable": 4264123149, + "app.logs.error.url_failed": 2872906070, + "app.logs.error.window_unavailable": 2350479185, + "app.logs.filters.aria": 2416988381, + "app.logs.filters.from_ms": 1695556959, + "app.logs.filters.search_placeholder": 2459121539, + "app.logs.filters.to_ms": 3879399801, + "app.logs.level.all": 3715000598, + "app.logs.level.debug": 2466649524, + "app.logs.level.error": 135430264, + "app.logs.level.info": 289833569, + "app.logs.level.warn": 3186734388, + "app.logs.pagination.aria": 887319320, + "app.logs.pagination.next": 2509771422, + "app.logs.pagination.prev": 3713405982, + "app.logs.status.copy_ok": 4173530628, + "app.logs.status.download_ok": 251408416, + "app.logs.status.dump_empty": 3480269256, + "app.logs.status.idle": 3608732267, + "app.logs.status.loading": 2750762471, + "app.logs.status.support_bundle_ready": 3166859448, + "app.logs.status.support_copy_ok": 1839220955, + "app.logs.summary.limit": 3457741666, + "app.logs.summary.of": 2155553479, + "app.logs.summary.page": 1355876668, + "app.logs.summary.showing": 2923985603, + "app.logs.support.button.bundle": 2889964186, + "app.logs.support.button.copy_instructions": 3553383813, + "app.logs.support.instructions.download": 483676330, + "app.logs.support.instructions.notes": 3320932212, + "app.logs.support.instructions.share": 1138860050, + "app.logs.support.instructions.title": 3942736448, + "app.logs.support.title": 1704291719, + "app.logs.title": 2765296598, "app.nav.home": 2360822558, "app.nav.logs": 952998791, "app.nav.primary_aria": 3495744422, diff --git a/app/i18n/build/id_map_hash b/app/i18n/build/id_map_hash @@ -1 +1 @@ -sha256:4026e886da6d0000d936662b86f6674c3afe1a103ed9277e57de9195166abf48 +sha256:1402a9727c23f2b6b70fd54bcb04138b40bfe0f8a9b464bcce626d9f0b48517d 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:4026e886da6d0000d936662b86f6674c3afe1a103ed9277e57de9195166abf48","mf2_packs":{"en":{"kind":"base","url":"packs/en.mf2pack","hash":"sha256:2c500463fe1deac6b200df23c67892adaf68760182c370ff45d59f387758c36d","size":7347,"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:1402a9727c23f2b6b70fd54bcb04138b40bfe0f8a9b464bcce626d9f0b48517d","mf2_packs":{"en":{"kind":"base","url":"packs/en.mf2pack","hash":"sha256:95aaf5fadb5d1f0f26d4b59d43a96858bd7bceb60894b5f9e18e7b5f57632c95","size":9683,"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 @@ -201,6 +201,101 @@ app.setup.eula.acceptance.title = Acceptance of Terms app.setup.eula.acceptance.body = By using our application, you signify your acceptance of this EULA. If you do not agree to this EULA, you may not use our application. +# logs +app.logs.title = logs + +app.logs.action.refresh = refresh + +app.logs.action.clear = clear + +app.logs.action.copy_dump = copy dump + +app.logs.action.download_dump = download dump + +app.logs.status.idle = idle + +app.logs.status.loading = loading + +app.logs.status.dump_empty = dump empty + +app.logs.status.copy_ok = copied + +app.logs.status.download_ok = download ready + +app.logs.status.support_copy_ok = instructions copied + +app.logs.status.support_bundle_ready = support bundle ready + +app.logs.error.copy_unavailable = copy unavailable + +app.logs.error.copy_failed = copy failed + +app.logs.error.window_unavailable = window unavailable + +app.logs.error.download_unavailable = download unavailable + +app.logs.error.document_unavailable = document unavailable + +app.logs.error.blob_failed = blob creation failed + +app.logs.error.url_failed = url creation failed + +app.logs.error.anchor_failed = anchor creation failed + +app.logs.error.anchor_cast_failed = anchor cast failed + +app.logs.filters.aria = filters + +app.logs.filters.search_placeholder = search code/message/context + +app.logs.filters.from_ms = from ms + +app.logs.filters.to_ms = to ms + +app.logs.level.all = all + +app.logs.level.debug = debug + +app.logs.level.info = info + +app.logs.level.warn = warn + +app.logs.level.error = error + +app.logs.summary.showing = showing + +app.logs.summary.of = of + +app.logs.summary.limit = limit + +app.logs.summary.page = page + +app.logs.pagination.aria = pagination + +app.logs.pagination.prev = prev + +app.logs.pagination.next = next + +app.logs.support.button.bundle = support bundle + +app.logs.support.button.copy_instructions = copy instructions + +app.logs.content.aria = log content + +app.logs.entries.title = entries + +app.logs.dump.title = dump (jsonl) + +app.logs.support.title = support instructions + +app.logs.support.instructions.title = support bundle + +app.logs.support.instructions.download = 1) download the log dump jsonl file + +app.logs.support.instructions.share = 2) share the file with support + +app.logs.support.instructions.notes = 3) include notes about the issue and time window + # errors error.app.init.idb = storage unavailable diff --git a/app/src/logs.rs b/app/src/logs.rs @@ -27,6 +27,7 @@ use crate::{ RadrootsAppLogEntry, RadrootsAppLogLevel, }; +use crate::t; #[cfg(target_arch = "wasm32")] use js_sys::Array; @@ -60,6 +61,28 @@ fn log_level_color(level: RadrootsAppLogLevel) -> &'static str { } } +fn log_status_label(value: &str) -> String { + match value { + "idle" => t!("app.logs.status.idle"), + "loading" => t!("app.logs.status.loading"), + "dump_empty" => t!("app.logs.status.dump_empty"), + "copy_ok" => t!("app.logs.status.copy_ok"), + "download_ok" => t!("app.logs.status.download_ok"), + "support_copy_ok" => t!("app.logs.status.support_copy_ok"), + "support_bundle_ready" => t!("app.logs.status.support_bundle_ready"), + "copy_unavailable" => t!("app.logs.error.copy_unavailable"), + "copy_failed" => t!("app.logs.error.copy_failed"), + "window_unavailable" => t!("app.logs.error.window_unavailable"), + "download_unavailable" => t!("app.logs.error.download_unavailable"), + "document_unavailable" => t!("app.logs.error.document_unavailable"), + "blob_failed" => t!("app.logs.error.blob_failed"), + "url_failed" => t!("app.logs.error.url_failed"), + "anchor_failed" => t!("app.logs.error.anchor_failed"), + "anchor_cast_failed" => t!("app.logs.error.anchor_cast_failed"), + _ => value.to_string(), + } +} + fn log_level_matches(level: RadrootsAppLogLevel, filter: &str) -> bool { if filter.is_empty() || filter == "all" { return true; @@ -211,10 +234,10 @@ fn log_dump_with_context(entries: &[RadrootsAppLogEntry], header: String) -> Str fn support_instructions_text() -> String { let lines = [ - "support bundle", - "1) download the log dump jsonl file", - "2) share the file with support", - "3) include notes about the issue and time window", + t!("app.logs.support.instructions.title"), + t!("app.logs.support.instructions.download"), + t!("app.logs.support.instructions.share"), + t!("app.logs.support.instructions.notes"), ]; lines.join("\n") } @@ -558,12 +581,28 @@ pub fn RadrootsAppLogsPage() -> impl IntoView { }); on_cleanup(move || abort_handle_cleanup.abort()); }); - let status_label = move || if loading.get() { "loading" } else { "idle" }; - let dump_action_label = - move || dump_status.get().unwrap_or_else(|| "idle".to_string()); + let status_label = move || { + if loading.get() { + t!("app.logs.status.loading") + } else { + t!("app.logs.status.idle") + } + }; + let dump_action_label = move || { + dump_status + .get() + .as_deref() + .map(log_status_label) + .unwrap_or_else(|| t!("app.logs.status.idle")) + }; let dump_action_disabled = move || dump_action_running.get(); - let support_label = - move || support_status.get().unwrap_or_else(|| "idle".to_string()); + let support_label = move || { + support_status + .get() + .as_deref() + .map(log_status_label) + .unwrap_or_else(|| t!("app.logs.status.idle")) + }; let support_disabled = move || support_running.get(); let prev_disabled = move || page_index.get() == 0; let next_disabled = move || { @@ -573,18 +612,18 @@ pub fn RadrootsAppLogsPage() -> impl IntoView { view! { <main id="app-logs" class="app-page app-page-scroll"> <header id="app-logs-header" style="display:flex;align-items:center;gap:12px;"> - <h1 id="app-logs-title" style="font-size:18px;font-weight:600;">"logs"</h1> - <button on:click=move |_| refresh()>"refresh"</button> - <button on:click=move |_| clear()>"clear"</button> - <button on:click=move |_| copy_dump() disabled=dump_action_disabled>"copy dump"</button> - <button on:click=move |_| download_dump() disabled=dump_action_disabled>"download dump"</button> + <h1 id="app-logs-title" style="font-size:18px;font-weight:600;">{t!("app.logs.title")}</h1> + <button on:click=move |_| refresh()>{t!("app.logs.action.refresh")}</button> + <button on:click=move |_| clear()>{t!("app.logs.action.clear")}</button> + <button on:click=move |_| copy_dump() disabled=dump_action_disabled>{t!("app.logs.action.copy_dump")}</button> + <button on:click=move |_| download_dump() disabled=dump_action_disabled>{t!("app.logs.action.download_dump")}</button> <div id="app-logs-status" style="font-size:12px;color:#6b7280;">{status_label}</div> <div id="app-logs-dump-status" style="font-size:12px;color:#6b7280;">{dump_action_label}</div> </header> - <section id="app-logs-filters" aria-label="Filters" style="margin-top:12px;display:flex;flex-wrap:wrap;gap:12px;align-items:center;"> + <section id="app-logs-filters" aria-label=t!("app.logs.filters.aria") style="margin-top:12px;display:flex;flex-wrap:wrap;gap:12px;align-items:center;"> <input type="text" - placeholder="search code/message/context" + placeholder=t!("app.logs.filters.search_placeholder") prop:value=move || filter_query.get() on:input=move |ev| { filter_query.set(event_target_value(&ev)); @@ -598,15 +637,15 @@ pub fn RadrootsAppLogsPage() -> impl IntoView { } style="border:1px solid #e5e7eb;border-radius:8px;padding:6px 8px;font-size:12px;" > - <option value="all">"all"</option> - <option value="debug">"debug"</option> - <option value="info">"info"</option> - <option value="warn">"warn"</option> - <option value="error">"error"</option> + <option value="all">{t!("app.logs.level.all")}</option> + <option value="debug">{t!("app.logs.level.debug")}</option> + <option value="info">{t!("app.logs.level.info")}</option> + <option value="warn">{t!("app.logs.level.warn")}</option> + <option value="error">{t!("app.logs.level.error")}</option> </select> <input type="number" - placeholder="from ms" + placeholder=t!("app.logs.filters.from_ms") prop:value=move || filter_from.get() on:input=move |ev| { filter_from.set(event_target_value(&ev)); @@ -615,7 +654,7 @@ pub fn RadrootsAppLogsPage() -> impl IntoView { /> <input type="number" - placeholder="to ms" + placeholder=t!("app.logs.filters.to_ms") prop:value=move || filter_to.get() on:input=move |ev| { filter_to.set(event_target_value(&ev)); @@ -644,11 +683,22 @@ pub fn RadrootsAppLogsPage() -> impl IntoView { let limit = filter_limit.get(); let pages = page_total.get(); let page = if pages == 0 { 0 } else { page_index.get() + 1 }; - format!("showing {visible} of {total} (limit {limit}) page {page}/{pages}") + format!( + "{} {} {} {} ({} {}) {} {}/{}", + t!("app.logs.summary.showing"), + visible, + t!("app.logs.summary.of"), + total, + t!("app.logs.summary.limit"), + limit, + t!("app.logs.summary.page"), + page, + pages + ) }} </div> </section> - <section id="app-logs-pagination" aria-label="Pagination" style="margin-top:8px;display:flex;align-items:center;gap:8px;"> + <section id="app-logs-pagination" aria-label=t!("app.logs.pagination.aria") style="margin-top:8px;display:flex;align-items:center;gap:8px;"> <button on:click=move |_| { let next = page_index.get().saturating_sub(1); @@ -656,7 +706,7 @@ pub fn RadrootsAppLogsPage() -> impl IntoView { } disabled=prev_disabled > - "prev" + {t!("app.logs.pagination.prev")} </button> <button on:click=move |_| { @@ -665,19 +715,19 @@ pub fn RadrootsAppLogsPage() -> impl IntoView { } disabled=next_disabled > - "next" + {t!("app.logs.pagination.next")} </button> <button on:click=move |_| support_bundle() disabled=support_disabled> - "support bundle" + {t!("app.logs.support.button.bundle")} </button> <button on:click=move |_| copy_support() disabled=support_disabled> - "copy instructions" + {t!("app.logs.support.button.copy_instructions")} </button> <div id="app-logs-support-status" style="font-size:12px;color:#6b7280;">{support_label}</div> </section> - <section id="app-logs-content" aria-label="Log content" style="margin-top:12px;display:flex;flex-wrap:wrap;gap:16px;"> + <section id="app-logs-content" aria-label=t!("app.logs.content.aria") style="margin-top:12px;display:flex;flex-wrap:wrap;gap:16px;"> <section id="app-logs-entries" style="flex:1 1 520px;min-width:280px;"> - <h2 id="app-logs-entries-title" style="font-weight:600;font-size:14px;">"entries"</h2> + <h2 id="app-logs-entries-title" style="font-weight:600;font-size:14px;">{t!("app.logs.entries.title")}</h2> <div id="app-logs-entries-list" style="margin-top:8px;border:1px solid #e5e7eb;border-radius:8px;height:60vh;overflow:auto;padding:10px;display:flex;flex-direction:column;gap:10px;"> <For each=move || paged_entries.get() @@ -723,13 +773,13 @@ pub fn RadrootsAppLogsPage() -> impl IntoView { </div> </section> <section id="app-logs-dump" style="flex:1 1 320px;min-width:260px;"> - <h2 id="app-logs-dump-title" style="font-weight:600;font-size:14px;">"dump (jsonl)"</h2> + <h2 id="app-logs-dump-title" style="font-weight:600;font-size:14px;">{t!("app.logs.dump.title")}</h2> <textarea readonly prop:value=move || dump_text.get() style="margin-top:8px;width:100%;height:60vh;border:1px solid #e5e7eb;border-radius:8px;padding:8px;font-size:12px;font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,\"Liberation Mono\",\"Courier New\",monospace;" ></textarea> - <h3 id="app-logs-support-title" style="margin-top:12px;font-weight:600;font-size:14px;">"support instructions"</h3> + <h3 id="app-logs-support-title" style="margin-top:12px;font-weight:600;font-size:14px;">{t!("app.logs.support.title")}</h3> <textarea readonly prop:value=move || support_instructions_text() @@ -761,7 +811,8 @@ mod tests { logs_max_visible, logs_page_size_default, }; - use crate::{RadrootsAppLogEntry, RadrootsAppLogLevel, RadrootsAppLogMetadata}; + use crate::{app_i18n_init, RadrootsAppLogEntry, RadrootsAppLogLevel, RadrootsAppLogMetadata}; + use leptos::prelude::{provide_context, Owner}; #[test] fn logs_auto_refresh_is_positive() { @@ -890,6 +941,9 @@ mod tests { #[test] fn support_instructions_are_present() { + let owner = Owner::new(); + owner.set(); + provide_context(app_i18n_init()); let text = support_instructions_text(); assert!(text.contains("support bundle")); }