@shipi18n/cli 2.4.0 → 2.5.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,13 @@
1
1
  # @shipi18n/cli
2
2
 
3
+ ## 2.5.0
4
+
5
+ - New: `--base-url <url>` on `translate` and `check` — run against any OpenAI-compatible endpoint
6
+ with `-p openai`: Ollama (`http://localhost:11434/v1`, no API key at all), Gemini's compatibility
7
+ endpoint, Groq, LM Studio, vLLM. Makes the API key optional; the judge's published accuracy was
8
+ measured on `claude-haiku-4-5`, so run `evals/semantic/run.mjs` before trusting a different judge.
9
+ - Guard: `--base-url` without `-p openai` is an explicit error rather than a silent provider switch.
10
+
3
11
  ## 2.4.0
4
12
 
5
13
  - Fix: `check --semantic` explains a `judged 0`. When every translated key has a structural error
package/README.md CHANGED
@@ -56,6 +56,7 @@ Options:
56
56
  -p, --provider <name> LLM provider: anthropic (default) or openai
57
57
  --api-key <key> LLM API key (else ANTHROPIC_API_KEY / OPENAI_API_KEY env)
58
58
  --model <model> Override the provider's default model
59
+ --base-url <url> OpenAI-compatible endpoint (Ollama, Gemini compat, ...); needs -p openai
59
60
  -i, --incremental Reuse existing output files; only translate new/missing keys
60
61
  ```
61
62
 
@@ -70,6 +71,13 @@ shipi18n translate en.json -p openai -t de --api-key $OPENAI_API_KEY
70
71
 
71
72
  # Incremental — only translate keys not already in the target file
72
73
  shipi18n translate en.json -t es --incremental
74
+
75
+ # Ollama — fully local, no API key at all (any OpenAI-compatible server works)
76
+ shipi18n translate en.json -p openai --base-url http://localhost:11434/v1 --model llama3.2 -t es
77
+
78
+ # Google Gemini, via its OpenAI-compatible endpoint
79
+ shipi18n translate en.json -p openai --base-url https://generativelanguage.googleapis.com/v1beta/openai/ \
80
+ --model gemini-2.5-flash --api-key $GEMINI_API_KEY -t es
73
81
  ```
74
82
 
75
83
  ## Check — validate translations in CI (no LLM, no key)
@@ -204,6 +212,13 @@ A missing or corrupt lock file is a cold start, not a crash.
204
212
  Set `ANTHROPIC_API_KEY` (default provider) or use `-p openai` with `OPENAI_API_KEY`. Your keys, your
205
213
  models — nothing is sent to a Shipi18n server. Built on [`@shipi18n/core`](https://www.npmjs.com/package/@shipi18n/core).
206
214
 
215
+ `--base-url` points the OpenAI provider at **any OpenAI-compatible endpoint**: Ollama
216
+ (`http://localhost:11434/v1` — no key needed, fully offline), Gemini's compatibility endpoint, Groq,
217
+ Mistral, LM Studio, vLLM, or a corporate gateway. It works for `translate` and for the
218
+ `check --semantic` judge alike — pass `--model` for the model that server actually hosts. The judge's
219
+ published accuracy numbers were measured on `claude-haiku-4-5`; before trusting a different judge
220
+ model, run the eval harness in the repo (`evals/semantic/run.mjs`) against it.
221
+
207
222
  ## License
208
223
 
209
224
  Apache-2.0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shipi18n/cli",
3
- "version": "2.4.0",
3
+ "version": "2.5.0",
4
4
  "description": "Catch broken translations before you ship: dropped placeholders, missing keys, collapsed plurals and — with your own LLM key — mistranslations the structure checks cannot see. CI-ready (SARIF, JUnit, exit codes). Translates too.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -44,7 +44,7 @@
44
44
  "chalk": "^5.3.0",
45
45
  "commander": "^12.0.0",
46
46
  "ora": "^8.0.1",
47
- "@shipi18n/core": "^2.4.0"
47
+ "@shipi18n/core": "^2.5.0"
48
48
  },
