{"_id":"@dennish02/ai-translate","_rev":"5-15a1c23e412805906e1405a6077490b8","name":"@dennish02/ai-translate","dist-tags":{"latest":"0.1.5"},"versions":{"0.1.1":{"name":"@dennish02/ai-translate","version":"0.1.1","keywords":["i18n","internationalization","translation","ai","llm","openrouter","anthropic","claude","openai"],"license":"MIT","_id":"@dennish02/ai-translate@0.1.1","maintainers":[{"name":"dennish02","email":"dennishesler02@gmail.com"}],"bin":{"ai-translate":"dist/cli.js"},"dist":{"shasum":"cbf51177941d312087d24d4ad8144b96bc139d2e","tarball":"https://registry.npmjs.org/@dennish02/ai-translate/-/ai-translate-0.1.1.tgz","fileCount":10,"integrity":"sha512-+/n+0BhqAsgbK6Clx0nBbTNEuIpcvGh/ly4wrx16oaZ6Lu4hJsxDMQnIjlppCHJDY9KNCZmT8/nfSO3Jh3ElNw==","signatures":[{"sig":"MEQCIH71jj/WmlMaCbd2aGwPvBGk7mVcXo/sU9VKHh7Gzhc1AiAlIOFfL+NAPsHPwQvs2JASLceLLiNitDTL5N6NcP0w7Q==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":88355},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","module":"./dist/index.js","engines":{"node":">=18"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"gitHead":"adf93621d09823ceb47a0d960fe7b8434844ebe4","scripts":{"dev":"tsup --watch","test":"vitest run","build":"tsup","typecheck":"tsc --noEmit","test:watch":"vitest","prepublishOnly":"npm run build"},"_npmUser":{"name":"dennish02","email":"dennishesler02@gmail.com"},"_npmVersion":"11.2.0","description":"i18n con traducción automática por IA (OpenRouter / Claude / GPT). CLI build-time + runtime opcional.","directories":{},"_nodeVersion":"22.22.1","dependencies":{"jiti":"^2.4.2"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"tsup":"^8.3.5","vitest":"^2.1.8","typescript":"^5.7.2","@types/node":"^22.10.0"},"peerDependencies":{"@huggingface/transformers":"^3.0.0"},"peerDependenciesMeta":{"@huggingface/transformers":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/ai-translate_0.1.1_1782264080104_0.7046848213236929","host":"s3://npm-registry-packages-npm-production"}},"0.1.2":{"name":"@dennish02/ai-translate","version":"0.1.2","keywords":["i18n","internationalization","translation","ai","llm","openrouter","anthropic","claude","openai"],"license":"MIT","_id":"@dennish02/ai-translate@0.1.2","maintainers":[{"name":"dennish02","email":"dennishesler02@gmail.com"}],"bin":{"ai-translate":"dist/cli.js"},"dist":{"shasum":"0ead8347eae981c47f9dc0b97c9f9221f82fe154","tarball":"https://registry.npmjs.org/@dennish02/ai-translate/-/ai-translate-0.1.2.tgz","fileCount":10,"integrity":"sha512-B4aYJ93RRarwft2KZ+YUkVqyvYtIEq0QqbB0iMhja6r+7jtuAnh1cgl1oMi5c7inFjcEA9f3QgHGNtp2AokmvA==","signatures":[{"sig":"MEQCIDc/ZF5uVi7m1iFz0PzfZFMm7ZlIr9uBovysy4i2xgjPAiB7BNPqRUSn1N/lrBYnedhbiavSqJUtUBwrHPQUIW2qJQ==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":95695},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","module":"./dist/index.js","engines":{"node":">=18"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"gitHead":"038a075aa2071c7048973f25bab3c2313ddfcd1c","scripts":{"dev":"tsup --watch","test":"vitest run","build":"tsup","typecheck":"tsc --noEmit","test:watch":"vitest","prepublishOnly":"npm run build"},"_npmUser":{"name":"dennish02","email":"dennishesler02@gmail.com"},"_npmVersion":"11.2.0","description":"i18n con traducción automática por IA (OpenRouter / Claude / GPT). CLI build-time + runtime opcional.","directories":{},"_nodeVersion":"22.22.1","dependencies":{"jiti":"^2.4.2"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"tsup":"^8.3.5","vitest":"^2.1.8","typescript":"^5.7.2","@types/node":"^22.10.0"},"peerDependencies":{"@huggingface/transformers":"^3.0.0"},"peerDependenciesMeta":{"@huggingface/transformers":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/ai-translate_0.1.2_1782267227993_0.2646485424222982","host":"s3://npm-registry-packages-npm-production"}},"0.1.3":{"name":"@dennish02/ai-translate","version":"0.1.3","keywords":["i18n","internationalization","translation","ai","llm","openrouter","anthropic","claude","openai"],"license":"MIT","_id":"@dennish02/ai-translate@0.1.3","maintainers":[{"name":"dennish02","email":"dennishesler02@gmail.com"}],"bin":{"ai-translate":"dist/cli.js"},"dist":{"shasum":"ec18cdc2953e1d4c37a7c84d39c4c544c3b3b03b","tarball":"https://registry.npmjs.org/@dennish02/ai-translate/-/ai-translate-0.1.3.tgz","fileCount":18,"integrity":"sha512-0mJLTqLu4TB2qnXGBydJ6m5a1YRGvBr6hUcRcWn+7sdVmGINujlWWbBvBa+UO2FYuOlYIw49iPFzHAPSN3BaQw==","signatures":[{"sig":"MEQCIF1sz6Gl+1Mys4rP8irbVznwRkZwJZYg5B7s6HEPi8LzAiAJyX2wM9woS4cnj/jpj5K+Nq4FWFPJiV16g+Kudnu2vw==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":107247},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","module":"./dist/index.js","engines":{"node":">=18"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"},"./runtime":{"types":"./dist/runtime.d.ts","import":"./dist/runtime.js"}},"gitHead":"bab862478b427e3d1329475c194de94f475cd477","scripts":{"dev":"tsup --watch","test":"vitest run","build":"tsup","typecheck":"tsc --noEmit","test:watch":"vitest","prepublishOnly":"npm run build"},"_npmUser":{"name":"dennish02","email":"dennishesler02@gmail.com"},"_npmVersion":"11.2.0","description":"i18n con traducción automática por IA (OpenRouter / Claude / GPT). CLI build-time + runtime opcional.","directories":{},"_nodeVersion":"22.22.1","dependencies":{"jiti":"^2.4.2"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"tsup":"^8.3.5","vitest":"^2.1.8","typescript":"^5.7.2","@types/node":"^22.10.0"},"peerDependencies":{"@huggingface/transformers":"^3.0.0"},"peerDependenciesMeta":{"@huggingface/transformers":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/ai-translate_0.1.3_1782269641742_0.05788410643172259","host":"s3://npm-registry-packages-npm-production"}},"0.1.4":{"name":"@dennish02/ai-translate","version":"0.1.4","keywords":["i18n","internationalization","translation","ai","llm","openrouter","anthropic","claude","openai"],"license":"MIT","_id":"@dennish02/ai-translate@0.1.4","maintainers":[{"name":"dennish02","email":"dennishesler02@gmail.com"}],"bin":{"ai-translate":"dist/cli.js"},"dist":{"shasum":"cf01d95bb2ea036c8814df149e515d9249c71d82","tarball":"https://registry.npmjs.org/@dennish02/ai-translate/-/ai-translate-0.1.4.tgz","fileCount":18,"integrity":"sha512-ez2I3P6TdWdYpOePxemVEswEra8bSfKyZF2mMVkDprUrxCCvx6GkDdoPXpsqT4W3M1yvw7mz1rtXsH7TttYD3Q==","signatures":[{"sig":"MEYCIQCp0bUAO4ma43/2wMAwa+8tZULypYnxOgOda2W7na4sBQIhAJ23fjuLyWufWnxzFMbvzN4hu63zvvotekJWPaJyGItD","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":107247},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","module":"./dist/index.js","engines":{"node":">=18"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"},"./runtime":{"types":"./dist/runtime.d.ts","import":"./dist/runtime.js"}},"gitHead":"9436a8ad233c73c413e06a2e6340ef79efda2fb5","scripts":{"dev":"tsup --watch","test":"vitest run","build":"tsup","typecheck":"tsc --noEmit","test:watch":"vitest","prepublishOnly":"npm run build"},"_npmUser":{"name":"dennish02","email":"dennishesler02@gmail.com"},"_npmVersion":"11.2.0","description":"i18n con traducción automática por IA (OpenRouter / Claude / GPT). CLI build-time + runtime opcional.","directories":{},"_nodeVersion":"22.22.1","dependencies":{"jiti":"^2.4.2"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"tsup":"^8.3.5","vitest":"^2.1.8","typescript":"^5.7.2","@types/node":"^22.10.0"},"peerDependencies":{"@huggingface/transformers":"^3.0.0"},"peerDependenciesMeta":{"@huggingface/transformers":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/ai-translate_0.1.4_1782270358266_0.032714769089312146","host":"s3://npm-registry-packages-npm-production"}},"0.1.5":{"name":"@dennish02/ai-translate","version":"0.1.5","publishConfig":{"access":"public"},"description":"i18n con traducción automática por IA (OpenRouter / Claude / GPT). CLI build-time + runtime opcional.","type":"module","license":"MIT","keywords":["i18n","internationalization","translation","ai","llm","openrouter","anthropic","claude","openai"],"bin":{"ai-translate":"dist/cli.js"},"main":"./dist/index.js","module":"./dist/index.js","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"},"./runtime":{"types":"./dist/runtime.d.ts","import":"./dist/runtime.js"}},"engines":{"node":">=18"},"scripts":{"build":"tsup","dev":"tsup --watch","test":"vitest run","test:watch":"vitest","typecheck":"tsc --noEmit","prepublishOnly":"npm run build"},"dependencies":{"jiti":"^2.4.2"},"peerDependencies":{"@huggingface/transformers":"^3.0.0"},"peerDependenciesMeta":{"@huggingface/transformers":{"optional":true}},"devDependencies":{"@types/node":"^22.10.0","tsup":"^8.3.5","typescript":"^5.7.2","vitest":"^2.1.8"},"_id":"@dennish02/ai-translate@0.1.5","gitHead":"eab5c964cf80c241d445541c906f06bab4ecd94d","_nodeVersion":"22.22.1","_npmVersion":"11.2.0","dist":{"integrity":"sha512-0oNSoNj3Os8CTRy74n00BoMTR9DTi22mI6XxiQn1txDg8FWxmk8wIOxkmXSe0mXbOg97eSmV3MuwrcvxkyO8RA==","shasum":"4147b5ee730fb6640dcf125153eb4fe379c8b60b","tarball":"https://registry.npmjs.org/@dennish02/ai-translate/-/ai-translate-0.1.5.tgz","fileCount":18,"unpackedSize":111259,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEQCIETLye4FMdU/GDl0v5gioiDU2EVgXWHCYvZIm2NvVPETAiANvXd7EnmmsboB7qE9f9sWksUI3uq2EsrNGBIGT+CUpg=="}]},"_npmUser":{"name":"dennish02","email":"dennishesler02@gmail.com"},"directories":{},"maintainers":[{"name":"dennish02","email":"dennishesler02@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/ai-translate_0.1.5_1782271216910_0.9102881375363985"},"_hasShrinkwrap":false}},"time":{"created":"2026-06-24T01:21:19.882Z","modified":"2026-06-24T03:20:17.136Z","0.1.1":"2026-06-24T01:21:20.260Z","0.1.2":"2026-06-24T02:13:48.205Z","0.1.3":"2026-06-24T02:54:01.921Z","0.1.4":"2026-06-24T03:05:58.443Z","0.1.5":"2026-06-24T03:20:17.041Z"},"license":"MIT","keywords":["i18n","internationalization","translation","ai","llm","openrouter","anthropic","claude","openai"],"description":"i18n con traducción automática por IA (OpenRouter / Claude / GPT). CLI build-time + runtime opcional.","maintainers":[{"name":"dennish02","email":"dennishesler02@gmail.com"}],"readme":"# ai-translate\n\ni18n con **traducción automática por IA**. Mantenés un solo idioma fuente (ej. `en.json`) y un comando traduce las claves faltantes a todos los idiomas usando un LLM vía [OpenRouter](https://openrouter.ai) (una sola key da acceso a Claude, GPT, etc.).\n\n- ✅ **Build-time CLI** (`ai-translate sync`): traduce en tu repo, cero latencia/costo en runtime.\n- ✅ **Dos providers**: `openrouter` (máxima calidad vía Claude/GPT) o `local` (NLLB-200 en tu máquina, **gratis y offline**).\n- ✅ **Validación de placeholders**: el modelo nunca rompe `{name}`, `%s`, ICU, etc. — si lo hace, se reintenta.\n- ✅ **Incremental**: re-traduce solo lo que falta o cambió (hash del fuente).\n- ✅ **Runtime opcional** (`createTranslator`): traducción al vuelo de contenido dinámico, con cache.\n- ✅ **TypeScript**, ESM, sin atarte a ningún framework.\n\n## Instalación\n\n```bash\nnpm install -D ai-translate\n```\n\n## Uso (build-time)\n\n1. Creá `ai-translate.config.ts`:\n\n```ts\nimport { defineConfig } from 'ai-translate'\n\nexport default defineConfig({\n  source: 'en',\n  targets: ['es', 'pt'],\n  path: './locales/{lang}.json',\n  model: 'anthropic/claude-sonnet-4-6', // cualquier modelo de OpenRouter\n  apiKey: process.env.OPENROUTER_API_KEY,\n})\n```\n\n2. Tené tu idioma fuente, ej. `locales/en.json`:\n\n```json\n{ \"home\": { \"greeting\": \"Hello {name}, you have {count} tasks\" } }\n```\n\n3. Traducí:\n\n```bash\nexport OPENROUTER_API_KEY=sk-or-...\nnpx ai-translate sync\n```\n\nGenera/actualiza `locales/es.json`, `locales/pt.json` traduciendo solo lo que falta.\n\n### En CI\n\n```bash\nnpx ai-translate check   # exit 1 si faltan traducciones\n```\n\n## Comandos\n\n| Comando | Descripción |\n| --- | --- |\n| `ai-translate sync` | Traduce las keys faltantes o cambiadas |\n| `ai-translate check` | Falla si hay faltantes (para CI) |\n| `--dry-run` | No escribe, solo reporta |\n| `--force` | Re-traduce todo |\n| `-c, --config <ruta>` | Config explícita |\n\n## Providers\n\n### `openrouter` (default) — máxima calidad\n\nUsa cualquier modelo vía OpenRouter (Claude, GPT, etc.). Mejor para tono, contexto e ICU. Requiere `OPENROUTER_API_KEY`. Costo: centavos para la UI de una app.\n\n### `local` — gratis y offline 🆓\n\nTraduce con un modelo MT corriendo **dentro de Node** vía [transformers.js](https://github.com/huggingface/transformers.js). Sin API key, sin costo, sin red.\n\n```bash\nnpm install @huggingface/transformers   # peer dependency opcional\n```\n\n```ts\nexport default defineConfig({\n  source: 'en',\n  targets: ['es'],\n  path: './locales/{lang}.json',\n  provider: 'local',\n})\n```\n\n```bash\nnpx ai-translate sync   # descarga el modelo la 1ª vez, después offline\n```\n\n**Modelos** (se eligen con `localModel`):\n\n| | default | cobertura | texto corto / UI | tamaño |\n| --- | --- | --- | --- | --- |\n| **MarianMT** (`opus-mt`) | ✅ por par | solo pares publicados | **muy bueno** | ~75 MB |\n| **NLLB-200** | `Xenova/nllb-200-distilled-600M` | 200 idiomas, 1 modelo | divaga con labels | ~1.9 GB |\n| **M2M-100** | `Xenova/m2m100_418M` | 100 idiomas, 1 modelo | flojo en labels | ~0.5 GB |\n\nPor defecto se usa **MarianMT por par** (`Xenova/opus-mt-{src}-{tgt}`): chico, rápido y el mejor con strings cortos de UI. Como es bilingüe, solo descarga el modelo del par que necesitás. Para **varios idiomas con un solo modelo**, cambiá a NLLB:\n\n```ts\nprovider: 'local',\nlocalModel: 'Xenova/nllb-200-distilled-600M', // multilingüe\n```\n\n**Par sin modelo Marian** (ej. `es→pt` no está en ONNX): por defecto falla con un error claro. Para que **caiga automáticamente** a un multilingüe en vez de fallar, seteá `localFallbackModel` (solo se descarga si de verdad falta el par directo):\n\n```ts\nprovider: 'local',                              // Marian por par cuando existe…\nlocalFallbackModel: 'Xenova/m2m100_418M',       // …y M2M (~0.5 GB) cuando no\n```\n\n> **Ojo:** ningún modelo MT local \"sigue instrucciones\" — no podés pedirle \"traducí literal, no interpretes\" (eso es solo para `openrouter`). Y la jerga de dominio la fallan: cubrila con `glossary`.\n\n**Cómo maneja los placeholders**: estos modelos tienden a traducir los `{name}`. Para evitarlo, `ai-translate` los **enmascara** (`[0]`, `[1]`) antes de traducir y los **restaura** después. Funciona muy bien con placeholders simples; el **ICU complejo** (`{count, plural, ...}`) puede no sobrevivir → ahí conviene `openrouter`.\n\n**Términos sueltos / repeticiones (NLLB/M2M)**: con palabras sueltas, NLLB repite o divaga (`\"Bull The bull Bull\"`). Para esas familias `ai-translate` aplica defaults anti-repetición (`no_repeat_ngram_size`, `repetition_penalty`) y un `max_new_tokens` proporcional al input. Marian no los necesita (decodifica limpio) así que no se los aplica. Podés ajustar todo con `localGeneration`:\n\n```ts\nprovider: 'local',\nlocalModel: 'Xenova/nllb-200-distilled-600M',\nlocalGeneration: {\n  no_repeat_ngram_size: 2, // más agresivo contra repeticiones\n  repetition_penalty: 1.5,\n  num_beams: 4,            // beam search: mejor calidad, más lento\n},\n```\n\n**Jerga de dominio → glosario**: para términos que el MT no acierta (ej. ganadería: `Novillo → \"The boy\"` 😬), fijá la traducción con `glossary`. Esas keys se escriben tal cual, **sin pasar por el modelo** (instantáneo y 100% confiable):\n\n```ts\nexport default defineConfig({\n  source: 'en',\n  targets: ['es'],\n  path: './locales/{lang}.json',\n  provider: 'local',\n  glossary: {\n    es: { 'category.NOV': 'Novillo', 'category.TORO': 'Toro' },\n  },\n})\n```\n\nEl glosario también sirve con `openrouter` y para imponer terminología de marca. Si cambiás un valor del glosario, esa key se reescribe en la próxima corrida aunque el texto fuente no haya cambiado. Ver el ejemplo completo en [`examples/ai-translate.local.config.ts`](examples/ai-translate.local.config.ts).\n\n**Modelos cuantizados** (`localDtype: 'fp16' | 'q8'`): cargan más rápido y usan menos RAM, pero degradan la calidad (en NLLB, bastante) y debilitan la predicción de fin-de-secuencia (frases cortas \"se contagian\" de las largas en un batch). Si usás cuantización con NLLB, agregá `batchSize: 1`. El default no lo necesita.\n\n| | `openrouter` | `local` |\n| --- | --- | --- |\n| Costo | centavos/uso | **$0** |\n| Red | requerida | **offline** (tras descargar) |\n| Calidad/tono | ⭐⭐⭐⭐ | ⭐⭐⭐ |\n| ICU complejo | ✅ | ⚠️ limitado |\n| Privacidad | sale a un tercero | **no sale de tu máquina** |\n\n## Contexto por key (opcional)\n\nMejorá la calidad pasando descripciones. En la config: `context: './locales/_context.json'`.\n\n```json\n{ \"actions.save\": \"Botón para guardar un formulario\" }\n```\n\nY `instructions` para tono global: `'Tono informal, voseo argentino.'`\n\n## Runtime (contenido dinámico)\n\nPara la UI usá los JSON generados (instantáneo). Para texto arbitrario, `dynamic()`.\n\n> **Browser / bundlers (Vite, Next, etc.):** importá desde el subpath `/runtime`.\n> El entry raíz (`.`) incluye el CLI y el provider local, que usan APIs Node-only\n> (`fs`, `jiti`, transformers) y **no** son bundleables para el cliente. El subpath\n> `/runtime` solo trae `createTranslator` y bundlea limpio.\n\n```ts\nimport { createTranslator } from '@dennish02/ai-translate/runtime'\nimport es from './locales/es.json'\n\nconst t = createTranslator({\n  lang: 'es',\n  messages: flatten(es), // tus mensajes\n  ai: { apiKey: process.env.OPENROUTER_API_KEY },\n})\n\nt('home.greeting', { name: 'Ana', count: 3 }) // de archivo, instantáneo\nawait t.dynamic('User generated content...')   // traduce al vuelo + cachea\n```\n\n### `dynamic()` en el browser: no expongas tu API key\n\nLlamar a OpenRouter directo desde el cliente **filtra tu API key** (queda en el\nbundle / en el tráfico). En browser, apuntá `baseUrl` a un proxy de tu backend que\nagregue la key del lado servidor, o pasá un `fetchImpl` propio:\n\n```ts\nconst t = createTranslator({\n  lang: 'es',\n  ai: {\n    baseUrl: '/api/translate-proxy', // tu endpoint; la key vive en el server\n    // o: fetchImpl: (url, init) => fetch('/api/translate-proxy', init),\n  },\n})\n```\n\n## Placeholders soportados\n\n`{name}` · `{{name}}` · `{count, plural, ...}` (ICU) · `%s %d %1$s` (printf) · `:name` · `${expr}`\n\n## Licencia\n\nMIT\n","readmeFilename":"README.md"}