改动 / #3293

#3293 [GUI] serve: a Chinese / English language switch for the web app (adapted to 0.2.0-beta)

open · @hogtinbao · 0 评论 · 去 GitHub 看

BenchmarksSetup & installServer & APIWindowsLinux

说明

## Problem and result

The web UI in `serve/web/` is English-only. On a Chinese Windows install the settings page, the sampling drawer, the monitor table and even the server's own validation errors are unreadable to the person running the model, and there is no hook to translate them without editing `app.js` / `monitor.js` — files upstream rewrites regularly.

This PR adds a self-contained translation layer that is opt-in at runtime: one new file `serve/web/i18n-zh.js` plus one `<script>` line in each page. A `Language` control in the header switches between **English**, **简体中文** and **中英文** (Chinese with the English original in small type beneath it). The default stays English, so anyone who never touches the control sees the current UI. Model output is never rewritten — chat bubbles, thinking, code blocks and tool-call payloads are in the skip list.

## Related discussion

This replaces #1836 — the earlier revision of this change (adapted to v0.1.45), closed by the 0.2.0-beta tracker reset, not on merit. `/reopen` on that PR failed with `Validation Failed` after the branch was force-pushed to this revision, so this is a fresh pull request with the same change, rebased onto `e88ee39d` and re-verified there.

## Approach

- One new file, no build step, no edits to `app.js`, `monitor.js` or `app.css`; each page gets a single script tag. If the file is absent the pages behave exactly as today.
- A dictionary for static strings plus regex rules for text that carries numbers (`56.9 tok/s`, `3,201 tokens · 12.3 tok/s`, `Saved (…); the earlier file is ….bak`). A `MutationObserver` translates nodes the pages create later, and `title` / `placeholder` / `aria-label` / `<option>` text is translated too.
- Both languages stay in the DOM and CSS picks one, so switching back to English restores the original page (attributes and option labels included).
- Only user-visible chrome is translated; config **values** posted to `/config` remain English (only the option text is translated), so the layer cannot change what the server stores.
- No new dependency, no network access, ~40 KB, MIT, same license header as the rest of the repo.

## Validation

**Passed:**

- `python -m unittest discover -s serve -p 'test_*.py'` on this branch — Windows 11, Python 3.12, revision `78e13ae` rebased on `e88ee39d`: `Ran 881 tests in 334.4s ... FAILED (errors=1, skipped=20)`. The one error is `serve/test_winjob.py::test_children_end_with_a_killed_server` (see **Failed**).
- `python -m unittest serve.test_winjob -v` on pristine `e88ee39d` in a clean worktree — the same error, 1 of 3 tests, so it is pre-existing on the base revision, not caused by this change.
- Real server on this branch: `serve/server.py` started with the layer attached; `/health` returned `{"status": "ok", "loaded": true, "images": true, "image_generation": true, ...}`; a `/v1/chat/completions` request with `reasoning_effort: "none"` answered normally (18 tokens, `finish_reason: "stop"`) — the API path is untouched.
- `curl /` and `curl /api-monitor` return the pages with the added script tag; `curl /web/i18n-zh.js` serves the file with HTTP 200.
- `node --check serve/web/i18n-zh.js` — no syntax errors.
- Server error strings round-trip: `POST /config {"set": {"lazy_load": true}}` returns the 400 message `lazy_load: lazy loading is text-only, and this model reads images ("vision")`; that exact string, and the other seven validation messages from `serve/runconfig.py`, are matched by the layer's rules (checked by evaluating the file's own dictionary + rules in node, and by POSTing the failing values).
- Manual use on this PC since v0.1.40: chat, monitor, image page and the settings page, in all three language modes, light and dark; switching back to English restores the original UI.

**Failed:**

- `serve/test_winjob.py::test_children_end_with_a_killed_server` — `TypeError: argument of type 'NoneType' is not iterable` at `test_winjob.py:32` (the job-object query returns nothing on this machine). Reproduces on `e88ee39d` without this branch, so it is environmental.

**Not run:**

- No automated browser test. Rendering was checked by eye (Edge and Chrome on Windows, 1366×768 and 1920×1080, light and dark, three language modes). Headless `--dump-dom` hangs against the live page on this machine because it streams `/metrics`, so there is no scripted UI check in this PR.
- No Linux or macOS check of the pages. The file is plain DOM code with no platform branches, but I did not run it there.
- No check of the desktop app (`apps/host`) — it has its own strings and is untouched here.

## Limits

- Covers the web UI only, not the Tauri app.
- The translations live in one file that needs a refresh whenever upstream rewords a string; an unmatched string simply stays English (no runtime error). The file is organised by upstream version, so the diff for such a refresh is small.
- Not a general i18n framework — no locale files, no plural rules. If upstream later wants first-class i18n, this should be replaced by it rather than extended.

本站相关内容

相关页面的快捷入口。