49
49
  "peerDependencies": {
50
50
  "@anthropic-ai/sdk": ">=0.30.0",
@@ -73,6 +73,10 @@ export function checkCommand(program) {
73
73
  .option('-p, --provider <name>', 'LLM provider for --semantic: anthropic | openai', 'anthropic')
74
74
  .option('--api-key <key>', 'LLM API key for --semantic (else provider env var)')
75
75
  .option('--semantic-model <model>', 'Judge model override')
76
+ .option(
77
+ '--base-url <url>',
78
+ 'OpenAI-compatible endpoint for the judge (Ollama, Gemini compat, ...). Needs -p openai; makes the key optional'
79
+ )
76
80
  .option('--semantic-passes <n>', 'Judge passes for the majority vote', (v) => parseInt(v, 10), 3)
77
81
  .option('--semantic-cache <file>', 'Verdict cache path', '.shipi18n/semantic-cache.json')
78
82
  .option('--locks <file>', 'Manual-translation lock file', DEFAULT_LOCKS_PATH)
@@ -115,10 +119,16 @@ export function checkCommand(program) {
115
119
  }
116
120
  }
117
121
  try {
122
+ if (opts.baseUrl && opts.provider !== 'openai') {
123
+ console.error(chalk.red("--base-url targets OpenAI-compatible endpoints. Add -p openai."))
124
+ process.exitCode = 2
125
+ return
126
+ }
118
127
  const judge = await runSemantic(result, {
119
128
  provider: opts.provider,
120
129
  apiKey: opts.apiKey,
121
130
  model: opts.semanticModel,
131
+ baseURL: opts.baseUrl,
122
132
  passes: opts.semanticPasses,
123
133
  glossary,
124
134
  cache,
@@ -16,6 +16,10 @@ export function translateCommand(program) {
16
16
  .option('-p, --provider <name>', 'LLM provider: anthropic | openai', 'anthropic')
17
17
  .option('--api-key <key>', 'LLM API key (else read from provider env var)')
18
18
  .option('--model <model>', 'Override the provider default model')
19
+ .option(
20
+ '--base-url <url>',
21
+ 'OpenAI-compatible endpoint (Ollama, Gemini compat, Groq, a gateway). Needs -p openai; makes the key optional'
22
+ )
19
23
  .option('-i, --incremental', 'Only translate new/missing keys (reuse existing output files)')
20
24
  .action(async (input, options) => {
21
25
  const provider = options.provider
@@ -23,8 +27,18 @@ export function translateCommand(program) {
23
27
  console.error(chalk.red(`Unknown provider '${provider}'. Use 'anthropic' or 'openai'.`))
24
28
  process.exit(1)
25
29
  }
30
+ // --base-url speaks the OpenAI wire protocol, so it only makes sense with
31
+ // the openai adapter. Explicit beats magic: error rather than silently
32
+ // switching providers out from under the default.
33
+ if (options.baseUrl && provider !== 'openai') {
34
+ console.error(chalk.red(`--base-url targets OpenAI-compatible endpoints. Add ${chalk.yellow('-p openai')}.`))
35
+ console.error(chalk.gray('Works with Ollama (http://localhost:11434/v1), Gemini compat, Groq, LM Studio, vLLM…'))
36
+ process.exit(1)
37
+ }
26
38
  const apiKey = options.apiKey || process.env[PROVIDER_ENV[provider]]
27
- if (!apiKey) {
39
+ // Local/keyless endpoints (Ollama) need no key; a real provider behind
40
+ // --base-url will reject the request itself if one was required.
41
+ if (!apiKey && !options.baseUrl) {
28
42
  console.error(chalk.red(`No API key. Set ${chalk.yellow(PROVIDER_ENV[provider])} or pass --api-key.`))
29
43
  console.error(chalk.gray(`This tool uses YOUR own ${provider} key — no account or Shipi18n key needed.`))
30
44
  process.exit(1)
@@ -61,6 +75,7 @@ export function translateCommand(program) {
61
75
  provider,
62
76
  apiKey,
63
77
  model: options.model,
78
+ baseURL: options.baseUrl,
64
79
  existing,
65
80
  })
66
81
  writeFileSync(outPath, JSON.stringify(result, null, 2) + '\n', 'utf8')