From 7b8a65173091c1182ffcacf3017f3855772cd3fa Mon Sep 17 00:00:00 2001 From: iamdoubz <> Date: Sun, 12 Jul 2026 10:57:52 -0500 Subject: [PATCH] =?UTF-8?q?docs(i18n):=20clarify=20chrome-only=20scope=20?= =?UTF-8?q?=E2=80=94=20never=20translate=20user=20content?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/i18n-tracking.md | 30 ++++++++++++++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/docs/i18n-tracking.md b/docs/i18n-tracking.md index c11a4d7..768a53f 100644 --- a/docs/i18n-tracking.md +++ b/docs/i18n-tracking.md @@ -11,6 +11,25 @@ files, one batch at a time. Tick boxes as views are converted. - **Scope decision:** display language is separate from the transcription `whisper_language` setting — don't conflate them. +## What we translate (and what we never do) + +**Translate: UI chrome only** — fixed labels, buttons, headings, placeholders, +empty/loading/status states, tooltips, and app-generated default *labels*. + +**Never translate: user-authored content.** Meeting titles, tag names, notes, transcript +text, search snippets, dates — these are bound straight from data (`item.title`, tag +values, `s.text`, …) and stay exactly as the user wrote them. If a string comes from the +user or the recording, it does not get a key. + +So "convert view X" always means "key its fixed labels", never "touch its content". Most +views are mostly content with a thin shell of labels — MeetingsList, for example, is ~10 +fixed labels (search placeholder, empty states, filter/bulk-export labels) wrapped around +a list whose rows are pure user data. + +> Caveat — the `"Untitled meeting"` **default title is generated in the Rust backend**, not +> the frontend, so it never reaches `t()`. Localizing app-generated defaults is a separate +> backend decision, out of scope for this frontend effort. + ## How to convert a string (the pattern) 1. Add a key to `en.json`. Naming: `..`, dotted, grouped by view — @@ -49,8 +68,11 @@ its visible text **and** its `title`/`aria-label`/`placeholder` attributes are k dialog prompts, empty states. Prefix `app.*`. ### Batch B — meetings list (small, high traffic) -- [ ] `src/lib/views/MeetingsList.svelte` (~373 lines, ~9 attrs) — list headers, empty - state, search/filter placeholders, date/tag labels. Prefix `meetings.*`. +- [ ] `src/lib/views/MeetingsList.svelte` (~373 lines) — **labels only** (~10 strings): + search placeholder, `"All tags"`, empty/loading states (`"No meetings yet…"`, + `"No matches."`, `"Loading meetings…"`), status badges (`"recovering"`, `"error"`), + bulk-export button, delete-confirm text, and aria-labels. The list rows themselves + (titles, tags, dates, snippets) are user data — leave them. Prefix `meetings.*`. ### Batch C — transcript & notes (large surface) - [ ] `src/lib/views/TranscriptNotes.svelte` (~869 lines, ~40 attrs) — export button @@ -84,6 +106,10 @@ The file is big — do it section by section, one sub-batch each: ## Not translated (intentional) +- **User-authored content** — meeting titles, tag names, notes, transcript text, search + snippets. Bound from data; stays as the user wrote it (see "What we translate" above). +- App-generated defaults created in the backend (e.g. `"Untitled meeting"`) — a separate + backend concern; the frontend `t()` never sees them. - Backend / Rust error messages surfaced via `WaError` — out of scope for the frontend `t()`; revisit only if we localize command errors. - Provider/proper names (Ollama, Anthropic, OpenAI, Obsidian, WebDAV, WhispAssist),