dsh-cait 1.0.3 → 1.0.5

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,466 @@
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: 15 suites" src="https://img.shields.io/badge/tests-15%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, EPUB, 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
+ ### Preprocessing
108
+
109
+ Imported source text passes through a preprocessing stage before segmentation:
110
+
111
+ - Collapse repeated spaces and tabs, trim indentation, and merge consecutive
112
+ blank lines.
113
+ - Remove zero-width characters and stray control characters.
114
+ - Placeholder tokens (`{1}`, `{2}`, …), punctuation, casing, and paragraph order
115
+ are left untouched, so XLIFF/HTML/EPUB inline markup still round-trips.
116
+ - The three steps are independently switchable in **Settings → Preprocessing** and
117
+ are persisted with the other settings. They apply to new imports and
118
+ `createDocument` calls; existing documents are not rewritten.
119
+
120
+ ### MT assistance
121
+
122
+ CAIT does not own an endpoint or key. The selector is built from DSH:
123
+
124
+ - `ctx.llm.listProviders()` supplies registered provider routes.
125
+ - `ctx.loader.entries()` is consulted for `config.providers` when available.
126
+ - `ctx.llm.listModels(provider)` supplies model ids; CAIT selects the first model
127
+ by default.
128
+ - `ctx.llm.stream({ provider, model, messages, temperature, purpose: 'cait-mt' })`
129
+ performs the call; CAIT concatenates `text-delta` chunks.
130
+
131
+ If no usable route exists, the selected route disappears, or it has no models,
132
+ CAIT falls back to offline glossary substitution and says so in the UI and in the
133
+ `cat_suggest_mt` result. Stream failures are logged in the Host with
134
+ provider/model/code/status; the client receives only `MT_PROVIDER_FAILED`.
135
+
136
+ ## Supported formats
137
+
138
+ | Format | Import | Export | Notes |
139
+ | --- | :---: | :---: | --- |
140
+ | TXT / Markdown | ✅ | ✅ | Paragraph and sentence segmentation |
141
+ | CSV | ✅ | ✅ | Comma, semicolon, tab, or pipe delimiter auto-detected; source/target column by header name or 1-based number; existing bilingual export |
142
+ | DOCX | ✅ | ✅ | Body paragraphs; table blocks are excluded from import. Export patches the original package, keeping styles, numbering, images, headers, tables, and layout. |
143
+ | EPUB | ✅ | ✅ | EPUB 3 spine order; XHTML text blocks with inline markup kept as tokens and patched back into the retained package. DRM-protected, fixed-layout, and scripted EPUBs are not supported. |
144
+ | TMX 1.4 | ✅ | ✅ | Common `<tu>/<tuv>/<seg>` subset; inline markup flattened with warning |
145
+ | SRT / WebVTT | ✅ | ✅ | One cue per segment, timings/settings/identifier retained; semantic tags kept; invalid ranges rejected |
146
+ | PO | ✅ | ✅ | Contexts, plural forms, translator comments, obsolete `#~` entries |
147
+ | XLIFF 1.2 | ✅ | ✅ | Inline tags become `{n}` tokens; missing tokens block confirmation |
148
+ | XLIFF 2.0 | ✅ | ✅ | Basic `<unit><segment><source>/<target>` subset |
149
+ | SDLXLIFF | ✅ | ✅ | Basic XLIFF 1.2 path; SDL-specific extensions are not round-tripped |
150
+ | HTML | ✅ | ✅ | Text blocks and inline tags preserved; document CSS/layout is not rebuilt |
151
+ | JSON | ✅ | ✅ | String-path subset; object/array structure preserved via a template; keys and numbers are not translated |
152
+ | XLSX | ✅ | ✅ | First worksheet; `Source`/`Target` headers or columns A/B; export is a new two-column workbook |
153
+ | PPTX | ✅ | ✅* | Source-preserving export; requires the imported `.pptx` package. Body text only; notes/charts/SmartArt not imported. |
154
+ | RTF | ✅ | ✅ | Basic paragraphs, `\uN`, `\'hh`, control words; original styling not round-tripped |
155
+
156
+ \* PPTX export fails with `PPTX_SOURCE_REQUIRED` when the retained source package
157
+ is missing rather than silently producing an unformatted deck.
158
+
159
+ Exchange metadata (subtitle timings, PO contexts/plurals/comments, XLIFF ids and
160
+ inline token maps, JSON paths/templates, XLSX rows, PPTX slide/paragraph positions,
161
+ EPUB spine hrefs and block indexes) is stored on the document/segments and persists
162
+ in the snapshot.
163
+
164
+ ### PDF is not supported
165
+
166
+ CAIT does not import PDF, and future releases will not add PDF support. PDF is a
167
+ fixed-layout print format: it has no logical paragraphs, reading order, or inline
168
+ markup. Text extracted from it is fragmented across drawing operations, loses
169
+ headings, tables, and inline emphasis, and produces segments that are poor input
170
+ for translation memory and MT — in other words, PDF is extremely unsuitable for
171
+ AI-related work.
172
+
173
+ To translate PDF content, convert it to a supported format first, for example:
174
+
175
+ ```sh
176
+ pandoc input.pdf -t docx -o output.docx # or -t html, -t markdown
177
+ ```
178
+
179
+ Adobe Acrobat's **Export to Word/HTML**, LibreOffice, or any other converter that
180
+ produces DOCX, HTML, EPUB, or plain text works too. Scanned PDFs need OCR before
181
+ conversion; CAIT has no image OCR stage.
182
+
183
+ ## Install
184
+
185
+ CAIT installs as a DSH bundle; the published package name is `dsh-cait` and the
186
+ plugin itself shows up as **CAIT** in the left sidebar. The in-app plugin
187
+ manager owns the `desktop` profile, so use it rather than editing the profile's
188
+ `package.json`.
189
+
190
+ ### From npm
191
+
192
+ In DSH Desktop (or any profile with the `plugin_manager` tool):
193
+
194
+ ```text
195
+ plugin_manager install_bundle target: dsh-cait
196
+ ```
197
+
198
+ Pin a release with `dsh-cait@1.0.5`, or ask for the newest one with
199
+ `dsh-cait@latest`. The manager resolves the registry package, applies the bundle
200
+ patch, and activates the plugin — no hand-written profile rows.
201
+
202
+ With a CLI-managed profile:
203
+
204
+ ```sh
205
+ dsh plugin --profile web add dsh-cait
206
+ ```
207
+
208
+ Replace `web` with your profile name, and restart `dsh web` if your host
209
+ requires it.
210
+
211
+ ### From this repository
212
+
213
+ GitHub:
214
+
215
+ ```text
216
+ plugin_manager install_bundle target: github:JoviLecca/dsh-CAIT
217
+ ```
218
+
219
+ Local checkout:
220
+
221
+ ```text
222
+ plugin_manager install_bundle target: /absolute/path/to/dsh-CAIT
223
+ ```
224
+
225
+ Then reload DSH. The **CAIT** entry appears in the left sidebar.
226
+
227
+ ### From source
228
+
229
+ ```sh
230
+ git clone <this-repository> dsh-CAIT
231
+ cd dsh-CAIT
232
+ npm run build:client
233
+ ```
234
+
235
+ For a local `--patch` overlay, add a row pointing at this directory:
236
+
237
+ ```yaml
238
+ # _dev/cait.patch.yml
239
+ - insert:
240
+ - id: cait-dev
241
+ name: 'C:\absolute\path\to\dsh-CAIT'
242
+ ```
243
+
244
+ ```powershell
245
+ dsh web --patch C:\absolute\path\to\dsh-CAIT\_dev\cait.patch.yml
246
+ ```
247
+
248
+ `--patch` is a launcher option; place it after the app name (`web`) and before
249
+ any app arguments. Use either the overlay or the installed bundle in a profile,
250
+ not both.
251
+
252
+ ## Configuration
253
+
254
+ | Option | Default | Meaning |
255
+ | --- | --- | --- |
256
+ | `dataDir` | `""` | Directory for `store.json` and `sources/`. Empty means `$DSH_HOME/cait`. Absolute, relative, and `~/...` paths are accepted. |
257
+
258
+ ```yaml
259
+ - id: dsh-cait
260
+ name: dsh-cait
261
+ config:
262
+ dataDir: 'D:\CAT\cait-data'
263
+ ```
264
+
265
+ The snapshot stores documents, open tabs, TM, glossary, libraries, mounts, and
266
+ non-secret settings. Imported DOCX/PPTX/EPUB source packages live in `sources/`.
267
+
268
+ In-app settings (**Settings**) live in the snapshot rather than in the YAML
269
+ above: source/target language, fuzzy threshold, TM result limit, MT
270
+ route/model/temperature, and the interface language (`uiLang`: `auto` | `zh` |
271
+ `en` | `ja`, default `auto` = follow the DSH locale).
272
+ Segment confirmations use a strict write: if the snapshot cannot be written, the
273
+ confirm fails with `PERSISTENCE_FAILED` (503) and rolls back the in-memory
274
+ confirmation. Other structural edits remain best-effort and surface
275
+ `bootstrap().storage.error` in the workbench banner.
276
+
277
+ If the snapshot is corrupt or has an unknown version, CAIT logs a warning, moves
278
+ it to `store.json.corrupt-<timestamp>`, and starts empty instead of failing
279
+ activation. Version-1 snapshots are migrated to version 2 with default TM and
280
+ glossary libraries.
281
+
282
+ ## Agent tools
283
+
284
+ | Tool | Purpose |
285
+ | --- | --- |
286
+ | `cat_get_segment` | Current segment with TM matches and glossary hits |
287
+ | `cat_submit_translation` | Confirm a target, write to a mounted TM (optional `targetTmId`), advance |
288
+ | `cat_lookup_tm` | Graded TM lookup across mounted libraries |
289
+ | `cat_lookup_glossary` | Query mounted glossaries; honors `caseSensitive` |
290
+ | `cat_list_documents` | List documents and progress |
291
+ | `cat_switch_document` | Select the active document |
292
+ | `cat_suggest_mt` | MT via the selected DSH route, or the explicit offline fallback |
293
+
294
+ The workbench and tools share one `CatStore`, so an agent can translate the same
295
+ document the user has open.
296
+
297
+ ## Architecture
298
+
299
+ ```mermaid
300
+ flowchart LR
301
+ UI["DSH Web UI<br/>(sidebar + workbench)"] --> Client["client.js<br/>generated Web bundle"]
302
+ Client --> API["/cait/api/rpc<br/>same-origin JSON"]
303
+ Tools["7 agent tools"] --> Store["CatStore"]
304
+ API --> Store
305
+ Store --> Snapshot[("store.json<br/>+ sources/")]
306
+ ```
307
+
308
+ - **Host** (`index.js`, `lib/`) owns the store, persistence, import/export, tools,
309
+ and the private API.
310
+ - **Client** (`client/`) is plain React loaded through DSH's module loader and
311
+ rendered into `sidebar.panellist` and `main`. It styles only with
312
+ `--dsw-alias-*` tokens.
313
+ - `client.js` is generated; edit `client/*.js` and run `npm run build:client`.
314
+
315
+ <details>
316
+ <summary><strong>Project layout</strong></summary>
317
+
318
+ ```text
319
+ cait/
320
+ ├─ package.json
321
+ ├─ cordis.patch.yml
322
+ ├─ index.js # Host half
323
+ ├─ client.js # generated Web bundle
324
+ ├─ client/ # helpers, styles, RPC, components, locales, entry
325
+ ├─ scripts/build-client.mjs
326
+ ├─ icon.svg
327
+ ├─ locale/{en,zh,ja}.json
328
+ ├─ NOTICE
329
+ ├─ README.md
330
+ ├─ README.zh-CN.md
331
+ ├─ README.ja.md
332
+ ├─ CHANGELOG.md
333
+ ├─ lib/
334
+ │ ├─ store.js # CatStore facade + document/segment core
335
+ │ ├─ errors.js
336
+ │ ├─ store-utils.js
337
+ │ ├─ libraries.js # TM/glossary registries + mounts
338
+ │ ├─ tm.js
339
+ │ ├─ glossary.js
340
+ │ ├─ persistence.js
341
+ │ ├─ import-export.js
342
+ │ ├─ formats.js # CSV/SRT/VTT/PO/XLIFF/HTML/JSON/RTF
343
+ │ ├─ html.js # Shared HTML/XHTML run scanner + patcher
344
+ │ ├─ epub.js # EPUB package read / spine import / patch export
345
+ │ ├─ preprocess.js # Import-time source cleaning stage
346
+ │ ├─ xlsx.js
347
+ │ ├─ pptx.js
348
+ │ ├─ segmenter.js
349
+ │ ├─ similarity.js
350
+ │ ├─ tmx.js
351
+ │ ├─ docx.js
352
+ │ ├─ markdown.js
353
+ │ ├─ xml.js
354
+ │ ├─ mt.js
355
+ │ ├─ tools.js
356
+ │ └─ api.js
357
+ └─ _dev/ # tests, fixtures, local patch overlay
358
+ ```
359
+
360
+ </details>
361
+
362
+ ## Development
363
+
364
+ Requires Node.js 22+ and DSH `0.2.x`. Tested with DSH `0.2.2-rc.1` and Node
365
+ `26.7.0`.
366
+
367
+ ```sh
368
+ npm run build:client
369
+ node --check client.js
370
+
371
+ node _dev/test-core.mjs
372
+ node _dev/test-imports.mjs
373
+ node _dev/test-formats.mjs
374
+ node _dev/test-epub.mjs
375
+ node _dev/test-preprocess.mjs
376
+ node _dev/test-second-tier.mjs
377
+ node _dev/test-docx-export.mjs
378
+ node _dev/test-api.mjs
379
+ node _dev/test-plugin.mjs
380
+ node _dev/test-persistence.mjs
381
+ node _dev/test-documents.mjs
382
+ node _dev/test-libraries.mjs
383
+ node _dev/test-mounts.mjs
384
+ node _dev/test-ui-scale.mjs
385
+ node _dev/test-mt.mjs
386
+ ```
387
+
388
+ The import/export fixtures are `_dev/sample.docx`, `_dev/sample-table.docx`, and
389
+ `_dev/sample-format.docx`; regenerate them with the matching
390
+ `python _dev/make-*.py` scripts. All 15 suites should pass.
391
+
392
+ ### Releasing
393
+
394
+ `npm publish` runs `prepublishOnly`, which rebuilds `client.js` from the
395
+ `client/` fragments and syntax-checks the result, so a release cannot ship a
396
+ stale bundle. Publishing from the repository root:
397
+
398
+ ```sh
399
+ npm publish
400
+ ```
401
+
402
+ `.github/workflows/publish.yml` does the same from CI on a published GitHub
403
+ release, using npm Trusted Publishing (OIDC) instead of a stored token, and it
404
+ refuses to publish when the release tag does not match `package.json`.
405
+
406
+ The very first release has to be published locally, because a trusted publisher
407
+ can only be configured for a package that already exists:
408
+
409
+ ```sh
410
+ npm login
411
+ npm publish
412
+ ```
413
+
414
+ Afterwards open the package on npmjs.com, go to
415
+ **Settings → Trusted publishing**, and add the repository `JoviLecca/dsh-CAIT`
416
+ with the workflow file `publish.yml`. Later releases can then be published from
417
+ GitHub with a release.
418
+
419
+ ## Security model
420
+
421
+ - The Host API is mounted at `/cait/api`, restricted to same-origin/trusted
422
+ requests by `ctx.connection.requestRejection`.
423
+ - RPC bodies are JSON, capped at 32 MB, and return `413 BODY_TOO_LARGE` beyond
424
+ that.
425
+ - `clearTM` / `clearGlossary` require `params.confirm === true` in addition to
426
+ the browser trust check; the UI asks before sending it.
427
+ - CAIT never receives, stores, or returns an API key. DSH credentials stay inside
428
+ the selected provider adapter.
429
+ - Persistence is one snapshot with last-write-wins semantics; it is not a
430
+ multi-user journal.
431
+
432
+ ## Known limits and roadmap
433
+
434
+ - No PDF import; PDF is intentionally out of scope (see
435
+ [PDF is not supported](#pdf-is-not-supported)). No image OCR yet.
436
+ - EPUB import follows the spine and body XHTML text blocks; DRM-protected,
437
+ fixed-layout, scripted, and MathML-heavy books are not supported.
438
+ - DOCX import skips tables, headers, footnotes, fields, alt text, and tracked
439
+ deletions. DOCX export leaves those regions in the source language.
440
+ - Mixed-run DOCX/PPTX paragraphs inherit the first run's formatting.
441
+ - PPTX import covers body text, not notes, charts, or SmartArt.
442
+ - XLSX import/export is a two-column table; cell styles, formulas, merged cells,
443
+ images, and extra sheets are not preserved.
444
+ - HTML/RTF export does not round-trip original styling; JSON translates string
445
+ values only.
446
+ - TM matching is lexical, not semantic; no stemming, synonyms, or embeddings.
447
+ - Library rename/delete and per-tab mount state are not implemented yet.
448
+ - The workbench UI ships in Chinese, English, and Japanese, switchable in
449
+ Settings; this README ships in English, Simplified Chinese, and Japanese, and
450
+ the English version is the source of truth.
451
+
452
+ Planned next: image/vision input through a multimodal DSH route, richer library
453
+ management, style-preserving XLSX output, and XLIFF `<alt-trans>` / notes support.
454
+
455
+ ## Changelog and versioning
456
+
457
+ See [CHANGELOG.md](CHANGELOG.md). `v1.0.0` was the first public release;
458
+ `v1.0.1` adds the Japanese UI and the in-app interface language switch,
459
+ `v1.0.2` makes *Follow DSH language* honour the DSH locale, `v1.0.3`
460
+ publishes the package to npm, `v1.0.4` moves later releases to the
461
+ provenance-publishing workflow, and `v1.0.5` adds EPUB, import preprocessing,
462
+ and the PDF policy. The project was developed as `0.1.0` and
463
+ renumbered for the GitHub release.
464
+
465
+ > **License:** Apache License 2.0 — see [LICENSE](LICENSE). Redistributions must
466
+ > retain the attribution in [NOTICE](NOTICE).