Skip to content

🌐 feat: Add Simplified Chinese (zh-Hans) localization + language setting - #148

Open
SwakinX wants to merge 15 commits into
LibreChat-AI:mainfrom
SwakinX:feat/zh-hans-localization
Open

SwakinX wants to merge 15 commits into
LibreChat-AI:mainfrom
SwakinX:feat/zh-hans-localization

Conversation

@SwakinX

@SwakinX SwakinX commented Sep 24, 2026

Copy link
Copy Markdown

Summary

  • Add a complete Simplified Chinese (zh-Hans) locale covering the full English key set,
    with terminology aligned to the LibreChat main zh-Hans translation (shared com_*
    strings reuse the official wording so the panel and chat UI read consistently)
  • Add a language setting to the settings dialog — a Select dropdown (English /
    中文(简体)) below the theme row, persisting the choice via the i18nextLng
    cache key and keeping document.documentElement.lang in sync
  • Localize two dropdowns that rendered raw enum values (create-user role select
    showed USER/ADMIN; audit-log target-type filter showed raw principal types)

Locale detection

Detection is adapted from the LibreChat client implementation
(client/src/locales/i18n.ts): explicit stored choice wins, then navigator language,
normalized through an alias table (zh, zh-CN, zh-SG → zh-Hans). This replaces
i18next-browser-languagedetector, whose nonExplicitSupportedLngs handling strips
zh-Hans to zh before the whitelist check — meaning a registered zh-Hans locale
could never actually be resolved.

SSR renders the pre-hydration loading shell in the default locale deterministically,
and the shell text suppresses the expected hydration mismatch (<html lang> already
had suppressHydrationWarning).

Tests

  • New src/locales/locales.test.ts guards en/zh key-set parity, interpolation
    placeholder parity, language switching (incl. zh-CN/zh resolution), and
    locale normalization
  • Full suite passes (bun run test, 831 tests)

Notes for reviewers

Three commits, intentionally split for reviewability:

  1. 🌐 feat: Add Simplified Chinese (zh-Hans) locale
  2. 🌐 feat: Add language setting to settings dialog
  3. 🐛 fix: Localize role and target type dropdown options

Happy to adjust the translation wording or split/merge commits as preferred.

- Add src/locales/zh-Hans/translation.json covering the full en key set
  (terminology aligned with the LibreChat main zh-Hans translation)
- Register zh-Hans in i18n init with LibreChat-style locale normalization
  and detection (stored choice > navigator, alias mapping zh/zh-CN/zh-SG),
  replacing the browser-languagedetector whose nonExplicitSupportedLngs
  handling rejected zh-Hans
- Render the SSR loading shell in the default locale deterministically and
  suppress the expected hydration text mismatch
- Guard en/zh key-set and placeholder parity plus language switching in
  locales.test.ts
- Add a language row with a Select dropdown (English / 中文(简体)) below
  the theme row, reusing the existing click-ui Select component
- Persist the choice via i18nextLng (the detection cache key) and keep
  document.documentElement.lang in sync
- CreateUserDialog role select rendered raw SystemRoles values
  (USER/ADMIN); use localized labels instead
- Audit log target-type filter rendered raw PrincipalType enum values;
  map them to localized labels
New /tools route lists the full Tool Gateway catalog (disabled
included) via the LibreChat proxy /api/terravox/tools/all: tool id,
version, exposure fronts, allowed groups, renderer and enable state,
with search and zh-Hans/en localization.
- Add GiteaImportDialog: owner/repo dropdowns from the anonymous repo
  listing, check report (error blocks, warn allows continuing), prefill
  into the edit dialog; manual-create secondary path
- ToolEditDialog: desktop execution now registers owner/repo
  distribution (tool.json fields as optional fallback), prefill support
- Desktop manifest assembly uses the distribution form (catalog_tool_id
  contract change); owner+repo required, sha256 format validated
- Tool create/update toasts; com_tools_gitea_* / dist field locales
Misclicks on the overlay discarded filled-in forms. All admin-panel
dialogs now ignore pointer-downs outside the content; explicit close,
cancel buttons and Escape still close.
Pending updates governance (contracts 2.8.0): repo releases ahead of the
approved manifest are hidden from users until an admin confirms them.

- getPendingUpdatesFn + pendingUpdatesQueryOptions (60s poll, aligned
  with the gateway's resolver cache TTL)
- ToolCatalogTab: amber pending section above the table; Review & confirm
  runs the existing Gitea check endpoint, prefills the edit dialog via
  buildPrefill with repo content fields while governance fields
  (tool_id/expose/allowed_groups/enabled) stay as approved — submitting
  the dialog is the existing PUT confirm
- export buildPrefill from GiteaImportDialog (shared by import + update)
- com_tools_pending_* locale keys (en/zh-Hans)
- vitest: panel render/empty, confirm merge, check failure, query
  failure, catalog loading
…ds repo omits

Confirming a pending update prefilled the dialog from tool.json@tag, so a
stale tool.json version clobbered the release-tag version and the pending
row looped forever. buildUpdateMerge now:

- sets version (top-level + distribution) from the pending target (tag);
  the tag is the distribution identity resolver/Toolhost install by
- keeps approved values for anything the repo does not provide
  (parameters/form/result/dangerous/description/display_name/sha256/
  launcher/runtime) instead of overwriting with undefined/fallbacks
- keeps governance fields (tool_id/expose/allowed_groups/enabled)

Check results are now surfaced in the pending panel: hard errors block
the dialog with a notice; warns (e.g. version_mismatch) show as a notice.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
…o error)

Gateway check_repo returns version_mismatch as a hard error when
tool.json@tag carries a version that differs from the tag, so the
confirm flow blocks with the reason instead of opening a dialog for a
version the Toolhost would refuse to install. Missing version stays a
warn (manifest fallback at install).

🤖 Generated with [Claude Code](https://claude.com/claude-code)
- server fns: paged GET + full export (200/page, 10k cap) against the
  gateway admin run-reports endpoint, zod-parsed like the rest
- /usage route, sidebar entry, en/zh-Hans locales (parity-tested)
- AuditLogTab pattern reuse: DatePickerCell extracted to shared
- CSV built client-side with UTF-8 BOM + RFC 4180 escaping
toolsQueryOptions is a static queryOptions object in the server module,
but UsagePage invoked it as a factory (toolsQueryOptions()), throwing
"is not a function" in the production bundle. The spec's @/server mock
shaped it as a function, which is why tests stayed green — the mock now
mirrors the real export shape.
- ToolEditDialog: exec kind extends to plugin/web; web gets a URL
  field, plugin gets host_launcher + host-root candidates + install/
  uninstall script fields; desktop distribution block shared
- GiteaImportDialog: import-kind selector — desktop/plugin run the
  existing check flow (plugin prefill carries host fields from
  tool.json), web skips the check and takes a required URL with the
  repo as optional name/description prefill
- locales +13 keys en/zh-Hans (parity-checked); 859 tests green
…oup visibility); tool editor gains display_group/help_url/usage_stats; dashboard quick links
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant