Skip to content

Commit 5b1cdf6

Browse files
committed
Add AI translation workflow, script and docs
Add AI-assisted PO translation workflow and tooling: new repository skill (.github/skills/translate-po/SKILL.md) and i18n docs (docs/i18n/*) including agent instructions, Spanish glossary, translation rules, and workflow guidance. Introduce a small Node task (tasks/gettext-update-po.js) that wraps GNU gettext tools (msgmerge + msgattrib) to sync po/<lang>.po from po/superdesk.pot, plus an npm script (gettext-update-po) in package.json. The script validates inputs, checks files, and emits a helpful error if gettext tools are missing.
1 parent 0e3633c commit 5b1cdf6

7 files changed

Lines changed: 354 additions & 0 deletions

File tree

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
---
2+
name: translate-po
3+
description: Use this when asked to update a PO translation catalog with GitHub Copilot. It refreshes the POT file, syncs the target PO file, fills safe untranslated entries, validates the result, and prepares a pull request.
4+
---
5+
6+
# Translate PO catalogs
7+
8+
Use this skill when asked to update translations in `superdesk-client-core`.
9+
10+
## Inputs
11+
12+
- Target language code, for example `es`
13+
- Target PO file, for example `po/es.po`
14+
- Source of truth: `po/superdesk.pot`
15+
- Optional glossary file, for example `docs/i18n/es-glossary.md`
16+
- Translation rules: `docs/i18n/translation-rules.md`
17+
18+
## Required workflow
19+
20+
1. Refresh the source template:
21+
22+
```bash
23+
npm run gettext-extract
24+
```
25+
26+
2. Sync the PO file structurally from the template:
27+
28+
```bash
29+
npm run gettext-update-po -- <lang>
30+
```
31+
32+
3. Fill only empty `msgstr` and `msgstr[n]` entries in `po/<lang>.po`.
33+
34+
4. Validate the result:
35+
36+
```bash
37+
grunt nggettext_compile
38+
```
39+
40+
5. Prepare a pull request against `develop`.
41+
42+
## Hard rules
43+
44+
- Never modify runtime JSON files directly.
45+
- Never edit `msgid`.
46+
- Never rewrite unrelated entries.
47+
- Never overwrite existing non-empty translations.
48+
- Preserve all `{{...}}` placeholders exactly, including spacing and nested expressions.
49+
- Preserve plural structures exactly.
50+
- Keep diffs minimal and reviewable.
51+
- Follow the target-language glossary when one exists.
52+
53+
## Skip rules
54+
55+
Skip the entry and leave it unchanged if it is:
56+
57+
- HTML-heavy
58+
- ambiguous without UI or product context
59+
- unsafe because of unusual placeholders or formatting
60+
- likely to require broader terminology review
61+
62+
## Current Spanish configuration
63+
64+
For the current Spanish workflow:
65+
66+
- target language: `es`
67+
- target file: `po/es.po`
68+
- glossary: `docs/i18n/es-glossary.md`
69+
- rules: `docs/i18n/translation-rules.md`
70+
71+
When asked to update Spanish translations, use this skill together with the Spanish glossary and rules.

docs/i18n/ai-agent-instructions.md

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
# AI Agent Instructions For PO Updates
2+
3+
Follow these rules when updating translations in `superdesk-client-core`.
4+
5+
## Inputs
6+
7+
- Source of truth: `po/superdesk.pot`
8+
- Target language code: `<lang>`
9+
- Target PO file: `po/<lang>.po`
10+
- Optional excluded PO files: any locale-specific files that the workflow marks as out of scope
11+
- Optional glossary: a language-specific glossary file if one exists
12+
13+
## Required Workflow
14+
15+
1. Assume `npm run gettext-extract` has refreshed `po/superdesk.pot`.
16+
2. Assume `npm run gettext-update-po -- <lang>` has structurally synced `po/<lang>.po`.
17+
3. Only then fill missing translations in `po/<lang>.po`.
18+
19+
## Hard Rules
20+
21+
- Never modify runtime JSON files directly.
22+
- Never edit `msgid`.
23+
- Never rewrite unrelated entries.
24+
- Never touch excluded locale files.
25+
- Never overwrite existing non-empty translations.
26+
- Only fill empty `msgstr` or empty plural `msgstr[n]` values when safe.
27+
- Preserve all `{{...}}` placeholders exactly, including spacing and nested expressions.
28+
- Preserve plural structures exactly.
29+
- Keep diffs minimal and reviewable.
30+
- Follow the glossary for the target language if one is provided.
31+
32+
## Skip Rules
33+
34+
Skip the entry and leave it unchanged if it is:
35+
36+
- HTML-heavy
37+
- ambiguous without UI or product context
38+
- unsafe because of unusual placeholders or formatting
39+
- likely to require broader terminology review
40+
41+
## Validation Mindset
42+
43+
- Existing compile and validation tooling is the final safety net.
44+
- If a translation would require changing placeholders, plural structure, or source identifiers, do not make the change.

docs/i18n/es-glossary.md

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
# Spanish Glossary
2+
3+
Use neutral product Spanish. Prefer consistency over stylistic variation.
4+
5+
## Core Terms
6+
7+
- article -> artículo
8+
- item -> elemento
9+
- content -> contenido
10+
- headline -> titular
11+
- slugline -> slugline
12+
- byline -> firma
13+
- desk -> mesa
14+
- stage -> etapa
15+
- workspace -> espacio de trabajo
16+
- assignment -> asignación
17+
- planning -> planificación
18+
- event -> evento
19+
- coverage -> cobertura
20+
- vocabulary -> vocabulario
21+
- dictionary -> diccionario
22+
- template -> plantilla
23+
- preview -> vista previa
24+
- publish -> publicar
25+
- unpublish -> retirar de publicación
26+
- schedule -> programar
27+
- save -> guardar
28+
- close -> cerrar
29+
- filter -> filtro
30+
- search -> buscar
31+
- settings -> configuración
32+
- user -> usuario
33+
- language -> idioma
34+
- translation -> traducción
35+
36+
## Tone
37+
38+
- Keep labels short and UI-friendly.
39+
- Prefer imperative verbs for actions, for example `Guardar`, `Cerrar`, `Publicar`.
40+
- Prefer neutral Spanish over regional wording.
41+
- Keep product names such as `Superdesk` untranslated.
42+
43+
## Consistency Rules
44+
45+
- Preserve established translations already present in `po/es.po` unless they are clearly wrong and separately approved for correction.
46+
- Do not translate placeholders, field identifiers, or internal codes.
47+
- If a source term is ambiguous, leave it for manual review instead of forcing a glossary match.

docs/i18n/translation-rules.md

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
# AI-Assisted Spanish Translation Rules
2+
3+
This document defines the current rules for AI-assisted Spanish localization in `superdesk-client-core`.
4+
5+
## 1. Scope
6+
7+
- Source of truth for new and changed strings is `po/superdesk.pot`.
8+
- Initial write target is `po/es.po` only.
9+
- Do not modify runtime JSON files directly.
10+
- Run `npm run gettext-update-po -- es` before asking AI to translate.
11+
- Do not change extraction, compile, or validation tooling.
12+
- Existing compile and validation steps remain the final safety net.
13+
14+
## 2. Translation Rules
15+
16+
- Translate only entries that exist in `po/superdesk.pot`.
17+
- Update only matching entries in `po/es.po`.
18+
- Let the sync step add or remove catalog entries structurally.
19+
- Never modify `msgid`.
20+
- Never rewrite unrelated entries.
21+
- Do not overwrite existing non-empty Spanish translations.
22+
- Only fill empty Spanish translations that are safe and clear.
23+
- Keep wording neutral Spanish unless the source clearly requires a specific term.
24+
- Keep diffs minimal and easy to review.
25+
26+
## 3. Placeholder Preservation Rules
27+
28+
- Preserve all `{{...}}` placeholders exactly as they appear in the source.
29+
- Preserve placeholder spacing exactly, including forms like `{{name}}`, `{{ name }}`, `{{ $count}}`, and nested expressions such as `{{user.full_name}}`.
30+
- Do not translate, rename, reorder, add, or remove placeholders.
31+
- Preserve plural structures exactly: `msgid`, `msgid_plural`, and every `msgstr[n]` slot required by the target entry.
32+
- Never collapse plural entries into singular entries or vice versa.
33+
- Preserve inline markup and escaped characters exactly when translating around them.
34+
35+
## 4. Skip Conditions
36+
37+
Skip an entry if any of the following is true:
38+
39+
- The entry already has a non-empty `msgstr` or plural `msgstr[n]`.
40+
- The entry is HTML-heavy or contains markup that makes safe translation unclear.
41+
- The meaning is ambiguous without product or UI context.
42+
- The entry contains unsafe or unclear placeholder usage.
43+
- The entry has unusual formatting, broken source text, or translator notes suggesting manual review is safer.
44+
- The entry would require changing anything outside the exact target message block.
45+
46+
When skipping, leave the entry unchanged.
47+
48+
## 5. Validation Steps
49+
50+
After preparing changes:
51+
52+
1. Refresh the template with `npm run gettext-extract`.
53+
2. Sync the Spanish catalog with `npm run gettext-update-po -- es`.
54+
3. Ensure AI changes are limited to translation content in `po/es.po`.
55+
4. Ensure `msgid` values are untouched.
56+
5. Ensure placeholders and plural structures still match the source exactly.
57+
6. Run the existing compile/validation flow so current tooling can reject invalid placeholder changes:
58+
59+
```bash
60+
npm run gettext-extract
61+
npm run gettext-update-po -- es
62+
grunt nggettext_compile
63+
```
64+
65+
If a broader build check is needed, use the normal project build flow instead of editing generated JSON by hand.
66+
67+
## 6. Definition Of Done
68+
69+
The translation step is done when:
70+
71+
- `po/superdesk.pot` has been refreshed from source.
72+
- `po/es.po` has been structurally synced from the POT via `npm run gettext-update-po -- es`.
73+
- Only safe, empty Spanish entries in `po/es.po` were filled by the AI step.
74+
- Runtime JSON files were not modified.
75+
- No existing non-empty Spanish translations were overwritten.
76+
- All placeholders, plural forms, and source identifiers were preserved exactly.
77+
- The diff is small, targeted, and reviewable.
78+
- Existing compile/validation tooling passes or remains the final blocker for anything invalid.

docs/i18n/translation-workflow.md

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
# AI Translation Workflow
2+
3+
This is the manual GitHub Copilot agent workflow for AI-assisted translations in `superdesk-client-core`.
4+
5+
## Flow
6+
7+
1. Ask the GitHub Copilot agent to update Spanish translations using the repository skill in `.github/skills/translate-po/`.
8+
9+
2. The agent refreshes the source template:
10+
11+
```bash
12+
npm run gettext-extract
13+
```
14+
15+
3. The agent syncs the target PO file from the template:
16+
17+
```bash
18+
npm run gettext-update-po -- es
19+
```
20+
21+
4. The agent fills empty `msgstr` and `msgstr[n]` entries in `po/es.po`, using `po/superdesk.pot` as the source of truth.
22+
23+
5. The agent validates with existing tooling:
24+
25+
```bash
26+
grunt nggettext_compile
27+
```
28+
29+
6. The agent prepares a PR against `develop`.
30+
31+
7. Review the diff and the PR.
32+
33+
## AI Step Boundaries
34+
35+
- Read `po/superdesk.pot` as the source of truth.
36+
- Update only `po/es.po`.
37+
- Translate only empty entries in `po/es.po`.
38+
- Never modify runtime JSON files directly.
39+
- Never edit `msgid`.
40+
- Never overwrite non-empty Spanish translations.
41+
- Preserve placeholders and plural blocks exactly.
42+
- Skip HTML-heavy, ambiguous, or unsafe entries.
43+
44+
## Notes
45+
46+
- `npm run gettext-update-po -- <lang>` is generic and can be reused for other existing top-level PO files later.
47+
- The current workflow remains Spanish-only for AI translation.
48+
- The primary trigger is manual: ask the GitHub Copilot agent to perform the translation update.
49+
- The repository skill for this lives in `.github/skills/translate-po/SKILL.md`.
50+
- `docs/i18n/ai-agent-instructions.md` contains the generic agent rules; this document defines the current Spanish run configuration.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,7 @@
165165
},
166166
"scripts": {
167167
"gettext-extract": "grunt gettext:extract",
168+
"gettext-update-po": "node ./tasks/gettext-update-po.js",
168169
"postinstall": "node ./tasks/patch-package.js && node tasks/generate-placeholder-file-for-extension-styles.js",
169170
"test": "npm run lint && npm run unit && npm run verify-client-api-changes",
170171
"debug-unit-tests": "karma start --reporters=progress --browsers=Chrome",

tasks/gettext-update-po.js

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
const {execFileSync} = require('child_process');
2+
const fs = require('fs');
3+
const path = require('path');
4+
5+
const languageCode = process.argv[2];
6+
7+
if (languageCode == null || languageCode.trim() === '') {
8+
console.error('Usage: npm run gettext-update-po -- <language-code>');
9+
process.exit(1);
10+
}
11+
12+
const lang = languageCode.trim();
13+
14+
if (/^[A-Za-z0-9_.@-]+$/.test(lang) !== true) {
15+
console.error(`Invalid language code: ${lang}`);
16+
process.exit(1);
17+
}
18+
19+
const poFile = path.join(__dirname, '..', 'po', `${lang}.po`);
20+
const potFile = path.join(__dirname, '..', 'po', 'superdesk.pot');
21+
22+
if (fs.existsSync(potFile) !== true) {
23+
console.error(`POT file not found: ${potFile}`);
24+
process.exit(1);
25+
}
26+
27+
if (fs.existsSync(poFile) !== true) {
28+
console.error(`PO file not found for language "${lang}": ${poFile}`);
29+
process.exit(1);
30+
}
31+
32+
try {
33+
execFileSync(
34+
'msgmerge',
35+
[
36+
'--update',
37+
'--backup=none',
38+
'--no-fuzzy-matching',
39+
'--no-wrap',
40+
poFile,
41+
potFile,
42+
],
43+
{stdio: 'inherit'}
44+
);
45+
46+
execFileSync(
47+
'msgattrib',
48+
[
49+
'--no-obsolete',
50+
'--no-wrap',
51+
'-o',
52+
poFile,
53+
poFile,
54+
],
55+
{stdio: 'inherit'}
56+
);
57+
} catch (error) {
58+
if (error.code === 'ENOENT') {
59+
console.error('GNU gettext tools are required. Ensure "msgmerge" and "msgattrib" are installed.');
60+
}
61+
62+
process.exit(error.status ?? 1);
63+
}

0 commit comments

Comments
 (0)