Coverage was over-reported twice, both times by measuring the wrong thing: once by scanning a plugin root instead of the served bundle set, once with a non-global regex that returned one hit per file instead of a character count. Both produced a confident "100%" while the interface was still Chinese, so the measurement is now part of the repo instead of being done by eye. - scripts/served.mjs resolves the boot HTML's combo URLs to the on-disk files the browser receives, searching lib/, client/, dist/ and the package root. - scripts/measure.mts counts Chinese on the runtime output, decoding \uXXXX and separating comment text from text that renders. - README documents the merge order, the comment dictionary, and both traps.
127 lines
5.2 KiB
Markdown
127 lines
5.2 KiB
Markdown
# chinese2english
|
|
|
|
> Renders Chinese UI text in [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) third-party plugins as English.
|
|
|
|
Most community plugins ship hardcoded Chinese, and the interface language setting
|
|
does not reach it. This plugin rewrites that text server-side, before any of it
|
|
reaches the browser.
|
|
|
|
---
|
|
|
|
## How it works
|
|
|
|
The text arrives by two routes, so there are two seams.
|
|
|
|
**Plugin bundles.** `clientModules` serves every plugin bundle over
|
|
`/plugins/...`. Wrapping `clientModules.bundleResource` decodes each JavaScript
|
|
response, rewrites the Chinese string literals it can translate, and returns the
|
|
result. One interception covers every plugin, including combination bundles that
|
|
carry dozens, and it runs before any plugin code is evaluated.
|
|
|
|
**API copy.** Plugin descriptors, model and provider names, and settings
|
|
descriptions never appear in bundles at all: they reach the browser as JSON over
|
|
`/api`. The gateway resolves its own `dispatchRpc` per request, so overriding
|
|
that instance method intercepts those responses without claiming the interceptor
|
|
slot the gateway already owns.
|
|
|
|
Three deliberate choices:
|
|
|
|
- **All-or-nothing literals.** Chinese has no word boundaries, so replacing known
|
|
words wherever they appear corrupts larger words (`当前工作区` would become
|
|
`Saat IniWorkspace`). A literal is translated only when every Chinese character
|
|
in it is covered by a dictionary entry; anything else is left in Chinese, since
|
|
half-translated text reads worse than untranslated text.
|
|
- **A compile gate.** Each rewritten bundle is parsed with `new Function` before
|
|
it is served. If a rewrite ever produced invalid JavaScript, the original bytes
|
|
are served instead, so a bad dictionary entry cannot break the UI.
|
|
- **An endpoint allowlist.** Only endpoints returning server-authored copy are
|
|
rewritten. `/api/session/list`, for example, returns your own prompts and
|
|
transcripts, and is left untouched.
|
|
|
|
Where an author already wrote English, that English is used rather than the
|
|
dictionary: locale maps (`{ zh, en }`) resolve to the English sibling, and
|
|
bilingual descriptors joined by ` · ` keep their English half.
|
|
|
|
The client half does one thing: it selects the `en` locale through
|
|
`ctx.locale.setLocale`, which persists the preference. It never touches the DOM.
|
|
|
|
---
|
|
|
|
## What it does not cover
|
|
|
|
- Text rendered inside an `<img>`, canvas, or other non-text payload.
|
|
- Content fetched from a third-party server at runtime (announcements, remote
|
|
catalogs) rather than from DSH itself.
|
|
- Chinese inside regular expressions that match Chinese *input*. `/失败|错误|无法/`
|
|
classifies a status message and `/^[一-龥]{2}$/` validates a two-character name;
|
|
translating those would change plugin behaviour instead of removing text.
|
|
This is 111 characters across four bundles, all of it code rather than copy.
|
|
|
|
Everything the interface renders is translated. Measured through the runtime
|
|
path over the bundles a live instance served: Chinese in string literals
|
|
50,399 → 0, and Chinese anywhere in the served bytes 55,345 → 111.
|
|
|
|
---
|
|
|
|
## Installation
|
|
|
|
```bash
|
|
dsh plugin --profile web add chinese2english
|
|
```
|
|
|
|
Remove it with `dsh plugin --profile web remove chinese2english`.
|
|
|
|
Restart DSH after installing, so the plugin is loaded and its transport wrapper
|
|
is installed before the first bundle request.
|
|
|
|
---
|
|
|
|
## Development
|
|
|
|
```bash
|
|
npm run dict # rebuild the dictionaries from their sources
|
|
npm run build # bundle the server and client halves into lib/
|
|
```
|
|
|
|
Dictionary sources, merged in this order (later wins):
|
|
|
|
1. `dshmarket_dict.json` — harvested pairs.
|
|
2. `UI_TRANSLATIONS` in `patch-plugins.py` — harvested pairs.
|
|
3. `src/dictionary.extra.json` — curated entries.
|
|
4. `src/dictionary.mt.json` — machine translations of harvested literals.
|
|
5. `src/dictionary.residual.json` — literals measured as still untranslated.
|
|
6. `locale-pairs.json` — author-written `{ zh, en }` pairs lifted from plugin
|
|
locale maps; highest quality, so it overrides everything above.
|
|
|
|
Comments are a separate dictionary, `src/dictionary.comments.json`, built by
|
|
`scripts/merge-comments.mjs`. A comment is replaced only on an exact
|
|
whole-comment match, which keeps the box-drawing runs and `*` alignment that
|
|
give those notes their structure intact.
|
|
|
|
Add or fix translations in `src/dictionary.extra.json`, then run
|
|
`npm run dict && npm run build`.
|
|
|
|
### Measuring coverage
|
|
|
|
Progress was over-reported twice, both times by measuring the wrong thing, so
|
|
measurement is scripted rather than done by eye:
|
|
|
|
```bash
|
|
node scripts/served.mjs /tmp/boot.html served.json # resolve served bundles
|
|
npm run measure served.json # count Chinese left
|
|
```
|
|
|
|
Save the boot HTML from a running instance first (`/?token=…`), because the
|
|
served set is what reaches the browser. Two traps these scripts close:
|
|
|
|
- **Scan the served set, not a plugin root.** 55 of the 58 bundles live in the
|
|
dsh install tree. A scan of the profile tree alone reports nearly everything
|
|
translated while whole screens stay Chinese.
|
|
- **Decode before counting.** Bundlers emit non-ASCII as `\uXXXX`, so a bundle
|
|
can be entirely Chinese while containing no CJK character at all.
|
|
|
|
---
|
|
|
|
## License
|
|
|
|
MIT © [oiupoyt](https://git.oiupoyt.space/oiupoyt)
|