git/list[1] front-page[2] threads[3] people[4] search[5] about
 

[PATCH v2 4/5] docs(l10n): add AI agent instructions for translating PO files

From
Jiang Xin <worldhello.net@gmail.com>
Date
Mar 3, 2026, 15:33 UTC
Message-ID
<d6785db1dfedeccca1cddc17d8c95b99eb266500.1772551123.git.worldhello.net@gmail.com>
In-Reply-To
<cover.1772551123.git.worldhello.net@gmail.com>

Add a new "Translating po/XX.po" section to po/AGENTS.md with detailed workflow and procedures for AI agents to translate language-specific PO files. Users can invoke AI-assisted translation in coding tools with a prompt such as:

    "Translate the po/XX.po file by referring to @po/AGENTS.md"

Translation results serve as a reference; human contributors must review and approve before submission.

To address the translation efficiency issues of some LLMs, batch translation replaces entry-by-entry translation. git-po-helper implements a gettext JSON format for translation files, replacing PO format during translation to enable batch processing.

Evaluation test using the qwen model:
    git-po-helper agent-run --agent=qwen translate po/zh_CN.po
Test translation (127 entries, 50 per batch):
    Initial state:  5998 translated, 91 fuzzy, 36 untranslated
    Final state:    6125 translated, 0 fuzzy, 0 untranslated
    Successfully translated: 127 entries (91 fuzzy + 36 untranslated)
    Success rate: 100%
Benchmark results (3-run average):
AI Agent using gettext tools:
    | Metric           | Value                          |
    |------------------|--------------------------------|
    | Avg Num turns    | 86 (176, 44, 40)               |
    | Avg Exec Time    | 20m44s (39m56s, 14m38s, 7m38s) |
    | Successful runs  | 3/3                            |
AI Agent using git-po-helper (JSON batch flow):
    | Metric           | Value                          |
    |------------------|--------------------------------|
    | Avg Num turns    | 56 (68, 39, 63)                |
    | Avg Exec Time    | 19m8s (28m55s, 9m1s, 19m28s)   |
    | Successful runs  | 3/3                            |

The git-po-helper flow reduces turns (86 → 56) with similar execution time; the bottleneck appears to be LLM processing rather than network interaction.

Signed-off-by: Jiang Xin <worldhello.net@gmail.com>
---
 po/AGENTS.md | 643 ++++++++++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 642 insertions(+), 1 deletion(-)
diff --git a/po/AGENTS.md b/po/AGENTS.md
index 5eb1a606e1..3bb8fb3858 100644
--- a/po/AGENTS.md
+++ b/po/AGENTS.md
@@ -5,7 +5,11 @@ housekeeping tasks for Git l10n. Use of AI is optional; many successful
 l10n teams work well without it.
 
 The section "Housekeeping tasks for localization workflows" documents the
-most commonly used housekeeping tasks.
+most commonly used housekeeping tasks:
+
+1. Generating or updating po/git.pot
+2. Updating po/XX.po
+3. Translating po/XX.po
 
 
 ## Background knowledge for localization workflows
@@ -51,6 +55,384 @@ The header provides: translation metadata (translator, language, dates);
 pluralization rules (`Plural-Forms`); encoding and MIME type; project/version.
 
 
