dsh-cait 1.0.3 → 1.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,423 +1,424 @@
1
- <p align="center">
2
- <img src="icon.svg" width="76" alt="CAIT icon" />
3
- </p>
4
-
5
- <h1 align="center">CAIT</h1>
6
-
7
- <p align="center">
8
- <strong>Computer-aided translation, inside DeepSeek Harness.</strong><br/>
9
- Translation memory · glossary · segment editor · MT assistance · document tabs · Office / subtitle / localization formats
10
- </p>
11
- <p align="center">
12
- <a href="README.zh-CN.md">简体中文</a> · <a href="README.ja.md">日本語</a> · <a href="README.md">English</a>
13
- </p>
14
-
15
-
16
- <p align="center">
17
- <img alt="Version 1.0.3" src="https://img.shields.io/badge/version-1.0.3-blue.svg" />
18
- <img alt="DSH 0.2.x" src="https://img.shields.io/badge/DSH-0.2.x-4d6bfe.svg" />
19
- <img alt="Tests: 13 suites" src="https://img.shields.io/badge/tests-13%20suites-brightgreen.svg" />
20
- <img alt="No API key required" src="https://img.shields.io/badge/API%20key-not%20required-6b7280.svg" />
21
- </p>
22
-
23
- CAIT adds a CAT workbench to the DSH Web UI and Desktop. It opens from a
24
- left-sidebar entry, keeps one shared store behind the workbench and seven agent
25
- tools, and covers the practical path from importing a source file to exporting a
26
- translated document.
27
-
28
- > **Tested with DSH `0.2.2-rc.1`.** CAIT uses DSH's own model routes and
29
- > credentials; it never asks for an API key. The internal fallback provider is
30
- > still called `mock`, but the UI labels it **Glossary substitution (offline)**.
31
-
32
- ## Why CAIT
33
-
34
- | | |
35
- |---|---|
36
- | **Native to DSH** | A real sidebar panel, a main workbench, DSH theme tokens, and the same store for humans and agents. No iframe, no separate app. |
37
- | **TM that explains itself** | Multiple TM libraries, mounted-only lookup, exact / near-exact / high / medium / low fuzzy grades, and automatic write-back on confirm. |
38
- | **Glossary-aware editing** | Multiple term bases, source-language filtering, inline highlighting, click-to-insert terms, and per-term case sensitivity. |
39
- | **A document workflow** | Tabs, close/reopen, recent-document cleanup, duplicate-import reuse, and progress per document. |
40
- | **Real format coverage** | TXT, Markdown, DOCX, TMX, CSV, SRT/VTT, PO, XLIFF 1.2/2.0/SDLXLIFF, HTML, JSON, XLSX, PPTX, and RTF. |
41
- | **Agent-ready** | Seven tools share the open document and store, so an agent can translate alongside you. |
42
-
43
- ## Quick start
44
-
45
- 1. Install CAIT and open it from the **CAIT** row in the left sidebar.
46
- 2. Choose **New page**, then drop a source file or paste text.
47
- 3. Set the source and target languages.
48
- 4. Translate the current segment; the right panel shows TM matches, glossary hits,
49
- and an MT suggestion.
50
- 5. **Confirm & next** writes the pair to the default mounted TM.
51
- 6. Export target-only, bilingual, or a format-preserving document.
52
-
53
- Typing in the editor is a draft and stays in memory. Confirming a segment (or an
54
- explicit structural change) writes the snapshot. The open workbench polls the
55
- Host revision every two seconds, so changes made by an agent appear without a
56
- manual reload.
57
-
58
- ## Features
59
-
60
- ### Translation memory
61
-
62
- - Multiple TM libraries with id, name, mount state, and a default write library.
63
- - Exact, near-exact, and three fuzzy bands:
64
- - `near_exact` compares a key that ignores digits, punctuation, and whitespace;
65
- it does not depend on a score cutoff.
66
- - `high_fuzzy` is `>= 0.85`, `medium_fuzzy` is `0.75–0.85`, `low_fuzzy` is
67
- below `0.75` (above the configurable floor, default `0.55`).
68
- - Fuzzy scoring is lexical: 72% character-bigram Dice + 28% token Jaccard.
69
- Latin text uses words; CJK text uses individual characters. There is no
70
- stemming, synonym, or semantic matching.
71
- - Lookups only see mounted libraries. `cat_submit_translation` accepts an optional
72
- `targetTmId`; an unmounted target is rejected with `TM_NOT_MOUNTED`.
73
- - `bootstrap().tm.entries` is a 40-entry preview of mounted entries;
74
- `bootstrap().tm.count` is the mounted count and `bootstrap().tm.total` is the
75
- all-library count. Use `cat_lookup_tm` or the library modal for full data.
76
- - TMX 1.4 `<tu>/<tuv>/<seg>` import/export. Inline TMX placeholders are flattened
77
- to plain text with an explicit warning.
78
-
79
- ### Glossary
80
-
81
- - Multiple glossary libraries with mount state and a default write library.
82
- - Live source highlighting and click-to-insert term chips.
83
- - CSV/TSV import, per-term notes, source-language filtering, and a
84
- `caseSensitive` flag honored by both the workbench and `cat_lookup_glossary`.
85
- - Language codes are canonicalized (`EN` → `en`, `ZH_cn` → `zh-CN`).
86
-
87
- ### Workbench
88
-
89
- - Paragraph/sentence segmentation with a per-segment target editor.
90
- - Draft saving, confirm-and-next, previous/next navigation, and progress.
91
- - Document tabs; closing a tab does not delete the document.
92
- - **Open existing document** reopens a closed document.
93
- - Interface language switch in **Settings → Interface**: *Follow DSH language*
94
- (default), 简体中文, English, or 日本語. It applies to the workbench and the
95
- sidebar label right away, and changes only the CAIT UI — never the document's
96
- source or target language.
97
- - The trash dialog beside the picker deletes one closed document or all closed
98
- documents; deletion is permanent and does not touch TM, glossary, or other
99
- documents.
100
- - Re-importing identical content in the same format reuses the existing document
101
- instead of creating a duplicate. The fingerprint covers languages, source,
102
- format, and exchange metadata.
103
- - Responsive density: the workbench measures its DSH panel and applies a
104
- `--cait-u` multiplier of 1×–1.8×. A 1600×900 panel renders at ~1.32× and a
105
- 2560×1440 panel at 1.8×; short windows stay at 1× rather than shrinking.
106
-
107
- ### MT assistance
108
-
109
- CAIT does not own an endpoint or key. The selector is built from DSH:
110
-
111
- - `ctx.llm.listProviders()` supplies registered provider routes.
112
- - `ctx.loader.entries()` is consulted for `config.providers` when available.
113
- - `ctx.llm.listModels(provider)` supplies model ids; CAIT selects the first model
114
- by default.
115
- - `ctx.llm.stream({ provider, model, messages, temperature, purpose: 'cait-mt' })`
116
- performs the call; CAIT concatenates `text-delta` chunks.
117
-
118
- If no usable route exists, the selected route disappears, or it has no models,
119
- CAIT falls back to offline glossary substitution and says so in the UI and in the
120
- `cat_suggest_mt` result. Stream failures are logged in the Host with
121
- provider/model/code/status; the client receives only `MT_PROVIDER_FAILED`.
122
-
123
- ## Supported formats
124
-
125
- | Format | Import | Export | Notes |
126
- | --- | :---: | :---: | --- |
127
- | TXT / Markdown | ✅ | ✅ | Paragraph and sentence segmentation |
128
- | CSV | ✅ | ✅ | Optional source/target column by header name or 1-based number; existing bilingual export |
129
- | DOCX | ✅ | ✅ | Body paragraphs; table blocks are excluded from import. Export patches the original package, keeping styles, numbering, images, headers, tables, and layout. |
130
- | TMX 1.4 | ✅ | ✅ | Common `<tu>/<tuv>/<seg>` subset; inline markup flattened with warning |
131
- | SRT / WebVTT | ✅ | ✅ | One cue per segment, timings/settings/identifier retained; semantic tags kept; invalid ranges rejected |
132
- | PO | ✅ | ✅ | Contexts, plural forms, translator comments, obsolete `#~` entries |
133
- | XLIFF 1.2 | ✅ | ✅ | Inline tags become `{n}` tokens; missing tokens block confirmation |
134
- | XLIFF 2.0 | ✅ | ✅ | Basic `<unit><segment><source>/<target>` subset |
135
- | SDLXLIFF | ✅ | ✅ | Basic XLIFF 1.2 path; SDL-specific extensions are not round-tripped |
136
- | HTML | ✅ | ✅ | Text blocks only; original markup/CSS not preserved |
137
- | JSON | ✅ | ✅ | String-path subset; object/array structure preserved via a template; keys and numbers are not translated |
138
- | XLSX | ✅ | ✅ | First worksheet; `Source`/`Target` headers or columns A/B; export is a new two-column workbook |
139
- | PPTX | ✅ | ✅* | Source-preserving export; requires the imported `.pptx` package. Body text only; notes/charts/SmartArt not imported. |
140
- | RTF | ✅ | ✅ | Basic paragraphs, `\uN`, `\'hh`, control words; original styling not round-tripped |
141
-
142
- \* PPTX export fails with `PPTX_SOURCE_REQUIRED` when the retained source package
143
- is missing rather than silently producing an unformatted deck.
144
-
145
- Exchange metadata (subtitle timings, PO contexts/plurals/comments, XLIFF ids and
146
- inline token maps, JSON paths/templates, XLSX rows, PPTX slide/paragraph positions)
147
- is stored on the document/segments and persists in the snapshot.
148
-
149
- ## Install
150
-
151
- CAIT installs as a DSH bundle; the published package name is `dsh-cait` and the
152
- plugin itself shows up as **CAIT** in the left sidebar. The in-app plugin
153
- manager owns the `desktop` profile, so use it rather than editing the profile's
154
- `package.json`.
155
-
156
- ### From npm
157
-
158
- In DSH Desktop (or any profile with the `plugin_manager` tool):
159
-
160
- ```text
161
- plugin_manager install_bundle target: dsh-cait
162
- ```
163
-
164
- Pin a release with `dsh-cait@1.0.3`, or ask for the newest one with
165
- `dsh-cait@latest`. The manager resolves the registry package, applies the bundle
166
- patch, and activates the plugin — no hand-written profile rows.
167
-
168
- With a CLI-managed profile:
169
-
170
- ```sh
171
- dsh plugin --profile web add dsh-cait
172
- ```
173
-
174
- Replace `web` with your profile name, and restart `dsh web` if your host
175
- requires it.
176
-
177
- ### From this repository
178
-
179
- GitHub:
180
-
181
- ```text
182
- plugin_manager install_bundle target: github:JoviLecca/dsh-CAIT
183
- ```
184
-
185
- Local checkout:
186
-
187
- ```text
188
- plugin_manager install_bundle target: /absolute/path/to/dsh-CAIT
189
- ```
190
-
191
- Then reload DSH. The **CAIT** entry appears in the left sidebar.
192
-
193
- ### From source
194
-
195
- ```sh
196
- git clone <this-repository> dsh-CAIT
197
- cd dsh-CAIT
198
- npm run build:client
199
- ```
200
-
201
- For a local `--patch` overlay, add a row pointing at this directory:
202
-
203
- ```yaml
204
- # _dev/cait.patch.yml
205
- - insert:
206
- - id: cait-dev
207
- name: 'C:\absolute\path\to\dsh-CAIT'
208
- ```
209
-
210
- ```powershell
211
- dsh web --patch C:\absolute\path\to\dsh-CAIT\_dev\cait.patch.yml
212
- ```
213
-
214
- `--patch` is a launcher option; place it after the app name (`web`) and before
215
- any app arguments. Use either the overlay or the installed bundle in a profile,
216
- not both.
217
-
218
- ## Configuration
219
-
220
- | Option | Default | Meaning |
221
- | --- | --- | --- |
222
- | `dataDir` | `""` | Directory for `store.json` and `sources/`. Empty means `$DSH_HOME/cait`. Absolute, relative, and `~/...` paths are accepted. |
223
-
224
- ```yaml
225
- - id: dsh-cait
226
- name: dsh-cait
227
- config:
228
- dataDir: 'D:\CAT\cait-data'
229
- ```
230
-
231
- The snapshot stores documents, open tabs, TM, glossary, libraries, mounts, and
232
- non-secret settings. Imported DOCX/PPTX source packages live in `sources/`.
233
-
234
- In-app settings (**Settings**) live in the snapshot rather than in the YAML
235
- above: source/target language, fuzzy threshold, TM result limit, MT
236
- route/model/temperature, and the interface language (`uiLang`: `auto` | `zh` |
237
- `en` | `ja`, default `auto` = follow the DSH locale).
238
- Segment confirmations use a strict write: if the snapshot cannot be written, the
239
- confirm fails with `PERSISTENCE_FAILED` (503) and rolls back the in-memory
240
- confirmation. Other structural edits remain best-effort and surface
241
- `bootstrap().storage.error` in the workbench banner.
242
-
243
- If the snapshot is corrupt or has an unknown version, CAIT logs a warning, moves
244
- it to `store.json.corrupt-<timestamp>`, and starts empty instead of failing
245
- activation. Version-1 snapshots are migrated to version 2 with default TM and
246
- glossary libraries.
247
-
248
- ## Agent tools
249
-
250
- | Tool | Purpose |
251
- | --- | --- |
252
- | `cat_get_segment` | Current segment with TM matches and glossary hits |
253
- | `cat_submit_translation` | Confirm a target, write to a mounted TM (optional `targetTmId`), advance |
254
- | `cat_lookup_tm` | Graded TM lookup across mounted libraries |
255
- | `cat_lookup_glossary` | Query mounted glossaries; honors `caseSensitive` |
256
- | `cat_list_documents` | List documents and progress |
257
- | `cat_switch_document` | Select the active document |
258
- | `cat_suggest_mt` | MT via the selected DSH route, or the explicit offline fallback |
259
-
260
- The workbench and tools share one `CatStore`, so an agent can translate the same
261
- document the user has open.
262
-
263
- ## Architecture
264
-
265
- ```mermaid
266
- flowchart LR
267
- UI["DSH Web UI<br/>(sidebar + workbench)"] --> Client["client.js<br/>generated Web bundle"]
268
- Client --> API["/cait/api/rpc<br/>same-origin JSON"]
269
- Tools["7 agent tools"] --> Store["CatStore"]
270
- API --> Store
271
- Store --> Snapshot[("store.json<br/>+ sources/")]
272
- ```
273
-
274
- - **Host** (`index.js`, `lib/`) owns the store, persistence, import/export, tools,
275
- and the private API.
276
- - **Client** (`client/`) is plain React loaded through DSH's module loader and
277
- rendered into `sidebar.panellist` and `main`. It styles only with
278
- `--dsw-alias-*` tokens.
279
- - `client.js` is generated; edit `client/*.js` and run `npm run build:client`.
280
-
281
- <details>
282
- <summary><strong>Project layout</strong></summary>
283
-
284
- ```text
285
- cait/
286
- ├─ package.json
287
- ├─ cordis.patch.yml
288
- ├─ index.js # Host half
289
- ├─ client.js # generated Web bundle
290
- ├─ client/ # helpers, styles, RPC, components, locales, entry
291
- ├─ scripts/build-client.mjs
292
- ├─ icon.svg
293
- ├─ locale/{en,zh,ja}.json
294
- ├─ NOTICE
295
- ├─ README.md
296
- ├─ README.zh-CN.md
297
- ├─ README.ja.md
298
- ├─ CHANGELOG.md
299
- ├─ lib/
300
- │ ├─ store.js # CatStore facade + document/segment core
301
- │ ├─ errors.js
302
- │ ├─ store-utils.js
303
- │ ├─ libraries.js # TM/glossary registries + mounts
304
- │ ├─ tm.js
305
- │ ├─ glossary.js
306
- │ ├─ persistence.js
307
- │ ├─ import-export.js
308
- │ ├─ formats.js # CSV/SRT/VTT/PO/XLIFF/HTML/JSON/RTF
309
- │ ├─ xlsx.js
310
- │ ├─ pptx.js
311
- │ ├─ segmenter.js
312
- │ ├─ similarity.js
313
- │ ├─ tmx.js
314
- │ ├─ docx.js
315
- │ ├─ markdown.js
316
- │ ├─ xml.js
317
- │ ├─ mt.js
318
- │ ├─ tools.js
319
- │ └─ api.js
320
- └─ _dev/ # tests, fixtures, local patch overlay
321
- ```
322
-
323
- </details>
324
-
325
- ## Development
326
-
327
- Requires Node.js 22+ and DSH `0.2.x`. Tested with DSH `0.2.2-rc.1` and Node
328
- `26.7.0`.
329
-
330
- ```sh
331
- npm run build:client
332
- node --check client.js
333
-
334
- node _dev/test-core.mjs
335
- node _dev/test-imports.mjs
336
- node _dev/test-formats.mjs
337
- node _dev/test-second-tier.mjs
338
- node _dev/test-docx-export.mjs
339
- node _dev/test-api.mjs
340
- node _dev/test-plugin.mjs
341
- node _dev/test-persistence.mjs
342
- node _dev/test-documents.mjs
343
- node _dev/test-libraries.mjs
344
- node _dev/test-mounts.mjs
345
- node _dev/test-ui-scale.mjs
346
- node _dev/test-mt.mjs
347
- ```
348
-
349
- The import/export fixtures are `_dev/sample.docx`, `_dev/sample-table.docx`, and
350
- `_dev/sample-format.docx`; regenerate them with the matching
351
- `python _dev/make-*.py` scripts. All 13 suites should pass.
352
-
353
- ### Releasing
354
-
355
- `npm publish` runs `prepublishOnly`, which rebuilds `client.js` from the
356
- `client/` fragments and syntax-checks the result, so a release cannot ship a
357
- stale bundle. Publishing from the repository root:
358
-
359
- ```sh
360
- npm publish
361
- ```
362
-
363
- `.github/workflows/publish.yml` does the same from CI on a published GitHub
364
- release, using npm Trusted Publishing (OIDC) instead of a stored token, and it
365
- refuses to publish when the release tag does not match `package.json`.
366
-
367
- The very first release has to be published locally, because a trusted publisher
368
- can only be configured for a package that already exists:
369
-
370
- ```sh
371
- npm login
372
- npm publish
373
- ```
374
-
375
- Afterwards open the package on npmjs.com, go to
376
- **Settings → Trusted publishing**, and add the repository `JoviLecca/dsh-CAIT`
377
- with the workflow file `publish.yml`. Later releases can then be published from
378
- GitHub with a release.
379
-
380
- ## Security model
381
-
382
- - The Host API is mounted at `/cait/api`, restricted to same-origin/trusted
383
- requests by `ctx.connection.requestRejection`.
384
- - RPC bodies are JSON, capped at 32 MB, and return `413 BODY_TOO_LARGE` beyond
385
- that.
386
- - `clearTM` / `clearGlossary` require `params.confirm === true` in addition to
387
- the browser trust check; the UI asks before sending it.
388
- - CAIT never receives, stores, or returns an API key. DSH credentials stay inside
389
- the selected provider adapter.
390
- - Persistence is one snapshot with last-write-wins semantics; it is not a
391
- multi-user journal.
392
-
393
- ## Known limits and roadmap
394
-
395
- - No PDF, EPUB, or image OCR import yet.
396
- - DOCX import skips tables, headers, footnotes, fields, alt text, and tracked
397
- deletions. DOCX export leaves those regions in the source language.
398
- - Mixed-run DOCX/PPTX paragraphs inherit the first run's formatting.
399
- - PPTX import covers body text, not notes, charts, or SmartArt.
400
- - XLSX import/export is a two-column table; cell styles, formulas, merged cells,
401
- images, and extra sheets are not preserved.
402
- - HTML/RTF export does not round-trip original styling; JSON translates string
403
- values only.
404
- - TM matching is lexical, not semantic; no stemming, synonyms, or embeddings.
405
- - Library rename/delete and per-tab mount state are not part of v1.0.0.
406
- - The workbench UI ships in Chinese, English, and Japanese, switchable in
407
- Settings; this README ships in English, Simplified Chinese, and Japanese, and
408
- the English version is the source of truth.
409
-
410
- Planned next: PDF and EPUB adapters, image/vision input through a multimodal DSH
411
- route, richer library management, style-preserving XLSX output, and XLIFF
412
- `<alt-trans>` / notes support.
413
-
414
- ## Changelog and versioning
415
-
416
- See [CHANGELOG.md](CHANGELOG.md). `v1.0.0` was the first public release;
417
- `v1.0.1` adds the Japanese UI and the in-app interface language switch,
418
- `v1.0.2` makes *Follow DSH language* honour the DSH locale, and `v1.0.3`
419
- publishes the package to npm. The project was developed as `0.1.0` and
420
- renumbered for the GitHub release.
421
-
422
- > **License:** Apache License 2.0 — see [LICENSE](LICENSE). Redistributions must
423
- > retain the attribution in [NOTICE](NOTICE).
1
+ <p align="center">
2
+ <img src="icon.svg" width="76" alt="CAIT icon" />
3
+ </p>
4
+
5
+ <h1 align="center">CAIT</h1>
6
+
7
+ <p align="center">
8
+ <strong>Computer-aided translation, inside DeepSeek Harness.</strong><br/>
9
+ Translation memory · glossary · segment editor · MT assistance · document tabs · Office / subtitle / localization formats
10
+ </p>
11
+ <p align="center">
12
+ <a href="README.zh-CN.md">简体中文</a> · <a href="README.ja.md">日本語</a> · <a href="README.md">English</a>
13
+ </p>
14
+
15
+
16
+ <p align="center">
17
+ <a href="https://www.npmjs.com/package/dsh-cait"><img alt="npm version" src="https://img.shields.io/npm/v/dsh-cait.svg" /></a>
18
+ <img alt="DSH 0.2.x" src="https://img.shields.io/badge/DSH-0.2.x-4d6bfe.svg" />
19
+ <img alt="Tests: 13 suites" src="https://img.shields.io/badge/tests-13%20suites-brightgreen.svg" />
20
+ <img alt="No API key required" src="https://img.shields.io/badge/API%20key-not%20required-6b7280.svg" />
21
+ </p>
22
+
23
+ CAIT adds a CAT workbench to the DSH Web UI and Desktop. It opens from a
24
+ left-sidebar entry, keeps one shared store behind the workbench and seven agent
25
+ tools, and covers the practical path from importing a source file to exporting a
26
+ translated document.
27
+
28
+ > **Tested with DSH `0.2.2-rc.1`.** CAIT uses DSH's own model routes and
29
+ > credentials; it never asks for an API key. The internal fallback provider is
30
+ > still called `mock`, but the UI labels it **Glossary substitution (offline)**.
31
+
32
+ ## Why CAIT
33
+
34
+ | | |
35
+ |---|---|
36
+ | **Native to DSH** | A real sidebar panel, a main workbench, DSH theme tokens, and the same store for humans and agents. No iframe, no separate app. |
37
+ | **TM that explains itself** | Multiple TM libraries, mounted-only lookup, exact / near-exact / high / medium / low fuzzy grades, and automatic write-back on confirm. |
38
+ | **Glossary-aware editing** | Multiple term bases, source-language filtering, inline highlighting, click-to-insert terms, and per-term case sensitivity. |
39
+ | **A document workflow** | Tabs, close/reopen, recent-document cleanup, duplicate-import reuse, and progress per document. |
40
+ | **Real format coverage** | TXT, Markdown, DOCX, TMX, CSV, SRT/VTT, PO, XLIFF 1.2/2.0/SDLXLIFF, HTML, JSON, XLSX, PPTX, and RTF. |
41
+ | **Agent-ready** | Seven tools share the open document and store, so an agent can translate alongside you. |
42
+
43
+ ## Quick start
44
+
45
+ 1. Install CAIT and open it from the **CAIT** row in the left sidebar.
46
+ 2. Choose **New page**, then drop a source file or paste text.
47
+ 3. Set the source and target languages.
48
+ 4. Translate the current segment; the right panel shows TM matches, glossary hits,
49
+ and an MT suggestion.
50
+ 5. **Confirm & next** writes the pair to the default mounted TM.
51
+ 6. Export target-only, bilingual, or a format-preserving document.
52
+
53
+ Typing in the editor is a draft and stays in memory. Confirming a segment (or an
54
+ explicit structural change) writes the snapshot. The open workbench polls the
55
+ Host revision every two seconds, so changes made by an agent appear without a
56
+ manual reload.
57
+
58
+ ## Features
59
+
60
+ ### Translation memory
61
+
62
+ - Multiple TM libraries with id, name, mount state, and a default write library.
63
+ - Exact, near-exact, and three fuzzy bands:
64
+ - `near_exact` compares a key that ignores digits, punctuation, and whitespace;
65
+ it does not depend on a score cutoff.
66
+ - `high_fuzzy` is `>= 0.85`, `medium_fuzzy` is `0.75–0.85`, `low_fuzzy` is
67
+ below `0.75` (above the configurable floor, default `0.55`).
68
+ - Fuzzy scoring is lexical: 72% character-bigram Dice + 28% token Jaccard.
69
+ Latin text uses words; CJK text uses individual characters. There is no
70
+ stemming, synonym, or semantic matching.
71
+ - Lookups only see mounted libraries. `cat_submit_translation` accepts an optional
72
+ `targetTmId`; an unmounted target is rejected with `TM_NOT_MOUNTED`.
73
+ - `bootstrap().tm.entries` is a 40-entry preview of mounted entries;
74
+ `bootstrap().tm.count` is the mounted count and `bootstrap().tm.total` is the
75
+ all-library count. Use `cat_lookup_tm` or the library modal for full data.
76
+ - TMX 1.4 `<tu>/<tuv>/<seg>` import/export. Inline TMX placeholders are flattened
77
+ to plain text with an explicit warning.
78
+
79
+ ### Glossary
80
+
81
+ - Multiple glossary libraries with mount state and a default write library.
82
+ - Live source highlighting and click-to-insert term chips.
83
+ - CSV/TSV import, per-term notes, source-language filtering, and a
84
+ `caseSensitive` flag honored by both the workbench and `cat_lookup_glossary`.
85
+ - Language codes are canonicalized (`EN` → `en`, `ZH_cn` → `zh-CN`).
86
+
87
+ ### Workbench
88
+
89
+ - Paragraph/sentence segmentation with a per-segment target editor.
90
+ - Draft saving, confirm-and-next, previous/next navigation, and progress.
91
+ - Document tabs; closing a tab does not delete the document.
92
+ - **Open existing document** reopens a closed document.
93
+ - Interface language switch in **Settings → Interface**: *Follow DSH language*
94
+ (default), 简体中文, English, or 日本語. It applies to the workbench and the
95
+ sidebar label right away, and changes only the CAIT UI — never the document's
96
+ source or target language.
97
+ - The trash dialog beside the picker deletes one closed document or all closed
98
+ documents; deletion is permanent and does not touch TM, glossary, or other
99
+ documents.
100
+ - Re-importing identical content in the same format reuses the existing document
101
+ instead of creating a duplicate. The fingerprint covers languages, source,
102
+ format, and exchange metadata.
103
+ - Responsive density: the workbench measures its DSH panel and applies a
104
+ `--cait-u` multiplier of 1×–1.8×. A 1600×900 panel renders at ~1.32× and a
105
+ 2560×1440 panel at 1.8×; short windows stay at 1× rather than shrinking.
106
+
107
+ ### MT assistance
108
+
109
+ CAIT does not own an endpoint or key. The selector is built from DSH:
110
+
111
+ - `ctx.llm.listProviders()` supplies registered provider routes.
112
+ - `ctx.loader.entries()` is consulted for `config.providers` when available.
113
+ - `ctx.llm.listModels(provider)` supplies model ids; CAIT selects the first model
114
+ by default.
115
+ - `ctx.llm.stream({ provider, model, messages, temperature, purpose: 'cait-mt' })`
116
+ performs the call; CAIT concatenates `text-delta` chunks.
117
+
118
+ If no usable route exists, the selected route disappears, or it has no models,
119
+ CAIT falls back to offline glossary substitution and says so in the UI and in the
120
+ `cat_suggest_mt` result. Stream failures are logged in the Host with
121
+ provider/model/code/status; the client receives only `MT_PROVIDER_FAILED`.
122
+
123
+ ## Supported formats
124
+
125
+ | Format | Import | Export | Notes |
126
+ | --- | :---: | :---: | --- |
127
+ | TXT / Markdown | ✅ | ✅ | Paragraph and sentence segmentation |
128
+ | CSV | ✅ | ✅ | Optional source/target column by header name or 1-based number; existing bilingual export |
129
+ | DOCX | ✅ | ✅ | Body paragraphs; table blocks are excluded from import. Export patches the original package, keeping styles, numbering, images, headers, tables, and layout. |
130
+ | TMX 1.4 | ✅ | ✅ | Common `<tu>/<tuv>/<seg>` subset; inline markup flattened with warning |
131
+ | SRT / WebVTT | ✅ | ✅ | One cue per segment, timings/settings/identifier retained; semantic tags kept; invalid ranges rejected |
132
+ | PO | ✅ | ✅ | Contexts, plural forms, translator comments, obsolete `#~` entries |
133
+ | XLIFF 1.2 | ✅ | ✅ | Inline tags become `{n}` tokens; missing tokens block confirmation |
134
+ | XLIFF 2.0 | ✅ | ✅ | Basic `<unit><segment><source>/<target>` subset |
135
+ | SDLXLIFF | ✅ | ✅ | Basic XLIFF 1.2 path; SDL-specific extensions are not round-tripped |
136
+ | HTML | ✅ | ✅ | Text blocks only; original markup/CSS not preserved |
137
+ | JSON | ✅ | ✅ | String-path subset; object/array structure preserved via a template; keys and numbers are not translated |
138
+ | XLSX | ✅ | ✅ | First worksheet; `Source`/`Target` headers or columns A/B; export is a new two-column workbook |
139
+ | PPTX | ✅ | ✅* | Source-preserving export; requires the imported `.pptx` package. Body text only; notes/charts/SmartArt not imported. |
140
+ | RTF | ✅ | ✅ | Basic paragraphs, `\uN`, `\'hh`, control words; original styling not round-tripped |
141
+
142
+ \* PPTX export fails with `PPTX_SOURCE_REQUIRED` when the retained source package
143
+ is missing rather than silently producing an unformatted deck.
144
+
145
+ Exchange metadata (subtitle timings, PO contexts/plurals/comments, XLIFF ids and
146
+ inline token maps, JSON paths/templates, XLSX rows, PPTX slide/paragraph positions)
147
+ is stored on the document/segments and persists in the snapshot.
148
+
149
+ ## Install
150
+
151
+ CAIT installs as a DSH bundle; the published package name is `dsh-cait` and the
152
+ plugin itself shows up as **CAIT** in the left sidebar. The in-app plugin
153
+ manager owns the `desktop` profile, so use it rather than editing the profile's
154
+ `package.json`.
155
+
156
+ ### From npm
157
+
158
+ In DSH Desktop (or any profile with the `plugin_manager` tool):
159
+
160
+ ```text
161
+ plugin_manager install_bundle target: dsh-cait
162
+ ```
163
+
164
+ Pin a release with `dsh-cait@1.0.4`, or ask for the newest one with
165
+ `dsh-cait@latest`. The manager resolves the registry package, applies the bundle
166
+ patch, and activates the plugin — no hand-written profile rows.
167
+
168
+ With a CLI-managed profile:
169
+
170
+ ```sh
171
+ dsh plugin --profile web add dsh-cait
172
+ ```
173
+
174
+ Replace `web` with your profile name, and restart `dsh web` if your host
175
+ requires it.
176
+
177
+ ### From this repository
178
+
179
+ GitHub:
180
+
181
+ ```text
182
+ plugin_manager install_bundle target: github:JoviLecca/dsh-CAIT
183
+ ```
184
+
185
+ Local checkout:
186
+
187
+ ```text
188
+ plugin_manager install_bundle target: /absolute/path/to/dsh-CAIT
189
+ ```
190
+
191
+ Then reload DSH. The **CAIT** entry appears in the left sidebar.
192
+
193
+ ### From source
194
+
195
+ ```sh
196
+ git clone <this-repository> dsh-CAIT
197
+ cd dsh-CAIT
198
+ npm run build:client
199
+ ```
200
+
201
+ For a local `--patch` overlay, add a row pointing at this directory:
202
+
203
+ ```yaml
204
+ # _dev/cait.patch.yml
205
+ - insert:
206
+ - id: cait-dev
207
+ name: 'C:\absolute\path\to\dsh-CAIT'
208
+ ```
209
+
210
+ ```powershell
211
+ dsh web --patch C:\absolute\path\to\dsh-CAIT\_dev\cait.patch.yml
212
+ ```
213
+
214
+ `--patch` is a launcher option; place it after the app name (`web`) and before
215
+ any app arguments. Use either the overlay or the installed bundle in a profile,
216
+ not both.
217
+
218
+ ## Configuration
219
+
220
+ | Option | Default | Meaning |
221
+ | --- | --- | --- |
222
+ | `dataDir` | `""` | Directory for `store.json` and `sources/`. Empty means `$DSH_HOME/cait`. Absolute, relative, and `~/...` paths are accepted. |
223
+
224
+ ```yaml
225
+ - id: dsh-cait
226
+ name: dsh-cait
227
+ config:
228
+ dataDir: 'D:\CAT\cait-data'
229
+ ```
230
+
231
+ The snapshot stores documents, open tabs, TM, glossary, libraries, mounts, and
232
+ non-secret settings. Imported DOCX/PPTX source packages live in `sources/`.
233
+
234
+ In-app settings (**Settings**) live in the snapshot rather than in the YAML
235
+ above: source/target language, fuzzy threshold, TM result limit, MT
236
+ route/model/temperature, and the interface language (`uiLang`: `auto` | `zh` |
237
+ `en` | `ja`, default `auto` = follow the DSH locale).
238
+ Segment confirmations use a strict write: if the snapshot cannot be written, the
239
+ confirm fails with `PERSISTENCE_FAILED` (503) and rolls back the in-memory
240
+ confirmation. Other structural edits remain best-effort and surface
241
+ `bootstrap().storage.error` in the workbench banner.
242
+
243
+ If the snapshot is corrupt or has an unknown version, CAIT logs a warning, moves
244
+ it to `store.json.corrupt-<timestamp>`, and starts empty instead of failing
245
+ activation. Version-1 snapshots are migrated to version 2 with default TM and
246
+ glossary libraries.
247
+
248
+ ## Agent tools
249
+
250
+ | Tool | Purpose |
251
+ | --- | --- |
252
+ | `cat_get_segment` | Current segment with TM matches and glossary hits |
253
+ | `cat_submit_translation` | Confirm a target, write to a mounted TM (optional `targetTmId`), advance |
254
+ | `cat_lookup_tm` | Graded TM lookup across mounted libraries |
255
+ | `cat_lookup_glossary` | Query mounted glossaries; honors `caseSensitive` |
256
+ | `cat_list_documents` | List documents and progress |
257
+ | `cat_switch_document` | Select the active document |
258
+ | `cat_suggest_mt` | MT via the selected DSH route, or the explicit offline fallback |
259
+
260
+ The workbench and tools share one `CatStore`, so an agent can translate the same
261
+ document the user has open.
262
+
263
+ ## Architecture
264
+
265
+ ```mermaid
266
+ flowchart LR
267
+ UI["DSH Web UI<br/>(sidebar + workbench)"] --> Client["client.js<br/>generated Web bundle"]
268
+ Client --> API["/cait/api/rpc<br/>same-origin JSON"]
269
+ Tools["7 agent tools"] --> Store["CatStore"]
270
+ API --> Store
271
+ Store --> Snapshot[("store.json<br/>+ sources/")]
272
+ ```
273
+
274
+ - **Host** (`index.js`, `lib/`) owns the store, persistence, import/export, tools,
275
+ and the private API.
276
+ - **Client** (`client/`) is plain React loaded through DSH's module loader and
277
+ rendered into `sidebar.panellist` and `main`. It styles only with
278
+ `--dsw-alias-*` tokens.
279
+ - `client.js` is generated; edit `client/*.js` and run `npm run build:client`.
280
+
281
+ <details>
282
+ <summary><strong>Project layout</strong></summary>
283
+
284
+ ```text
285
+ cait/
286
+ ├─ package.json
287
+ ├─ cordis.patch.yml
288
+ ├─ index.js # Host half
289
+ ├─ client.js # generated Web bundle
290
+ ├─ client/ # helpers, styles, RPC, components, locales, entry
291
+ ├─ scripts/build-client.mjs
292
+ ├─ icon.svg
293
+ ├─ locale/{en,zh,ja}.json
294
+ ├─ NOTICE
295
+ ├─ README.md
296
+ ├─ README.zh-CN.md
297
+ ├─ README.ja.md
298
+ ├─ CHANGELOG.md
299
+ ├─ lib/
300
+ │ ├─ store.js # CatStore facade + document/segment core
301
+ │ ├─ errors.js
302
+ │ ├─ store-utils.js
303
+ │ ├─ libraries.js # TM/glossary registries + mounts
304
+ │ ├─ tm.js
305
+ │ ├─ glossary.js
306
+ │ ├─ persistence.js
307
+ │ ├─ import-export.js
308
+ │ ├─ formats.js # CSV/SRT/VTT/PO/XLIFF/HTML/JSON/RTF
309
+ │ ├─ xlsx.js
310
+ │ ├─ pptx.js
311
+ │ ├─ segmenter.js
312
+ │ ├─ similarity.js
313
+ │ ├─ tmx.js
314
+ │ ├─ docx.js
315
+ │ ├─ markdown.js
316
+ │ ├─ xml.js
317
+ │ ├─ mt.js
318
+ │ ├─ tools.js
319
+ │ └─ api.js
320
+ └─ _dev/ # tests, fixtures, local patch overlay
321
+ ```
322
+
323
+ </details>
324
+
325
+ ## Development
326
+
327
+ Requires Node.js 22+ and DSH `0.2.x`. Tested with DSH `0.2.2-rc.1` and Node
328
+ `26.7.0`.
329
+
330
+ ```sh
331
+ npm run build:client
332
+ node --check client.js
333
+
334
+ node _dev/test-core.mjs
335
+ node _dev/test-imports.mjs
336
+ node _dev/test-formats.mjs
337
+ node _dev/test-second-tier.mjs
338
+ node _dev/test-docx-export.mjs
339
+ node _dev/test-api.mjs
340
+ node _dev/test-plugin.mjs
341
+ node _dev/test-persistence.mjs
342
+ node _dev/test-documents.mjs
343
+ node _dev/test-libraries.mjs
344
+ node _dev/test-mounts.mjs
345
+ node _dev/test-ui-scale.mjs
346
+ node _dev/test-mt.mjs
347
+ ```
348
+
349
+ The import/export fixtures are `_dev/sample.docx`, `_dev/sample-table.docx`, and
350
+ `_dev/sample-format.docx`; regenerate them with the matching
351
+ `python _dev/make-*.py` scripts. All 13 suites should pass.
352
+
353
+ ### Releasing
354
+
355
+ `npm publish` runs `prepublishOnly`, which rebuilds `client.js` from the
356
+ `client/` fragments and syntax-checks the result, so a release cannot ship a
357
+ stale bundle. Publishing from the repository root:
358
+
359
+ ```sh
360
+ npm publish
361
+ ```
362
+
363
+ `.github/workflows/publish.yml` does the same from CI on a published GitHub
364
+ release, using npm Trusted Publishing (OIDC) instead of a stored token, and it
365
+ refuses to publish when the release tag does not match `package.json`.
366
+
367
+ The very first release has to be published locally, because a trusted publisher
368
+ can only be configured for a package that already exists:
369
+
370
+ ```sh
371
+ npm login
372
+ npm publish
373
+ ```
374
+
375
+ Afterwards open the package on npmjs.com, go to
376
+ **Settings → Trusted publishing**, and add the repository `JoviLecca/dsh-CAIT`
377
+ with the workflow file `publish.yml`. Later releases can then be published from
378
+ GitHub with a release.
379
+
380
+ ## Security model
381
+
382
+ - The Host API is mounted at `/cait/api`, restricted to same-origin/trusted
383
+ requests by `ctx.connection.requestRejection`.
384
+ - RPC bodies are JSON, capped at 32 MB, and return `413 BODY_TOO_LARGE` beyond
385
+ that.
386
+ - `clearTM` / `clearGlossary` require `params.confirm === true` in addition to
387
+ the browser trust check; the UI asks before sending it.
388
+ - CAIT never receives, stores, or returns an API key. DSH credentials stay inside
389
+ the selected provider adapter.
390
+ - Persistence is one snapshot with last-write-wins semantics; it is not a
391
+ multi-user journal.
392
+
393
+ ## Known limits and roadmap
394
+
395
+ - No PDF, EPUB, or image OCR import yet.
396
+ - DOCX import skips tables, headers, footnotes, fields, alt text, and tracked
397
+ deletions. DOCX export leaves those regions in the source language.
398
+ - Mixed-run DOCX/PPTX paragraphs inherit the first run's formatting.
399
+ - PPTX import covers body text, not notes, charts, or SmartArt.
400
+ - XLSX import/export is a two-column table; cell styles, formulas, merged cells,
401
+ images, and extra sheets are not preserved.
402
+ - HTML/RTF export does not round-trip original styling; JSON translates string
403
+ values only.
404
+ - TM matching is lexical, not semantic; no stemming, synonyms, or embeddings.
405
+ - Library rename/delete and per-tab mount state are not part of v1.0.0.
406
+ - The workbench UI ships in Chinese, English, and Japanese, switchable in
407
+ Settings; this README ships in English, Simplified Chinese, and Japanese, and
408
+ the English version is the source of truth.
409
+
410
+ Planned next: PDF and EPUB adapters, image/vision input through a multimodal DSH
411
+ route, richer library management, style-preserving XLSX output, and XLIFF
412
+ `<alt-trans>` / notes support.
413
+
414
+ ## Changelog and versioning
415
+
416
+ See [CHANGELOG.md](CHANGELOG.md). `v1.0.0` was the first public release;
417
+ `v1.0.1` adds the Japanese UI and the in-app interface language switch,
418
+ `v1.0.2` makes *Follow DSH language* honour the DSH locale, `v1.0.3`
419
+ publishes the package to npm, and `v1.0.4` moves later releases to the
420
+ provenance-publishing workflow. The project was developed as `0.1.0` and
421
+ renumbered for the GitHub release.
422
+
423
+ > **License:** Apache License 2.0 — see [LICENSE](LICENSE). Redistributions must
424
+ > retain the attribution in [NOTICE](NOTICE).