@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 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
- Open-source, **bring-your-own-LLM** i18n translation engine. Translate locale JSON with your own
4
- OpenAI or Anthropic key no Shipi18n account, no hosted API, no per-word fees. Provider-agnostic and
5
- extensible.
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 @anthropic-ai/sdk # or: npm i @shipi18n/core openai
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.3.0",
4
- "description": "Open-source, bring-your-own-LLM i18n translation engine. Provider-agnostic (OpenAI, Anthropic, ...).",
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",
@@ -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 new Error(
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 new Error(
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
- * @returns aggregated judge stats { judged, cached, flagged, calls, parseFailures }
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)) continue
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(totals)) totals[k] += stats[k] ?? 0
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)