claude-translator 1.4.0 → 2.0.0

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.
@@ -0,0 +1,14 @@
1
+ {
2
+ "$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json",
3
+ "name": "conveythis-translator",
4
+ "version": "2.0.0",
5
+ "description": "Localize a built static site into dozens of languages as real static pages, by substituting into the built HTML instead of re-rendering — so Core Web Vitals hold. Works with Astro, Next export, Hugo, Eleventy, Jekyll or plain HTML, via Claude, Gemini, any OpenAI-compatible endpoint, or a fully local model.",
6
+ "author": {
7
+ "name": "ConveyThis",
8
+ "url": "https://www.conveythis.com"
9
+ },
10
+ "homepage": "https://github.com/ConveyThis/claude-translator",
11
+ "repository": "https://github.com/ConveyThis/claude-translator",
12
+ "license": "AGPL-3.0-or-later",
13
+ "keywords": ["i18n", "l10n", "localization", "translation", "static-site", "hreflang", "seo"]
14
+ }
package/CHANGELOG.md CHANGED
@@ -5,6 +5,186 @@ All notable changes to this project are documented here.
5
5
  The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [2.0.0] — 2026-08-30
9
+
10
+ Answers to a set of questions from a localization professional, who asked what the tool
11
+ does about terminology, brand-name sense, register in tightly-spaced CTAs, locale number
12
+ conventions, and whether there was any TQA evidence at all. Four of those had no answer.
13
+ Now they do — and two README claims that did not survive being checked are corrected.
14
+
15
+ ### Added
16
+
17
+ - **A glossary.** `glossary` in the config, inline or a path to a JSON file. Two rules:
18
+ `keep` (leave in the source language, case-sensitive by default) and `translate` with
19
+ per-locale `targets`. Matching is whole-word, so `Apple` never matches inside
20
+ `Applesauce`, and `matchCase` is what separates **Apple** the company from **apple** the
21
+ fruit — a distinction the old flat `doNotTranslate` list could not express.
22
+
23
+ Only the terms present in a batch are injected into that batch's prompt, so a large
24
+ glossary does not inflate every request. `doNotTranslate.brands` is folded in
25
+ automatically as case-sensitive `keep` rules, so existing configs gain word-boundary
26
+ matching and verification without being edited.
27
+
28
+ Changing a term re-translates **only** the units that contain it, tracked by a
29
+ fingerprint sidecar at `i18n/tm/{lang}.meta.json`. It is a sidecar rather than a key
30
+ inside the memory because the memory is documented as a hand-editable `hash -> string`
31
+ map, and three other scripts iterate it — a `__meta` key would have made every coverage
32
+ count off by one.
33
+
34
+ - **Locale conventions for numbers, percentages and currency.** `localeFormat`. The model
35
+ is still told to leave numbers alone; `Intl` reformats them afterwards, deterministically.
36
+ `1,234.56` becomes `1.234,56` in German, `$5` becomes `5,00 $US` in French.
37
+
38
+ **Currency is formatted, never converted, and there is no option to convert it.** Every
39
+ monetary amount found is written to `i18n/locale-format.json` for a human to price per
40
+ market instead. Version numbers, times, IP addresses, ISO dates, phone numbers, fractions
41
+ and ungrouped numbers are deliberately left untouched — during development a greedy
42
+ pattern turned `192.168.1.1` into `1.921.681,1`, which is why only unambiguous quantities
43
+ are matched and why that case is now a test.
44
+
45
+ - **`tqa.mjs` — MQM translation quality assessment.** Scores a seeded, frequency-stratified
46
+ sample using the standard MQM typology and severity weights (minor 1, major 5,
47
+ critical 10). Writes `i18n/tqa/{lang}.json` and a Markdown scorecard.
48
+
49
+ The judge defaults to a **different provider than the translator**, because models prefer
50
+ their own output; when no second key is available it says so in the run and in the
51
+ report. `--repeat` scores the same sample twice and reports the gap, because a quality
52
+ number without its variance is marketing. A unit the judge cannot assess is **excluded,
53
+ not counted as clean** — an early version printed `100.00 / 100` from a sample where
54
+ every single unit had failed, and it now refuses to report a score at all in that case.
55
+
56
+ - **Element context for register (`el`).** The model now learns *what* a string is, not
57
+ only what it says. `extract.mjs` always knew — it walks the DOM with `node.tagName` in
58
+ hand — and threw it away, so rule 5's "Headings stay headings; button labels stay short"
59
+ was unenforceable: every string arrived looking like prose.
60
+
61
+ A `<button>`, a standalone `<a>`, a heading, a `<label>`, an `alt` attribute and a meta
62
+ description now each carry a short label, and the prompt gains a CONTEXT block describing
63
+ **only the roles present in that batch**. Ordinary prose sends no field at all, so a site
64
+ of nothing but paragraphs produces a byte-identical payload to 1.x.
65
+
66
+ Two deliberate restraints. The button guidance does **not** demand the imperative — German
67
+ UI prefers a verbal noun and French the infinitive, and ordering a literal command
68
+ everywhere is the exact defect this exists to avoid. And when the same string appears as
69
+ both a button and a paragraph, the hint is **cleared** rather than guessed: one hash means
70
+ one translation, and a confident wrong answer is worse than none.
71
+
72
+ `kind` is untouched. It looks like the natural place for this, but `build-locales.mjs`
73
+ switches on it to choose an escaper, and a value like `'block:button'` would fall through
74
+ to `escHtml` and print literal `&lt;0&gt;` placeholder tokens as visible text on every
75
+ localized page — with no test to catch it. The element travels in a new field instead.
76
+
77
+ **No translation memory is invalidated.** Unit hashes are computed from text alone, and a
78
+ fixture run proves the hashes are byte-identical before and after the change.
79
+
80
+ - **Gate 7 — glossary compliance.** Reports terms that were supposed to survive, or to be
81
+ rendered a particular way, and were not. Reports by default; `--strict` makes it fail the
82
+ build. It only warns because target languages inflect pinned terms, and
83
+ `references/quality-review.md` is explicit that over-flagging is worse than no check.
84
+
85
+ - **Gate 8 — numeric integrity.** Fails the build when the *value* of a number changes
86
+ between source and translation. A model that ships `$39` where the source said `$49`
87
+ passes every other gate: identical markup, matching placeholders, plausible length,
88
+ fluent target language.
89
+
90
+ ### Fixed
91
+
92
+ - **The README claimed to emit things it only rewrites.** "What you get" promised "a
93
+ complete `hreflang` set" and "per-locale sitemaps". Neither is generated: the only write
94
+ into the build directory is one HTML file per page, and every locale-identity rule
95
+ *replaces an attribute on a tag the template already emits*. The section is now split
96
+ into what the tool rewrites and what your template must supply — and it explains that
97
+ the rewrite-only design is what preserves byte-identical markup.
98
+
99
+ - **`i18nDir` silently did nothing** in `verify.mjs` and `audit-seo.mjs`, which imported
100
+ `I18N_DIR` and then used hardcoded `i18n/...` paths. Same for `$I18N_ROOT`, which three
101
+ scripts ignored in favour of `process.cwd()`.
102
+
103
+ - **The generator tag stamped a stale version** — `1.4.0` while the package said `1.5.0`.
104
+ It cannot be read from `package.json` (once vendored, the nearest manifest is the user's
105
+ own application), so CI now asserts the literal matches instead.
106
+
107
+ - **`init` vendored our own contract tests** into the user's project, where they import a
108
+ test runner and assert on our internals.
109
+
110
+ ### Changed
111
+
112
+ - `verify.mjs` runs eight gates, not six.
113
+ - `init` scaffolds a starter `glossary.json`.
114
+ - CI now runs the fixture through build and all eight gates, not just extraction, and
115
+ checks that the packaged CLI scaffolds every new script.
116
+
117
+ > **Upgrading:** `init` copies the pipeline into your project, so an existing install
118
+ > keeps running the scripts it already has. Re-run `npx claude-translator init --force` to
119
+ > pick up the new stages. Nothing in this release changes existing translations, and no
120
+ > memory is invalidated unless you add a glossary.
121
+
122
+ ## [1.5.0] — 2026-08-25
123
+
124
+ ### Added
125
+
126
+ - **The repo is now a Claude Code plugin, not only a skill.** A `.claude-plugin/plugin.json`
127
+ manifest gives it the name, version, description and licence the plugin directory needs in
128
+ order to list it. The plugin is named `conveythis-translator`, so the skill is invoked as
129
+ `/conveythis-translator:translate-site`
130
+
131
+ - **README section on installing it as a plugin**, covering the community-marketplace install,
132
+ `claude --plugin-dir` for a local clone, and the `~/.claude/skills/` directory-plugin path
133
+
134
+ ### Changed
135
+
136
+ - **`SKILL.md` moved from the repo root to `skills/translate-site/SKILL.md`.** This was forced,
137
+ not cosmetic. The plugin documentation states that a single-skill plugin may keep `SKILL.md` at
138
+ the plugin root, but on Claude Code 2.1.126 that skill is never discovered — verified against
139
+ three variants (manifest present, manifest absent, and `"skills": "./"`), all of which loaded
140
+ no skill at all. The conventional `skills/<name>/SKILL.md` layout loads correctly
141
+
142
+ - **`references/` moved into `skills/translate-site/references/`, so the skill directory is
143
+ self-contained.** This keeps both install paths working from one copy: as a plugin, and as a
144
+ plain personal skill symlinked into `~/.claude/skills/`. The reference links in the skill stay
145
+ plain relative paths; only `LICENSING.md` and `i18n.config.example.json`, which remain at the
146
+ repo root, are addressed as `${CLAUDE_PLUGIN_ROOT}/…`
147
+
148
+ - Contrary to what an earlier draft of this entry claimed, a directory in `~/.claude/skills/`
149
+ carrying a `.claude-plugin/plugin.json` is **not** auto-loaded as a plugin on 2.1.126 —
150
+ `claude plugin list` reports nothing installed. The README now documents symlinking the skill
151
+ directory itself, which is verified to work
152
+
153
+ - The skill's frontmatter `name` is now `translate-site` rather than `claude-translator`, so the
154
+ namespaced invocation does not read `/conveythis-translator:claude-translator`
155
+
156
+ - `package.json` `files` ships `.claude-plugin/` and `skills/` in place of the root `SKILL.md`
157
+
158
+ ### Fixed
159
+
160
+ - **The Pipeline commands in `SKILL.md` pointed at the wrong directory.** They read
161
+ `node scripts/extract.mjs`, but `claude-translator init` scaffolds into `scripts/i18n/`
162
+ (its `--dir` default), as the README has always shown correctly. Every command in that block
163
+ would have failed with `Cannot find module` for anyone following the skill rather than the
164
+ README
165
+
166
+ - **Setup instructions assumed a hand-clone.** The skill hardcoded
167
+ `~/.claude/skills/claude-translator/bin/claude-translator.mjs`, a path that does not exist for
168
+ a plugin install. It now leads with `npx claude-translator init` and offers
169
+ `"${CLAUDE_PLUGIN_ROOT}"/bin/claude-translator.mjs` as the offline fallback, which resolves to
170
+ wherever the plugin actually landed
171
+
172
+ ### Verified
173
+
174
+ - `claude plugin validate .` passes with no errors and no warnings on Claude Code 2.1.126.
175
+ Note that this version rejects both `displayName` and `"skills": ["."]`, which the current
176
+ plugin reference documents — neither is used here
177
+ - Loaded through `claude --plugin-dir`, the skill is discovered and namespaced correctly. The
178
+ check used a uniquely renamed copy of the skill, so a same-named skill already installed in
179
+ `~/.claude/skills/` could not be mistaken for it
180
+ - `${CLAUDE_PLUGIN_ROOT}` resolution and the corrected setup line were confirmed end to end:
181
+ running the documented command against a throwaway project scaffolds `scripts/i18n/` with all
182
+ nine scripts, `i18n.config.json`, and the `.gitignore` entries
183
+ - Both install paths were checked against the final layout: the plugin loads through
184
+ `--plugin-dir`, and the skill directory symlinked into `~/.claude/skills/` is discovered too
185
+ - `npm run check` and `npm test` (39 tests) pass unchanged; `npm pack` ships
186
+ `.claude-plugin/plugin.json`, the skill and its references
187
+
8
188
  ## [1.4.0] — 2026-08-25
