feat(viewer): add Chinese locale#673
Conversation
Inventory of visible English strings in the viewer (~237 keys across nav, dashboard, memories, sessions, timeline, lessons, actions, crystals, audit, activity, profile, replay, graph, status, types, table, buttons, loading, modal). Source of truth for upcoming i18n work; no runtime behavior change yet. Signed-off-by: Christian Walter <chris.walter@mail.de>
Adds src/viewer/locales.ts with:
- resolveViewerLanguage() — reads VIEWER_LANGUAGE env, normalizes
de-DE/de_DE → de, lowercases, defaults to en
- loadLocale(lang) — reads src/viewer/locales/<lang>.json with
process-level cache, returns {} when missing (no throw)
- buildLocaleBundle(lang) — returns { lang, messages, fallback } with
en fallback for non-en languages, empty fallback for en itself
Tests cover env normalization, missing-file handling, and bundle shape.
No wiring into the rendered viewer yet — that comes in the next commit.
Signed-off-by: Christian Walter <chris.walter@mail.de>
Adds VIEWER_LOCALE_PLACEHOLDER and wires buildLocaleBundle() through renderViewerDocument(). The bundle JSON is injected as window.__AM_LOCALE__ inside the existing nonced <script> tag — no new script element, no external fetch, the CSP nonce model is unchanged. < is escaped to < in the payload so a translation containing '</script' cannot break out of the inline script. The viewer template gets a single seeding line that holds the placeholder; the runtime t() helper arrives in the next commit. Signed-off-by: Christian Walter <chris.walter@mail.de>
Adds a 30-line IIFE in the viewer's inline script:
- t(key, vars) — dot-path lookup against window.__AM_LOCALE__.messages
with en fallback per key and {placeholder} interpolation
- applyI18n(root) — replaces textContent on [data-i18n] elements and
attributes on [data-i18n-attr="attr:key,attr:key"] elements
- DOMContentLoaded hook runs applyI18n(document) once and sets the
<html lang> attribute
Markup tagging and dynamic-string conversion come in the next two
commits.
Signed-off-by: Christian Walter <chris.walter@mail.de>
Tags visible static elements with data-i18n="<namespace>.<key>" matching
the keys in src/viewer/locales/en.json. The original English text stays
in place as a no-JS fallback; the DOMContentLoaded pass added in the
previous commit overwrites textContent (and selected attributes via
data-i18n-attr) with the resolved locale string.
Dynamic strings built inside the inline <script> render functions are
not touched here — those go through t() calls in the next commit.
No visible change in the en case: t("nav.dashboard") returns
"Dashboard", identical to the literal already in markup.
Signed-off-by: Christian Walter <chris.walter@mail.de>
Converts template-literal strings inside the viewer's inline script (renderDashboard, renderMemories, renderSessions, renderTimeline, renderLessons, renderActions, renderCrystals, renderAudit, renderActivity, renderProfile, renderReplay, empty-state generators, table headers, type badges, status labels, loading placeholders) to call t(key) instead of embedding English literals. API enum values (m.type, node.type, s.status, etc.) are untouched — only their display form is mapped via t(). Filters, search, and persistence keep operating on the raw enum strings. Adds var t = window.t at module scope so that render functions can call t() as a plain identifier in VM sandbox contexts (required by the viewer-session-id unit tests). Signed-off-by: Christian Walter <chris.walter@mail.de>
Mirrors the structure of src/viewer/locales/en.json with German values for ~237 UI strings: nav, dashboard cards, gauges, first-run hero, memories empty state, sessions table, status labels (AKTIV/ ABGESCHLOSSEN/…), type and graph-node-type display names, replay detail labels, modal text, and per-tab content for timeline, lessons, actions, crystals, audit, activity, profile, replay, graph. Adds a structural-parity test that fails if a future en.json change adds a top-level key without a corresponding de.json entry. Signed-off-by: Christian Walter <chris.walter@mail.de>
Extends the build script so src/viewer/locales/*.json is copied to dist/viewer/locales/ during build. Without this, an npm-installed agentmemory would not find any locale files at runtime and would silently fall back to the empty-locale path. Signed-off-by: Christian Walter <chris.walter@mail.de>
- README gains a 'Viewer language' subsection showing the env switch and pointing contributors at CONTRIBUTING.md. - .env.example documents VIEWER_LANGUAGE next to the other UI flags. - CONTRIBUTING.md gets a 'Contributing a translation' how-to. - CHANGELOG.md records the change under Unreleased. Signed-off-by: Christian Walter <chris.walter@mail.de>
Final i18n coverage pass. Adds six new keys covering compound or
suffix strings that the per-render conversion missed:
- dashboard.edges_count — '{n} edges' suffix on Graph Nodes card
- dashboard.token_savings_sub — Token Savings compound sub-label
- timeline.no_obs_filter_suffix — empty-state filter branch
- timeline.no_obs_session_suffix — empty-state session branch
- actions.three_ways_intro — Actions empty-state intro line
- audit.empty_body — Audit empty-state second paragraph
en.json / de.json both updated; structural parity test still green.
Signed-off-by: Christian Walter <chris.walter@mail.de>
Addresses CodeRabbit review on rohitg00#541. t() now HTML-escapes the resolved translation string before interpolation. Translations arrive via community PRs; escaping at the boundary stops a malicious translation from injecting markup at the ~200 sites that concatenate t() output into innerHTML. Interpolation vars stay raw so the hardcoded HTML fragments used in memories.title_intro (e.g. <code>memory_remember</code>) still render. A new tRaw() returns the unescaped value, for the small number of callers that assign to textContent or setAttribute on safe attributes — textContent treats entities literally, so escape would otherwise show "&" as four characters in the DOM. data-i18n-attr now enforces a SAFE_I18N_ATTRS allowlist (placeholder, title, alt, aria-label/labelledby/describedby/ roledescription). Any other attribute name is silently skipped, so a contributor cannot turn the mechanism into an href:/src:/onclick: injection vector by adding a data-i18n-attr to an element. loadLocale() validates lang against /^[a-z]{2,3}$/i before touching the filesystem. resolveViewerLanguage() already normalizes inputs down to the primary subtag, so callers in this codebase are unchanged; this is a defence-in-depth guard for the exported boundary. Tests: - new: rejects path-traversal sequences (../etc/passwd, ..\\windows, en/../en) - new: rejects non-language inputs ("", "123", "en-US", single letter, 4+ letters) - new: verifies the IIFE in index.html ships escI18n() wired into t() - new: verifies SAFE_I18N_ATTRS allowlist exists and excludes href/src/on* - new: structural parity now covers every nested leaf path, not just top level - new: placeholder-marker parity between en.json and de.json Signed-off-by: Christian Walter <chris.walter@mail.de>
Addresses CodeRabbit review on rohitg00#541. Both README.md (Viewer language section + the env table at the bottom) and .env.example referenced 'Drop src/viewer/locales/<lang>.json', which is misleading for npm/global installs that ship dist/ without a src/ tree. Rewords to explicitly call out the PR-against-repo path (source checkout required) so users do not assume a runtime drop-in mechanism exists. Signed-off-by: Christian Walter <chris.walter@mail.de>
Four follow-up findings from the review on rohitg00#541. 1. SAFE_I18N_ATTRS no longer includes IDREF ARIA attributes. aria-labelledby and aria-describedby must reference element IDs, not free text — the first caller using data-i18n-attr on either would have silently broken the accessible name/description wiring. Removed both; kept aria-label and aria-roledescription. 2. Aliased tRaw beside t for the VM sandbox. The viewer unit tests run render functions inside a VM where window !== globalThis. There was already a 'var t = window.t;' module-scope alias for the same reason; the new tRaw() calls added in the previous commit would have thrown ReferenceError in that environment. Added 'var tRaw = window.tRaw;'. 3. loadLocale() now normalizes lang before validate/cache/file. VALID_LANG was case-insensitive, so loadLocale("EN") passed validation but then tried to read EN.json (which does not exist) and cached the failure under the uppercase key. Now lowercases and trims before everything, so loadLocale("EN"), " en ", and "En" all resolve to the same en.json bundle and the same cache slot. VALID_LANG simplified to /^[a-z]{2,3}$/ (no /i). 4. Placeholder-parity test no longer hides empty-string regressions. Previously 'if (!deVal) continue;' skipped both missing keys and intentional empty-string translations. Switched to a typeof check so empty strings are still validated for placeholder parity. Signed-off-by: Christian Walter <chris.walter@mail.de>
Signed-off-by: rayshark <13261091606@163.com>
Signed-off-by: rayshark <13261091606@163.com>
|
@RayShark is attempting to deploy a commit to the rohitg00's projects Team on Vercel. A member of the Team first needs to authorize it. |
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (2)
🚧 Files skipped from review as they are similar to previous changes (2)
📝 WalkthroughWalkthroughThis PR implements comprehensive internationalization for the viewer UI, adding language resolution, locale file discovery, client-side translation runtime, and localized strings across all dashboard pages (dashboard, graph, memories, timeline, sessions, lessons, actions, crystals, audit, profile, replay), plus German and Chinese translations with structural parity testing. ChangesViewer i18n System
Sequence DiagramsequenceDiagram
participant Server as Server
participant ViewerDoc as renderViewerDocument()
participant LocaleModule as Locale Module
participant HTML as Viewer HTML
participant Runtime as Client-side Runtime
participant Browser as Browser DOM
Server->>ViewerDoc: Render viewer document
ViewerDoc->>LocaleModule: resolveViewerLanguage()
LocaleModule-->>ViewerDoc: Resolved language (env or "en")
ViewerDoc->>LocaleModule: buildLocaleBundle(lang)
LocaleModule-->>ViewerDoc: {lang, messages, fallback}
ViewerDoc->>ViewerDoc: Serialize JSON, escape <
ViewerDoc->>HTML: Replace __AGENTMEMORY_LOCALE__ placeholder
HTML->>Browser: Load HTML + embedded locale
Runtime->>Runtime: Initialize window.__AM_LOCALE__
Runtime->>Runtime: Define window.t(), tRaw(), applyI18n()
Runtime->>Browser: Apply data-i18n bindings to DOM
Browser-->>Runtime: Translated UI rendered
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Warning There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure. 🔧 ESLint
ESLint skipped: no ESLint configuration detected in root package.json. To enable, add Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@CHANGELOG.md`:
- Line 11: The CHANGELOG entry "Viewer i18n: `VIEWER_LANGUAGE` env switches
viewer UI..." was added in a feature PR but per the repo contribution policy
changelog edits must land in release PRs; remove this line from the current PR
(revert the CHANGELOG.md addition) and instead add the entry to the upcoming
release PR's changelog section (or update the contribution policy if you intend
to change practice), ensuring the same text is used when moved.
In `@src/viewer/locales.ts`:
- Around line 99-103: buildLocaleBundle currently returns the normalized tag
from normalizeLanguageTag(lang) even when it doesn't match VALID_LANG; change it
to validate the normalized value against VALID_LANG and hard-fallback the
returned bundle.lang to FALLBACK_LANG (e.g., "en") when validation fails.
Specifically, in buildLocaleBundle, call normalizeLanguageTag(lang) ->
normalized, then if (!VALID_LANG.test(normalized)) set normalized =
FALLBACK_LANG before calling loadLocale and constructing the returned
LocaleBundle ({ lang: normalized, messages, fallback }), keeping loadLocale
fallback behavior unchanged.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 204aff83-65b5-4e6c-92d0-a4b8903e114f
📒 Files selected for processing (14)
.env.exampleCHANGELOG.mdCONTRIBUTING.mdREADME.mdpackage.jsonsrc/auth.tssrc/viewer/document.tssrc/viewer/index.htmlsrc/viewer/locales.tssrc/viewer/locales/de.jsonsrc/viewer/locales/en.jsonsrc/viewer/locales/zh.jsontest/viewer-i18n.test.tstest/viewer-security.test.ts
Signed-off-by: rayshark <13261091606@163.com>
|
Addressed the current review items:
Verification:
|
Summary
zh.jsonfor theVIEWER_LANGUAGEi18n framework from feat(viewer): add VIEWER_LANGUAGE env + EN/DE locales #541en,de, andzhas built-in viewer locales and extend locale parity tests to all bundled non-English localeszh-CNtozh, localize runtime WebSocket status text, and add locale helper docstrings for the docstring-coverage checkRelated
mainso this stack is mergeable against the upstream branchNote: the Chinese locale file is
zh.json, notcn.json, because #541 normalizes regional tags likezh-CN/zh_CNto the primary language subtag used for locale filenames.Testing
npm run buildenv HOME=/tmp/agentmemory-i18n-test-home npm testgit diff --check origin/main...HEADSummary by CodeRabbit
New Features
VIEWER_LANGUAGEenvironment variable.Documentation