Commit graph

11 commits

Author SHA1 Message Date
spearzificial
23327d2bd5 feat: translate code comments, reaching zero Chinese in the served UI
The last Chinese lived in source comments: notes the plugin authors wrote
about why a module exists and which bug it fixed, plus two stylesheet
comment blocks. Comments never render, but they still shipped Chinese, and
the user asked for all of it gone.

- src/dictionary.comments.json: 274 whole comments (block and line form).
- src/rewrite.ts: rewriteComments replaces a comment only on an exact
  whole-comment match, so box-drawing runs and the `*` alignment that give
  these notes their structure cannot be reflowed by a partial replacement.
  Comment rewriting runs on the source between literals; running it over
  the assembled output would also scan literal contents, where a URL like
  `https://…` reads as a line comment.
- scripts/merge-comments.mjs emits the dictionary and enforces the
  invariants: no CJK, delimiters preserved and balanced, line count
  unchanged, box runs preserved in block comments. In a single-line `//`
  banner the run is decoration and may shift with the text.

Verified on the bytes a fresh instance actually served, escapes decoded:
total Chinese 55,345 -> 111 (99.80% removed). In the browser, both visible
text and the full page HTML report 0 CJK.

The 111 characters left are inside functional regular expressions that
match Chinese *input*: /失败|错误|无法/ classifies a status message,
dshmarket's PREVIEW_WORDS match Chinese plugin descriptions, and
/^[一-龥]{2}$/ validates a two-character name. Translating those would
break plugin behaviour, so they are deliberately left alone.
2026-10-18 22:25:19 +05:30
spearzificial
e99d4fee26 fix: translate bundles that store Chinese as \uXXXX escapes
dsh-skills-manager rendered its entire UI in Chinese while the plugin
reported success. Its bundle contains no CJK character at all: the bundler
wrote every one as a \uXXXX escape, so `hasChinese(source)` was false and
rewriteScript returned before scanning. 2,835 escaped characters - field
labels, dialogs, errors, the whole plugin - were never candidates.

- src/rewrite.ts: the early-return gate now also accepts an escaped-CJK
  form. Decoding was already correct; only the gate was wrong.
- src/dictionary.residual.json: +150 UI strings measured from the escaped
  bundle, taking the residual dictionary to 1,602 entries.

Also corrects two measurement errors in the previous report. `count()`
used a non-global regex, so match() returned one hit per file rather than
the character count, and a sum over all 81 combo URLs double-counted
shared bundles. Measurement now decodes every literal and dedupes to the
79 single-entry URLs.

Verified over the served bundle set with escapes decoded:
renderable Chinese in string literals 50,399 -> 3,006 (94.04% removed).
The 3,006 remaining characters are all inside CSS comments in two
stylesheets - zero appear outside comments, so no rendered text is
affected. 55 of 78 bundles are rewritten.
2026-10-18 21:01:34 +05:30
spearzificial
64831fe317 feat: translate 100% of served UI literals, including template literals
The remaining Chinese was one measurement bug and one structural gap.

Measurement: the harvester and every earlier gap report walked the profile
tree only. 55 of the 58 served client bundles live in the dsh install tree,
so whole sentences were never in scope and the strict coverage rule left
them in Chinese. Scanning the tree the browser actually receives showed
74.27% coverage, not the ~100% previously claimed.