9
189
 
10
190
  ### Changed
package/PRIVACY.md ADDED
@@ -0,0 +1,71 @@
1
+ # Privacy
2
+
3
+ Short version: this tool runs on your machine, and it sends us nothing.
4
+
5
+ The canonical policy is the **Open-Source and Downloadable Software** section of the
6
+ [ConveyThis Privacy Policy](https://www.conveythis.com/legal/privacy-policy#open-source-and-downloadable-software).
7
+ This file is the code-adjacent summary, and it is meant to be checkable against the source.
8
+
9
+ ## What it sends, and where
10
+
11
+ There is exactly **one** outbound network call in this repository. It lives in
12
+ [`scripts/translate.mjs`](scripts/translate.mjs), and its URL comes from whichever provider
13
+ adapter you selected in `i18n.config.json`:
14
+
15
+ | Provider | Endpoint | Key it reads |
16
+ | --- | --- | --- |
17
+ | `anthropic` (default) | `https://api.anthropic.com/v1` | `ANTHROPIC_API_KEY` |
18
+ | `gemini` | `https://generativelanguage.googleapis.com/v1beta/models` | `GEMINI_API_KEY` / `GOOGLE_API_KEY` |
19
+ | `openai` | `https://api.openai.com/v1`, or whatever you set as `apiBaseUrl` | `OPENAI_API_KEY` / `OPENAI_COMPATIBLE_API_KEY` |
20
+
21
+ Only `translate.mjs` makes any request at all. `extract`, `review`, `build-locales`, `verify`
22
+ and `audit-seo` are entirely offline — they read and write files on your disk and nothing else.
23
+
24
+ Verify it yourself:
25
+
26
+ ```bash
27
+ grep -rn "fetch(" scripts bin # one hit, in translate.mjs
28
+ ```
29
+
30
+ ## What it never sends
31
+
32
+ - **Nothing goes to ConveyThis.** No analytics, no usage reporting, no error reporting, no
33
+ licence check. We do not know you installed this, ran it, or what you translated.
34
+ - **Your API key goes only to the provider it belongs to.** It is read from the environment or a
35
+ gitignored `.env`, used as that provider's auth header, and never written to output.
36
+ - **Your content goes only to the provider you chose.** Translating means sending your page text
37
+ to a model, which is inherent to the task — but it goes there directly from your machine.
38
+
39
+ ## Running with zero network egress
40
+
41
+ Set the `openai` provider against a local endpoint and nothing leaves the machine:
42
+
43
+ ```json
44
+ { "provider": "openai", "apiBaseUrl": "http://localhost:11434/v1", "model": "qwen2.5:14b" }
45
+ ```
46
+
47
+ Ollama, LM Studio and vLLM all work this way. No key, no quota, no third party. Use this if the
48
+ content is confidential.
49
+
50
+ ## The attribution tag
51
+
52
+ Generated pages carry a `<meta name="generator">` tag and an HTML comment naming ConveyThis,
53
+ written by [`scripts/credit.mjs`](scripts/credit.mjs). Both are **static markup**: they make no
54
+ request, load no script, set no cookie, and report nothing to us when someone views your page.
55
+ The `conveythis.com` and `doctranslator.com` strings in that file are written into HTML — they
56
+ are never fetched.
57
+
58
+ To remove them, set `credit.generatorTag` and `credit.htmlComment` to `false` in
59
+ `i18n.config.json`.
60
+
61
+ ## Your translation memory
62
+
63
+ `i18n/tm/{lang}.json` holds your source and translated copy, and is meant to be committed —
64
+ losing it means paying to re-translate. Check that this suits your repository's visibility
65
+ before committing it to a public repo.
66
+
67
+ ## Elsewhere
68
+
69
+ Opening issues or pull requests on GitHub, or installing from npm, is governed by those
70
+ platforms' own privacy policies. Security reports go to **security@conveythis.com** — see
71
+ [SECURITY.md](SECURITY.md).