From e2afc68d5a27e703ff8214a240d85ff7db484010 Mon Sep 17 00:00:00 2001 From: Arcadio Quintero Date: Wed, 2 Sep 2026 20:17:33 -0400 Subject: [PATCH 1/2] fix: lint-glossary only checked the first path, and the skills taught the wrong anchor rule Lessons from the first nine translation PRs, where five of them broke the docs build. - lint-glossary took `argv._[0]`, so `lint-glossary -- a.md b.md` silently checked only `a.md` and reported success. It now takes every path, and fails on one that matches no translation instead of passing green on a typo. Covered by tests. - The quality checklist said "internal links point to the translated anchors", which is exactly what broke the build: translating a heading changes its anchor and orphans every link pointing at it, from this page and from others. Headings now keep the English anchor via `{#anchor}`. batch-translate carried the same wrong instruction. - The skills said nothing about delivery. They now state one commit per issue, an English message with `Fixes #`, `.md` and `.en.md` together, and no tool attribution in the commit or the PR body. CONTRIBUTING carries the same two rules for human contributors. --- .agents/skills/batch-translate/SKILL.md | 13 +++++- .../skills/translate-angular-docs/SKILL.md | 44 +++++++++++++++++-- .agents/skills/translate-delta/SKILL.md | 7 +++ CONTRIBUTING.md | 9 +++- tools/glossary.test.mjs | 33 +++++++++++++- tools/lib/glossary.mjs | 17 +++++++ tools/lint-glossary.mjs | 15 ++++--- 7 files changed, 125 insertions(+), 13 deletions(-) diff --git a/.agents/skills/batch-translate/SKILL.md b/.agents/skills/batch-translate/SKILL.md index 49f4d000..db6004e8 100644 --- a/.agents/skills/batch-translate/SKILL.md +++ b/.agents/skills/batch-translate/SKILL.md @@ -59,9 +59,18 @@ Aplica todas las reglas del skill `/translate-angular-docs`: Sobreescribe `archivo.md` con la traducción. -### 6. Verificar anchors +### 6. Fijar anchors -Si se tradujeron encabezados con enlaces internos, actualiza los anchors. +Cada encabezado traducido conserva su anchor inglés con `{#anchor}`: + +```markdown +### Versiones con soporte activo {#actively-supported-versions} +``` + +Los enlaces —los internos de la página y los que llegan desde otras— apuntan a +ese anchor inglés. Si dejas que el anchor se derive del español, el build de +Angular falla al validar los enlaces. Ver +[`translate-angular-docs`](../translate-angular-docs/SKILL.md), paso 4. ### 7. Stage en git diff --git a/.agents/skills/translate-angular-docs/SKILL.md b/.agents/skills/translate-angular-docs/SKILL.md index 226efd6a..32352571 100644 --- a/.agents/skills/translate-angular-docs/SKILL.md +++ b/.agents/skills/translate-angular-docs/SKILL.md @@ -104,7 +104,42 @@ Si el archivo afecta la navegación del sitio, revisa: adev-es/src/app/routing/sub-navigation-data.ts ``` -### Paso 5 — Checklist de calidad +### Paso 5 — Entregar: commit y pull request + +Un solo commit por issue, con el `.md` y su `.en.md` **juntos**. Separarlos deja +el archivo marcado como desactualizado de forma permanente, porque la detección +busca el commit donde se tocaron ambos. + +El mensaje va **en inglés**, aunque el contenido que traduces sea español: lo que +se traduce es la documentación, no el historial. + +``` +translate: signals debounced and effect guides (Angular 22.1) + +Translate guide/signals/debounced.md and guide/signals/effect.md into +Spanish and keep the English originals as .en.md backups. + +Fixes #186 +``` + +- Prefijo `translate:`, también cuando el trabajo es actualizar una traducción. +- `Fixes #` en el cuerpo del commit **y** en la descripción del PR. +- **Nada de atribución a herramientas**: ni `Co-Authored-By: Claude…`, ni + `Claude-Session:`, ni «Generated with Claude Code», ni en el commit ni en el PR. + El historial de este repo se lee como trabajo de la comunidad. + +Comprueba lo que vas a entregar antes de commitear: + +```shell +npm run lint-glossary -- ... # acepta varias rutas +npm run check-translations # ya no deben aparecer +git status # solo .md y .en.md tuyos +``` + +`lint-glossary` falla si una de las rutas no casa con ninguna traducción, así que +una ruta mal escrita se nota en vez de pasar en verde. + +### Paso 6 — Checklist de calidad Ejecuta el checklist al final de este documento antes de entregar. @@ -459,10 +494,13 @@ Antes de finalizar, verifica: - [ ] **Etiquetas ``:** contenido interno traducido, estructura preservada - [ ] **Archivos y rutas:** sin traducir - [ ] **Versiones:** en formato original ("Angular 17", no "Angular diecisiete") -- [ ] **Anchors actualizados:** enlaces internos apuntan a los anchors traducidos +- [ ] **Anchors fijados:** cada encabezado traducido conserva su anchor inglés con `{#anchor}` +- [ ] **Enlaces internos:** apuntan al anchor inglés, no al que derivaría del español - [ ] **Comentarios en código:** traducidos - [ ] **Naturalidad:** el texto español suena natural, no como traducción literal - [ ] **Consistencia:** mismo término español para mismo concepto en inglés - [ ] **Preposición:** "en Angular" en lugar de "de Angular" - [ ] **Navegación:** si aplica, `sub-navigation-data.ts` actualizado -- [ ] **Git:** archivos `.md` y `.en.md` staged para el commit +- [ ] **Git:** `.md` y `.en.md` en el mismo commit, y solo eso +- [ ] **Commit:** uno solo, mensaje en inglés, con `Fixes #` +- [ ] **Sin atribución:** ni `Co-Authored-By`, ni `Claude-Session`, ni menciones a herramientas diff --git a/.agents/skills/translate-delta/SKILL.md b/.agents/skills/translate-delta/SKILL.md index 924f6da0..c1ae6ba3 100644 --- a/.agents/skills/translate-delta/SKILL.md +++ b/.agents/skills/translate-delta/SKILL.md @@ -94,6 +94,13 @@ Comprueba cuatro cosas. Si alguna falla, **no commitees**: `.md` y `.en.md` **en el mismo commit**. Romper ese invariante deja el archivo marcado como desactualizado para siempre: es exactamente el origen del falso positivo de `selectors.md`. +Un solo commit por issue, con el mensaje **en inglés** y `Fixes #` en el cuerpo. Prefijo +`translate:`, también aquí. Sin atribución a herramientas: ni `Co-Authored-By: Claude…`, ni +`Claude-Session:`, ni en el commit ni en la descripción del PR. + +Si el delta toca un encabezado, conserva su anchor inglés con `{#anchor}`: cambiarlo rompe los +enlaces de otras páginas y tumba el build. + ## Reglas de edición Aplica el glosario de [`translate-angular-docs`](../translate-angular-docs/SKILL.md), más estas diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 8f0410d0..c7d9840a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -114,10 +114,15 @@ Si deseas traducir un documento nuevo: 1. Haz push de los cambios a tu fork: ```bash - git add . - git commit -m "translate: complete translation of components guide" + git add adev-es/src/content/guide/components.md adev-es/src/content/guide/components.en.md + git commit -m "translate: components guide" git push origin translate-components-guide ``` + + El mensaje del commit va **en inglés**, con prefijo `translate:` y + `Fixes #` en el cuerpo. Lo que se traduce es la documentación, no el + historial. Y el `.md` y su `.en.md` **en el mismo commit**: separarlos deja el + archivo marcado como desactualizado de forma permanente. 2. Ve a tu fork en GitHub 3. Haz clic en "Compare & pull request" 4. Completa la descripción del PR con detalles de tu traducción diff --git a/tools/glossary.test.mjs b/tools/glossary.test.mjs index a3ec2bb6..61f8808d 100644 --- a/tools/glossary.test.mjs +++ b/tools/glossary.test.mjs @@ -3,7 +3,7 @@ import assert from 'node:assert/strict'; import { readFileSync } from 'node:fs'; import { resolve } from 'node:path'; import { YAML } from 'zx'; -import { mask, lintText } from './lib/glossary.mjs'; +import { mask, lintText, selectFiles } from './lib/glossary.mjs'; const ROOT = resolve(import.meta.dirname, '..'); const { rules } = YAML.parse(readFileSync(resolve(ROOT, 'glosario.yml'), 'utf8')); @@ -117,3 +117,34 @@ test('sí aplica al texto visible junto a un atributo', () => { test('no aplica en definiciones de enlace de referencia', () => { assert.deepEqual(hits('[GuiaX]: tools/cli/librería-y "Título"'), []); }); + +// --- selección de archivos --- + +const ALL = [ + 'adev-es/src/content/ai/webmcp.md', + 'adev-es/src/content/guide/di/lazy-loading-services.md', + 'adev-es/src/content/reference/releases.md', +]; + +test('sin filtros revisa todas las traducciones', () => { + assert.deepEqual(selectFiles(ALL, []), { files: ALL, unmatched: [] }); +}); + +test('acepta varias rutas a la vez, no solo la primera', () => { + const { files, unmatched } = selectFiles(ALL, [ + 'adev-es/src/content/ai/webmcp.md', + 'adev-es/src/content/reference/releases.md', + ]); + assert.deepEqual(files, [ALL[0], ALL[2]]); + assert.deepEqual(unmatched, []); +}); + +test('delata la ruta que no casa con ninguna traducción', () => { + const { unmatched } = selectFiles(ALL, ['guide/di', 'reference/no-existe.md']); + assert.deepEqual(unmatched, ['reference/no-existe.md']); +}); + +test('no cuenta un archivo dos veces aunque casen dos filtros', () => { + const { files } = selectFiles(ALL, ['ai/', 'webmcp']); + assert.deepEqual(files, [ALL[0]]); +}); diff --git a/tools/lib/glossary.mjs b/tools/lib/glossary.mjs index 88561f62..b90b9991 100644 --- a/tools/lib/glossary.mjs +++ b/tools/lib/glossary.mjs @@ -59,3 +59,20 @@ export function lintText(file, text, rules) { return found.sort((a, b) => a.line - b.line); } + +/** + * Selecciona qué traducciones revisar a partir de los filtros de la línea de + * comandos. Sin filtros, se revisan todas. + * + * Devuelve también los filtros que no casaron con nada: un filtro que no + * encuentra archivos casi siempre es una ruta mal escrita, y darlo por bueno + * haría pasar la revisión sin haber mirado nada. + */ +export function selectFiles(all, filters) { + if (filters.length === 0) return { files: all, unmatched: [] }; + + const unmatched = filters.filter((p) => !all.some((f) => f.includes(p))); + const files = all.filter((f) => filters.some((p) => f.includes(p))); + + return { files, unmatched }; +} diff --git a/tools/lint-glossary.mjs b/tools/lint-glossary.mjs index 5601e221..59c0902f 100644 --- a/tools/lint-glossary.mjs +++ b/tools/lint-glossary.mjs @@ -1,7 +1,7 @@ import { readFile } from 'node:fs/promises'; import { resolve } from 'node:path'; import { $, argv, chalk, glob, YAML } from 'zx'; -import { lintText } from './lib/glossary.mjs'; +import { lintText, selectFiles } from './lib/glossary.mjs'; /** * Verifica la consistencia terminológica de las traducciones al español. @@ -14,8 +14,9 @@ import { lintText } from './lib/glossary.mjs'; * rutas de archivo y anchors explícitos `{#id}`. * * Uso: - * npm run lint-glossary (todas las traducciones) - * npm run lint-glossary -- guide/forms (solo una ruta) + * npm run lint-glossary (todas las traducciones) + * npm run lint-glossary -- guide/forms (una ruta) + * npm run lint-glossary -- guide/forms ai/ (varias) */ $.verbose = false; @@ -27,9 +28,13 @@ try { const raw = await readFile(resolve(ROOT, 'glosario.yml'), 'utf8'); const { rules } = YAML.parse(raw); - const filter = argv._[0]; const all = await glob([`${CONTENT_DIR}/**/*.md`, `!${CONTENT_DIR}/**/*.en.md`], { cwd: ROOT }); - const files = filter ? all.filter((f) => f.includes(filter)) : all; + const { files, unmatched } = selectFiles(all, argv._.map(String)); + + if (unmatched.length > 0) { + console.error(chalk.red(`\nNo hay ninguna traducción que case con: ${unmatched.join(', ')}\n`)); + process.exit(1); + } const findings = []; From 20dda87438381eb3fd542918b928f01e5aed7898 Mon Sep 17 00:00:00 2001 From: Ricardo Chavarria Date: Thu, 3 Sep 2026 11:18:33 -0600 Subject: [PATCH 2/2] fix: check-translations ran out of memory listing origin files listOrigin() used glob('**/*') which followed the broken pnpm symlinks under origin/adev/node_modules (pointing to origin/node_modules/.pnpm, which does not exist), causing the traversal to blow up until the process OOM'd. Skip symbolic links and ignore node_modules entirely, since it is not translatable content. --- tools/check-translations.mjs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/tools/check-translations.mjs b/tools/check-translations.mjs index 48a946eb..32b6c352 100644 --- a/tools/check-translations.mjs +++ b/tools/check-translations.mjs @@ -271,7 +271,12 @@ async function listOrigin(ref) { // pregunta que hay que poder responder es "¿esta ruta existe upstream?", y // limitarla a los objetivos de copia daría falsos positivos con cualquier // archivo de adev-es que viva fuera de ellos. - const files = await glob('**/*', { cwd: dir, onlyFiles: true }); + const files = await glob('**/*', { + cwd: dir, + onlyFiles: true, + followSymbolicLinks: false, + ignore: ['**/node_modules/**'], + }); return files.length ? new Set(files) : null; }