@shipi18n/core 2.3.0 → 2.4.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.
- package/CHANGELOG.md +12 -0
- package/README.md +12 -4
- package/package.json +6 -2
- package/src/adapters/index.js +22 -6
- package/src/tree.js +11 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# @shipi18n/core
|
|
2
2
|
|
|
3
|
+
## 2.4.0
|
|
4
|
+
|
|
5
|
+
- Fix: `runSemantic` now returns `excluded` — the number of pairs it skipped because the key already
|
|
6
|
+
carried a structural error. Without it a fully-broken tree reported `judged 0` and was
|
|
7
|
+
indistinguishable from a clean one, which reads as a dead feature rather than correct behaviour.
|
|
8
|
+
- Fix: the missing-SDK error names a fix that actually works. `npm i @anthropic-ai/sdk` does nothing
|
|
9
|
+
for the `npx @shipi18n/cli` path — that copy of the CLI resolves imports against npm's cache, not
|
|
10
|
+
your project — so the message now says to install the SDK next to the CLI and run `npx shipi18n`.
|
|
11
|
+
- Note: the npm description on this page was stale until this release. npm only refreshes it on
|
|
12
|
+
publish, so the registry still described a translation engine after the project had repositioned
|
|
13
|
+
around translation QA.
|
|
14
|
+
|
|
3
15
|
## 2.3.0
|
|
4
16
|
|
|
5
17
|
- New: manual-translation locks (`lockId`, `lockEntry`, `lockFinding`, `normalizeLocks`) — record
|
package/README.md
CHANGED
|
@@ -1,11 +1,19 @@
|
|
|
1
1
|
# @shipi18n/core
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
**The engine behind Shipi18n's translation QA** — placeholder and plural validation, key parity,
|
|
4
|
+
coverage and an LLM-as-judge semantic review — plus a structure-preserving translation engine. Open
|
|
5
|
+
source, **bring your own LLM**, no account and no hosted API.
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
|
-
npm i @shipi18n/core
|
|
8
|
+
npm i @shipi18n/core # checking needs nothing else
|
|
9
|
+
npm i @shipi18n/core @anthropic-ai/sdk # add a provider SDK to translate or judge
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
```js
|
|
13
|
+
import { runCheck } from '@shipi18n/core'
|
|
14
|
+
|
|
15
|
+
// deterministic, no model, no key
|
|
16
|
+
const { languages, totals } = runCheck({ input: './locales', source: 'en' })
|
|
9
17
|
```
|
|
10
18
|
|
|
11
19
|
## Quickstart
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shipi18n/core",
|
|
3
|
-
"version": "2.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "2.4.0",
|
|
4
|
+
"description": "Translation QA for i18n locale files: placeholder and plural validation, key parity, coverage, and an LLM-as-judge semantic review. Also a structure-preserving translation engine — bring your own OpenAI or Anthropic key.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.js",
|
|
7
7
|
"exports": {
|
|
@@ -20,6 +20,10 @@
|
|
|
20
20
|
"access": "public"
|
|
21
21
|
},
|
|
22
22
|
"keywords": [
|
|
23
|
+
"validation",
|
|
24
|
+
"lint",
|
|
25
|
+
"translation-quality",
|
|
26
|
+
"i18n-qa",
|
|
23
27
|
"i18n",
|
|
24
28
|
"translation",
|
|
25
29
|
"llm",
|
package/src/adapters/index.js
CHANGED
|
@@ -10,6 +10,26 @@
|
|
|
10
10
|
* @property {string} name
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
+
/**
|
|
14
|
+
* The SDKs are optional peer deps, so a missing one is the single most common
|
|
15
|
+
* first-run failure. Naming `npm i <sdk>` alone is a trap for the npx path:
|
|
16
|
+
* `npx @shipi18n/cli` runs the CLI out of npm's throwaway cache, which resolves
|
|
17
|
+
* imports against itself and never sees the project's node_modules. The only
|
|
18
|
+
* fix that works there is installing both, then running the local binary.
|
|
19
|
+
* @param {string} provider
|
|
20
|
+
* @param {string} sdk
|
|
21
|
+
* @returns {Error}
|
|
22
|
+
*/
|
|
23
|
+
function missingSdkError(provider, sdk) {
|
|
24
|
+
return new Error(
|
|
25
|
+
`The '${provider}' provider requires the '${sdk}' package.\n` +
|
|
26
|
+
` Install it next to the CLI: npm i -D @shipi18n/cli ${sdk}\n` +
|
|
27
|
+
` then run: npx shipi18n <command>\n` +
|
|
28
|
+
` If you ran 'npx @shipi18n/cli', installing ${sdk} on its own will not help — ` +
|
|
29
|
+
`that copy of the CLI cannot see your project's node_modules.`
|
|
30
|
+
)
|
|
31
|
+
}
|
|
32
|
+
|
|
13
33
|
/**
|
|
14
34
|
* Anthropic Claude adapter. Requires the optional peer dep `@anthropic-ai/sdk`.
|
|
15
35
|
* Key resolved from opts.apiKey or the ANTHROPIC_API_KEY env var (SDK default).
|
|
@@ -24,9 +44,7 @@ export function anthropicAdapter(config = {}) {
|
|
|
24
44
|
clientPromise = import('@anthropic-ai/sdk')
|
|
25
45
|
.then(({ default: Anthropic }) => new Anthropic(config.apiKey ? { apiKey: config.apiKey } : {}))
|
|
26
46
|
.catch(() => {
|
|
27
|
-
throw
|
|
28
|
-
"The 'anthropic' provider requires the '@anthropic-ai/sdk' package. Install it with: npm i @anthropic-ai/sdk"
|
|
29
|
-
)
|
|
47
|
+
throw missingSdkError('anthropic', '@anthropic-ai/sdk')
|
|
30
48
|
})
|
|
31
49
|
}
|
|
32
50
|
return clientPromise
|
|
@@ -63,9 +81,7 @@ export function openaiAdapter(config = {}) {
|
|
|
63
81
|
clientPromise = import('openai')
|
|
64
82
|
.then(({ default: OpenAI }) => new OpenAI(config.apiKey ? { apiKey: config.apiKey } : {}))
|
|
65
83
|
.catch(() => {
|
|
66
|
-
throw
|
|
67
|
-
"The 'openai' provider requires the 'openai' package. Install it with: npm i openai"
|
|
68
|
-
)
|
|
84
|
+
throw missingSdkError('openai', 'openai')
|
|
69
85
|
})
|
|
70
86
|
}
|
|
71
87
|
return clientPromise
|
package/src/tree.js
CHANGED
|
@@ -313,11 +313,15 @@ export function runCheck({ input, source = 'en', ignoreKeys, glossary, locks } =
|
|
|
313
313
|
* placeholder. Semantic findings are WARNINGS unless `fail` is set; a noisy
|
|
314
314
|
* gate that blocks PRs gets uninstalled.
|
|
315
315
|
*
|
|
316
|
-
*
|
|
316
|
+
* `excluded` counts the pairs skipped for that reason. It exists so callers can
|
|
317
|
+
* tell "nothing was wrong" apart from "everything was too wrong to judge" — a
|
|
318
|
+
* fully-broken tree otherwise reports `judged 0` and reads like a dead feature.
|
|
319
|
+
*
|
|
320
|
+
* @returns aggregated judge stats { judged, cached, flagged, calls, parseFailures, excluded }
|
|
317
321
|
*/
|
|
318
322
|
|
|
319
323
|
export async function runSemantic(result, { provider, apiKey, model, passes, glossary, cache, fail = false }) {
|
|
320
|
-
const totals = { judged: 0, cached: 0, flagged: 0, calls: 0, parseFailures: 0 }
|
|
324
|
+
const totals = { judged: 0, cached: 0, flagged: 0, calls: 0, parseFailures: 0, excluded: 0 }
|
|
321
325
|
|
|
322
326
|
for (const l of result.languages) {
|
|
323
327
|
const pairs = result.semanticPairs?.[l.lang]
|
|
@@ -331,7 +335,10 @@ export async function runSemantic(result, { provider, apiKey, model, passes, glo
|
|
|
331
335
|
const src = {}
|
|
332
336
|
const tgt = {}
|
|
333
337
|
for (const key of Object.keys(pairs.source)) {
|
|
334
|
-
if (errorPaths.has(key))
|
|
338
|
+
if (errorPaths.has(key)) {
|
|
339
|
+
totals.excluded++
|
|
340
|
+
continue
|
|
341
|
+
}
|
|
335
342
|
src[key] = pairs.source[key]
|
|
336
343
|
tgt[key] = pairs.target[key]
|
|
337
344
|
}
|
|
@@ -341,7 +348,7 @@ export async function runSemantic(result, { provider, apiKey, model, passes, glo
|
|
|
341
348
|
source: src, target: tgt, from: result.source, to: l.lang,
|
|
342
349
|
provider, apiKey, model, passes, glossary, cache,
|
|
343
350
|
})
|
|
344
|
-
for (const k of Object.keys(
|
|
351
|
+
for (const k of Object.keys(stats)) totals[k] = (totals[k] ?? 0) + (stats[k] ?? 0)
|
|
345
352
|
|
|
346
353
|
for (const f of findings) {
|
|
347
354
|
const sepAt = f.path.indexOf(SEP)
|