+### Glossary Section
+
+PO files may have a glossary in comments before the header entry (first
+`msgid ""`), giving terminology guidelines:
+
+```po
+# Git glossary for Chinese translators
+#
+#   English                          |  Chinese
+#   ---------------------------------+--------------------------------------
+#   3-way merge                      |  三路合并
+#   branch                           |  分支
+#   commit                           |  提交
+#   ...
+```
+
+**IMPORTANT**: Read and use the glossary when translating or reviewing. It is
+in `#` comments and is preserved when extracting with `msgattrib`.
+
+
+### Single-line vs Multi-line Entries
+
+**Single-line entries**:
+```po
+msgid "commit message"
+msgstr "提交说明"
+```
+
+**Multi-line entries** (first line of `msgid` and `msgstr` is empty string):
+```po
+msgid ""
+"Line 1\n"
+"Line 2"
+msgstr ""
+"行 1\n"
+"行 2"
+```
+
+**CRITICAL** for multi-line: first line is `msgid ""` / `msgstr ""`; following
+lines are quoted strings; use `\n` for line breaks. Preserve quotes and
+structure exactly.
+
+Because multi-line entries also use `msgstr ""` on the first line, `grep
+'^msgstr ""'` yields false positives when locating untranslated strings. See
+the next section for the correct approach.
+
+
+### Locating untranslated, fuzzy, and obsolete entries
+
+**The commands below are used in "Task 3: translating po/XX.po".** For
+translation tasks, follow Task 3 steps strictly; do not run these commands in
+isolation.
+
+This section describes how to locate untranslated, fuzzy, and obsolete entries.
+Do **not** use `grep '^msgstr ""$'`—it matches multi-line entries and causes
+false positives. Use `msgattrib`:
+
+- **Untranslated**: `msgattrib --untranslated --no-obsolete po/XX.po`
+- **Fuzzy**: `msgattrib --only-fuzzy --no-obsolete po/XX.po`
+- **Obsolete** (`#~`): `msgattrib --obsolete --no-wrap po/XX.po`
+
+To get only message IDs:
+`msgattrib --untranslated --no-obsolete po/XX.po | sed -n '/^msgid /,/^$/p'`
+(Same pattern for fuzzy with `--only-fuzzy`.)
+
+When counting entries, the header is included; subtract 1 to exclude it.
+
+
+### Translating fuzzy entries
+
+Fuzzy entries need re-translation because the source text changed. The format
+differs by file type:
+
+- **PO file**: A `#, fuzzy` tag in the entry comments marks the entry as fuzzy.
+- **JSON file**: The entry has `"fuzzy": true`.
+
+**Translation principles**: Re-translate the `msgstr` (and, for plural entries,
+`msgstr[n]`) into the target language. Do **not** modify `msgid` or
+`msgid_plural`. After translation, **clear the fuzzy mark**: in PO, remove the
+`#, fuzzy` tag from comments; in JSON, omit or set `fuzzy` to `false`.
+
+
+### Preserving Special Characters
+
+Preserve escape sequences (`\n`, `\"`, `\\`, `\t`), placeholders (`%s`, `%d`,
+etc.), and quotes exactly as in `msgid`. Only reorder placeholders with
+positional syntax when needed (see Placeholder Reordering below).
+
+**Correct**: `msgstr "行 1\n行 2"` (keep `\n` as escape).
+**Wrong**: `msgstr "行 1\\n行 2"` or actual line breaks inside the string.
+
+
+### Placeholder Reordering
+
+When reordering placeholders from the original `msgid`, use positional syntax
+(`%n$`) so each argument maps to the correct value. Keep width/precision
+modifiers and put the position before them.
+
+**Example 1** (precision):
+```po
+#, c-format
+msgid "missing environment variable '%s' for configuration '%.*s'"
+msgstr "配置 '%3$.*2$s' 缺少环境变量 '%1$s'"
+```
+`%s` → argument 1 → `%1$s`. `%.*s` needs precision (arg 2) and string (arg 3) →
+`%3$.*2$s`.
+
+**Example 2** (multi-line, four `%s` reordered):
+```po
+#, c-format
+msgid ""
+"the 'submodule.%s.gitdir' config does not exist for module '%s'. Please "
+"ensure it is set, for example by running something like: 'git config "
+"submodule.%s.gitdir .git/modules/%s'. For details see the "
+"extensions.submodulePathConfig documentation."
+msgstr ""
+"模块 '%2$s' 的 'submodule.%1$s.gitdir' 配置不存在。请确保已设置,例如运行类"
+"似:'git config submodule.%3$s.gitdir .git/modules/%4$s'。详细信息请参见 "
+"extensions.submodulePathConfig 文档。"
+```
+
+Original order 1,2,3,4; in translation 2,1,3,4. Each line must be a complete
+quoted string.
+
+**Rules**: Use `%n$` (n = 1-based position); place position before
+width/precision; for `%.*s` map both precision and string; verify all
+placeholders are mapped.
+
+
+### Validating PO File Format
+
+Validate any PO file (e.g. `po/XX.po`, `po/l10n-pending.po`):
+
+```shell
+msgfmt --check -o /dev/null po/XX.po
+```
+
+Common validation errors include:
+- Unclosed quotes
+- Missing escape sequences
+- Invalid placeholder syntax
+- Malformed multi-line entries
+- Incorrect line breaks in multi-line strings
+
+**Handling validation errors with automatic repair**:
+When `msgfmt` reports an error, it provides the line number where the error
+was detected. Use this information to locate and fix the issue.
+
+
+### Using git-po-helper
+
+[git-po-helper](https://github.com/git-l10n/git-po-helper) is a helper program
+for Git localization (l10n) contributions. It serves two main purposes:
+**quality checking** (conventions for git-l10n pull requests) and
+**AI-assisted translation** (evaluate; help establish and assess the impact
+of this document on automated translation). git-po-helper provides subcommands
+that simplify the AI translation workflow and improve efficiency. When
+available, this document uses `git-po-helper` for PO operations; otherwise it
+falls back to gettext tools.
+
+**This section serves as reference for Housekeeping tasks.** AI Agent should
+follow the Task steps when executing; this content provides command reference
+information. Do not run commands in isolation.
+
+
+#### Splitting large PO files
+
+When a PO file is too large for translation or review, use `git-po-helper
+msg-select` to split it by entry index.
+
+- **Entry 0** is the header (included by default; use `--no-header` to omit).
+- **Entries 1, 2, 3, …** are content entries.
+- **Range format**: `--range "1-50"` (entries 1 through 50), `--range "-50"`
+  (first 50 entries), `--range "51-"` (from entry 51 to end).
+- **Output format**: PO by default; use `--json` for GETTEXT JSON. See the
+  "GETTEXT JSON format" section (under git-po-helper) for details.
+- **State filter**: Use `--translated`, `--untranslated`, `--fuzzy` to filter
+  by state (OR relationship). Use `--no-obsolete` to exclude obsolete entries;
+  `--with-obsolete` to include (default). Use `--only-same` or `--only-obsolete`
+  for a single state. Range applies to the filtered list.
+
+```shell
+# First 50 entries (header + entries 1–50)
+git-po-helper msg-select --range "-50" po/in.po -o po/out1.po
+
+# Entries 51–100
+git-po-helper msg-select --range "51-100" po/in.po -o po/out2.po
+
+# Entries 101 to end
+git-po-helper msg-select --range "101-" po/in.po -o po/out3.po
+
+# Entries 1–50 without header (content only)
+git-po-helper msg-select --range "1-50" --no-header po/in.po -o po/frag.po
+
+# Output as JSON; select untranslated and fuzzy entries, exclude obsolete
+git-po-helper msg-select --json --untranslated --fuzzy --no-obsolete po/in.po >po/filtered.json
+```
+
+
+#### Comparing PO files for translation and review
+
+Use `git-po-helper compare` for scenarios that `git diff` or `git show` cannot
+handle well:
+
+- **Show changes with full context**: Get new and modified entries with
+  complete `msgid` and `msgstr`. Plain `git diff` fragments or loses PO
+  context.
+- **Detect msgid tampering**: When an AI-generated PO file may have altered
+  `msgid`, a translation becomes an add instead of a replace. Use `--msgid`
+  to compare by msgid only. No diff output means the target and source files
+  are consistent in the data source (msgid).
+
+These capabilities support both translation workflows and code review. Redirect
+output to a file:
+
+```shell
+# Check msgid consistency (detect tampering); no output means target matches source
+git-po-helper compare --msgid po/old.po po/new.po >po/out.po
+
+# Get full context of local changes (HEAD vs working tree)
+git-po-helper compare po/XX.po -o po/out.po
+
+# Get full context of changes in a specific commit (parent vs commit)
+git-po-helper compare --commit <commit> po/XX.po -o po/out.po
+
+# Get full context of changes since a commit (commit vs working tree)
+git-po-helper compare --since <commit> po/XX.po -o po/out.po
+
+# Get full context between two commits
+git-po-helper compare -r <commit1>..<commit2> po/XX.po -o po/out.po
+
+# Get full context of two worktree files
+git-po-helper compare po/old.po po/new.po -o po/out.po
+```
+
+**Options summary**
+
+| Option              | Meaning                                        |
+|---------------------|------------------------------------------------|
+| (none)              | Compare HEAD with working tree (local changes) |
+| `--commit <commit>` | Compare parent of commit with the commit       |
+| `--since <commit>`  | Compare commit with working tree               |
+| `-r x..y`           | Compare revision x with revision y             |
+| `-r x..`            | Compare revision x with working tree           |
+| `-r x`              | Compare parent of x with x                     |
+
+Output is empty when there are no new or changed entries; otherwise it
+includes a valid PO header.
+
+
+#### Concatenating multiple PO/JSON files
+
+Use `git-po-helper msg-cat` to merge one or more input files (PO, POT, or
+gettext JSON) into a single output. Input format is auto-detected by content
+or extension. For duplicate `msgid`, the first occurrence by file order wins.
+Use `-o <file>` for output; omit or use `-o -` for stdout. Use `--json` for
+JSON output; otherwise output is PO format.
+
+```shell
+# Convert JSON to PO (e.g. after translation)
+git-po-helper msg-cat --unset-fuzzy -o po/out.po po/in.json
+
+# Merge multiple PO files
+git-po-helper msg-cat -o po/out.po po/in-1.po po/in-2.po
+```
+
+
+#### GETTEXT JSON format
+
+The **GETTEXT JSON** format is an internal format defined by `git-po-helper`
+for convenient batch processing of translation and related tasks by AI models.
+`git-po-helper msg-select`, `git-po-helper msg-cat`, and `git-po-helper compare`
+read and write this format.
+
+**Top-level structure**:
+
+```json
+{
+  "header_comment": "string",
+  "header_meta": "string",
+  "entries": [ /* array of entry objects */ ]
+}
+```
+
+| Field            | Description                                                                   |
+|------------------|-------------------------------------------------------------------------------|
+| `header_comment` | Lines above the first `msgid ""` (comments, glossary). Directly concatenated. |
+| `header_meta`    | Decoded `msgstr` of the header entry (Project-Id-Version, Plural-Forms, etc.).|
+| `entries`        | List of PO entries. Order matches source.                                     |
+
+**Entry object** (each element of `entries`):
+
+| Field           | Type     | Description                                           |
+|-----------------|----------|-------------------------------------------------------|
+| `msgid`         | string   | Singular message ID. PO escapes encoded.              |
+| `msgstr`        | string   | Singular message string. Empty for plural entries.    |
+| `msgid_plural`  | string   | Plural form of msgid. Omit for non-plural.            |
+| `msgstr_plural` | []string | Array of msgstr[0], msgstr[1], … Omit for non-plural. |
+| `comments`      | []string | Comment lines (`#`, `#.`, `#:`, `#,`, etc.).          |
+| `fuzzy`         | bool     | True if entry has fuzzy flag.                         |
+| `obsolete`      | bool     | True for `#~` obsolete entries. Omit if false.        |
+
+**Example (single-line entry)**:
+
+```json
+{
+  "header_comment": "# Glossary:\\n# term1\\tTranslation 1\\n#\\n",
+  "header_meta": "Project-Id-Version: git\\nContent-Type: text/plain; charset=UTF-8\\n",
+  "entries": [
+    {
+      "msgid": "Hello",
+      "msgstr": "你好",
+      "comments": ["#. Comment for translator\\n", "#: src/file.c:10\\n"],
+      "fuzzy": false
+    }
+  ]
+}
+```
+
+**Example (plural entry)**:
+
+```json
+{
+  "msgid": "One file",
+  "msgstr": "",
+  "msgid_plural": "%d files",
+  "msgstr_plural": ["一个文件", "%d 个文件"],
+  "comments": ["#, c-format\\n"],
+  "fuzzy": false
+}
+```
+
+**Example (fuzzy entry before translation)**:
+
+```json
+{
+  "msgid": "Old message",
+  "msgstr": "旧翻译",
+  "comments": ["#, fuzzy\\n"],
+  "fuzzy": true
+}
+```
+
+**Translation notes for GETTEXT JSON files**:
+
+- **Preserve structure**: Keep `header_comment`, `header_meta`, `comments`,
+  `msgid`, `msgid_plural` unchanged. Only modify `msgstr` and `msgstr_plural`.
+- **Fuzzy entries**: Entries extracted from fuzzy PO entries have `"fuzzy": true`.
+  After translating, **remove the `fuzzy` field** or set it to `false` in the
+  output (`po/l10n-done.json`). The merge step uses `--unset-fuzzy`, which can
+  also remove the `fuzzy` field.
+- **Placeholders**: Preserve `%s`, `%d`, etc. exactly; use `%n$` when
+  reordering (see "Placeholder Reordering" above).
+
+
+### Quality checklist
+
+- **Accuracy**: Faithful to original meaning; no omissions or distortions.
+- **Fuzzy entries**: Re-translate fully and clear the fuzzy flag (see
+  "Translating fuzzy entries" above).
+- **Terminology**: Consistent with glossary (see "Glossary Section" above) or
+  domain standards.
+- **Grammar and fluency**: Correct and natural in the target language.
+- **Placeholders**: Preserve variables (`%s`, `{name}`, `$1`) exactly; use
+  positional parameters when reordering (see "Placeholder Reordering" above).
+- **Special characters**: Preserve escape sequences (`\n`, `\"`, `\\`, `\t`),
+  placeholders, and quotes exactly as in `msgid`. Correct: `msgstr "行 1\n行 2"`
+  (keep `\n` as escape). Wrong: `"行 1\\n行 2"` or actual line breaks inside the
+  string. See "Preserving Special Characters" above.
+- **Plurals and gender**: Correct forms and agreement.
+- **Context fit**: Suitable for UI space, tone, and use (e.g. error vs. tooltip).
+- **Cultural appropriateness**: No offensive or ambiguous content.
+- **Consistency**: Match prior translations of the same source.
+- **Technical integrity**: Do not translate code, paths, commands, brands, or
+  proper nouns.
+- **Readability**: Clear, concise, and user-friendly.
+
+
 ## Housekeeping tasks for localization workflows
 
 This section describes housekeeping tasks listed in the introduction. Read
@@ -88,6 +470,265 @@ The command will handle all necessary steps including generating
 automatically.
 
 
+### Task 3: Translating po/XX.po
+
+When asked to translate `po/XX.po`, follow the steps below. The workflow
+**automatically selects** the tool based on availability: use `git-po-helper`
+if present, otherwise use gettext tools. With `git-po-helper`, the content to
+translate is converted to JSON, enabling batch translation instead of
+entry-by-entry translation for better efficiency. Translate every untranslated
+and fuzzy entry; do not stop before the loop completes.
+
+1. **Extract entries to translate**: Generate `po/l10n-pending.po` with
+   untranslated and fuzzy messages. If the generated `po/l10n-pending.po` file
+   is empty or does not exist, translation is complete. In that case, you
+   **MUST** skip to the last step (clean up); do not run further translation
+   steps.
+
+   ```shell
+   po_extract_pending () {
+       test $# -ge 1 || { echo "Usage: po_extract_pending <po-file>" >&2; exit 1; }
+       PO_FILE="$1"
+       PENDING="po/l10n-pending.po"
+       rm -f "$PENDING"
+
+       if command -v git-po-helper >/dev/null 2>&1
+       then
+           git-po-helper msg-select --untranslated --fuzzy --no-obsolete -o "$PENDING" "$PO_FILE"
+       else
+           msgattrib --untranslated --no-obsolete "$PO_FILE" >"${PENDING}.untranslated"
+           msgattrib --only-fuzzy --no-obsolete --clear-fuzzy --empty "$PO_FILE" >"${PENDING}.fuzzy"
+           msgattrib --only-fuzzy --no-obsolete "$PO_FILE" >"${PENDING}.fuzzy.reference"
+           msgcat --use-first "${PENDING}.untranslated" "${PENDING}.fuzzy" >"$PENDING"
+           rm -f "${PENDING}.untranslated" "${PENDING}.fuzzy"
+       fi
+   }
+   # Run the extraction. Example: po_extract_pending po/zh_CN.po
+   po_extract_pending po/XX.po
+   ```
+
+2. **Prepare one batch for translation**: **BEFORE translating**, run the
+   script below. It truncates large tasks so each run processes one chunk,
+   keeping file size within model capacity.
+
+   Output: `po/l10n-todo.json` (git-po-helper) or `po/l10n-todo.po` (gettext
+   only). If `po/l10n-todo.json` exists, go to step 3a; if `po/l10n-todo.po`
+   exists, go to step 3b.
+
+   ```shell
+   l10n_one_batch () {
+       test $# -ge 1 || { echo "Usage: l10n_one_batch <po-file> [min_batch_size]" >&2; exit 1; }
+       PO_FILE="$1"
+       min_batch_size=${2:-100}
+       PENDING="po/l10n-pending.po"
+       rm -f po/l10n-todo.json po/l10n-done.json po/l10n-todo.po po/l10n-done.po
+
+       ENTRY_COUNT=$(grep -c '^msgid ' "$PENDING" 2>/dev/null || true)
+       ENTRY_COUNT=$((ENTRY_COUNT > 0 ? ENTRY_COUNT - 1 : 0))
+
+       if test "$ENTRY_COUNT" -gt $min_batch_size
+       then
+           if test "$ENTRY_COUNT" -gt $((min_batch_size * 8))
+           then
+               NUM=$((min_batch_size * 2))
+           elif test "$ENTRY_COUNT" -gt $((min_batch_size * 4))
+           then
+               NUM=$((min_batch_size + min_batch_size / 2))
+           else
+               NUM=$min_batch_size
+           fi
+           BATCHING=1
+       else
+           NUM=$ENTRY_COUNT
+           BATCHING=
+       fi
+
+       if command -v git-po-helper >/dev/null 2>&1
+       then
+           if test -n "$BATCHING"
+           then
+               git-po-helper msg-select --json --range "-$NUM" -o po/l10n-todo.json "$PENDING"
+               echo "Processing batch of $NUM entries (out of $ENTRY_COUNT remaining)"
+           else
+               git-po-helper msg-select --json -o po/l10n-todo.json "$PENDING"
+               echo "Processing all $ENTRY_COUNT entries at once"
+           fi
+       else
+           if test -n "$BATCHING"
+           then
+               awk -v num="$NUM" '/^msgid / && count++ > num {exit} 1' "$PENDING" |
+                   tac | awk '/^$/ {found=1} found' | tac >po/l10n-todo.po
+               echo "Processing batch of $NUM entries (out of $ENTRY_COUNT remaining)"
+           else
+               cp "$PENDING" po/l10n-todo.po
+               echo "Processing all $ENTRY_COUNT entries at once"
+           fi
+       fi
+   }
+   # Prepare batch for translation. Second param controls batch size; reduce if
+   # the batch file is too large for the Agent to process.
+   l10n_one_batch po/XX.po 100
+   ```
+
+3a. **Translate JSON batch** (`po/l10n-todo.json` → `po/l10n-done.json`):
+
+   - **Task**: Translate `po/l10n-todo.json` (input, GETTEXT JSON) into
+     `po/l10n-done.json` (output, GETTEXT JSON). See the "GETTEXT JSON format"
+     section above for format details and translation rules.
+   - **Reference glossary**: Read the glossary from the batch file's
+     `header_comment` (see "Glossary Section" above) and use it for
+     consistent terminology.
+   - **When translating**: Follow the "Quality checklist" above for correctness
+     and quality. Handle escape sequences (`\n`, `\"`, `\\`, `\t`), placeholders,
+     and quotes correctly as in `msgid`. For JSON, correctly escape and unescape
+     these sequences when reading and writing. Modify `msgstr` and `msgstr[n]`
+     (for plural entries); clear the fuzzy flag (omit or set `fuzzy` to `false`).
+     Do **not** modify `msgid` or `msgid_plural`.
+
+3b. **Translate PO batch** (`po/l10n-todo.po` → `po/l10n-done.po`):
+
+   - **Task**: Translate `po/l10n-todo.po` (input, PO) into `po/l10n-done.po`
+     (output, PO).
+   - **Reference glossary**: Read the glossary from the pending file header
+     (see "Glossary Section" above) and use it for consistent terminology.
+   - **When translating**: Follow the "Quality checklist" above for correctness
+     and quality. Preserve escape sequences (`\n`, `\"`, `\\`, `\t`), placeholders,
+     and quotes as in `msgid`. Modify `msgstr` and `msgstr[n]` (for plural
+     entries); remove the `#, fuzzy` tag from comments when done. Do **not**
+     modify `msgid` or `msgid_plural`.
+
+4. **Validate `po/l10n-done.po`**:
+
+   Whether from step 3a (JSON converted to PO) or step 3b (PO output directly),
+   the result may have two kinds of issues. Run the validation script; proceed to
+   step 5 only if it succeeds:
+
+   ```shell
+   l10n_validate_done () {
+       DONE_PO="po/l10n-done.po"
+       DONE_JSON="po/l10n-done.json"
+       PENDING="po/l10n-pending.po"
+
+       if test -f "$DONE_JSON" && { ! test -f "$DONE_PO" || test "$DONE_JSON" -nt "$DONE_PO"; }
+       then
+           git-po-helper msg-cat --unset-fuzzy -o "$DONE_PO" "$DONE_JSON" || {
+               echo "ERROR [JSON to PO conversion]: Fix $DONE_JSON and re-run." >&2
+               return 1
+           }
+       fi
+
+       # Check 1: msgid should not be modified
+       MSGID_OUT=$(git-po-helper compare -q --msgid --assert-no-changes \
+           "$PENDING" "$DONE_PO" 2>&1)
+       MSGID_RC=$?
+       if test $MSGID_RC -ne 0 || test -n "$MSGID_OUT"
+       then
+           echo "ERROR [msgid modified]: The following entries appeared after" >&2
+           echo "translation because msgid was altered. Fix in $DONE_PO." >&2
+           echo "$MSGID_OUT" >&2
+           return 1
+       fi
+
+       # Check 2: PO format (see "Validating PO File Format" for error handling)
+       MSGFMT_OUT=$(msgfmt --check -o /dev/null "$DONE_PO" 2>&1)
+       MSGFMT_RC=$?
+       if test $MSGFMT_RC -ne 0
+       then
+           echo "ERROR [PO format]: Fix errors in $DONE_PO." >&2
+           echo "$MSGFMT_OUT" >&2
+           return 1
+       fi
+
+       echo "Validation passed."
+   }
+   l10n_validate_done
+   ```
+
+   If the script fails, fix **directly in `po/l10n-done.po`**. Editing
+   `po/l10n-done.json` is not recommended because it adds an extra JSON-to-PO
+   conversion step. Use the error message to decide:
+
+   - **`[msgid modified]`**: The listed entries have altered `msgid`; restore
+     them to match `po/l10n-pending.po`.
+   - **`[PO format]`**: `msgfmt` reports line numbers; fix the errors in place.
+     See "Validating PO File Format" for common issues.
+
+   Re-run `l10n_validate_done` until it succeeds. If repair fails, exit
+   immediately.
+
+5. **Merge translation results into `po/XX.po`**: Run the following script:
+
+   ```shell
+   l10n_merge_batch () {
+       test $# -ge 1 || { echo "Usage: l10n_merge_batch <po-file>" >&2; exit 1; }
+       PO_FILE="$1"
+       DONE_PO="po/l10n-done.po"
+       DONE_JSON="po/l10n-done.json"
+       MERGED="po/l10n-done.merged"
+       PENDING="po/l10n-pending.po"
+       if test -f "$DONE_JSON" && { ! test -f "$DONE_PO" || test "$DONE_JSON" -nt "$DONE_PO"; }
+       then
+           git-po-helper msg-cat --unset-fuzzy -o "$DONE_PO" "$DONE_JSON" || {
+               echo "ERROR [JSON to PO conversion]: Fix $DONE_JSON and re-run." >&2
+               return 1
+           }
+       fi
+       msgcat --use-first "$DONE_PO" "$PO_FILE" >"$MERGED" || {
+           echo "ERROR [msgcat merge]: Fix errors in $DONE_PO and re-run." >&2
+           exit 1
+       }
+       mv "$MERGED" "$PO_FILE"
+       rm -f "$PENDING"
+   }
+   # Run the merge. Example: l10n_merge_batch po/zh_CN.po
+   l10n_merge_batch po/XX.po
+   ```
+
+   If `msgcat` fails, fix **directly in `po/l10n-done.po`**. Editing
+   `po/l10n-done.json` is not recommended because it adds an extra JSON-to-PO
+   conversion step. If repair fails, exit immediately.
+
+6. **Repeat steps 1–5** until `po/l10n-pending.po` is empty (or does not exist).
+   Do not stop early.
+
+7. **Final verification**:
+
+   ```shell
+   # Final check
+   UNTRANS=$(msgattrib --untranslated --no-obsolete po/XX.po 2>/dev/null | grep -c '^msgid ' || true)
+   UNTRANS=$((UNTRANS > 0 ? UNTRANS - 1 : 0))
+   FUZZY=$(msgattrib --only-fuzzy --no-obsolete po/XX.po 2>/dev/null | grep -c '^msgid ' || true)
+   FUZZY=$((FUZZY > 0 ? FUZZY - 1 : 0))
+   if test "$UNTRANS" -eq 0 && test "$FUZZY" -eq 0
+   then
+       echo "Translation complete! All entries translated."
+   else
+       echo "WARNING: Still have $UNTRANS untranslated + $FUZZY fuzzy entries."
+       echo "Do not clean up. Continue with step 1."
+       exit 1
+   fi
+   ```
+
+8. **Clean up** (only after step 7 passes):
+
+   ```shell
+   po_cleanup () {
+       rm -f "po/l10n-pending.po"
+       rm -f "po/l10n-pending.po.fuzzy"
+       rm -f "po/l10n-pending.po.fuzzy.reference"
+       rm -f "po/l10n-pending.po.untranslated"
+       rm -f "po/l10n-todo.json"
+       rm -f "po/l10n-todo.po"
+       rm -f "po/l10n-done.json"
+       rm -f "po/l10n-done.merged"
+       rm -f "po/l10n-done.po"
+       echo "Cleanup complete. Translation finished successfully."
+   }
+   # Run cleanup
+   po_cleanup
+   ```
+
+
 ## Human translators remain in control
 
 Git translation is human-driven; language team leaders and contributors are
-- 
2.53.0.rc2.20.g532543fa46
Previous: Jiang XinNext: Jiang Xin
Message 21 of 45 in “[RFC] Introducing AI Agents to Git Localization”
  1. Jiang XinFeb 4, 2026
  2. Peter KreftingFeb 4, 2026
  3. Michal SuchánekFeb 4, 2026
  4. 依云Feb 4, 2026
  5. Jiang XinFeb 5, 2026
  6. Michal SuchánekFeb 5, 2026
  7. Jiang XinFeb 5, 2026
  8. Michal SuchánekFeb 5, 2026
  9. Jiang XinFeb 5, 2026
  10. brian m. carlsonFeb 5, 2026
  11. 1/2 l10n: add .gitattributes to simplify location filteringJiang Xin, Feb 5, 2026
  12. Junio C HamanoFeb 5, 2026
  13. 2/2 l10n: README: document AI assistant guidelinesJiang Xin, Feb 5, 2026
  14. Junio C HamanoFeb 5, 2026
  15. Jiang XinFeb 6, 2026
  16. 0/5 docs(l10n): AI agent instructions and workflow improvementsJiang Xin, Mar 3, 2026
  17. 1/5 l10n: add .gitattributes to simplify location filteringJiang Xin, Mar 3, 2026
  18. 2/5 docs(l10n): add AGENTS.md with optimized update-pot instructionsJiang Xin, Mar 3, 2026
  19. Jiang XinMar 12, 2026
  20. 3/5 docs(l10n): add AI agent instructions for updating po/XX.po filesJiang Xin, Mar 3, 2026
  21. 4/5 docs(l10n): add AI agent instructions for translating PO filesJiang Xin, Mar 3, 2026
  22. Jiang XinMar 12, 2026
  23. 5/5 docs(l10n): add AI agent instructions to review translationsJiang Xin, Mar 3, 2026
  24. Jiang XinMar 12, 2026
  25. 0/5 docs(l10n): AI agent instructions and workflow improvementsJiang Xin, Mar 14, 2026
  26. 1/5 l10n: add .gitattributes to simplify location filteringJiang Xin, Mar 14, 2026
  27. Johannes SixtMar 15, 2026
  28. Junio C HamanoMar 15, 2026
  29. Jiang XinMar 16, 2026
  30. Jiang XinMar 16, 2026
  31. Johannes SixtMar 16, 2026
  32. 2/5 docs(l10n): add AGENTS.md with optimized update-pot instructionsJiang Xin, Mar 14, 2026
  33. 3/5 docs(l10n): add AI agent instructions for updating po/XX.po filesJiang Xin, Mar 14, 2026
  34. 4/5 docs(l10n): add AI agent instructions for translating PO filesJiang Xin, Mar 14, 2026
  35. 5/5 docs(l10n): add AI agent instructions to review translationsJiang Xin, Mar 14, 2026
  36. 0/5 docs(l10n): AI agent instructions and workflow improvementsJiang Xin, Mar 16, 2026
  37. 1/5 l10n: add .gitattributes to simplify location filteringJiang Xin, Mar 16, 2026
  38. 2/5 docs(l10n): add AGENTS.md with optimized update-pot instructionsJiang Xin, Mar 16, 2026
  39. 3/5 docs(l10n): add AI agent instructions for updating po/XX.po filesJiang Xin, Mar 16, 2026
  40. 4/5 docs(l10n): add AI agent instructions for translating PO filesJiang Xin, Mar 16, 2026
  41. 5/5 docs(l10n): add AI agent instructions to review translationsJiang Xin, Mar 16, 2026
  42. Jiang XinMar 31, 2026
  43. Junio C HamanoMar 31, 2026
  44. Jiang XinMar 31, 2026
  45. Jiang XinFeb 5, 2026

Read the whole thread, see it on lore, or plain text.

$ cat FOOTERMessages come from the public archive at lore.kernel.org/git, fetched every hour. The front page is chosen and written each morning by an AI editor and can be wrong; the threads themselves are the record. About and API. For agents: an MCP server at https://gitlist.dev/mcp, and any thread, story or person page as Markdown by adding .md to its URL (or sending Accept: text/markdown). Details in /llms.txt.