Structure: rewriteScript skipped every backtick literal, and skipped any
literal containing ${. That is where user-visible text like
`剩余 ${hours} 小时 ${minutes} 分` lived, so it could never be translated.

- scripts/analyze.ts walks the resolved served-bundle set (exported to
  ~/.cache/c2e/served.json by scripts/served.mjs) instead of a plugin root.
- src/dictionary.residual.json: 1,452 entries covering the measured gap.
- src/rewrite.ts: substituteInterpolated handles template literals. Only an
  exact dictionary hit is accepted, because segmenting a template rebuilds
  the sentence from word fragments (`已退出 ${n} 个网页端会话` became
  `Exited ${n} itemsWeb pageSessions`). A translation that changes the
  number of interpolations is rejected.
- src/strings.ts: scanLiteralEnd tracks ${ depth through nested strings and
  nested template literals. Previously `` `a ${c ? `b` : ""}` `` was cut at
  the inner backtick and rewritten as a fragment.
- scripts/merge-dict.mjs: bilingual keys (`中文 / English`) resolve to their
  human-written tail instead of being machine-translated. Also merges the
  residual dictionary.
- scripts/mt-fill.mjs: guards templates by interpolation count, skips
  bilingual keys, and walks both plugin roots.

Verified over all 78 served bundles via the runtime entry point
translateScript: 54/54 bundles rewritten, residual Chinese in display
literals 9 -> 2 strings (2 CJK chars), both inside CSS comments within a
stylesheet string, so no user-visible text remains. All 5,437 Chinese-bearing
display literals now resolve.
2026-10-18 20:22:35 +05:30
spearzificial
68a7fc608d fix: translate the API copy that actually renders
Bundle translation worked, but the UI still showed Chinese. The visible text
does not come from plugin bundles at all: plugin descriptors, model and
provider names, and settings descriptions reach the browser as JSON over /api.
Measured in the Plugins view, all 76 distinct Chinese characters came from
/api/pluginManager/listBundles, which the bundle seam cannot see.

Add a second seam on the typert gateway. It resolves its own dispatchRpc per
request, so overriding that instance method intercepts every /api call without
claiming the interceptor slot the gateway already owns.

Rewriting JSON is deliberately narrow. An allowlist covers the six endpoints
that return server-authored copy; everything else, notably /api/session/list,
carries the operator's own prompts and transcripts and is left alone.

Three shapes needed handling, each found by testing against the live server:

- Locale maps ({ zh, en }) resolve to the English sibling, because the author's
  own English beats a dictionary pass.
- Bilingual descriptors joined by " · " keep their English half, which covers
  every such descriptor without a dictionary entry.
- Keys are not always trustworthy: dshmarket publishes its bilingual string
  under `en` and a short English one under `zh`, and one plugin repeats the same
  string under both. A locale map is therefore detected by shape, and an English
  sibling is only used when it is itself free of Chinese.

Also harvest author-written { zh, en } pairs from plugin locale maps (331
entries) as the highest-precedence dictionary source, and extend the curated
supplement with the vocabulary that unlocks whole families at once: "免费" alone
covers about twenty model names.

Measured on a live server: 107 -> 0 visible Chinese characters across the home,
Plugins, and Automation tasks views, 0 residual Chinese across all six
allowlisted endpoints, no page errors, and both bundle combos still compile.
2026-10-15 23:44:17 +05:30
spearzificial
e3962c1591 fix: translate plugin bundles at the transport layer
The plugin never translated anything. Its client half monkey-patched locale
internals that do not mutate the locale snapshot, and its DOM walker was
disabled after it crashed the marketplace UI. Nothing else was left.

Move translation to the bundle transport instead. Every third-party bundle is
served through /plugins/ by the clientModules service, so wrapping
bundleResource reaches all of them before any code runs, with no client-side
ordering race and no React tree mutation.

Two bugs surfaced while verifying against the live server:

- The script check tested the path before the query string, but combo requests
  are /plugins/??a/client.js,b/client.js&rev=... where the path is just
  /plugins/. Every request was rejected.
- bundleResource yields a Buffer, not a string, so the string guard rejected
  every request a second time.

Rewriting is all-or-nothing per literal: Chinese has no word boundaries, so
replacing known keys wherever they appear corrupts larger words (当前工作区
became "Saat IniWorkspace"). A literal is translated only when every Chinese
character is covered by a dictionary entry. Two upstream entries were Malay
rather than English and are now corrected.

Each rewritten bundle is parsed with new Function before being served; if that
fails the original bytes are returned, so a bad dictionary entry cannot break
the UI. Verified against the live bundles: both combos compile, and a browser
A/B shows 105 Chinese characters in the Plugins view without the plugin and 0
with it.

The dictionary grows to 1,195 entries (45% of literals, zero partial rewrites)
via a curated supplement merged last so it overrides harvested sources.
2026-10-15 22:53:20 +05:30
dd5629bc6a fix: disable intrusive DOM mutation observer to prevent React tree crashes in dshmarket 2026-10-12 16:30:35 +05:30
a2d208023a feat: implement direct plugin bundle patching with comprehensive 500+ DSH dictionary 2026-10-12 15:44:54 +05:30
53f3639814 chore: rename package to chinese2english 2026-10-11 18:33:25 +05:30
f27175d752 fix: prevent broken sentence replacement, enforce active English locale, and patch registered DSH dictionaries 2026-10-11 17:41:42 +05:30
f441d0cb12 feat: add immediately: true, TreeWalker engine, and comprehensive 500+ term DSH dictionary 2026-10-11 17:25:20 +05:30
519ffefaef feat: initial commit of dsh-plugin-english 2026-10-11 16:57:03 +05:30