From f12f0fd955e9f715ce37c50820d1a95d349f9f58 Mon Sep 17 00:00:00 2001 From: nomo-fe Date: Wed, 11 Feb 2026 22:28:26 +0100 Subject: [PATCH 01/16] rm cname --- CNAME | 1 - 1 file changed, 1 deletion(-) delete mode 100644 CNAME diff --git a/CNAME b/CNAME deleted file mode 100644 index c0e65981..00000000 --- a/CNAME +++ /dev/null @@ -1 +0,0 @@ -attranslate.xyz \ No newline at end of file From 3364b69eb048d6513b7815983b9b1d63d85f52d3 Mon Sep 17 00:00:00 2001 From: nomo-fe Date: Wed, 11 Feb 2026 22:56:14 +0100 Subject: [PATCH 02/16] document the agentic pivot --- README-es.md | 124 ++++++++++++------------------------- README.md | 103 ++++++++++-------------------- docs/AGENTIC_REVOLUTION.md | 20 ++++++ docs/DEV_MANIFESTO-es.md | 49 +++++++-------- docs/DEV_MANIFESTO.md | 14 ++--- docs/OPERATION_MODES.md | 2 + docs/SERVICE_CONFIG-es.md | 31 ---------- docs/SERVICE_CONFIG.md | 35 ----------- docs/TypeChat.md | 39 ------------ 9 files changed, 120 insertions(+), 297 deletions(-) create mode 100644 docs/AGENTIC_REVOLUTION.md delete mode 100644 docs/SERVICE_CONFIG-es.md delete mode 100644 docs/SERVICE_CONFIG.md delete mode 100644 docs/TypeChat.md diff --git a/README-es.md b/README-es.md index db9ed222..a00fc14f 100644 --- a/README-es.md +++ b/README-es.md @@ -6,14 +6,8 @@ macOS/Ubuntu/Windows: [![Actions Status](https://github.com/fkirc/attranslate/workflows/Tests/badge.svg/?branch=master)](https://github.com/fkirc/attranslate/actions?query=branch%3Amaster) -`attranslate` es una herramienta para sincronizar archivos de traducción, incluyendo JSON/YAML/XML y otros formatos. -A diferencia de los servicios de pago, cualquier desarrollador puede integrar `attranslate` en cuestión de minutos. -`attranslate` dejará las traducciones existentes sin cambios y solo sincronizará las nuevas traducciones. - -Opcionalmente `attranslate` trabaja con servicios de traducción automática. -Por ejemplo, supongamos que un servicio de traducción logra un 80% de traducciones correctas. -Con `attranslate`, una solución rápida del 20% restante puede ser más rápida que hacer todo a mano. -Aparte de eso, `attranslate` admite traducciones puramente manuales o incluso conversiones de formato de archivo sin cambiar el idioma. +`attranslate` es una herramienta CLI para sincronizar archivos de traducción (JSON/YAML/XML) diseñada para asistir a Agentes de Código en traducir eficientemente con uso mínimo de tokens. +Las traducciones existentes permanecen sin cambios; solo se sincronizan las nuevas cadenas de texto. # Funciones @@ -25,103 +19,61 @@ Por lo tanto, siempre que no esté satisfecho con los resultados producidos, `at ## Servicios disponibles -`attranslate` admite los siguientes servicios de traducción; Muchos de ellos son gratis: +- `agent`: Para usar con Agentes de Código. Solicita al agente traducir nuevas cadenas vía stdin cuando se detectan. +- `sync-without-translate`: Verifica la integridad de las traducciones sin traducir (p. ej. para tuberías CI/CD). -- `openai`: Utiliza un modelo como ChatGPT; gratis hasta un límite -- [google-translate](https://cloud.google.com/translate): Necesita una cuenta de GCloud; gratis hasta un límite -- [azure](https://azure.microsoft.com/en-us/services/cognitive-services/translator-text-api/): Necesita una cuenta de Microsoft; cuesta dinero -- `sync-without-translate`: No cambia el idioma. Esto puede ser útil para convertir entre formatos de archivo o para mantener diferencias específicas de la región. -- `manual`: Traducir textos manualmente +Otros servicios (openai, google-translate, azure, manual, typechat, etc.) están deprecados pero se retienen para compatibilidad hacia atrás. # Ejemplos de uso -Traducir un solo archivo es tan simple como la siguiente línea: +Traducir un archivo único es tan simple como la siguiente línea: - attranslate --srcFile=json-simple/en.json --srcLng=English --srcFormat=nested-json --targetFile=json-simple/de.json --targetLng=German --targetFormat=nested-json --service=openai +``` +attranslate --srcFile=json-simple/en.json --srcLng=English --srcFormat=nested-json --targetFile=json-simple/es.json --targetLng=Spanish --targetFormat=nested-json --service=agent +``` -Si tiene varios idiomas de destino, necesitará varias llamadas a `attranslate`. -Puede escribir algo como el siguiente script: +Para varios idiomas de destino, invoca `attranslate` para cada uno: ```bash -# Este ejemplo traduce un archivo JSON en inglés a español y alemán. -BASE_DIR="json-advanced" -SERVICE_ACCOUNT_KEY="gcloud/gcloud_service_account.json" -COMMON_ARGS=( "--srcLng=en" "--srcFormat=nested-json" "--targetFormat=nested-json" "--service=google-translate" "--serviceConfig=$SERVICE_ACCOUNT_KEY" ) - -# instalar attranslate si aún no está instalado -attranslate --version || npm install --global attranslate - -attranslate --srcFile=$BASE_DIR/en/fruits.json --targetFile=$BASE_DIR/es/fruits.json --targetLng=es "${COMMON_ARGS[@]}" -attranslate --srcFile=$BASE_DIR/en/fruits.json --targetFile=$BASE_DIR/de/fruits.json --targetLng=de "${COMMON_ARGS[@]}" +attranslate --srcFile=en/fruits.json --targetFile=es/fruits.json --targetLng=es --srcLng=en --srcFormat=nested-json --targetFormat=nested-json --service=agent +attranslate --srcFile=en/fruits.json --targetFile=de/fruits.json --targetLng=de --srcLng=en --srcFormat=nested-json --targetFormat=nested-json --service=agent ``` -Del mismo modo, puede utilizar `attranslate` para convertir entre formatos de archivo. -Ver [scripts de ejemplo](https://github.com/fkirc/attranslate/tree/master/sample-scripts) para más ejemplos. - -# Guía de integración - -En primer lugar, asegúrese de que [nodejs](https://nodejs.org/) está instalado en el equipo. -Una vez que tengas `nodejs`, puede instalar `attranslate` Vía: - -`npm install --global attranslate` - -Alternativamente, si usted es un desarrollador de JavaScript, entonces debe instalar `attranslate` Vía: - -`npm install --save-dev attranslate` - -A continuación, debe escribir un script específico del proyecto que invoque `attranslate` para sus archivos específicos. -Ver [scripts de ejemplo](https://github.com/fkirc/attranslate/tree/master/sample-scripts) para obtener orientación sobre cómo traducir los archivos específicos del proyecto. - # Opciones de uso -Correr `attranslate --help` para ver una lista de opciones disponibles: +Ejecuta `attranslate --help` para ver una lista de opciones disponibles: ``` - Usage: attranslate [options] - - Options: - --srcFile The source file to be translated - --srcLng A language code for the source language - --srcFormat One of "flat-json", "nested-json", - "yaml", "po", "xml", "ios-strings", - "arb", "csv" - --targetFile The target file for the translations - --targetLng A language code for the target language - --targetFormat One of "flat-json", "nested-json", - "yaml", "po", "xml", "ios-strings", - "arb", "csv" - --service One of "openai", "manual", - "sync-without-translate", - "google-translate", "azure" - --serviceConfig supply configuration for a translation - service (either a path to a key-file or - an API-key) - --matcher An optional feature for string replacements. One of "none", "icu", "i18next", - "sprintf" (default: "none") - --prompt Prompt opcional para guiar al servicio de traducción. Útil para especificar - términos técnicos que no deben traducirse u otras preferencias de traducción. - -v, --version output the version number +Usage: attranslate [options] + +Options: + --srcFile The source file to be translated + --srcLng A language code for the source language + --srcFormat One of "flat-json", "nested-json", "yaml", + "po", "xml", "ios-strings", "arb", "csv" + --targetFile The target file for the translations + --targetLng A language code for the target language + --targetFormat One of "flat-json", "nested-json", "yaml", + "po", "xml", "ios-strings", "arb", "csv" + --service One of "agent", "sync-without-translate" + -v, --version output the version number + -h, --help display help for command ``` ## Ejemplos de Prompt -Puedes usar el parámetro `--prompt` para proporcionar instrucciones específicas al servicio de traducción. Aquí tienes un ejemplo: +Se recomienda expandir tu AGENTS.md/CLAUDE.md o similar para instruir a tus Agentes de Código sobre cómo deben hacer traducciones. +Por ejemplo, agrega algo como esto a tu prompt del sistema: -```bash -attranslate --srcFile=json-simple/en.json --srcLng=English --srcFormat=nested-json \ - --targetFile=json-simple/es.json --targetLng=Spanish --targetFormat=nested-json \ - --service=openai \ - --prompt="Estoy desarrollando una aplicación de salud para profesionales médicos. Términos técnicos como 'EKG', 'MRI', 'CT scan', 'blood pressure', 'pulse oximeter' y 'vital signs' deben permanecer en inglés. Por favor, mantenga la terminología médica adecuada y un tono formal en las traducciones." +``` +Al realizar traducciones, recuerda que estás desarrollando una aplicación de salud para profesionales médicos. Términos técnicos como 'EKG', 'MRI', 'CT scan', 'blood pressure', 'pulse oximeter' y 'vital signs' deben permanecer en inglés. Por favor, mantén la terminología médica apropiada y un tono formal en las traducciones. +Invoca `attranslate` después de agregar una nueva traducción al archivo en.json en inglés. +Por ejemplo: +attranslate --service=agent --srcFile=translations/en.json --targetFile=translations/es.json --targetLng=es --srcLng=en --srcFormat=nested-json --targetFormat=nested-json ``` -Este prompt asegurará que los términos técnicos permanezcan en inglés mientras se traduce el resto del contenido. Puedes personalizar el prompt según tus necesidades específicas, como: -- Especificar qué términos no deben traducirse -- Solicitar reglas específicas de capitalización -- Proporcionar contexto sobre el dominio de tu aplicación -- Establecer preferencias de tono o estilo para las traducciones - -# Contribuidores - -Nos gustaría agradecer a todos los contribuidores que han ayudado a mejorar attranslate: +Para reducir el uso de contexto, esto puede envolverse en una declaración condicional: -- [@esuljic](https://github.com/esuljic): Agregó la característica de parámetro de prompt para servicios de IA (OpenAI y TypeChat). +``` +Al agregar nuevas claves de traducción, consulta para ver cómo deben hacerse las nuevas traducciones. +``` diff --git a/README.md b/README.md index d5ed32dc..5f81b46f 100644 --- a/README.md +++ b/README.md @@ -4,75 +4,50 @@ macOS/Ubuntu/Windows: [![Actions Status](https://github.com/fkirc/attranslate/workflows/Tests/badge.svg/?branch=master)](https://github.com/fkirc/attranslate/actions?query=branch%3Amaster) -`attranslate` is a tool for syncing translation-files, including JSON/YAML/XML and other formats. -In contrast to paid services, any developer can integrate `attranslate` in a matter of minutes. -`attranslate` will leave existing translations unchanged and only synchronize new translations. - -Optionally, `attranslate` works with automated translation-services. -For example, let's say that a translation-service achieves 80% correct translations. -With `attranslate`, a fix of the remaining 20% may be faster than doing everything by hand. -Other than that, `attranslate` supports purely manual translations or even file-format-conversions without changing the language. +`attranslate` is a CLI-tool for syncing translation files (JSON/YAML/XML) designed to assist Coding Agents in translating efficiently with minimal token-usage. +Existing translations remain unchanged; only new strings are synchronized. # Features ## Preserve Manual Translations -`attranslate` recognizes that machine translations are not perfect. +`attranslate` recognizes that machine translations are not yet perfect. Therefore, whenever you are unhappy with the produced text, `attranslate` allows you to simply overwrite text in your target-files. `attranslate` will never overwrite any manual corrections in subsequent runs. ## Available Services -`attranslate` supports the following services; many of them are free of charge: +- `agent`: For use with Coding Agents. Prompts the agent to translate new strings via stdin when detected. +- `sync-without-translate`: Verifies translation completeness without translating (e.g. for CI/CD pipelines). -- `openai`: Uses a model like ChatGPT; free up to a limit -- [google-translate](https://cloud.google.com/translate): Needs a GCloud account; free up to a limit -- [azure](https://azure.microsoft.com/en-us/services/cognitive-services/translator-text-api/): Needs a Microsoft account; costs money -- `sync-without-translate`: Does not change the language. This can be useful for converting between file formats, or for maintaining region-specific differences. -- `manual`: Translates text with manual typing -- `key-as-translation`: Uses the key as the translation, useful for debugging or placeholder translations. -- [`typechat`](./docs/TypeChat.md): Translates text using OpenAI's language models or a self-hosted model with an OpenAI-compatible API. -- [`typechat-manual`](./docs/TypeChat.md): Provides manual translation workflows by leveraging clipboard operations. +Other services (openai, google-translate, azure, manual, typechat, etc.) are deprecated but retained for backwards-compatibility. # Usage Examples Translating a single file is as simple as the following line: ``` -attranslate --srcFile=json-simple/en.json --srcLng=English --srcFormat=nested-json --targetFile=json-simple/es.json --targetLng=Spanish --targetFormat=nested-json --service=openai +attranslate --srcFile=json-simple/en.json --srcLng=English --srcFormat=nested-json --targetFile=json-simple/es.json --targetLng=Spanish --targetFormat=nested-json --service=agent ``` -If you have multiple target-languages, then you will need multiple calls to `attranslate`. -You can write something like the following script: +For multiple target languages, call `attranslate` for each: ```bash -# This example translates an english JSON-file into spanish and german. -BASE_DIR="json-advanced" -COMMON_ARGS=( "--srcLng=en" "--srcFormat=nested-json" "--targetFormat=nested-json" "--service=google-translate" "--serviceConfig=gcloud/gcloud_service_account.json" ) - -# install attranslate if it is not installed yet -attranslate --version || npm install --global attranslate - -attranslate --srcFile=$BASE_DIR/en/fruits.json --targetFile=$BASE_DIR/es/fruits.json --targetLng=es "${COMMON_ARGS[@]}" -attranslate --srcFile=$BASE_DIR/en/fruits.json --targetFile=$BASE_DIR/de/fruits.json --targetLng=de "${COMMON_ARGS[@]}" +attranslate --srcFile=en/fruits.json --targetFile=es/fruits.json --targetLng=es --srcLng=en --srcFormat=nested-json --targetFormat=nested-json --service=agent +attranslate --srcFile=en/fruits.json --targetFile=de/fruits.json --targetLng=de --srcLng=en --srcFormat=nested-json --targetFormat=nested-json --service=agent ``` -Similarly, you can use `attranslate` to convert between file-formats. -See [sample scripts](https://github.com/fkirc/attranslate/tree/master/sample-scripts) for more examples. - -# Integration Guide - -Firstly, ensure that [nodejs](https://nodejs.org/) is installed on your machine. -Once you have `nodejs`, you can install `attranslate` via: +# Installation -`npm install --global attranslate` - -Alternatively, if you are a JavaScript-developer, then you can install `attranslate` via: - -`npm install --save-dev attranslate` +Install globally: +```bash +npm install --global attranslate +``` -Next, you should write a project-specific script that invokes `attranslate` for your specific files. -See [sample scripts](https://github.com/fkirc/attranslate/tree/master/sample-scripts) for guidance on how to translate your project-specific files. +Or in a Node.js project: +```bash +npm install --save-dev attranslate +``` # Usage Options @@ -90,41 +65,25 @@ Options: --targetLng A language code for the target language --targetFormat One of "flat-json", "nested-json", "yaml", "po", "xml", "ios-strings", "arb", "csv" - --service One of "openai", "typechat", - "typechat-manual", "manual", - "sync-without-translate", - "google-translate", "azure", - "key-as-translation" - --serviceConfig supply configuration for a translation - service (either a path to a key-file or an - API-key) - --matcher One of "none", "icu", "i18next", "sprintf" - (default: "none") - --prompt supply a prompt for the AI translation - service + --service One of "agent", "sync-without-translate" -v, --version output the version number -h, --help display help for command ``` ## Prompt Examples -You can use the `--prompt` parameter to provide specific instructions to the translation service. Here's an example: +It is recommended to expand your AGENTS.md/CLAUDE.md or similar to instruct your Coding Agents on how they should do translations. +For example, add something like this to your system prompt: -```bash -attranslate --srcFile=json-simple/en.json --srcLng=English --srcFormat=nested-json \ - --targetFile=json-simple/es.json --targetLng=Spanish --targetFormat=nested-json \ - --service=openai \ - --prompt="I am building a healthcare app for medical professionals. Technical terms like 'EKG', 'MRI', 'CT scan', 'blood pressure', 'pulse oximeter', and 'vital signs' should remain in English. Please maintain proper medical terminology and formal tone in translations." +``` +When doing translations, remember that you are building a healthcare app for medical professionals. Technical terms like 'EKG', 'MRI', 'CT scan', 'blood pressure', 'pulse oximeter', and 'vital signs' should remain in English. Please maintain proper medical terminology and formal tone in translations. +Invoke `attranslate` after adding a new translation to the English en.json. +For example: +attranslate --service=agent --srcFile=translations/en.json --targetFile=translations/es.json --targetLng=es --srcLng=en --srcFormat=nested-json --targetFormat=nested-json ``` -This prompt will ensure that technical terms remain in English while translating the rest of the content. You can customize the prompt based on your specific needs, such as: -- Specifying which terms should remain untranslated -- Requesting specific capitalization rules -- Providing context about your application domain -- Setting tone or style preferences for translations - -# Contributors - -We would like to thank all contributors who have helped improve attranslate: +To reduce context-usage, this can be wrapped into a conditional statement: -- [@esuljic](https://github.com/esuljic): Added the prompt parameter feature for AI services (OpenAI and TypeChat). +``` +When adding new translation-keys, lookup to see how new translations should be done. +``` diff --git a/docs/AGENTIC_REVOLUTION.md b/docs/AGENTIC_REVOLUTION.md new file mode 100644 index 00000000..df53a081 --- /dev/null +++ b/docs/AGENTIC_REVOLUTION.md @@ -0,0 +1,20 @@ +# Agentic Revolution - A new wave of CLI tools + +The target group of attranslate has always been and continues to be "indie hackers" and fast-moving startups. +Hence, attranslate serves to quickly translate products without setting up any complex platforms like weblate. + +However, the world is no longer the same as back in the days when the first version of attranslate was released. +Coding agents like Claude Code or Codex are getting more powerful as we speak, and every decent startup-programmer is using those coding agents. + +As a result, the old way of configuring attranslate no longer makes sense. +After all, when the developer already has an LLM-based coding agent, then it no longer makes sense to configure any external service for attranslate. + +That being said, CLI-tools like attranslate are still relevant in the new agentic world. +In fact, we are seeing a revived interest in CLI-tools because Coding Agents can interact very neatly with CLI-tools. + +Therefore, attranslate v2.3.0 is an upgrade to adapt for the new agentic world. +attranslate v2.3.0 introduces a new flag `--service=agent`, and all other services are considered legacy and only kept for backwards-compat. + +The new value-proposition of attranslate is to **reduce token-usage for Coding Agents**, and also to provide an efficient way for verifying the completeness of translations (e.g. in CI/CD-pipelines). +By using attranslate, Coding Agents can either add new translations or verify translation-completeness with only minimal token-usage. + diff --git a/docs/DEV_MANIFESTO-es.md b/docs/DEV_MANIFESTO-es.md index 5332188d..611f67f4 100644 --- a/docs/DEV_MANIFESTO-es.md +++ b/docs/DEV_MANIFESTO-es.md @@ -2,39 +2,36 @@ El desarrollo de `attranslate` se guía por los principios que se describen en este documento. -## Concéntrese en el "tiempo de obtención de valor" +## Enfocarse en el uso con Agentes de Código -`attranslate` es una herramienta semiautomatizada. -Las herramientas semiautomatizadas solo son valiosas si el costo de configuración es menor que la recompensa de la automatización. -Por lo tanto, es importante minimizar el tiempo de configuración para los nuevos usuarios: +`attranslate` es una herramienta CLI que debe ser fácil y eficiente de invocar para Agentes de Código. +Esto significa que: -* Piénselo dos veces antes de agregar cualquier nueva configuración. -* Piénselo dos veces sobre cualquier cosa que requiera una documentación más compleja. -* La documentación debe ser mínima, pero garantizada para funcionar y siempre actualizada. -* Evita los momentos gotcha inesperados. En su lugar, haga cosas que los usuarios esperan. +- Las opciones deben ser autoexplicativas, listadas en `--help`. +- La salida debe ser breve y concisa sin ceremonias, para evitar desperdiciar tokens. +- Los mensajes de error deben estar escritos de manera que permita a los Agentes de Código corregir automáticamente el error. -## Evita ser obstinado +## Evitar ser obstinado -`attranslate` es una herramienta genérica que no debe aplicar ningún flujo de trabajo específico. -Por ejemplo `attranslate` no debe aplicar ninguna estructura de directorios específica. +`attranslate` es una herramienta genérica que no debe enforcer ningún flujo de trabajo específico. +Por ejemplo, `attranslate` no debe enforcer ninguna estructura de directorios específica. -## Desarrollo basado en el rendimiento de pruebas +## Desarrollo Basado en Rendimiento de Pruebas -*Desarrollo basado en el rendimiento de pruebas (TPDD)* es una extensión de Test Driven Development (TDD). TPDD sigue los siguientes principios básicos de TDD: +_Desarrollo Basado en Rendimiento de Pruebas (TPDD)_ es una extensión de Test Driven Development (TDD). TPDD sigue los siguientes principios básicos de TDD: +- Todas las características deben ser probadas, incluso si es solo una prueba de humo mínima. +- Idealmente, todas las correcciones de errores deben ser probadas por regresión. +- El código se considera "razonablemente probado" si la funcionalidad o correcciones no se pueden eliminar sin romper una prueba. -* Todas las características deben ser probadas, incluso si es solo una prueba de humo mínima. -* Idealmente, todas las correcciones de errores deben ser probadas por regresión. -* El código se "prueba razonablemente" si la funcionalidad o las correcciones de errores no se pueden eliminar sin romper una prueba. +Sin embargo, TPDD tiene requisitos adicionales sobre cómo deben realizarse las pruebas: -Sin embargo, TPDD tiene requisitos adicionales sobre cómo se deben realizar las pruebas: +- Optimizar agresivamente el tiempo de ejecución general de los conjuntos de pruebas. +- Hacer que las pruebas sean independientes entre sí para permitir pruebas multihilo. +- Simular operaciones costosas como llamadas de red (pero no para todas las pruebas). +- Preferir modificaciones menores de pruebas sobre nuevas pruebas (pero no a costa de pruebas demasiado complejas). +- Preferir archivos de referencia sobre código de prueba. +- Preferir estabilidad y robustez sobre un número excesivo de pruebas. +- Probar lógica de alto nivel en lugar de detalles de implementación. -* Optimice agresivamente el tiempo de ejecución general de los conjuntos de pruebas. -* Haga que las pruebas sean independientes entre sí para permitir las pruebas de múltiples núcleos. -* Simular operaciones costosas como llamadas de red (pero no para todas las pruebas). -* Prefiera modificaciones menores de las pruebas sobre las nuevas pruebas (pero no a expensas de pruebas demasiado complejas). -* Prefiere los archivos de referencia a los de prueba. -* Prefiere la estabilidad y la robustez a un número excesivo de pruebas. -* Pruebe la lógica de alto nivel en lugar de los detalles de implementación. - -No tengas miedo de esta larga lista de requisitos. +No temas esta larga lista de requisitos. En muchos casos, probar una nueva característica es tan simple como agregar un nuevo archivo de entrada y luego generar un archivo de referencia. diff --git a/docs/DEV_MANIFESTO.md b/docs/DEV_MANIFESTO.md index 1455e6fd..287305c7 100644 --- a/docs/DEV_MANIFESTO.md +++ b/docs/DEV_MANIFESTO.md @@ -2,16 +2,14 @@ The development of `attranslate` is guided by the principles that are described in this document. -## Focus on "Time-to-Value" +## Focus on use for Coding Agents -`attranslate` is a semi-automated tool. -Semi-automated tools are only valuable if the setup-cost is smaller than the reward of automation. -Therefore, it is important to minimize setup-time for new users: +`attranslate` is a CLI-tool that should be easy and efficient to invoke for Coding Agents. +This means that: -- Think twice before adding any new configuration. -- Think twice about anything that would require a more complex documentation. -- Documentation should be minimal, but guaranteed to work and always up-to-date. -- Avoid unexpected gotcha-moments. Instead, do things that are expected by users. +- Options should be self-explaining, and listed with `--help`. +- Output should be short and concise without much ceremony, to avoid wasting token-usage. +- Error messages should be written in a way that allows Coding Agents to automatically fix the error. ## Avoid being opinionated diff --git a/docs/OPERATION_MODES.md b/docs/OPERATION_MODES.md index 834f5ea2..02854972 100644 --- a/docs/OPERATION_MODES.md +++ b/docs/OPERATION_MODES.md @@ -1,3 +1,5 @@ +This file is an internal architecture-documentation that can be ignored by Coding Agents who call attranslate. + # Operation Modes When generating target files, attranslate has the following two operation modes: diff --git a/docs/SERVICE_CONFIG-es.md b/docs/SERVICE_CONFIG-es.md deleted file mode 100644 index 9e781170..00000000 --- a/docs/SERVICE_CONFIG-es.md +++ /dev/null @@ -1,31 +0,0 @@ -# Configuración del servicio - -Actualmente, necesita obtener una clave de API si usa `attranslate` con servicios de traducción automática. -Este documento proporciona orientación sobre cómo obtener claves de API para servicios específicos. -Una vez que tenga una clave de API, pase su clave de API a `attranslate` a través del `--serviceConfig`-bandera. - -### Traductor de Google - -Sigue estos pasos para obtener una clave de API para Google Translate: - -1. [Seleccionar o crear un proyecto en la nube][projects] -2. [Habilitar la API de traducción de Google Cloud][enable_api] -3. [Crear una cuenta de servicio][auth] para obtener un `service_account_key.json`-archivo. - -[projects]: https://console.cloud.google.com/project - -[billing]: https://support.google.com/cloud/answer/6293499#enable-billing - -[enable_api]: https://console.cloud.google.com/flows/enableapi?apiid=translate.googleapis.com - -[auth]: https://cloud.google.com/docs/authentication/getting-started - -Una vez que tenga una cuenta de servicio, pase una ruta a su `service_account_key.json` a través del `--serviceConfig`-bandera a `attranslate`. - -### Traductor de Azure - -Siga estos pasos para obtener una clave de API para Azure Translator: - -* [Únete](https://azure.microsoft.com/en-us/free/) para un - Cuenta de Azure si aún no tiene una. -* Crear una nueva instancia de traductor a través de https://azure.microsoft.com/services/cognitive-services/translator/ diff --git a/docs/SERVICE_CONFIG.md b/docs/SERVICE_CONFIG.md deleted file mode 100644 index 65f8de64..00000000 --- a/docs/SERVICE_CONFIG.md +++ /dev/null @@ -1,35 +0,0 @@ -# Service Configuration - -Currently, you need to obtain an API-key if you use `attranslate` with automated translation-services. -This document provides guidance on how to obtain API-keys for specific services. -Once you have an API-key, pass your API-key to `attranslate` via the `--serviceConfig`-flag. - -### OpenAI (ChatGPT) - -- Create an account on https://platform.openai.com/ -- Create and copy an API key from https://platform.openai.com/account/api-keys -- Pass your API key via the `--serviceConfig`-flag to `attranslate`. - -### Google Translate - -Follow these steps to get an API-key for Google Translate: - -1. [Select or create a Cloud project][projects] -2. [Enable the Google Cloud Translation API][enable_api] -3. [Create a service account][auth] to obtain a `service_account_key.json`-file. - -[projects]: https://console.cloud.google.com/project -[billing]: https://support.google.com/cloud/answer/6293499#enable-billing -[enable_api]: - https://console.cloud.google.com/flows/enableapi?apiid=translate.googleapis.com -[auth]: https://cloud.google.com/docs/authentication/getting-started - -Once you have a service account, pass a path to your `service_account_key.json` via the `--serviceConfig`-flag to `attranslate`. - -### Azure Translator - - Follow these steps to get an API-key for Azure Translator: - - - [Sign Up](https://azure.microsoft.com/en-us/free/) for an - Azure account if you don't have one already. - - Create a new translator instance via https://azure.microsoft.com/services/cognitive-services/translator/ diff --git a/docs/TypeChat.md b/docs/TypeChat.md deleted file mode 100644 index f47b8ac2..00000000 --- a/docs/TypeChat.md +++ /dev/null @@ -1,39 +0,0 @@ -# TypeChat Documentation - -## Description - -The `typechat` service is for translating your files using OpenAI's language models or any self-hosted model with an OpenAI-compatible API. -The `typechat-manual` service provides a manual translation workflow by leveraging clipboard operations. - -## Environment Variables - -The following environment variables can be configured: - -| Name | Description | Default | -|---------------------------|--------------------------------------------|----------------------------------------------| -| `OPENAI_API_KEY` | API key for OpenAI authentication. | | -| `OPENAI_ENDPOINT` | OpenAI API endpoint URL. | `https://api.openai.com/v1/chat/completions` | -| `OPENAI_MODEL` | Model to use for translation. | `gpt-4o-mini-2024-07-18` | -| `OPEN_AI_BATCH_SIZE` | Number of strings to process in a batch. | `10` | -| `TYPECHAT_SCHEMA_NAME` | Name for the generated schema. | `AppLocalizations` | -| `TYPECHAT_SCHEMA_COMMENT` | Optional comment for the generated schema. | | - -## Usage Example - -Below is an example of how to use `typechat` with a bash script: - -```bash -#!/bin/bash - -# Set environment variables -export OPENAI_API_KEY="your_openai_api_key" -export OPENAI_ENDPOINT="https://api.openai.com/v1/chat/completions" -export OPENAI_MODEL="gpt-4o-mini-2024-07-18" -export OPEN_AI_BATCH_SIZE="20" - -# TYPECHAT_SCHEMA_COMMENT can be used to give the model more context -export TYPECHAT_SCHEMA_COMMENT="Translations for a chess game" - -# Run attranslate -attranslate --srcFormat=yaml --targetFormat=yaml --srcFile=en.yaml --targetFile=de.xml --srcLng=English --targetLng=German --service=typechat -``` From 30d13e4560b03241e72e94df39fd3cf3fcf6855d Mon Sep 17 00:00:00 2001 From: nomo-fe Date: Wed, 11 Feb 2026 23:10:33 +0100 Subject: [PATCH 03/16] implement agent-translate --- README-es.md | 8 ++++---- README.md | 8 ++++---- package.json | 6 ++---- src/services/agent-translate.ts | 29 +++++++++++++++++++++++++++++ src/services/service-definitions.ts | 3 +++ 5 files changed, 42 insertions(+), 12 deletions(-) create mode 100644 src/services/agent-translate.ts diff --git a/README-es.md b/README-es.md index a00fc14f..5e0d8307 100644 --- a/README-es.md +++ b/README-es.md @@ -19,7 +19,7 @@ Por lo tanto, siempre que no esté satisfecho con los resultados producidos, `at ## Servicios disponibles -- `agent`: Para usar con Agentes de Código. Solicita al agente traducir nuevas cadenas vía stdin cuando se detectan. +- `agent`: Para usar con Agentes de Código. Solicita al agente traducir nuevas cadenas de forma interactiva cuando se detectan. - `sync-without-translate`: Verifica la integridad de las traducciones sin traducir (p. ej. para tuberías CI/CD). Otros servicios (openai, google-translate, azure, manual, typechat, etc.) están deprecados pero se retienen para compatibilidad hacia atrás. @@ -35,8 +35,8 @@ attranslate --srcFile=json-simple/en.json --srcLng=English --srcFormat=nested-js Para varios idiomas de destino, invoca `attranslate` para cada uno: ```bash -attranslate --srcFile=en/fruits.json --targetFile=es/fruits.json --targetLng=es --srcLng=en --srcFormat=nested-json --targetFormat=nested-json --service=agent -attranslate --srcFile=en/fruits.json --targetFile=de/fruits.json --targetLng=de --srcLng=en --srcFormat=nested-json --targetFormat=nested-json --service=agent +attranslate --srcFile=en/fruits.json --targetFile=es/fruits.json --targetLng=Spanish --srcLng=English --srcFormat=nested-json --targetFormat=nested-json --service=agent +attranslate --srcFile=en/fruits.json --targetFile=de/fruits.json --targetLng=German --srcLng=English --srcFormat=nested-json --targetFormat=nested-json --service=agent ``` # Opciones de uso @@ -69,7 +69,7 @@ Por ejemplo, agrega algo como esto a tu prompt del sistema: Al realizar traducciones, recuerda que estás desarrollando una aplicación de salud para profesionales médicos. Términos técnicos como 'EKG', 'MRI', 'CT scan', 'blood pressure', 'pulse oximeter' y 'vital signs' deben permanecer en inglés. Por favor, mantén la terminología médica apropiada y un tono formal en las traducciones. Invoca `attranslate` después de agregar una nueva traducción al archivo en.json en inglés. Por ejemplo: -attranslate --service=agent --srcFile=translations/en.json --targetFile=translations/es.json --targetLng=es --srcLng=en --srcFormat=nested-json --targetFormat=nested-json +attranslate --service=agent --srcFile=translations/en.json --targetFile=translations/es.json --targetLng=Spanish --srcLng=English --srcFormat=nested-json --targetFormat=nested-json ``` Para reducir el uso de contexto, esto puede envolverse en una declaración condicional: diff --git a/README.md b/README.md index 5f81b46f..b828a43a 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ Therefore, whenever you are unhappy with the produced text, `attranslate` allows ## Available Services -- `agent`: For use with Coding Agents. Prompts the agent to translate new strings via stdin when detected. +- `agent`: For use with Coding Agents. Prompts the agent to translate new strings interactively when detected. - `sync-without-translate`: Verifies translation completeness without translating (e.g. for CI/CD pipelines). Other services (openai, google-translate, azure, manual, typechat, etc.) are deprecated but retained for backwards-compatibility. @@ -33,8 +33,8 @@ attranslate --srcFile=json-simple/en.json --srcLng=English --srcFormat=nested-js For multiple target languages, call `attranslate` for each: ```bash -attranslate --srcFile=en/fruits.json --targetFile=es/fruits.json --targetLng=es --srcLng=en --srcFormat=nested-json --targetFormat=nested-json --service=agent -attranslate --srcFile=en/fruits.json --targetFile=de/fruits.json --targetLng=de --srcLng=en --srcFormat=nested-json --targetFormat=nested-json --service=agent +attranslate --srcFile=en/fruits.json --targetFile=es/fruits.json --targetLng=Spanish --srcLng=English --srcFormat=nested-json --targetFormat=nested-json --service=agent +attranslate --srcFile=en/fruits.json --targetFile=de/fruits.json --targetLng=German --srcLng=English --srcFormat=nested-json --targetFormat=nested-json --service=agent ``` # Installation @@ -79,7 +79,7 @@ For example, add something like this to your system prompt: When doing translations, remember that you are building a healthcare app for medical professionals. Technical terms like 'EKG', 'MRI', 'CT scan', 'blood pressure', 'pulse oximeter', and 'vital signs' should remain in English. Please maintain proper medical terminology and formal tone in translations. Invoke `attranslate` after adding a new translation to the English en.json. For example: -attranslate --service=agent --srcFile=translations/en.json --targetFile=translations/es.json --targetLng=es --srcLng=en --srcFormat=nested-json --targetFormat=nested-json +attranslate --service=agent --srcFile=translations/en.json --targetFile=translations/es.json --targetLng=Spanish --srcLng=English --srcFormat=nested-json --targetFormat=nested-json ``` To reduce context-usage, this can be wrapped into a conditional statement: diff --git a/package.json b/package.json index f7e35de4..021433b2 100644 --- a/package.json +++ b/package.json @@ -1,15 +1,13 @@ { "name": "attranslate", "version": "2.2.1", - "description": "Semi-automated Text Translator for Websites and Apps", + "description": "Text Translator for Websites and Apps", "repository": { "type": "git", "url": "git+https://github.com/fkirc/attranslate.git" }, "keywords": [ - "semi-automated translation", - "openai-translate", - "google-translate", + "agentic translation", "website translation", "mobile app translation", "i18n" diff --git a/src/services/agent-translate.ts b/src/services/agent-translate.ts new file mode 100644 index 00000000..4639862f --- /dev/null +++ b/src/services/agent-translate.ts @@ -0,0 +1,29 @@ +import inquirer from "inquirer"; +import { TResult, TService, TServiceArgs } from "./service-definitions"; + +export class AgentTranslation implements TService { + async translateStrings(args: TServiceArgs) { + const results: TResult[] = []; + + console.info(`Translating ${args.strings.length} string(s) to ${args.targetLng}...`); + + for (const { key, value } of args.strings) { + const result = await inquirer.prompt<{ result: string }>([ + { + name: "result", + message: `[${key}] Translate "${value}" to ${args.targetLng}:`, + }, + ]); + const userInput = result.result; + if (userInput.trim().length) { + results.push({ + key, + translated: result.result, + }); + } + } + + console.info(`Done.`); + return results; + } +} diff --git a/src/services/service-definitions.ts b/src/services/service-definitions.ts index 1c7e97a3..03d4089d 100644 --- a/src/services/service-definitions.ts +++ b/src/services/service-definitions.ts @@ -29,6 +29,7 @@ export function getTServiceList(): TServiceType[] { } const serviceMap = { + agent: null, openai: null, typechat: null, "typechat-manual": null, @@ -58,6 +59,8 @@ export async function instantiateTService( * This is especially important for google-translate, which uses a huge bunch of packages. */ switch (service) { + case "agent": + return new (await import("./agent-translate")).AgentTranslation(); case "openai": return new (await import("./openai-translate")).OpenAITranslate(); case "typechat": From ffbe40af76406cc877d40a59121a82572ad78eb4 Mon Sep 17 00:00:00 2001 From: nomo-fe Date: Wed, 11 Feb 2026 23:15:34 +0100 Subject: [PATCH 04/16] update sample scripts --- sample-scripts/android_to_ios.sh | 18 +++++++++--------- sample-scripts/flutter.sh | 8 +++----- sample-scripts/json_advanced.sh | 7 +++---- sample-scripts/json_simple_windows.bat | 2 +- sample-scripts/po_generic.sh | 8 +++----- sample-scripts/xml_generic.sh | 5 ++--- sample-scripts/yaml_ecommerce.sh | 8 +++----- test/e2e/invalid-files.test.ts | 1 - 8 files changed, 24 insertions(+), 33 deletions(-) diff --git a/sample-scripts/android_to_ios.sh b/sample-scripts/android_to_ios.sh index 267240ef..87bc6435 100755 --- a/sample-scripts/android_to_ios.sh +++ b/sample-scripts/android_to_ios.sh @@ -7,24 +7,24 @@ set -e # Step 1: Translate an english XML into a spanish XML and a german XML. # Step 2: Covert those Android-XMLs into iOS-Strings, without changing the language. -SERVICE_ACCOUNT_KEY="gcloud/gcloud_service_account.json" +# Paths to app-specific XML-files: +ANDROID_EN="android/app/src/main/res/values/strings.xml" +ANDROID_DE="android/app/src/main/res/values-de/strings.xml" +ANDROID_ES="android/app/src/main/res/values-es/strings.xml" # Paths to app-specific XML-files: ANDROID_EN="android/app/src/main/res/values/strings.xml" ANDROID_DE="android/app/src/main/res/values-de/strings.xml" ANDROID_ES="android/app/src/main/res/values-es/strings.xml" -ANDROID_TO_ANDROID=( "--srcFile=$ANDROID_EN" "--srcLng=en" "--srcFormat=xml" "--targetFormat=xml" "--service=google-translate" "--serviceConfig=$SERVICE_ACCOUNT_KEY" ) -attranslate "${ANDROID_TO_ANDROID[@]}" --targetFile=$ANDROID_DE --targetLng="de" -attranslate "${ANDROID_TO_ANDROID[@]}" --targetFile=$ANDROID_ES --targetLng="es" +attranslate --srcFile=$ANDROID_EN --srcLng=English --srcFormat=xml --targetFile=$ANDROID_DE --targetLng=German --targetFormat=xml --service=agent +attranslate --srcFile=$ANDROID_EN --srcLng=English --srcFormat=xml --targetFile=$ANDROID_ES --targetLng=Spanish --targetFormat=xml --service=agent # Paths to app-specific iOS-Strings: iOS_EN="ios/Localizable/Base.lproj/Localizable.strings" iOS_DE="ios/Localizable/de.lproj/Localizable.strings" iOS_ES="ios/Localizable/es.lproj/Localizable.strings" -ANDROID_TO_iOS=( "--srcFormat=xml" "--targetFormat=ios-strings" "--service=sync-without-translate" ) - -attranslate "${ANDROID_TO_iOS[@]}" --srcFile=$ANDROID_EN --targetFile=$iOS_EN --srcLng="en" --targetLng="en" -attranslate "${ANDROID_TO_iOS[@]}" --srcFile=$ANDROID_DE --targetFile=$iOS_DE --srcLng="de" --targetLng="de" -attranslate "${ANDROID_TO_iOS[@]}" --srcFile=$ANDROID_ES --targetFile=$iOS_ES --srcLng="es" --targetLng="es" +attranslate --srcFile=$ANDROID_EN --srcLng=English --srcFormat=xml --targetFile=$iOS_EN --targetLng=English --targetFormat=ios-strings --service=sync-without-translate +attranslate --srcFile=$ANDROID_DE --srcLng=German --srcFormat=xml --targetFile=$iOS_DE --targetLng=German --targetFormat=ios-strings --service=sync-without-translate +attranslate --srcFile=$ANDROID_ES --srcLng=Spanish --srcFormat=xml --targetFile=$iOS_ES --targetLng=Spanish --targetFormat=ios-strings --service=sync-without-translate diff --git a/sample-scripts/flutter.sh b/sample-scripts/flutter.sh index ed1ce9b1..189b6c22 100755 --- a/sample-scripts/flutter.sh +++ b/sample-scripts/flutter.sh @@ -1,11 +1,9 @@ #!/bin/bash set -e -# This example translates an english ARB-file into spanish and german. It uses Google Cloud Translate. +# This example translates an english ARB-file into spanish and german. BASE_DIR=flutter/lib/l10n -SERVICE_ACCOUNT_KEY="gcloud/gcloud_service_account.json" -COMMON_ARGS=( "--srcFile=$BASE_DIR/intl_messages.arb" "--srcLng=en" "--srcFormat=arb" "--targetFormat=arb" "--service=google-translate" "--serviceConfig=$SERVICE_ACCOUNT_KEY" ) # Run "npm install --global attranslate" before you try this example. -attranslate "${COMMON_ARGS[@]}" --targetFile=$BASE_DIR/intl_es.arb --targetLng=es -attranslate "${COMMON_ARGS[@]}" --targetFile=$BASE_DIR/intl_de.arb --targetLng=de --matcher=icu +attranslate --srcFile=$BASE_DIR/intl_messages.arb --srcLng=English --srcFormat=arb --targetFile=$BASE_DIR/intl_es.arb --targetLng=Spanish --targetFormat=arb --service=agent +attranslate --srcFile=$BASE_DIR/intl_messages.arb --srcLng=English --srcFormat=arb --targetFile=$BASE_DIR/intl_de.arb --targetLng=German --targetFormat=arb --service=agent --matcher=icu diff --git a/sample-scripts/json_advanced.sh b/sample-scripts/json_advanced.sh index 55b8a72e..efef34ee 100755 --- a/sample-scripts/json_advanced.sh +++ b/sample-scripts/json_advanced.sh @@ -3,11 +3,10 @@ set -e # abort on errors # This example translates an english JSON-file into spanish, chinese and german. BASE_DIR="json-advanced" -COMMON_ARGS=( "--srcLng=en" "--srcFormat=nested-json" "--targetFormat=nested-json" "--service=google-translate" "--serviceConfig=gcloud/gcloud_service_account.json" ) # install attranslate if it is not installed yet attranslate --version || npm install --global attranslate -attranslate --srcFile=$BASE_DIR/en/fruits.json --targetFile=$BASE_DIR/es/fruits.json --targetLng=es "${COMMON_ARGS[@]}" -attranslate --srcFile=$BASE_DIR/en/fruits.json --targetFile=$BASE_DIR/zh/fruits.json --targetLng=zh "${COMMON_ARGS[@]}" -attranslate --srcFile=$BASE_DIR/en/fruits.json --targetFile=$BASE_DIR/de/fruits.json --targetLng=de "${COMMON_ARGS[@]}" +attranslate --srcFile=$BASE_DIR/en/fruits.json --srcLng=English --srcFormat=nested-json --targetFile=$BASE_DIR/es/fruits.json --targetLng=Spanish --targetFormat=nested-json --service=agent +attranslate --srcFile=$BASE_DIR/en/fruits.json --srcLng=English --srcFormat=nested-json --targetFile=$BASE_DIR/zh/fruits.json --targetLng=Chinese --targetFormat=nested-json --service=agent +attranslate --srcFile=$BASE_DIR/en/fruits.json --srcLng=English --srcFormat=nested-json --targetFile=$BASE_DIR/de/fruits.json --targetLng=German --targetFormat=nested-json --service=agent diff --git a/sample-scripts/json_simple_windows.bat b/sample-scripts/json_simple_windows.bat index e5a99676..25f88cfa 100644 --- a/sample-scripts/json_simple_windows.bat +++ b/sample-scripts/json_simple_windows.bat @@ -1,4 +1,4 @@ :: This example translates a single JSON-file from English into German. :: Run "npm install --global attranslate" before you try this example. -attranslate --srcFile=json-simple/en.json --srcLng=en --srcFormat=nested-json --targetFile=json-simple/de.json --targetLng=de --targetFormat=nested-json --service=openai +attranslate --srcFile=json-simple/en.json --srcLng=English --srcFormat=nested-json --targetFile=json-simple/de.json --targetLng=German --targetFormat=nested-json --service=agent diff --git a/sample-scripts/po_generic.sh b/sample-scripts/po_generic.sh index 9072048f..3b6e2fe5 100755 --- a/sample-scripts/po_generic.sh +++ b/sample-scripts/po_generic.sh @@ -1,14 +1,12 @@ #!/bin/bash set -e -# This example translates an english PO-file into spanish and german. It uses Google Cloud Translate. -SERVICE_ACCOUNT_KEY="gcloud/gcloud_service_account.json" +# This example translates an english PO-file into spanish and german. BASE_DIR=po-generic -COMMON_ARGS=( "--srcFile=$BASE_DIR/en.po" "--srcLng=en" "--srcFormat=po" "--targetFormat=po" "--service=google-translate" "--serviceConfig=$SERVICE_ACCOUNT_KEY" ) # Run "npm install --global attranslate" before you try this example. -attranslate "${COMMON_ARGS[@]}" --targetFile=$BASE_DIR/es.po --targetLng=es -attranslate "${COMMON_ARGS[@]}" --targetFile=$BASE_DIR/de.po --targetLng=de +attranslate --srcFile=$BASE_DIR/en.po --srcLng=English --srcFormat=po --targetFile=$BASE_DIR/es.po --targetLng=Spanish --targetFormat=po --service=agent +attranslate --srcFile=$BASE_DIR/en.po --srcLng=English --srcFormat=po --targetFile=$BASE_DIR/de.po --targetLng=German --targetFormat=po --service=agent # Convert a YAML to PO (just for the sake of test-coverage) attranslate --srcFile="$BASE_DIR/nested-fruits.yml" --srcFormat=yaml --srcLng=x --targetFile=$BASE_DIR/nested-fruits.po --targetFormat=po --targetLng=x --service=sync-without-translate diff --git a/sample-scripts/xml_generic.sh b/sample-scripts/xml_generic.sh index 1eb72b4d..63c0eefb 100755 --- a/sample-scripts/xml_generic.sh +++ b/sample-scripts/xml_generic.sh @@ -3,11 +3,10 @@ set -e # translate arbitrary (nested) XMLs that have their translatable content within closed tags like so: content to translate BASE_DIR=xml-generic -COMMON_ARGS=( "--srcFormat=xml" "--targetFormat=xml" "--service=typechat" ) # Run "npm install --global attranslate" before you try this example. -attranslate "${COMMON_ARGS[@]}" --srcFile=$BASE_DIR/en.xml --srcLng=en --targetFile=$BASE_DIR/ar.xml --targetLng=ar -attranslate "${COMMON_ARGS[@]}" --srcFile=$BASE_DIR/en.xml --srcLng=en --targetFile=$BASE_DIR/de.xml --targetLng=de +attranslate --srcFile=$BASE_DIR/en.xml --srcLng=English --srcFormat=xml --targetFile=$BASE_DIR/ar.xml --targetLng=Arabic --targetFormat=xml --service=agent +attranslate --srcFile=$BASE_DIR/en.xml --srcLng=English --srcFormat=xml --targetFile=$BASE_DIR/de.xml --targetLng=German --targetFormat=xml --service=agent # Convert an iOS string file into an XML (just for the sake of test-coverage) attranslate --srcFormat=ios-strings --srcFile=$BASE_DIR/nested-fruits.strings --srcLng=en --targetFormat=xml --targetFile=$BASE_DIR/nested-fruits.xml --targetLng=en --service=sync-without-translate diff --git a/sample-scripts/yaml_ecommerce.sh b/sample-scripts/yaml_ecommerce.sh index 44f2d7ad..24e67a0b 100755 --- a/sample-scripts/yaml_ecommerce.sh +++ b/sample-scripts/yaml_ecommerce.sh @@ -1,18 +1,16 @@ #!/bin/bash set -e -# This example translates an english YAML-file into spanish and german. It uses Google Cloud Translate. -SERVICE_ACCOUNT_KEY="gcloud/gcloud_service_account.json" -COMMON_ARGS=( "--srcFile=yaml/en_ecommerce.yml" "--srcLng=en" "--srcFormat=yaml" "--targetFormat=yaml" "--service=manual" "--serviceConfig=$SERVICE_ACCOUNT_KEY" ) +# This example translates an english YAML-file into spanish and german. # Run "npm install --global attranslate" before you try this example. node search_replace.js "yaml/es_ecommerce.yml" "es:" "en:" # preprocessing -attranslate "${COMMON_ARGS[@]}" --targetFile=yaml/es_ecommerce.yml --targetLng=es +attranslate --srcFile=yaml/en_ecommerce.yml --srcLng=English --srcFormat=yaml --targetFile=yaml/es_ecommerce.yml --targetLng=Spanish --targetFormat=yaml --service=agent node search_replace.js "yaml/es_ecommerce.yml" "en:" "es:" # postprocessing node search_replace.js "yaml/de_ecommerce.yml" "de:" "en:" # preprocessing -attranslate "${COMMON_ARGS[@]}" --targetFile=yaml/de_ecommerce.yml --targetLng=de +attranslate --srcFile=yaml/en_ecommerce.yml --srcLng=English --srcFormat=yaml --targetFile=yaml/de_ecommerce.yml --targetLng=German --targetFormat=yaml --service=agent node search_replace.js "yaml/de_ecommerce.yml" "en:" "de:" # postprocessing # Convert a JSON to YML (just for the sake of test-coverage) diff --git a/test/e2e/invalid-files.test.ts b/test/e2e/invalid-files.test.ts index e81dc672..10fe79d4 100644 --- a/test/e2e/invalid-files.test.ts +++ b/test/e2e/invalid-files.test.ts @@ -8,7 +8,6 @@ test("src not a JSON", async () => { srcFile: "test-assets/invalid/not-a-json", }; const output = await runTranslateExpectFailure(buildE2EArgs(args)); - expect(output).toContain("Unexpected token # in JSON at position 13"); expect(output).toContain( `error: Failed to parse ${getDebugPath(args.srcFile)}.\n` ); From 4c56fc6f1b42d55bb5dac3a50cd5abadd1a6ece8 Mon Sep 17 00:00:00 2001 From: nomo-fe Date: Thu, 12 Feb 2026 00:41:26 +0100 Subject: [PATCH 05/16] rewrite agentic translation to be non interactive --- src/services/agent-translate.ts | 67 +++++++++++++++++++++++---------- 1 file changed, 47 insertions(+), 20 deletions(-) diff --git a/src/services/agent-translate.ts b/src/services/agent-translate.ts index 4639862f..cb07fcf3 100644 --- a/src/services/agent-translate.ts +++ b/src/services/agent-translate.ts @@ -1,29 +1,56 @@ -import inquirer from "inquirer"; +// AgentTranslation: minimal agent protocol for attranslate +// - First invocation (stdin is TTY): prints missing sources and agent instructions. +// - Second invocation (stdin is piped): reads translations, one per line, in order. + import { TResult, TService, TServiceArgs } from "./service-definitions"; +function printMissingSources(strings: { key: string; value: string }[], useError = false) { + const log = useError ? console.error : console.log; + log("MISSING TRANSLATIONS:\n"); + for (const { key, value } of strings) { + log(`- key: ${key}`); + log(` source: ${value}\n`); + } +} + export class AgentTranslation implements TService { async translateStrings(args: TServiceArgs) { const results: TResult[] = []; - - console.info(`Translating ${args.strings.length} string(s) to ${args.targetLng}...`); - - for (const { key, value } of args.strings) { - const result = await inquirer.prompt<{ result: string }>([ - { - name: "result", - message: `[${key}] Translate "${value}" to ${args.targetLng}:`, - }, - ]); - const userInput = result.result; - if (userInput.trim().length) { - results.push({ - key, - translated: result.result, - }); - } + if (process.stdin.isTTY) { + printMissingSources(args.strings); + console.log("\nINSTRUCTIONS FOR AGENTS:"); + console.log("Provide one translation per line, matching the order above. Pipe them into attranslate."); + const cmd = process.argv.slice(2).join(" "); + console.log(`echo -e \"\\n\\n...\" | attranslate ${cmd} --service=agent`); + return results; + } + // Read piped stdin, split into lines, map to keys in order + const stdin = await this.readAllStdin(); + const lines = stdin.replace(/\r/g, "").split("\n").filter(line => line.trim() !== ""); + const translationCount = args.strings.length; + if (lines.length === 0) { + printMissingSources(args.strings, true); + console.error("ERROR: No translations provided. Pipe one translation per source listed above."); + process.exit(1); + } + if (lines.length !== translationCount) { + printMissingSources(args.strings, true); + console.error(`ERROR: ${lines.length} translations given, ${translationCount} required. Provide exactly one translation per source listed above.`); + process.exit(1); + } + for (let i = 0; i < translationCount; i++) { + results.push({ key: args.strings[i].key, translated: lines[i] }); } - - console.info(`Done.`); return results; } + private async readAllStdin(): Promise { + return new Promise((resolve) => { + let data = ""; + process.stdin.setEncoding("utf8"); + process.stdin.on("data", (chunk) => (data += chunk)); + process.stdin.on("end", () => resolve(data)); + }); + } } + + From 011c1ac0adf2a2e81e5bebcd60018f111ee0145f Mon Sep 17 00:00:00 2001 From: nomo-fe Date: Thu, 12 Feb 2026 00:50:46 +0100 Subject: [PATCH 06/16] document agent workflow --- README-es.md | 14 ++++++++++++++ README.md | 16 +++++++++++++++- sample-scripts/json_simple.sh | 9 +++++++-- 3 files changed, 36 insertions(+), 3 deletions(-) diff --git a/README-es.md b/README-es.md index 5e0d8307..368979b3 100644 --- a/README-es.md +++ b/README-es.md @@ -77,3 +77,17 @@ Para reducir el uso de contexto, esto puede envolverse en una declaración condi ``` Al agregar nuevas claves de traducción, consulta para ver cómo deben hacerse las nuevas traducciones. ``` + +# Flujo de trabajo del agente (basado en stdin) + +Cuando uses `--service=agent`, attranslate imprimirá una lista de fuentes faltantes e instrucciones para el agente. El agente debe proporcionar una traducción por línea, en el mismo orden, y canalizarlas a attranslate vía stdin. Ejemplo: + +``` +attranslate --srcFile=en.json --srcLng=English --srcFormat=nested-json --targetFile=es.json --targetLng=Spanish --targetFormat=nested-json --service=agent +``` + +Luego el agente canaliza las traducciones: + +``` +echo -e "\n\n..." | attranslate --srcFile=en.json --srcLng=English --srcFormat=nested-json --targetFile=es.json --targetLng=Spanish --targetFormat=nested-json --service=agent +``` diff --git a/README.md b/README.md index b828a43a..6dec42d6 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ Therefore, whenever you are unhappy with the produced text, `attranslate` allows ## Available Services -- `agent`: For use with Coding Agents. Prompts the agent to translate new strings interactively when detected. +- `agent`: For use with Coding Agents. Prompts the agent to translate new strings via stdin when detected. - `sync-without-translate`: Verifies translation completeness without translating (e.g. for CI/CD pipelines). Other services (openai, google-translate, azure, manual, typechat, etc.) are deprecated but retained for backwards-compatibility. @@ -87,3 +87,17 @@ To reduce context-usage, this can be wrapped into a conditional statement: ``` When adding new translation-keys, lookup to see how new translations should be done. ``` + +# Agent Workflow (stdin-based) + +When using `--service=agent`, attranslate will print a list of missing sources and instructions for the agent. The agent should provide one translation per line, in the same order, and pipe them into attranslate via stdin. Example: + +``` +attranslate --srcFile=en.json --srcLng=English --srcFormat=nested-json --targetFile=es.json --targetLng=Spanish --targetFormat=nested-json --service=agent +``` + +The agent then pipes translations: + +``` +echo -e "\n\n..." | attranslate --srcFile=en.json --srcLng=English --srcFormat=nested-json --targetFile=es.json --targetLng=Spanish --targetFormat=nested-json --service=agent +``` diff --git a/sample-scripts/json_simple.sh b/sample-scripts/json_simple.sh index c4d6c198..6b31dcb4 100755 --- a/sample-scripts/json_simple.sh +++ b/sample-scripts/json_simple.sh @@ -1,7 +1,12 @@ #!/bin/bash set -e -# This example translates a single JSON-file from English into German. +# This example translates a single JSON-file from English into German using the agent workflow. # Run "npm install --global attranslate" before you try this example. -attranslate --srcFile=json-simple/en.json --srcLng=English --srcFormat=nested-json --targetFile=json-simple/de.json --targetLng=German --targetFormat=nested-json --service=openai + +# First invocation: show missing translations and instructions for agent +attranslate --srcFile=json-simple/en.json --srcLng=English --srcFormat=nested-json --targetFile=json-simple/de.json --targetLng=German --targetFormat=nested-json --service=agent + +# Second invocation triggered by agent: pipe translations into stdin, one per line, in order; +# echo -e "\n\n..." | attranslate --srcFile=json-simple/en.json --srcLng=English --srcFormat=nested-json --targetFile=json-simple/de.json --targetLng=German --targetFormat=nested-json --service=agent From 66f4a0863e9992d561376b49368022285d66a0e6 Mon Sep 17 00:00:00 2001 From: nomo-fe Date: Thu, 12 Feb 2026 01:37:13 +0100 Subject: [PATCH 07/16] simplify args: nested-json -> json --- README-es.md | 20 ++++++++++---------- README.md | 16 ++++++++-------- sample-scripts/csv_to_other_formats.sh | 2 +- sample-scripts/json_advanced.sh | 6 +++--- sample-scripts/json_simple.sh | 6 +++--- sample-scripts/json_simple_windows.bat | 11 +++++++++-- sample-scripts/yaml_ecommerce.sh | 2 +- src/file-formats/file-format-definitions.ts | 2 ++ test/e2e/e2e-common.ts | 2 +- 9 files changed, 38 insertions(+), 29 deletions(-) diff --git a/README-es.md b/README-es.md index 368979b3..14c571df 100644 --- a/README-es.md +++ b/README-es.md @@ -19,7 +19,7 @@ Por lo tanto, siempre que no esté satisfecho con los resultados producidos, `at ## Servicios disponibles -- `agent`: Para usar con Agentes de Código. Solicita al agente traducir nuevas cadenas de forma interactiva cuando se detectan. +- `agent`: Para usar con Agentes de Código. Solicita al agente traducir nuevas cadenas vía stdin cuando se detectan. - `sync-without-translate`: Verifica la integridad de las traducciones sin traducir (p. ej. para tuberías CI/CD). Otros servicios (openai, google-translate, azure, manual, typechat, etc.) están deprecados pero se retienen para compatibilidad hacia atrás. @@ -29,14 +29,14 @@ Otros servicios (openai, google-translate, azure, manual, typechat, etc.) están Traducir un archivo único es tan simple como la siguiente línea: ``` -attranslate --srcFile=json-simple/en.json --srcLng=English --srcFormat=nested-json --targetFile=json-simple/es.json --targetLng=Spanish --targetFormat=nested-json --service=agent +attranslate --srcFile=en.json --srcLng=English --srcFormat=json --targetFile=es.json --targetLng=Spanish --targetFormat=json --service=agent ``` Para varios idiomas de destino, invoca `attranslate` para cada uno: ```bash -attranslate --srcFile=en/fruits.json --targetFile=es/fruits.json --targetLng=Spanish --srcLng=English --srcFormat=nested-json --targetFormat=nested-json --service=agent -attranslate --srcFile=en/fruits.json --targetFile=de/fruits.json --targetLng=German --srcLng=English --srcFormat=nested-json --targetFormat=nested-json --service=agent +attranslate --srcFile=en/fruits.json --targetFile=es/fruits.json --targetLng=Spanish --srcLng=English --srcFormat=json --targetFormat=json --service=agent +attranslate --srcFile=en/fruits.json --targetFile=de/fruits.json --targetLng=German --srcLng=English --srcFormat=json --targetFormat=json --service=agent ``` # Opciones de uso @@ -49,13 +49,13 @@ Usage: attranslate [options] Options: --srcFile The source file to be translated --srcLng A language code for the source language - --srcFormat One of "flat-json", "nested-json", "yaml", + --srcFormat Uno de "flat-json", "json", "yaml", "po", "xml", "ios-strings", "arb", "csv" --targetFile The target file for the translations --targetLng A language code for the target language - --targetFormat One of "flat-json", "nested-json", "yaml", + --targetFormat Uno de "flat-json", "json", "yaml", "po", "xml", "ios-strings", "arb", "csv" - --service One of "agent", "sync-without-translate" + --service Uno de "agent", "sync-without-translate" -v, --version output the version number -h, --help display help for command ``` @@ -69,7 +69,7 @@ Por ejemplo, agrega algo como esto a tu prompt del sistema: Al realizar traducciones, recuerda que estás desarrollando una aplicación de salud para profesionales médicos. Términos técnicos como 'EKG', 'MRI', 'CT scan', 'blood pressure', 'pulse oximeter' y 'vital signs' deben permanecer en inglés. Por favor, mantén la terminología médica apropiada y un tono formal en las traducciones. Invoca `attranslate` después de agregar una nueva traducción al archivo en.json en inglés. Por ejemplo: -attranslate --service=agent --srcFile=translations/en.json --targetFile=translations/es.json --targetLng=Spanish --srcLng=English --srcFormat=nested-json --targetFormat=nested-json +attranslate --service=agent --srcFile=translations/en.json --targetFile=translations/es.json --targetLng=Spanish --srcLng=English --srcFormat=json --targetFormat=json ``` Para reducir el uso de contexto, esto puede envolverse en una declaración condicional: @@ -83,11 +83,11 @@ Al agregar nuevas claves de traducción, consulta para ver Cuando uses `--service=agent`, attranslate imprimirá una lista de fuentes faltantes e instrucciones para el agente. El agente debe proporcionar una traducción por línea, en el mismo orden, y canalizarlas a attranslate vía stdin. Ejemplo: ``` -attranslate --srcFile=en.json --srcLng=English --srcFormat=nested-json --targetFile=es.json --targetLng=Spanish --targetFormat=nested-json --service=agent +attranslate --srcFile=en.json --srcLng=English --srcFormat=json --targetFile=es.json --targetLng=Spanish --targetFormat=json --service=agent ``` Luego el agente canaliza las traducciones: ``` -echo -e "\n\n..." | attranslate --srcFile=en.json --srcLng=English --srcFormat=nested-json --targetFile=es.json --targetLng=Spanish --targetFormat=nested-json --service=agent +echo -e "\n\n..." | attranslate --srcFile=en.json --srcLng=English --srcFormat=json --targetFile=es.json --targetLng=Spanish --targetFormat=json --service=agent ``` diff --git a/README.md b/README.md index 6dec42d6..3d21189a 100644 --- a/README.md +++ b/README.md @@ -27,14 +27,14 @@ Other services (openai, google-translate, azure, manual, typechat, etc.) are dep Translating a single file is as simple as the following line: ``` -attranslate --srcFile=json-simple/en.json --srcLng=English --srcFormat=nested-json --targetFile=json-simple/es.json --targetLng=Spanish --targetFormat=nested-json --service=agent +attranslate --srcFile=en.json --srcLng=English --srcFormat=json --targetFile=es.json --targetLng=Spanish --targetFormat=json --service=agent ``` For multiple target languages, call `attranslate` for each: ```bash -attranslate --srcFile=en/fruits.json --targetFile=es/fruits.json --targetLng=Spanish --srcLng=English --srcFormat=nested-json --targetFormat=nested-json --service=agent -attranslate --srcFile=en/fruits.json --targetFile=de/fruits.json --targetLng=German --srcLng=English --srcFormat=nested-json --targetFormat=nested-json --service=agent +attranslate --srcFile=en/fruits.json --targetFile=es/fruits.json --targetLng=Spanish --srcLng=English --srcFormat=json --targetFormat=json --service=agent +attranslate --srcFile=en/fruits.json --targetFile=de/fruits.json --targetLng=German --srcLng=English --srcFormat=json --targetFormat=json --service=agent ``` # Installation @@ -59,11 +59,11 @@ Usage: attranslate [options] Options: --srcFile The source file to be translated --srcLng A language code for the source language - --srcFormat One of "flat-json", "nested-json", "yaml", + --srcFormat One of "flat-json", "json", "yaml", "po", "xml", "ios-strings", "arb", "csv" --targetFile The target file for the translations --targetLng A language code for the target language - --targetFormat One of "flat-json", "nested-json", "yaml", + --targetFormat One of "flat-json", "json", "yaml", "po", "xml", "ios-strings", "arb", "csv" --service One of "agent", "sync-without-translate" -v, --version output the version number @@ -79,7 +79,7 @@ For example, add something like this to your system prompt: When doing translations, remember that you are building a healthcare app for medical professionals. Technical terms like 'EKG', 'MRI', 'CT scan', 'blood pressure', 'pulse oximeter', and 'vital signs' should remain in English. Please maintain proper medical terminology and formal tone in translations. Invoke `attranslate` after adding a new translation to the English en.json. For example: -attranslate --service=agent --srcFile=translations/en.json --targetFile=translations/es.json --targetLng=Spanish --srcLng=English --srcFormat=nested-json --targetFormat=nested-json +attranslate --service=agent --srcFile=translations/en.json --targetFile=translations/es.json --targetLng=Spanish --srcLng=English --srcFormat=json --targetFormat=json ``` To reduce context-usage, this can be wrapped into a conditional statement: @@ -93,11 +93,11 @@ When adding new translation-keys, lookup to see how new tr When using `--service=agent`, attranslate will print a list of missing sources and instructions for the agent. The agent should provide one translation per line, in the same order, and pipe them into attranslate via stdin. Example: ``` -attranslate --srcFile=en.json --srcLng=English --srcFormat=nested-json --targetFile=es.json --targetLng=Spanish --targetFormat=nested-json --service=agent +attranslate --srcFile=en.json --srcLng=English --srcFormat=json --targetFile=es.json --targetLng=Spanish --targetFormat=json --service=agent ``` The agent then pipes translations: ``` -echo -e "\n\n..." | attranslate --srcFile=en.json --srcLng=English --srcFormat=nested-json --targetFile=es.json --targetLng=Spanish --targetFormat=nested-json --service=agent +echo -e "\n\n..." | attranslate --srcFile=en.json --srcLng=English --srcFormat=json --targetFile=es.json --targetLng=Spanish --targetFormat=json --service=agent ``` diff --git a/sample-scripts/csv_to_other_formats.sh b/sample-scripts/csv_to_other_formats.sh index 580daca5..747c6699 100755 --- a/sample-scripts/csv_to_other_formats.sh +++ b/sample-scripts/csv_to_other_formats.sh @@ -7,7 +7,7 @@ set -e attranslate --srcFormat=csv --srcFile=csv/google-docs.csv --srcLng=en --targetFormat=xml --targetFile=csv/en.xml --targetLng=en --service=sync-without-translate attranslate --srcFormat=csv --srcFile=csv/google-docs.csv --srcLng=de --targetFormat=yaml --targetFile=csv/de.yaml --targetLng=de --service=sync-without-translate -attranslate --srcFormat=csv --srcFile=csv/google-docs.csv --srcLng=de --targetFormat=nested-json --targetFile=csv/de.json --targetLng=de --service=sync-without-translate +attranslate --srcFormat=csv --srcFile=csv/google-docs.csv --srcLng=de --targetFormat=json --targetFile=csv/de.json --targetLng=de --service=sync-without-translate attranslate --srcFormat=csv --srcFile=csv/translations.csv --srcLng=en --targetFormat=flat-json --targetFile=csv/en.json --targetLng=en --service=sync-without-translate attranslate --srcFormat=csv --srcFile=csv/translations.csv --srcLng=en --targetFormat=ios-strings --targetFile=csv/en.strings --targetLng=en --service=sync-without-translate diff --git a/sample-scripts/json_advanced.sh b/sample-scripts/json_advanced.sh index efef34ee..0d3b6af2 100755 --- a/sample-scripts/json_advanced.sh +++ b/sample-scripts/json_advanced.sh @@ -7,6 +7,6 @@ BASE_DIR="json-advanced" # install attranslate if it is not installed yet attranslate --version || npm install --global attranslate -attranslate --srcFile=$BASE_DIR/en/fruits.json --srcLng=English --srcFormat=nested-json --targetFile=$BASE_DIR/es/fruits.json --targetLng=Spanish --targetFormat=nested-json --service=agent -attranslate --srcFile=$BASE_DIR/en/fruits.json --srcLng=English --srcFormat=nested-json --targetFile=$BASE_DIR/zh/fruits.json --targetLng=Chinese --targetFormat=nested-json --service=agent -attranslate --srcFile=$BASE_DIR/en/fruits.json --srcLng=English --srcFormat=nested-json --targetFile=$BASE_DIR/de/fruits.json --targetLng=German --targetFormat=nested-json --service=agent +attranslate --srcFile=$BASE_DIR/en/fruits.json --srcLng=English --srcFormat=json --targetFile=$BASE_DIR/es/fruits.json --targetLng=Spanish --targetFormat=json --service=agent +attranslate --srcFile=$BASE_DIR/en/fruits.json --srcLng=English --srcFormat=json --targetFile=$BASE_DIR/zh/fruits.json --targetLng=Chinese --targetFormat=json --service=agent +attranslate --srcFile=$BASE_DIR/en/fruits.json --srcLng=English --srcFormat=json --targetFile=$BASE_DIR/de/fruits.json --targetLng=German --targetFormat=json --service=agent diff --git a/sample-scripts/json_simple.sh b/sample-scripts/json_simple.sh index 6b31dcb4..df178ca0 100755 --- a/sample-scripts/json_simple.sh +++ b/sample-scripts/json_simple.sh @@ -6,7 +6,7 @@ set -e # Run "npm install --global attranslate" before you try this example. # First invocation: show missing translations and instructions for agent -attranslate --srcFile=json-simple/en.json --srcLng=English --srcFormat=nested-json --targetFile=json-simple/de.json --targetLng=German --targetFormat=nested-json --service=agent +attranslate --srcFile=json-simple/en.json --srcLng=English --srcFormat=json --targetFile=json-simple/de.json --targetLng=German --targetFormat=json --service=agent -# Second invocation triggered by agent: pipe translations into stdin, one per line, in order; -# echo -e "\n\n..." | attranslate --srcFile=json-simple/en.json --srcLng=English --srcFormat=nested-json --targetFile=json-simple/de.json --targetLng=German --targetFormat=nested-json --service=agent +# Second invocation triggered by agent: pipe translations into stdin, one per line, in order +# echo -e "\n\n..." | attranslate --srcFile=json-simple/en.json --srcLng=English --srcFormat=json --targetFile=json-simple/de.json --targetLng=German --targetFormat=json --service=agent diff --git a/sample-scripts/json_simple_windows.bat b/sample-scripts/json_simple_windows.bat index 25f88cfa..7eb08583 100644 --- a/sample-scripts/json_simple_windows.bat +++ b/sample-scripts/json_simple_windows.bat @@ -1,4 +1,11 @@ -:: This example translates a single JSON-file from English into German. +:: This example translates a single JSON-file from English into German using the agent workflow. :: Run "npm install --global attranslate" before you try this example. -attranslate --srcFile=json-simple/en.json --srcLng=English --srcFormat=nested-json --targetFile=json-simple/de.json --targetLng=German --targetFormat=nested-json --service=agent + +:: First invocation: show missing translations and instructions for agent +attranslate --srcFile=json-simple/en.json --srcLng=English --srcFormat=json --targetFile=json-simple/de.json --targetLng=German --targetFormat=json --service=agent + +:: Second invocation triggered by agent (the agent will replace the echo with your translations, one per line, in order): +:: echo +:: echo +:: ... | attranslate --srcFile=json-simple/en.json --srcLng=English --srcFormat=json --targetFile=json-simple/de.json --targetLng=German --targetFormat=json --service=agent diff --git a/sample-scripts/yaml_ecommerce.sh b/sample-scripts/yaml_ecommerce.sh index 24e67a0b..1b3d287a 100755 --- a/sample-scripts/yaml_ecommerce.sh +++ b/sample-scripts/yaml_ecommerce.sh @@ -14,4 +14,4 @@ attranslate --srcFile=yaml/en_ecommerce.yml --srcLng=English --srcFormat=yaml -- node search_replace.js "yaml/de_ecommerce.yml" "en:" "de:" # postprocessing # Convert a JSON to YML (just for the sake of test-coverage) -attranslate --srcFile="yaml/nested-fruits.json" --srcFormat=nested-json --srcLng=x --targetFile=yaml/nested-fruits.yml --targetFormat=yaml --targetLng=x --service=sync-without-translate +attranslate --srcFile="yaml/nested-fruits.json" --srcFormat=json --srcLng=x --targetFile=yaml/nested-fruits.yml --targetFormat=yaml --targetLng=x --service=sync-without-translate diff --git a/src/file-formats/file-format-definitions.ts b/src/file-formats/file-format-definitions.ts index fb22d0f7..bb0463f7 100644 --- a/src/file-formats/file-format-definitions.ts +++ b/src/file-formats/file-format-definitions.ts @@ -30,6 +30,7 @@ export function getTFileFormatList(): TFileType[] { const fileFormatMap = { "flat-json": null, "nested-json": null, + json: null, yaml: null, po: null, xml: null, @@ -48,6 +49,7 @@ export async function instantiateTFileFormat( case "flat-json": return new FlatJson(); case "nested-json": + case "json": return new NestedJson(); case "yaml": return new (await import("./yaml/yaml-generic")).YamlGeneric(); diff --git a/test/e2e/e2e-common.ts b/test/e2e/e2e-common.ts index a1c1d687..d1618c53 100644 --- a/test/e2e/e2e-common.ts +++ b/test/e2e/e2e-common.ts @@ -15,7 +15,7 @@ const randomTargetMarker = "random_target"; export const defaultE2EArgs: E2EArgs = { srcFile: "test-assets/nested-json/count-en.json", srcLng: "en", - srcFormat: "nested-json", + srcFormat: "json", targetFile: getRandomTargetName("default_target"), refTargetFile: "default-ref-target", targetLng: "de", From 084ef87ea61f3604f5de18d846142f00707abf74 Mon Sep 17 00:00:00 2001 From: nomo-fe Date: Thu, 12 Feb 2026 02:02:49 +0100 Subject: [PATCH 08/16] format options refactor --- AGENTS.md | 12 +++++++++ README-es.md | 17 +++++------- README.md | 17 +++++------- sample-scripts/android_to_ios.sh | 4 +-- sample-scripts/csv_to_other_formats.sh | 2 +- sample-scripts/flutter.sh | 4 +-- sample-scripts/json_advanced.sh | 6 ++--- sample-scripts/json_simple.sh | 4 +-- sample-scripts/json_simple_windows.bat | 4 +-- sample-scripts/po_generic.sh | 4 +-- sample-scripts/xml_generic.sh | 4 +-- sample-scripts/yaml_ecommerce.sh | 4 +-- src/core/core-definitions.ts | 12 +++++++-- src/core/translate-cli.ts | 36 ++++++++++++++++++++++++++ src/index.ts | 23 +++++++++++----- test/e2e/scripts-json-multi.test.ts | 8 +++--- test/e2e/translate-add.test.ts | 12 ++++++--- 17 files changed, 119 insertions(+), 54 deletions(-) create mode 100644 AGENTS.md diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 00000000..a7be5992 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,12 @@ +# AGENTS.md + +Always do a build before executing tests: + +- npm run build +- npm run test + +Do NOT work on files in the dist-folder, because the dist-folder is auto-generated by the npm run build command. + +When working on unit-tests, see docs/DEV_MANIFESTO.md for details regarding our test philosophy. + +When asked for new features, only implement the logic that is requested. Do not implement new logic just because you assume that a user-base exists for it. diff --git a/README-es.md b/README-es.md index 14c571df..7a414499 100644 --- a/README-es.md +++ b/README-es.md @@ -29,14 +29,14 @@ Otros servicios (openai, google-translate, azure, manual, typechat, etc.) están Traducir un archivo único es tan simple como la siguiente línea: ``` -attranslate --srcFile=en.json --srcLng=English --srcFormat=json --targetFile=es.json --targetLng=Spanish --targetFormat=json --service=agent +attranslate --srcFile=en.json --srcLng=English --format=json --targetFile=es.json --targetLng=Spanish --service=agent ``` Para varios idiomas de destino, invoca `attranslate` para cada uno: ```bash -attranslate --srcFile=en/fruits.json --targetFile=es/fruits.json --targetLng=Spanish --srcLng=English --srcFormat=json --targetFormat=json --service=agent -attranslate --srcFile=en/fruits.json --targetFile=de/fruits.json --targetLng=German --srcLng=English --srcFormat=json --targetFormat=json --service=agent +attranslate --srcFile=en/fruits.json --targetFile=es/fruits.json --targetLng=Spanish --srcLng=English --format=json --service=agent +attranslate --srcFile=en/fruits.json --targetFile=de/fruits.json --targetLng=German --srcLng=English --format=json --service=agent ``` # Opciones de uso @@ -49,12 +49,9 @@ Usage: attranslate [options] Options: --srcFile The source file to be translated --srcLng A language code for the source language - --srcFormat Uno de "flat-json", "json", "yaml", - "po", "xml", "ios-strings", "arb", "csv" --targetFile The target file for the translations --targetLng A language code for the target language - --targetFormat Uno de "flat-json", "json", "yaml", - "po", "xml", "ios-strings", "arb", "csv" + --format Uno de "flat-json", "nested-json", "json", "yaml", "po", "xml", "ios-strings", "arb", "csv" --service Uno de "agent", "sync-without-translate" -v, --version output the version number -h, --help display help for command @@ -69,7 +66,7 @@ Por ejemplo, agrega algo como esto a tu prompt del sistema: Al realizar traducciones, recuerda que estás desarrollando una aplicación de salud para profesionales médicos. Términos técnicos como 'EKG', 'MRI', 'CT scan', 'blood pressure', 'pulse oximeter' y 'vital signs' deben permanecer en inglés. Por favor, mantén la terminología médica apropiada y un tono formal en las traducciones. Invoca `attranslate` después de agregar una nueva traducción al archivo en.json en inglés. Por ejemplo: -attranslate --service=agent --srcFile=translations/en.json --targetFile=translations/es.json --targetLng=Spanish --srcLng=English --srcFormat=json --targetFormat=json +attranslate --service=agent --srcFile=translations/en.json --targetFile=translations/es.json --targetLng=Spanish --srcLng=English --format=json ``` Para reducir el uso de contexto, esto puede envolverse en una declaración condicional: @@ -83,11 +80,11 @@ Al agregar nuevas claves de traducción, consulta para ver Cuando uses `--service=agent`, attranslate imprimirá una lista de fuentes faltantes e instrucciones para el agente. El agente debe proporcionar una traducción por línea, en el mismo orden, y canalizarlas a attranslate vía stdin. Ejemplo: ``` -attranslate --srcFile=en.json --srcLng=English --srcFormat=json --targetFile=es.json --targetLng=Spanish --targetFormat=json --service=agent +attranslate --srcFile=en.json --srcLng=English --format=json --targetFile=es.json --targetLng=Spanish --service=agent ``` Luego el agente canaliza las traducciones: ``` -echo -e "\n\n..." | attranslate --srcFile=en.json --srcLng=English --srcFormat=json --targetFile=es.json --targetLng=Spanish --targetFormat=json --service=agent +echo -e "\n\n..." | attranslate --srcFile=en.json --srcLng=English --format=json --targetFile=es.json --targetLng=Spanish --service=agent ``` diff --git a/README.md b/README.md index 3d21189a..61cd8e2a 100644 --- a/README.md +++ b/README.md @@ -27,14 +27,14 @@ Other services (openai, google-translate, azure, manual, typechat, etc.) are dep Translating a single file is as simple as the following line: ``` -attranslate --srcFile=en.json --srcLng=English --srcFormat=json --targetFile=es.json --targetLng=Spanish --targetFormat=json --service=agent +attranslate --srcFile=en.json --srcLng=English --format=json --targetFile=es.json --targetLng=Spanish --service=agent ``` For multiple target languages, call `attranslate` for each: ```bash -attranslate --srcFile=en/fruits.json --targetFile=es/fruits.json --targetLng=Spanish --srcLng=English --srcFormat=json --targetFormat=json --service=agent -attranslate --srcFile=en/fruits.json --targetFile=de/fruits.json --targetLng=German --srcLng=English --srcFormat=json --targetFormat=json --service=agent +attranslate --srcFile=en/fruits.json --targetFile=es/fruits.json --targetLng=Spanish --srcLng=English --format=json --service=agent +attranslate --srcFile=en/fruits.json --targetFile=de/fruits.json --targetLng=German --srcLng=English --format=json --service=agent ``` # Installation @@ -59,12 +59,9 @@ Usage: attranslate [options] Options: --srcFile The source file to be translated --srcLng A language code for the source language - --srcFormat One of "flat-json", "json", "yaml", - "po", "xml", "ios-strings", "arb", "csv" --targetFile The target file for the translations --targetLng A language code for the target language - --targetFormat One of "flat-json", "json", "yaml", - "po", "xml", "ios-strings", "arb", "csv" + --format One of "flat-json", "nested-json", "json", "yaml", "po", "xml", "ios-strings", "arb", "csv" --service One of "agent", "sync-without-translate" -v, --version output the version number -h, --help display help for command @@ -79,7 +76,7 @@ For example, add something like this to your system prompt: When doing translations, remember that you are building a healthcare app for medical professionals. Technical terms like 'EKG', 'MRI', 'CT scan', 'blood pressure', 'pulse oximeter', and 'vital signs' should remain in English. Please maintain proper medical terminology and formal tone in translations. Invoke `attranslate` after adding a new translation to the English en.json. For example: -attranslate --service=agent --srcFile=translations/en.json --targetFile=translations/es.json --targetLng=Spanish --srcLng=English --srcFormat=json --targetFormat=json +attranslate --service=agent --srcFile=translations/en.json --targetFile=translations/es.json --targetLng=Spanish --srcLng=English --format=json ``` To reduce context-usage, this can be wrapped into a conditional statement: @@ -93,11 +90,11 @@ When adding new translation-keys, lookup to see how new tr When using `--service=agent`, attranslate will print a list of missing sources and instructions for the agent. The agent should provide one translation per line, in the same order, and pipe them into attranslate via stdin. Example: ``` -attranslate --srcFile=en.json --srcLng=English --srcFormat=json --targetFile=es.json --targetLng=Spanish --targetFormat=json --service=agent +attranslate --srcFile=en.json --srcLng=English --format=json --targetFile=es.json --targetLng=Spanish --service=agent ``` The agent then pipes translations: ``` -echo -e "\n\n..." | attranslate --srcFile=en.json --srcLng=English --srcFormat=json --targetFile=es.json --targetLng=Spanish --targetFormat=json --service=agent +echo -e "\n\n..." | attranslate --srcFile=en.json --srcLng=English --format=json --targetFile=es.json --targetLng=Spanish --service=agent ``` diff --git a/sample-scripts/android_to_ios.sh b/sample-scripts/android_to_ios.sh index 87bc6435..95e2381e 100755 --- a/sample-scripts/android_to_ios.sh +++ b/sample-scripts/android_to_ios.sh @@ -17,8 +17,8 @@ ANDROID_EN="android/app/src/main/res/values/strings.xml" ANDROID_DE="android/app/src/main/res/values-de/strings.xml" ANDROID_ES="android/app/src/main/res/values-es/strings.xml" -attranslate --srcFile=$ANDROID_EN --srcLng=English --srcFormat=xml --targetFile=$ANDROID_DE --targetLng=German --targetFormat=xml --service=agent -attranslate --srcFile=$ANDROID_EN --srcLng=English --srcFormat=xml --targetFile=$ANDROID_ES --targetLng=Spanish --targetFormat=xml --service=agent +attranslate --srcFile=$ANDROID_EN --srcLng=English --format=xml --targetFile=$ANDROID_DE --targetLng=German --service=agent +attranslate --srcFile=$ANDROID_EN --srcLng=English --format=xml --targetFile=$ANDROID_ES --targetLng=Spanish --service=agent # Paths to app-specific iOS-Strings: iOS_EN="ios/Localizable/Base.lproj/Localizable.strings" diff --git a/sample-scripts/csv_to_other_formats.sh b/sample-scripts/csv_to_other_formats.sh index 747c6699..a7d58a08 100755 --- a/sample-scripts/csv_to_other_formats.sh +++ b/sample-scripts/csv_to_other_formats.sh @@ -14,5 +14,5 @@ attranslate --srcFormat=csv --srcFile=csv/translations.csv --srcLng=en --targetF attranslate --srcFormat=csv --srcFile=csv/translations.csv --srcLng=de --targetFormat=po --targetFile=csv/de.po --targetLng=de --service=sync-without-translate attranslate --srcFormat=csv --srcFile=csv/translations.csv --srcLng=es --targetFormat=arb --targetFile=csv/es.arb --targetLng=es --service=sync-without-translate -attranslate --srcFormat=csv --srcFile=csv/translations.csv --srcLng=es --targetFormat=csv --targetFile=csv/single-lang-es.csv --targetLng=es --service=sync-without-translate +attranslate --srcFile=csv/translations.csv --srcLng=es --format=csv --targetFile=csv/single-lang-es.csv --targetLng=es --service=sync-without-translate attranslate --srcFormat=ios-strings --srcFile=csv/en.strings --srcLng=en --targetFormat=csv --targetFile=csv/single-lang-en.csv --targetLng=en --service=sync-without-translate diff --git a/sample-scripts/flutter.sh b/sample-scripts/flutter.sh index 189b6c22..8c7eccc8 100755 --- a/sample-scripts/flutter.sh +++ b/sample-scripts/flutter.sh @@ -5,5 +5,5 @@ set -e BASE_DIR=flutter/lib/l10n # Run "npm install --global attranslate" before you try this example. -attranslate --srcFile=$BASE_DIR/intl_messages.arb --srcLng=English --srcFormat=arb --targetFile=$BASE_DIR/intl_es.arb --targetLng=Spanish --targetFormat=arb --service=agent -attranslate --srcFile=$BASE_DIR/intl_messages.arb --srcLng=English --srcFormat=arb --targetFile=$BASE_DIR/intl_de.arb --targetLng=German --targetFormat=arb --service=agent --matcher=icu +attranslate --srcFile=$BASE_DIR/intl_messages.arb --srcLng=English --format=arb --targetFile=$BASE_DIR/intl_es.arb --targetLng=Spanish --service=agent +attranslate --srcFile=$BASE_DIR/intl_messages.arb --srcLng=English --format=arb --targetFile=$BASE_DIR/intl_de.arb --targetLng=German --service=agent --matcher=icu diff --git a/sample-scripts/json_advanced.sh b/sample-scripts/json_advanced.sh index 0d3b6af2..ee81b981 100755 --- a/sample-scripts/json_advanced.sh +++ b/sample-scripts/json_advanced.sh @@ -7,6 +7,6 @@ BASE_DIR="json-advanced" # install attranslate if it is not installed yet attranslate --version || npm install --global attranslate -attranslate --srcFile=$BASE_DIR/en/fruits.json --srcLng=English --srcFormat=json --targetFile=$BASE_DIR/es/fruits.json --targetLng=Spanish --targetFormat=json --service=agent -attranslate --srcFile=$BASE_DIR/en/fruits.json --srcLng=English --srcFormat=json --targetFile=$BASE_DIR/zh/fruits.json --targetLng=Chinese --targetFormat=json --service=agent -attranslate --srcFile=$BASE_DIR/en/fruits.json --srcLng=English --srcFormat=json --targetFile=$BASE_DIR/de/fruits.json --targetLng=German --targetFormat=json --service=agent +attranslate --srcFile=$BASE_DIR/en/fruits.json --srcLng=English --format=json --targetFile=$BASE_DIR/es/fruits.json --targetLng=Spanish --service=agent +attranslate --srcFile=$BASE_DIR/en/fruits.json --srcLng=English --format=json --targetFile=$BASE_DIR/zh/fruits.json --targetLng=Chinese --service=agent +attranslate --srcFile=$BASE_DIR/en/fruits.json --srcLng=English --format=json --targetFile=$BASE_DIR/de/fruits.json --targetLng=German --service=agent diff --git a/sample-scripts/json_simple.sh b/sample-scripts/json_simple.sh index df178ca0..7b5d7936 100755 --- a/sample-scripts/json_simple.sh +++ b/sample-scripts/json_simple.sh @@ -6,7 +6,7 @@ set -e # Run "npm install --global attranslate" before you try this example. # First invocation: show missing translations and instructions for agent -attranslate --srcFile=json-simple/en.json --srcLng=English --srcFormat=json --targetFile=json-simple/de.json --targetLng=German --targetFormat=json --service=agent +attranslate --srcFile=json-simple/en.json --srcLng=English --format=json --targetFile=json-simple/de.json --targetLng=German --service=agent # Second invocation triggered by agent: pipe translations into stdin, one per line, in order -# echo -e "\n\n..." | attranslate --srcFile=json-simple/en.json --srcLng=English --srcFormat=json --targetFile=json-simple/de.json --targetLng=German --targetFormat=json --service=agent +# echo -e "\n\n..." | attranslate --srcFile=json-simple/en.json --srcLng=English --format=json --targetFile=json-simple/de.json --targetLng=German --service=agent diff --git a/sample-scripts/json_simple_windows.bat b/sample-scripts/json_simple_windows.bat index 7eb08583..665ed0c8 100644 --- a/sample-scripts/json_simple_windows.bat +++ b/sample-scripts/json_simple_windows.bat @@ -3,9 +3,9 @@ :: Run "npm install --global attranslate" before you try this example. :: First invocation: show missing translations and instructions for agent -attranslate --srcFile=json-simple/en.json --srcLng=English --srcFormat=json --targetFile=json-simple/de.json --targetLng=German --targetFormat=json --service=agent +attranslate --srcFile=json-simple/en.json --srcLng=English --format=json --targetFile=json-simple/de.json --targetLng=German --service=agent :: Second invocation triggered by agent (the agent will replace the echo with your translations, one per line, in order): :: echo :: echo -:: ... | attranslate --srcFile=json-simple/en.json --srcLng=English --srcFormat=json --targetFile=json-simple/de.json --targetLng=German --targetFormat=json --service=agent +:: ... | attranslate --srcFile=json-simple/en.json --srcLng=English --format=json --targetFile=json-simple/de.json --targetLng=German --service=agent diff --git a/sample-scripts/po_generic.sh b/sample-scripts/po_generic.sh index 3b6e2fe5..a8571f3e 100755 --- a/sample-scripts/po_generic.sh +++ b/sample-scripts/po_generic.sh @@ -5,8 +5,8 @@ set -e BASE_DIR=po-generic # Run "npm install --global attranslate" before you try this example. -attranslate --srcFile=$BASE_DIR/en.po --srcLng=English --srcFormat=po --targetFile=$BASE_DIR/es.po --targetLng=Spanish --targetFormat=po --service=agent -attranslate --srcFile=$BASE_DIR/en.po --srcLng=English --srcFormat=po --targetFile=$BASE_DIR/de.po --targetLng=German --targetFormat=po --service=agent +attranslate --srcFile=$BASE_DIR/en.po --srcLng=English --format=po --targetFile=$BASE_DIR/es.po --targetLng=Spanish --service=agent +attranslate --srcFile=$BASE_DIR/en.po --srcLng=English --format=po --targetFile=$BASE_DIR/de.po --targetLng=German --service=agent # Convert a YAML to PO (just for the sake of test-coverage) attranslate --srcFile="$BASE_DIR/nested-fruits.yml" --srcFormat=yaml --srcLng=x --targetFile=$BASE_DIR/nested-fruits.po --targetFormat=po --targetLng=x --service=sync-without-translate diff --git a/sample-scripts/xml_generic.sh b/sample-scripts/xml_generic.sh index 63c0eefb..f0d53963 100755 --- a/sample-scripts/xml_generic.sh +++ b/sample-scripts/xml_generic.sh @@ -5,8 +5,8 @@ set -e BASE_DIR=xml-generic # Run "npm install --global attranslate" before you try this example. -attranslate --srcFile=$BASE_DIR/en.xml --srcLng=English --srcFormat=xml --targetFile=$BASE_DIR/ar.xml --targetLng=Arabic --targetFormat=xml --service=agent -attranslate --srcFile=$BASE_DIR/en.xml --srcLng=English --srcFormat=xml --targetFile=$BASE_DIR/de.xml --targetLng=German --targetFormat=xml --service=agent +attranslate --srcFile=$BASE_DIR/en.xml --srcLng=English --format=xml --targetFile=$BASE_DIR/ar.xml --targetLng=Arabic --service=agent +attranslate --srcFile=$BASE_DIR/en.xml --srcLng=English --format=xml --targetFile=$BASE_DIR/de.xml --targetLng=German --service=agent # Convert an iOS string file into an XML (just for the sake of test-coverage) attranslate --srcFormat=ios-strings --srcFile=$BASE_DIR/nested-fruits.strings --srcLng=en --targetFormat=xml --targetFile=$BASE_DIR/nested-fruits.xml --targetLng=en --service=sync-without-translate diff --git a/sample-scripts/yaml_ecommerce.sh b/sample-scripts/yaml_ecommerce.sh index 1b3d287a..7fea78c3 100755 --- a/sample-scripts/yaml_ecommerce.sh +++ b/sample-scripts/yaml_ecommerce.sh @@ -6,11 +6,11 @@ set -e # Run "npm install --global attranslate" before you try this example. node search_replace.js "yaml/es_ecommerce.yml" "es:" "en:" # preprocessing -attranslate --srcFile=yaml/en_ecommerce.yml --srcLng=English --srcFormat=yaml --targetFile=yaml/es_ecommerce.yml --targetLng=Spanish --targetFormat=yaml --service=agent +attranslate --srcFile=yaml/en_ecommerce.yml --srcLng=English --format=yaml --targetFile=yaml/es_ecommerce.yml --targetLng=Spanish --service=agent node search_replace.js "yaml/es_ecommerce.yml" "en:" "es:" # postprocessing node search_replace.js "yaml/de_ecommerce.yml" "de:" "en:" # preprocessing -attranslate --srcFile=yaml/en_ecommerce.yml --srcLng=English --srcFormat=yaml --targetFile=yaml/de_ecommerce.yml --targetLng=German --targetFormat=yaml --service=agent +attranslate --srcFile=yaml/en_ecommerce.yml --srcLng=English --format=yaml --targetFile=yaml/de_ecommerce.yml --targetLng=German --service=agent node search_replace.js "yaml/de_ecommerce.yml" "en:" "de:" # postprocessing # Convert a JSON to YML (just for the sake of test-coverage) diff --git a/src/core/core-definitions.ts b/src/core/core-definitions.ts index 43cc56ee..9c6bce90 100644 --- a/src/core/core-definitions.ts +++ b/src/core/core-definitions.ts @@ -35,11 +35,19 @@ export interface CoreResults { export interface CliArgs extends Record { srcFile: string; srcLng: string; - srcFormat: string; + /** + * Preferred format option. + * - "" uses the same format for source and target. + */ + format?: string; + /** Legacy option (overrides `format` for the source). */ + srcFormat?: string; targetFile: string; targetLng: string; - targetFormat: string; + /** Legacy option (overrides `format` for the target). */ + targetFormat?: string; service: string; serviceConfig?: string; matcher: string; + prompt?: string; } diff --git a/src/core/translate-cli.ts b/src/core/translate-cli.ts index a3396323..1befccfe 100644 --- a/src/core/translate-cli.ts +++ b/src/core/translate-cli.ts @@ -36,6 +36,7 @@ export function formatCliOptions(options: string[]): string { export async function translateCli(cliArgs: CliArgs) { checkForEmptyStringOptions(cliArgs); + resolveFormatOptions(cliArgs); const fileFormats = getTFileFormatList(); const services = getTServiceList(); const matchers = getTMatcherList(); @@ -118,6 +119,41 @@ export async function translateCli(cliArgs: CliArgs) { } } +function resolveFormatOptions(cliArgs: CliArgs): void { + const legacyUsed = + cliArgs.srcFormat !== undefined || cliArgs.targetFormat !== undefined; + + // Legacy mode: explicit source/target formats. + // This is the only supported way to do format conversion. + if (legacyUsed) { + if (!cliArgs.srcFormat) { + logFatal( + "required option '--srcFormat ' not specified" + ); + } + if (!cliArgs.targetFormat) { + logFatal( + "required option '--targetFormat ' not specified" + ); + } + return; + } + + // New mode: one format for both source and target. + if (!cliArgs.format) { + logFatal("required option '--format ' not specified"); + } + + const raw = cliArgs.format; + const spec = raw.trim(); + // Empty-string is handled by checkForEmptyStringOptions(), but be defensive. + if (!spec.length) { + logFatal("required option '--format ' not specified"); + } + cliArgs.srcFormat = spec; + cliArgs.targetFormat = spec; +} + // function parseBooleanOption(rawOption: string, optionKey: string): boolean { // const option = rawOption.trim().toLowerCase(); // if (option === "true") { diff --git a/src/index.ts b/src/index.ts index 3219b696..b49739ea 100644 --- a/src/index.ts +++ b/src/index.ts @@ -27,10 +27,6 @@ export function run(process: NodeJS.Process, cliBinDir: string): void { "--srcLng ", "A language code for the source language" ) - .requiredOption( - "--srcFormat ", - formatOneOfOptions(getTFileFormatList()) - ) .requiredOption( "--targetFile ", "The target file for the translations" @@ -39,9 +35,23 @@ export function run(process: NodeJS.Process, cliBinDir: string): void { "--targetLng ", "A language code for the target language" ) - .requiredOption( + .option( + "--format ", + `Preferred format option (one format for both src and target). ${formatOneOfOptions( + getTFileFormatList() + )}` + ) + .option( + "--srcFormat ", + `Legacy. Overrides --format for the source. ${formatOneOfOptions( + getTFileFormatList() + )}` + ) + .option( "--targetFormat ", - formatOneOfOptions(getTFileFormatList()) + `Legacy. Overrides --format for the target. ${formatOneOfOptions( + getTFileFormatList() + )}` ) .requiredOption( "--service ", @@ -71,6 +81,7 @@ export function run(process: NodeJS.Process, cliBinDir: string): void { const args: CliArgs = { srcFile: commander.opts().srcFile, srcLng: commander.opts().srcLng, + format: commander.opts().format, srcFormat: commander.opts().srcFormat, targetFile: commander.opts().targetFile, targetLng: commander.opts().targetLng, diff --git a/test/e2e/scripts-json-multi.test.ts b/test/e2e/scripts-json-multi.test.ts index c2bd087b..2bc00c8d 100644 --- a/test/e2e/scripts-json-multi.test.ts +++ b/test/e2e/scripts-json-multi.test.ts @@ -10,7 +10,7 @@ test("json simple up-to-date", async () => { test("missing OpenAI key", async () => { const output = - await runCommandExpectFailure(`cd sample-scripts && attranslate --srcFile=json-simple/en.json --srcLng=English --srcFormat=nested-json --targetFile=json-simple/es.json --targetLng=German --targetFormat=nested-json --service=openai + await runCommandExpectFailure(`cd sample-scripts && attranslate --srcFile=json-simple/en.json --srcLng=English --format=nested-json --targetFile=json-simple/es.json --targetLng=German --service=openai `); expect(output).toContain( "error: Missing OpenAI API Key: Please get an API key from" @@ -19,7 +19,7 @@ test("missing OpenAI key", async () => { test("invalid OpenAI key", async () => { const output = - await runCommandExpectFailure(`cd sample-scripts && attranslate --srcFile=json-simple/en.json --srcLng=English --srcFormat=nested-json --targetFile=json-simple/es.json --targetLng=German --targetFormat=nested-json --service=openai --serviceConfig=garbageapikey + await runCommandExpectFailure(`cd sample-scripts && attranslate --srcFile=json-simple/en.json --srcLng=English --format=nested-json --targetFile=json-simple/es.json --targetLng=German --service=openai --serviceConfig=garbageapikey `); expect(output).toContain( "error: OpenAI: Request failed with status code 401, Status text:" @@ -28,7 +28,7 @@ test("invalid OpenAI key", async () => { test("missing OpenAI key (typechat)", async () => { const output = await runCommandExpectFailure( - `cd sample-scripts && attranslate --srcFile=json-simple/en.json --srcLng=English --srcFormat=nested-json --targetFile=json-simple/es.json --targetLng=German --targetFormat=nested-json --service=typechat + `cd sample-scripts && attranslate --srcFile=json-simple/en.json --srcLng=English --format=nested-json --targetFile=json-simple/es.json --targetLng=German --service=typechat `, undefined, { ...process.env, OPENAI_API_KEY: undefined } @@ -44,7 +44,7 @@ test("missing OpenAI key (typechat)", async () => { test("invalid OpenAI key (typechat)", async () => { const output = await runCommandExpectFailure( - `cd sample-scripts && attranslate --srcFile=json-simple/en.json --srcLng=English --srcFormat=nested-json --targetFile=json-simple/es.json --targetLng=German --targetFormat=nested-json --service=typechat + `cd sample-scripts && attranslate --srcFile=json-simple/en.json --srcLng=English --format=nested-json --targetFile=json-simple/es.json --targetLng=German --service=typechat `, undefined, { ...process.env, OPENAI_API_KEY: "garbageapikey" } diff --git a/test/e2e/translate-add.test.ts b/test/e2e/translate-add.test.ts index 9f82d33a..a2c998f9 100644 --- a/test/e2e/translate-add.test.ts +++ b/test/e2e/translate-add.test.ts @@ -16,10 +16,13 @@ const testArray: { args: { ...defaultE2EArgs, srcFile: "test-assets/misc-json/empty-props.json", - srcFormat: "flat-json", + // Use the new single-format flag for this test. + // (defaultE2EArgs uses legacy src/target formats for conversion tests) + srcFormat: undefined, + targetFormat: undefined, + format: "flat-json", targetFile: "test-assets/nested-json/count-de.clean.json", refTargetFile: "test-assets/misc-json/empty-props+count-de.json", - targetFormat: "flat-json", }, toContain: `Bypass 3 strings because they are empty...`, addCount: 3, @@ -28,10 +31,11 @@ const testArray: { args: { ...defaultE2EArgs, srcFile: "test-assets/android-xml/count-en.indent2.flat.xml", - srcFormat: "xml", + srcFormat: undefined, + targetFormat: undefined, + format: "xml", targetFile: "test-assets/android-xml/count-de.missing-entry.xml", refTargetFile: "test-assets/android-xml/count-de.xml", - targetFormat: "xml", targetLng: "en", service: "sync-without-translate", }, From e5476f651143981374de881c53b06a7a78be0176 Mon Sep 17 00:00:00 2001 From: nomo-fe Date: Thu, 12 Feb 2026 02:08:15 +0100 Subject: [PATCH 09/16] purge the prompt flag --- AGENTS.md | 8 ++++++++ src/core/core-definitions.ts | 2 -- src/core/invoke-translation-service.ts | 7 ------- src/core/translate-cli.ts | 1 - src/index.ts | 5 ----- src/services/openai-translate.ts | 1 - src/services/service-definitions.ts | 1 - src/services/typechat.ts | 6 +----- 8 files changed, 9 insertions(+), 22 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index a7be5992..708fd8b7 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -10,3 +10,11 @@ Do NOT work on files in the dist-folder, because the dist-folder is auto-generat When working on unit-tests, see docs/DEV_MANIFESTO.md for details regarding our test philosophy. When asked for new features, only implement the logic that is requested. Do not implement new logic just because you assume that a user-base exists for it. + +## Code Reviews + +- Check critically if logic works as intented. +- Check critical if changes introduce regression. +- Check changes against the master-branch. +- Check if the current branch is up-to-date with the master-branch. +- Check for obvious codesmells or spelling mistakes. diff --git a/src/core/core-definitions.ts b/src/core/core-definitions.ts index 9c6bce90..cb17d61f 100644 --- a/src/core/core-definitions.ts +++ b/src/core/core-definitions.ts @@ -11,7 +11,6 @@ export interface CoreArgs { service: TServiceType; serviceConfig: string | null; matcher: TMatcherType; - prompt: string; } export interface TChangeSet { @@ -49,5 +48,4 @@ export interface CliArgs extends Record { service: string; serviceConfig?: string; matcher: string; - prompt?: string; } diff --git a/src/core/invoke-translation-service.ts b/src/core/invoke-translation-service.ts index 4997375c..d6e49b68 100644 --- a/src/core/invoke-translation-service.ts +++ b/src/core/invoke-translation-service.ts @@ -16,12 +16,6 @@ export async function invokeTranslationService( serviceInputs: TSet, args: CoreArgs ): Promise { - if (args.prompt && !["openai", "typechat"].includes(args.service)) { - console.warn( - `Warning: The '--prompt' parameter is only supported by 'openai' and 'typechat' services. Your prompt will be ignored when using '${args.service}'.` - ); - } - /** * Some translation services throw errors if they see empty strings. * Therefore, we bypass empty strings without changing them. @@ -77,7 +71,6 @@ async function runTranslationService( srcLng: args.srcLng, targetLng: args.targetLng, serviceConfig: args.serviceConfig, - prompt: args.prompt, }; console.info( diff --git a/src/core/translate-cli.ts b/src/core/translate-cli.ts index 1befccfe..20aa3be1 100644 --- a/src/core/translate-cli.ts +++ b/src/core/translate-cli.ts @@ -98,7 +98,6 @@ export async function translateCli(cliArgs: CliArgs) { service: cliArgs.service as TServiceType, serviceConfig: cliArgs.serviceConfig ?? null, matcher: cliArgs.matcher as TMatcherType, - prompt: cliArgs.prompt ?? "", }; const result = await translateCore(coreArgs); diff --git a/src/index.ts b/src/index.ts index b49739ea..50ef7c93 100644 --- a/src/index.ts +++ b/src/index.ts @@ -66,10 +66,6 @@ export function run(process: NodeJS.Process, cliBinDir: string): void { formatOneOfOptions(getTMatcherList()), "none" ) - .option( - "--prompt ", - "supply a prompt for the AI translation service" - ) .version(extractVersion({ cliBinDir }), "-v, --version") .parse(process.argv); @@ -89,7 +85,6 @@ export function run(process: NodeJS.Process, cliBinDir: string): void { service: commander.opts().service, serviceConfig: commander.opts().serviceConfig, matcher: commander.opts().matcher, - prompt: commander.opts().prompt, }; translateCli(args) .then(() => { diff --git a/src/services/openai-translate.ts b/src/services/openai-translate.ts index 402f6e3e..ffbe8a08 100644 --- a/src/services/openai-translate.ts +++ b/src/services/openai-translate.ts @@ -71,7 +71,6 @@ function generatePrompt(str: string, key: string, args: TServiceArgs) { const initialPrompt = `only translate my software string from ${args.srcLng} to ${args.targetLng}. don't chat or explain. Using the correct terms for computer software in the target language, only show target language never repeat string. if you don't find something to translate, don't respond`; return ( initialPrompt + - (args.prompt ? `\n\n${args.prompt}` : "") + `\n\nkey (used for context): ${key}\n\nstring to translate: ${capitalizedText}` ); } diff --git a/src/services/service-definitions.ts b/src/services/service-definitions.ts index 03d4089d..7b6a9873 100644 --- a/src/services/service-definitions.ts +++ b/src/services/service-definitions.ts @@ -15,7 +15,6 @@ export interface TServiceArgs { srcLng: string; targetLng: string; serviceConfig: string | null; - prompt: string; } export interface TService { diff --git a/src/services/typechat.ts b/src/services/typechat.ts index 7f9bfc3f..792e1acc 100644 --- a/src/services/typechat.ts +++ b/src/services/typechat.ts @@ -40,11 +40,7 @@ function generatePrompt(batch: TString[], args: TServiceArgs): string { }, {}); const basePrompt = `Translate the following JSON object from ${args.srcLng} into ${args.targetLng}:\n`; - const customPrompt = args.prompt - ? `\nAdditional instructions: ${args.prompt}\n\n` - : "\n"; - - return basePrompt + customPrompt + JSON.stringify(entries, null, 2); + return basePrompt + "\n" + JSON.stringify(entries, null, 2); } function parseResponse( From bd186f937c8d4fa4c2e18179b1057e375161d4a2 Mon Sep 17 00:00:00 2001 From: nomo-fe Date: Thu, 12 Feb 2026 02:35:26 +0100 Subject: [PATCH 10/16] code review --- AGENTS.md | 10 ++++++---- sample-scripts/android_to_ios.sh | 7 +------ src/services/agent-translate.ts | 30 +++++++++++++++++++++--------- 3 files changed, 28 insertions(+), 19 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 708fd8b7..05380df4 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -13,8 +13,10 @@ When asked for new features, only implement the logic that is requested. Do not ## Code Reviews -- Check critically if logic works as intented. +Do code reviews from the eyes of a critical senior dev, and consider the following things: + +- Only review differences between the current branch and the master-branch. +- Check if the current branch is up-to-date with the master-branch. If not, then suggest to pull from master. +- Check critically if logic works as intended. - Check critical if changes introduce regression. -- Check changes against the master-branch. -- Check if the current branch is up-to-date with the master-branch. -- Check for obvious codesmells or spelling mistakes. +- Check for obvious code smells or spelling mistakes. diff --git a/sample-scripts/android_to_ios.sh b/sample-scripts/android_to_ios.sh index 95e2381e..cd427368 100755 --- a/sample-scripts/android_to_ios.sh +++ b/sample-scripts/android_to_ios.sh @@ -5,12 +5,7 @@ set -e # This example works in two steps: # Step 1: Translate an english XML into a spanish XML and a german XML. -# Step 2: Covert those Android-XMLs into iOS-Strings, without changing the language. - -# Paths to app-specific XML-files: -ANDROID_EN="android/app/src/main/res/values/strings.xml" -ANDROID_DE="android/app/src/main/res/values-de/strings.xml" -ANDROID_ES="android/app/src/main/res/values-es/strings.xml" +# Step 2: Convert those Android-XMLs into iOS-Strings, without changing the language. # Paths to app-specific XML-files: ANDROID_EN="android/app/src/main/res/values/strings.xml" diff --git a/src/services/agent-translate.ts b/src/services/agent-translate.ts index cb07fcf3..a0892343 100644 --- a/src/services/agent-translate.ts +++ b/src/services/agent-translate.ts @@ -4,7 +4,10 @@ import { TResult, TService, TServiceArgs } from "./service-definitions"; -function printMissingSources(strings: { key: string; value: string }[], useError = false) { +function printMissingSources( + strings: TServiceArgs["strings"], + useError = false, +) { const log = useError ? console.error : console.log; log("MISSING TRANSLATIONS:\n"); for (const { key, value } of strings) { @@ -19,23 +22,34 @@ export class AgentTranslation implements TService { if (process.stdin.isTTY) { printMissingSources(args.strings); console.log("\nINSTRUCTIONS FOR AGENTS:"); - console.log("Provide one translation per line, matching the order above. Pipe them into attranslate."); + console.log( + "Provide one translation per line, matching the order above. Pipe them into attranslate.", + ); const cmd = process.argv.slice(2).join(" "); - console.log(`echo -e \"\\n\\n...\" | attranslate ${cmd} --service=agent`); - return results; + console.log( + `echo -e \"\\n\\n...\" | attranslate ${cmd} --service=agent`, + ); + process.exit(0); } // Read piped stdin, split into lines, map to keys in order const stdin = await this.readAllStdin(); - const lines = stdin.replace(/\r/g, "").split("\n").filter(line => line.trim() !== ""); + const lines = stdin + .replace(/\r/g, "") + .split("\n") + .filter((line) => line.trim() !== ""); const translationCount = args.strings.length; if (lines.length === 0) { printMissingSources(args.strings, true); - console.error("ERROR: No translations provided. Pipe one translation per source listed above."); + console.error( + "ERROR: No translations provided. Pipe one translation per source listed above.", + ); process.exit(1); } if (lines.length !== translationCount) { printMissingSources(args.strings, true); - console.error(`ERROR: ${lines.length} translations given, ${translationCount} required. Provide exactly one translation per source listed above.`); + console.error( + `ERROR: ${lines.length} translations given, ${translationCount} required. Provide exactly one translation per source listed above.`, + ); process.exit(1); } for (let i = 0; i < translationCount; i++) { @@ -52,5 +66,3 @@ export class AgentTranslation implements TService { }); } } - - From ea0b5e08db88914d50126de287b05fa7187071ff Mon Sep 17 00:00:00 2001 From: nomo-fe Date: Thu, 12 Feb 2026 02:43:17 +0100 Subject: [PATCH 11/16] refine instructions for agents --- src/services/agent-translate.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/services/agent-translate.ts b/src/services/agent-translate.ts index a0892343..0bf65e78 100644 --- a/src/services/agent-translate.ts +++ b/src/services/agent-translate.ts @@ -21,13 +21,13 @@ export class AgentTranslation implements TService { const results: TResult[] = []; if (process.stdin.isTTY) { printMissingSources(args.strings); - console.log("\nINSTRUCTIONS FOR AGENTS:"); + console.log("INSTRUCTIONS FOR AGENTS:"); console.log( "Provide one translation per line, matching the order above. Pipe them into attranslate.", ); const cmd = process.argv.slice(2).join(" "); console.log( - `echo -e \"\\n\\n...\" | attranslate ${cmd} --service=agent`, + `echo -e \"\\n\\n...\" | attranslate ${cmd}` ); process.exit(0); } From 65f160507c144ec42914120b3c86e3e902bd307a Mon Sep 17 00:00:00 2001 From: nomo-fe Date: Thu, 12 Feb 2026 03:01:40 +0100 Subject: [PATCH 12/16] agent e2e test --- src/services/agent-translate.ts | 2 +- .../misc-json/agent-flow/de.expected.json | 5 ++ test-assets/misc-json/agent-flow/de.seed.json | 3 + test-assets/misc-json/agent-flow/en.json | 5 ++ test/e2e/agent-flow.test.ts | 71 +++++++++++++++++++ 5 files changed, 85 insertions(+), 1 deletion(-) create mode 100644 test-assets/misc-json/agent-flow/de.expected.json create mode 100644 test-assets/misc-json/agent-flow/de.seed.json create mode 100644 test-assets/misc-json/agent-flow/en.json create mode 100644 test/e2e/agent-flow.test.ts diff --git a/src/services/agent-translate.ts b/src/services/agent-translate.ts index 0bf65e78..bde6786d 100644 --- a/src/services/agent-translate.ts +++ b/src/services/agent-translate.ts @@ -19,7 +19,7 @@ function printMissingSources( export class AgentTranslation implements TService { async translateStrings(args: TServiceArgs) { const results: TResult[] = []; - if (process.stdin.isTTY) { + if (process.stdin.isTTY || process.env.ATTRANSLATE_AGENT_TTY === "1") { printMissingSources(args.strings); console.log("INSTRUCTIONS FOR AGENTS:"); console.log( diff --git a/test-assets/misc-json/agent-flow/de.expected.json b/test-assets/misc-json/agent-flow/de.expected.json new file mode 100644 index 00000000..d6d718f0 --- /dev/null +++ b/test-assets/misc-json/agent-flow/de.expected.json @@ -0,0 +1,5 @@ +{ + "hello": "Hallo", + "bye": "Tschüss", + "__agent_test_key": "Hallo vom Agenten" +} diff --git a/test-assets/misc-json/agent-flow/de.seed.json b/test-assets/misc-json/agent-flow/de.seed.json new file mode 100644 index 00000000..35e9c835 --- /dev/null +++ b/test-assets/misc-json/agent-flow/de.seed.json @@ -0,0 +1,3 @@ +{ + "hello": "Hallo" +} diff --git a/test-assets/misc-json/agent-flow/en.json b/test-assets/misc-json/agent-flow/en.json new file mode 100644 index 00000000..5b0b4050 --- /dev/null +++ b/test-assets/misc-json/agent-flow/en.json @@ -0,0 +1,5 @@ +{ + "hello": "Hello", + "bye": "Bye", + "__agent_test_key": "Hello from agent test" +} diff --git a/test/e2e/agent-flow.test.ts b/test/e2e/agent-flow.test.ts new file mode 100644 index 00000000..640aa530 --- /dev/null +++ b/test/e2e/agent-flow.test.ts @@ -0,0 +1,71 @@ +import { join } from "path"; +import { + buildTranslateCommand, + generateId, + runCommand, +} from "../test-util/test-util"; +import { buildE2EArgs, defaultE2EArgs, E2EArgs } from "./e2e-common"; + +(process.platform === "win32" ? describe.skip : describe)("agent flow", () => { + const fixtureDir = "test-assets/misc-json/agent-flow"; + const srcFile = join(fixtureDir, "en.json"); + const seedTargetFile = join(fixtureDir, "de.seed.json"); + const expectedTargetFile = join(fixtureDir, "de.expected.json"); + + function newRandomTargetFile(): string { + return join(fixtureDir, `de_random_target_${generateId()}.json`); + } + + const args: E2EArgs = { + ...defaultE2EArgs, + srcFile, + srcLng: "English", + // Use new single-format flag. (Disable legacy src/target format args) + srcFormat: undefined, + targetFormat: undefined, + format: "json", + targetFile: "", + refTargetFile: "default-ref-target", + targetLng: "German", + service: "agent", + serviceConfig: undefined, + matcher: "none", + }; + + let targetFile: string; + + beforeEach(async () => { + targetFile = newRandomTargetFile(); + await runCommand(`cp ${seedTargetFile} ${targetFile}`); + }); + + afterEach(async () => { + await runCommand(`rm -f ${targetFile}`); + }); + + test("part 1 (interactive): prints missing translations and exits", async () => { + const cmd = buildTranslateCommand(buildE2EArgs({ ...args, targetFile }, true)); + + // Force interactive agent output without needing a pseudo-TTY. + const output = await runCommand(`ATTRANSLATE_AGENT_TTY=1 ${cmd}`); + + expect(output).toContain("MISSING TRANSLATIONS:"); + expect(output).toContain("- key: bye"); + expect(output).toContain("- key: __agent_test_key"); + expect(output).toContain("INSTRUCTIONS FOR AGENTS:"); + + // Ensure the agent "interactive" run didn't modify the target file. + await runCommand(`diff ${seedTargetFile} ${targetFile}`); + }); + + test("part 2 (piped): consumes stdin and writes translations", async () => { + // Order must match the printed order (bye, __agent_test_key) + const cmd = buildTranslateCommand(buildE2EArgs({ ...args, targetFile }, true)); + const output = await runCommand( + `printf 'Tschüss\\nHallo vom Agenten\\n' | ${cmd}` + ); + expect(output).toContain("Write target"); + + await runCommand(`diff ${expectedTargetFile} ${targetFile}`); + }); +}); From 7263d6bca8bb21cf3dc34ced1976c4a4006377a3 Mon Sep 17 00:00:00 2001 From: nomo-fe Date: Thu, 12 Feb 2026 03:13:33 +0100 Subject: [PATCH 13/16] review 2 --- src/index.ts | 4 ++-- src/services/agent-translate.ts | 18 +++++++++++++----- 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/src/index.ts b/src/index.ts index 50ef7c93..304c552b 100644 --- a/src/index.ts +++ b/src/index.ts @@ -25,7 +25,7 @@ export function run(process: NodeJS.Process, cliBinDir: string): void { ) .requiredOption( "--srcLng ", - "A language code for the source language" + "The source language" ) .requiredOption( "--targetFile ", @@ -33,7 +33,7 @@ export function run(process: NodeJS.Process, cliBinDir: string): void { ) .requiredOption( "--targetLng ", - "A language code for the target language" + "The target language" ) .option( "--format ", diff --git a/src/services/agent-translate.ts b/src/services/agent-translate.ts index bde6786d..9baa0fb6 100644 --- a/src/services/agent-translate.ts +++ b/src/services/agent-translate.ts @@ -33,18 +33,26 @@ export class AgentTranslation implements TService { } // Read piped stdin, split into lines, map to keys in order const stdin = await this.readAllStdin(); - const lines = stdin - .replace(/\r/g, "") - .split("\n") - .filter((line) => line.trim() !== ""); const translationCount = args.strings.length; - if (lines.length === 0) { + + // Explicitly handle the case where nothing was piped at all. + // (Empty lines are allowed as empty-string translations; this only triggers for truly empty stdin.) + if (stdin.length === 0 && translationCount > 0) { printMissingSources(args.strings, true); console.error( "ERROR: No translations provided. Pipe one translation per source listed above.", ); process.exit(1); } + + // Keep empty lines (empty translations) intact. + // Only trim *trailing* empty lines which can occur due to a trailing newline. + const lines = stdin.replace(/\r/g, "").split("\n"); + while (lines.length > translationCount && lines[lines.length - 1] === "") { + lines.pop(); + } + + // Note: `split("\n")` never returns `[]`, so we don't need a separate `lines.length === 0` check. if (lines.length !== translationCount) { printMissingSources(args.strings, true); console.error( From 16cbcf948a83cc693aa08fa7e1271f25970f23f6 Mon Sep 17 00:00:00 2001 From: nomo-fe Date: Thu, 12 Feb 2026 03:19:35 +0100 Subject: [PATCH 14/16] migration docs --- docs/AGENTIC_MIGRATION.md | 35 +++++++++++++++++++++++++++++++++++ test/e2e/agent-flow.test.ts | 5 ++--- 2 files changed, 37 insertions(+), 3 deletions(-) create mode 100644 docs/AGENTIC_MIGRATION.md diff --git a/docs/AGENTIC_MIGRATION.md b/docs/AGENTIC_MIGRATION.md new file mode 100644 index 00000000..b9374bc2 --- /dev/null +++ b/docs/AGENTIC_MIGRATION.md @@ -0,0 +1,35 @@ +# Agentic Migration Guide + +This document summarizes the important changes when migrating from the pre-agentic CLI/workflow to the current agentic workflow. + +## What changed (high-level) + +- New translation service: `--service=agent`. +- New preferred flag for same-format workflows: `--format=`. +- Legacy format flags still work for conversions: `--srcFormat` / `--targetFormat` (intentionally not promoted in READMEs). +- `json` is a supported format and is treated the same as `nested-json` (implementation-wise). + +## New agent workflow (`--service=agent`) + +`agent` is a two-step workflow: + +1. **First run (no stdin piped):** prints missing keys + source strings and instructions. +2. **Piped run:** pipe exactly one translation per line (same order as printed) to write the target file. + +Notes: +- Empty translations are supported by piping an empty line (line count still must match). + +## Format flags: `--format` vs legacy `--srcFormat/--targetFormat` + +- Use `--format=` when source + target use the **same** file format. +- Use legacy `--srcFormat= --targetFormat=` when doing **format conversion** (e.g. XML → iOS strings). +- `--format` accepts **exactly one** format (no separators, no conversions). + +## Practical migration checklist + +- Replace same-format calls: + - From: `--srcFormat= --targetFormat=` + - To: `--format=` +- For format conversions, keep using legacy flags. +- If you relied on `--prompt`, remove it from scripts and automation (flag removed). +- Consider switching scripts to `--service=agent` if you want the agentic two-step workflow. diff --git a/test/e2e/agent-flow.test.ts b/test/e2e/agent-flow.test.ts index 640aa530..f4cdfd54 100644 --- a/test/e2e/agent-flow.test.ts +++ b/test/e2e/agent-flow.test.ts @@ -43,10 +43,9 @@ import { buildE2EArgs, defaultE2EArgs, E2EArgs } from "./e2e-common"; await runCommand(`rm -f ${targetFile}`); }); - test("part 1 (interactive): prints missing translations and exits", async () => { + test("part 1 (without pipe): prints missing translations and exits", async () => { const cmd = buildTranslateCommand(buildE2EArgs({ ...args, targetFile }, true)); - // Force interactive agent output without needing a pseudo-TTY. const output = await runCommand(`ATTRANSLATE_AGENT_TTY=1 ${cmd}`); expect(output).toContain("MISSING TRANSLATIONS:"); @@ -54,7 +53,7 @@ import { buildE2EArgs, defaultE2EArgs, E2EArgs } from "./e2e-common"; expect(output).toContain("- key: __agent_test_key"); expect(output).toContain("INSTRUCTIONS FOR AGENTS:"); - // Ensure the agent "interactive" run didn't modify the target file. + // Ensure the agent's first run didn't modify the target file. await runCommand(`diff ${seedTargetFile} ${targetFile}`); }); From ce6626918f39251e8ef64665dce5fb2cef388b53 Mon Sep 17 00:00:00 2001 From: nomo-fe Date: Thu, 12 Feb 2026 03:29:45 +0100 Subject: [PATCH 15/16] exit code --- README-es.md | 23 ++++++++++++++++++----- README.md | 11 ++++++----- docs/AGENTIC_MIGRATION.md | 2 ++ src/services/agent-translate.ts | 3 ++- test/e2e/agent-flow.test.ts | 5 ++++- 5 files changed, 32 insertions(+), 12 deletions(-) diff --git a/README-es.md b/README-es.md index 7a414499..f4059176 100644 --- a/README-es.md +++ b/README-es.md @@ -20,9 +20,8 @@ Por lo tanto, siempre que no esté satisfecho con los resultados producidos, `at ## Servicios disponibles - `agent`: Para usar con Agentes de Código. Solicita al agente traducir nuevas cadenas vía stdin cuando se detectan. -- `sync-without-translate`: Verifica la integridad de las traducciones sin traducir (p. ej. para tuberías CI/CD). -Otros servicios (openai, google-translate, azure, manual, typechat, etc.) están deprecados pero se retienen para compatibilidad hacia atrás. +Otros servicios (openai, google-translate, azure, manual, typechat, sync-without-translate) están deprecados pero se retienen para compatibilidad hacia atrás. # Ejemplos de uso @@ -39,6 +38,18 @@ attranslate --srcFile=en/fruits.json --targetFile=es/fruits.json --targetLng=Spa attranslate --srcFile=en/fruits.json --targetFile=de/fruits.json --targetLng=German --srcLng=English --format=json --service=agent ``` +# Instalación + +Instalar globalmente: +```bash +npm install --global attranslate +``` + +O en un proyecto Node.js: +```bash +npm install --save-dev attranslate +``` + # Opciones de uso Ejecuta `attranslate --help` para ver una lista de opciones disponibles: @@ -48,11 +59,11 @@ Usage: attranslate [options] Options: --srcFile The source file to be translated - --srcLng A language code for the source language + --srcLng The source language --targetFile The target file for the translations - --targetLng A language code for the target language + --targetLng The target language --format Uno de "flat-json", "nested-json", "json", "yaml", "po", "xml", "ios-strings", "arb", "csv" - --service Uno de "agent", "sync-without-translate" + --service "agent" -v, --version output the version number -h, --help display help for command ``` @@ -88,3 +99,5 @@ Luego el agente canaliza las traducciones: ``` echo -e "\n\n..." | attranslate --srcFile=en.json --srcLng=English --format=json --targetFile=es.json --targetLng=Spanish --service=agent ``` + +Nota: la primera ejecución (sin canalizar stdin) sale con un código distinto de cero por diseño; esto se puede usar en CI/CD para detectar traducciones faltantes. diff --git a/README.md b/README.md index 61cd8e2a..49e87d6a 100644 --- a/README.md +++ b/README.md @@ -18,9 +18,8 @@ Therefore, whenever you are unhappy with the produced text, `attranslate` allows ## Available Services - `agent`: For use with Coding Agents. Prompts the agent to translate new strings via stdin when detected. -- `sync-without-translate`: Verifies translation completeness without translating (e.g. for CI/CD pipelines). -Other services (openai, google-translate, azure, manual, typechat, etc.) are deprecated but retained for backwards-compatibility. +Other services (openai, google-translate, azure, manual, typechat, sync-without-translate) are deprecated but retained for backwards-compatibility. # Usage Examples @@ -58,11 +57,11 @@ Usage: attranslate [options] Options: --srcFile The source file to be translated - --srcLng A language code for the source language + --srcLng The source language --targetFile The target file for the translations - --targetLng A language code for the target language + --targetLng The target language --format One of "flat-json", "nested-json", "json", "yaml", "po", "xml", "ios-strings", "arb", "csv" - --service One of "agent", "sync-without-translate" + --service "agent" -v, --version output the version number -h, --help display help for command ``` @@ -98,3 +97,5 @@ The agent then pipes translations: ``` echo -e "\n\n..." | attranslate --srcFile=en.json --srcLng=English --format=json --targetFile=es.json --targetLng=Spanish --service=agent ``` + +Note: the first (no-pipe) run exits with a non-zero code by design, which can be used in CI/CD to detect missing translations. diff --git a/docs/AGENTIC_MIGRATION.md b/docs/AGENTIC_MIGRATION.md index b9374bc2..64c7d38d 100644 --- a/docs/AGENTIC_MIGRATION.md +++ b/docs/AGENTIC_MIGRATION.md @@ -16,6 +16,8 @@ This document summarizes the important changes when migrating from the pre-agent 1. **First run (no stdin piped):** prints missing keys + source strings and instructions. 2. **Piped run:** pipe exactly one translation per line (same order as printed) to write the target file. +The first run exits with a non-zero code by design, so CI/CD can detect that translations are missing. + Notes: - Empty translations are supported by piping an empty line (line count still must match). diff --git a/src/services/agent-translate.ts b/src/services/agent-translate.ts index 9baa0fb6..0a6ff69b 100644 --- a/src/services/agent-translate.ts +++ b/src/services/agent-translate.ts @@ -29,7 +29,8 @@ export class AgentTranslation implements TService { console.log( `echo -e \"\\n\\n...\" | attranslate ${cmd}` ); - process.exit(0); + // Non-zero exit code by design: allows CI/CD or tooling to detect "missing translations". + process.exit(2); } // Read piped stdin, split into lines, map to keys in order const stdin = await this.readAllStdin(); diff --git a/test/e2e/agent-flow.test.ts b/test/e2e/agent-flow.test.ts index f4cdfd54..be9e3bcc 100644 --- a/test/e2e/agent-flow.test.ts +++ b/test/e2e/agent-flow.test.ts @@ -3,6 +3,7 @@ import { buildTranslateCommand, generateId, runCommand, + runCommandExpectFailure, } from "../test-util/test-util"; import { buildE2EArgs, defaultE2EArgs, E2EArgs } from "./e2e-common"; @@ -46,7 +47,9 @@ import { buildE2EArgs, defaultE2EArgs, E2EArgs } from "./e2e-common"; test("part 1 (without pipe): prints missing translations and exits", async () => { const cmd = buildTranslateCommand(buildE2EArgs({ ...args, targetFile }, true)); - const output = await runCommand(`ATTRANSLATE_AGENT_TTY=1 ${cmd}`); + const output = await runCommandExpectFailure( + `ATTRANSLATE_AGENT_TTY=1 ${cmd}` + ); expect(output).toContain("MISSING TRANSLATIONS:"); expect(output).toContain("- key: bye"); From cf1984d1c13512d0103c6a066e762db6d5825ced Mon Sep 17 00:00:00 2001 From: nomo-fe Date: Thu, 12 Feb 2026 03:30:22 +0100 Subject: [PATCH 16/16] compile fix --- test/core/core-test-util.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/test/core/core-test-util.ts b/test/core/core-test-util.ts index 2e8e9ede..93ab9583 100644 --- a/test/core/core-test-util.ts +++ b/test/core/core-test-util.ts @@ -88,7 +88,6 @@ export const commonArgs: Omit = { matcher: "icu", srcLng: "en", targetLng: "de", - prompt: "", }; export async function translateCoreAssert(