l10n-2.54.0-v2

-----BEGIN PGP SIGNATURE-----
 
 iQIzBAABCAAdFiEE37vMEzKDqYvVxs51k24VDd1FMtUFAmnlaFEACgkQk24VDd1F
 MtUpsBAAoyMhQnxSHprl+xXd/JVOn/pswGUOvXyr8c/I/BowkG4wQfVyRwpw1uRR
 lO1jlEyyJjIAXJERnTODzuCEktBSfwedF7HgZhS68kTq7xV7hOuxJVS8GXCWI0O4
 TXQ/sNQcP93swIxqbTUhnP+sFwb3P7YoUDWIAbNJa7PLkGZbOdet4iWYSk3etcya
 JbE6MAZpTjNidleTcpjeC87Wy6X59VCSFHAr6x3AaOyaZ8W5VayiRJRb/Zy6gd2+
 ebc8qcPVXAWlwZfmPlQfCV964tbfhc8Lz/vHCmuNgA9bqqZZHPJSHE+UNhjuBOfv
 pDU8MVhKI2NTCqUdrW782fXCewL3iJ72T0AYTV6m8MhO67gi3OYk4Y0C82kH/6eg
 9FEk5uu1HnwhdVYb0yh5mS7nEbwjJgUcfJelWggHfl/tDlumw1EOrQbF3Pe1DIIH
 K4Km7sNgAiXyJ2oePkIR9Pghh/5yJgQ39NUc7houFgaz2UfnQ3taXgaDL5cakhOV
 42yCnLNsiTm4QmFbctMqLK7l7C2Ql0/UEm5moRosRS/KpaOMSlrmxhuE5Fad344f
 VeZsFiHJGtkB/hFb6lhfLEIryVjkb7mpt+6TMZD+P5os1dHWM4Xez09YHg+i6I1A
 lre6pVUSm59qQTmHhpPIl49Ak6A4t+OX+/3CIALotAkWucOuiCg=
 =WrJg
 -----END PGP SIGNATURE-----

Merge tag 'l10n-2.54.0-v2' of https://github.com/git-l10n/git-po

l10n-2.54.0-v2

* tag 'l10n-2.54.0-v2' of https://github.com/git-l10n/git-po:
  l10n: bg.po: Updated Bulgarian translation (6226t)
  l10n: zh_TW: update translation for Git 2.54
  l10n: Update Catalan Translation
  l10n: ga.po: update for Git 2.54
  l10n: fr: v2.54.0
  l10n: tr: Update Turkish translations
  l10n: sv.po: Update Swedish translation
  l10n: sv.po: correct various translations
  l10n: zh_CN: updated translation for 2.54
  l10n: bg.po: Updated Bulgarian translation (6226t)
  l10n: zh_CN: post-2.53 code review
  l10n: document AI and PO helper in po/README
  l10n: docs: add review instructions in AGENTS.md
  l10n: docs: add translation instructions in AGENTS.md
  l10n: docs: add update PO instructions in AGENTS.md
  l10n: docs: add AGENTS.md with update POT instructions
  l10n: add .gitattributes to simplify location filtering
  l10n: fix 'zh_TW.po' 'Applying patch'
maint
Junio C Hamano 2026-04-19 18:59:09 -07:00
commit 8ba07ec111
13 changed files with 11614 additions and 4718 deletions

35
po/.gitattributes vendored Normal file
View File

@ -0,0 +1,35 @@
# Git Attributes for PO Files
#
# This file configures Git filters to automatically strip location information
# from PO files when committing, producing cleaner diffs and saving repository
# space.
#
# Two filter types are used:
# 1. gettext-no-location: Strips both filenames and line numbers
# (e.g., removes "#: main.c:123" entirely)
# 2. gettext-no-line-number: Preserves filenames but removes line numbers, which
# requires gettext 0.20 or higher
# (e.g., "#: main.c:123" becomes "#: main.c")
#
# See `po/README.md` for instructions on setting up the required filter drivers.

# Default: Strip the whole location comments for all .po files
*.po filter=gettext-no-location

# Legacy, unmaintained PO files: filter disabled to avoid index vs
# working-tree mismatch (these files still have location comments).
el.po -filter
is.po -filter
it.po -filter
ko.po -filter
pl.po -filter
pt_PT.po -filter

# These files use gettext-no-line-number (keep filenames, strip line
# numbers). The choice is per l10n team preference. Requires gettext 0.20+.
# The only benefit is locating source files from location comments when
# the .po file is not updated from the POT via make po-update.
ca.po filter=gettext-no-line-number
id.po filter=gettext-no-line-number
zh_CN.po filter=gettext-no-line-number
zh_TW.po filter=gettext-no-line-number

877
po/AGENTS.md Normal file
View File

@ -0,0 +1,877 @@
# Instructions for AI Agents

This file gives specific instructions for AI agents that perform
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:

1. Generating or updating po/git.pot
2. Updating po/XX.po
3. Translating po/XX.po
4. Reviewing translation quality


## Background knowledge for localization workflows

Essential background for the workflows below; understand these concepts before
performing any housekeeping tasks in this document.

### Language code and notation (XX, ll, ll\_CC)

**XX** is a placeholder for the language code: either `ll` (ISO 639) or
`ll_CC` (e.g. `de`, `zh_CN`). It appears in the PO file header metadata
(e.g. `"Language: zh_CN\n"`) and is typically used to name the PO file:
`po/XX.po`.


### Header Entry

The **header entry** is the first entry in every `po/XX.po`. It has an empty
`msgid`; translation metadata (project, language, plural rules, encoding, etc.)
is stored in `msgstr`, as in this example:

```po
msgid ""
msgstr ""
"Project-Id-Version: Git\n"
"Language: zh_CN\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
```

**CRITICAL**: Do not edit the header's `msgstr` while translating. It holds
metadata only and must be left unchanged.


### Glossary Section

PO files may have a glossary in comments before the header entry (first
`msgid ""`), giving terminology guidelines (e.g.):

```po
# Git glossary for Chinese translators
#
# English | Chinese
# ---------------------------------+--------------------------------------
# 3-way merge | 三路合并
# ...
```

**IMPORTANT**: Read and use the glossary when translating or reviewing. It is
in `#` comments only. Leave that comment block unchanged.


### PO entry structure (single-line and multi-line)

PO entries are `msgid` / `msgstr` pairs. Plural messages add `msgid_plural` and
`msgstr[n]`. The `msgid` is the immutable source; `msgstr` is the target
translation. Each side may be a single quoted string or a multi-line block.
In the multi-line form the header line is often `msgid ""` / `msgstr ""`, with
the real text split across following quoted lines (concatenated by Gettext).

**Single-line entries**:

```po
msgid "commit message"
msgstr "提交说明"
```

**Multi-line entries**:

```po
msgid ""
"Line 1\n"
"Line 2"
msgstr ""
"行 1\n"
"行 2"
```

**CRITICAL**: Do **not** use `grep '^msgstr ""'` to find untranslated entries;
multi-line `msgstr` blocks use the same opening line, so grep gives false
positives. Use `msgattrib` (next section).


### Locating untranslated, fuzzy, and obsolete entries

Use `msgattrib` to list untranslated, fuzzy, and obsolete entries. Task 3
(translating `po/XX.po`) uses these commands.

- **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`


### 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).


### Placeholder Reordering

When reordering placeholders relative to `msgid`, use positional syntax (`%n$`)
where *n* is the 1-based argument index, so each argument still binds to the
right value. Preserve width and precision modifiers, and place `%n$` before
them (see examples below).

**Example 1** (placeholder reordering with precision):

```po
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
msgid ""
"Path updated: %s renamed to %s in %s, inside a directory that was renamed in "
"%s; moving it to %s."
msgstr ""
"路径已更新:%1$s 在 %3$s 中被重命名为 %2$s而其所在目录又在 %4$s 中被重命"
"名,因此将其移动到 %5$s。"
```

Original order 1,2,3,4,5; in translation 1,3,2,4,5. Each line must be a
complete quoted string.

**Example 3** (no placeholder reordering):

```po
msgid "MIDX %s must be an ancestor of %s"
msgstr "MIDX %s 必须是 %s 的祖先"
```

Argument order is still 1,2 in translation, so `%n$` is not needed.
If no placeholder reordering occurs, you **must not** introduce `%n$`
syntax; keep the original non-positional placeholders (`%s`, `%d`, etc.).


### Validating PO File Format

Check the PO file using the command below:

```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

On failure, `msgfmt` prints the line number; fix the PO at that line.


### Using git-po-helper

[git-po-helper](https://github.com/git-l10n/git-po-helper) supports Git l10n with
**quality checking** (git-l10n PR conventions) and **AI-assisted translation**
(subcommands for automated workflows). Housekeeping tasks in this document use
it when available; otherwise rely on gettext tools.


#### 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). Shortcuts:
`--head N` (first N), `--tail N` (last N), `--since N` (from N 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 150)
git-po-helper msg-select --range "-50" po/in.po -o po/out.po

# Entries 51100
git-po-helper msg-select --range "51-100" po/in.po -o po/out.po

# Entries 101 to end
git-po-helper msg-select --range "101-" po/in.po -o po/out.po

# Entries 150 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

`git-po-helper compare` shows PO changes with full entry context (unlike
`git diff`). Redirect output to a file: it is empty when there are no new or
changed entries; otherwise it contains a valid PO header.

```shell
# 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

# 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
```

**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 |


#### Concatenating multiple PO/JSON files

`git-po-helper msg-cat` merges PO, POT, or gettext JSON inputs into one stream.
Duplicate `msgid` values keep the first occurrence in file order. Write with
`-o <file>` or stdout (`-o -` or omit); `--json` selects JSON output, else PO.

```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.json
```


#### 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` | Encoded `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 (e.g. `\n` → `\\n`). |
| `msgstr` | []string | Translation forms as a **JSON array only**. Details below. |
| `msgid_plural` | string | Plural form of msgid. 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. |

**`msgstr` array (required shape)**:

- **Always** a JSON array of strings, never a single string. One element = singular
(PO `msgstr` / `msgstr[0]`); multiple elements = plural forms in order
(`msgstr[0]`, `msgstr[1]`, …).
- Omit the key or use an empty array when the entry is untranslated.

**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",
"msgid_plural": "%d files",
"msgstr": ["一个文件", "%d 个文件"],
"comments": ["#, c-format\\n"]
}
```

**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`, `msgid`,
`msgid_plural` unchanged.
- **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 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 exactly as in `msgid`. 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

For common housekeeping tasks, follow the steps in the matching subsection
below.


### Task 1: Generating or updating po/git.pot

When asked to generate or update `po/git.pot` (or the like):

1. **Directly execute** the command `make po/git.pot` without checking
if the file exists beforehand.

2. **Do not verify** the generated file after execution. Simply run the
command and consider the task complete.


### Task 2: Updating po/XX.po

When asked to update `po/XX.po` (or the like):

1. **Directly execute** the command `make po-update PO_FILE=po/XX.po`
without reading or checking the file content beforehand.

2. **Do not verify, translate, or review** the updated file after execution.
Simply run the command and consider the task complete.


### Task 3: Translating po/XX.po

To translate `po/XX.po`, use the steps below. The script uses gettext or
`git-po-helper` depending on what is installed; JSON export (when available)
supports batch translation rather than per-entry work.

**Workflow loop**: Steps 1→2→3→4→5→6→7 form a loop. After step 6 succeeds,
**always** go to step 7, which returns to step 1. The **only** exit to step 8
is when step 2 finds `po/l10n-pending.po` empty. Do not skip step 7 or jump to
step 8 after step 6.

1. **Extract entries to translate**: **Directly execute** the script below—it is
authoritative; do not reimplement. It generates `po/l10n-pending.po` with
messages that need translation.

```shell
l10n_extract_pending () {
test $# -ge 1 || { echo "Usage: l10n_extract_pending <po-file>" >&2; return 1; }
PO_FILE="$1"
PENDING="po/l10n-pending.po"
PENDING_FUZZY="${PENDING}.fuzzy"
PENDING_REFER="${PENDING}.fuzzy.reference"
PENDING_UNTRANS="${PENDING}.untranslated"
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_UNTRANS}"
msgattrib --only-fuzzy --no-obsolete --clear-fuzzy --empty "$PO_FILE" >"${PENDING_FUZZY}"
msgattrib --only-fuzzy --no-obsolete "$PO_FILE" >"${PENDING_REFER}"
msgcat --use-first "${PENDING_UNTRANS}" "${PENDING_FUZZY}" >"$PENDING"
rm -f "${PENDING_UNTRANS}" "${PENDING_FUZZY}"
fi
if test -s "$PENDING"
then
msgfmt --stat -o /dev/null "$PENDING" || true
echo "Pending file is not empty; there are still entries to translate."
else
echo "No entries need translation."
return 1
fi
}
# Run the extraction. Example: l10n_extract_pending po/zh_CN.po
l10n_extract_pending po/XX.po
```

2. **Check generated file**: If `po/l10n-pending.po` is empty or does not exist,
translation is complete; go to step 8. Otherwise proceed to step 3.

3. **Prepare one batch for translation**: Batching keeps each run small so the
model can complete translation within limited context. **BEFORE translating**,
**directly execute** the script below—it is authoritative; do not reimplement.
Based on which file the script produces: if `po/l10n-todo.json` exists, go to
step 4a; if `po/l10n-todo.po` exists, go to step 4b.

```shell
l10n_one_batch () {
test $# -ge 1 || { echo "Usage: l10n_one_batch <po-file> [min_batch_size]" >&2; return 1; }
PO_FILE="$1"
min_batch_size=${2:-100}
PENDING="po/l10n-pending.po"
TODO_JSON="po/l10n-todo.json"
TODO_PO="po/l10n-todo.po"
DONE_JSON="po/l10n-done.json"
DONE_PO="po/l10n-done.po"
rm -f "$TODO_JSON" "$TODO_PO" "$DONE_JSON" "$DONE_PO"

ENTRY_COUNT=$(grep -c '^msgid ' "$PENDING" 2>/dev/null || echo 0)
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 --head "$NUM" -o "$TODO_JSON" "$PENDING"
echo "Processing batch of $NUM entries (out of $ENTRY_COUNT remaining)"
else
git-po-helper msg-select --json -o "$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 >"$TODO_PO"
echo "Processing batch of $NUM entries (out of $ENTRY_COUNT remaining)"
else
cp "$PENDING" "$TODO_PO"
echo "Processing all $ENTRY_COUNT entries at once"
fi
fi
}
# Prepare one batch; shrink 2nd arg when batches exceed agent capacity.
l10n_one_batch po/XX.po 100
```

4a. **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`.

4b. **Translate PO batch** (`po/l10n-todo.po` → `po/l10n-done.po`):

- **Task**: Translate `po/l10n-todo.po` (input, GETTEXT PO) into
`po/l10n-done.po` (output, GETTEXT 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`.

5. **Validate `po/l10n-done.po`**:

Run the validation script below. If it fails, fix per the errors and notes,
re-run until 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`**. Re-run
`l10n_validate_done` until it succeeds. 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.


6. **Merge translation results into `po/XX.po`**: Run the script below. If it
fails, fix the file the error names: **`[JSON to PO conversion]`** →
`po/l10n-done.json`; **`[msgcat merge]`** → `po/l10n-done.po`. Re-run until
it succeeds.

```shell
l10n_merge_batch () {
test $# -ge 1 || { echo "Usage: l10n_merge_batch <po-file>" >&2; return 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"
PENDING_REFER="${PENDING}.fuzzy.reference"
TODO_JSON="po/l10n-todo.json"
TODO_PO="po/l10n-todo.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
return 1
}
mv "$MERGED" "$PO_FILE"
rm -f "$TODO_JSON" "$TODO_PO" "$DONE_JSON" "$DONE_PO" "$PENDING_REFER"
}
# Run the merge. Example: l10n_merge_batch po/zh_CN.po
l10n_merge_batch po/XX.po
```

7. **Loop**: **MUST** return to step 1 (Extract entries) and repeat the cycle.
Do **not** skip this step or go to step 8. Step 8 (below) runs **only**
when step 2 finds no more entries and redirects there.

8. **Only after loop exits**: Run the command below to validate the PO file and
display the report. The process ends here.

```shell
msgfmt --check --stat -o /dev/null po/XX.po
```


### Task 4: Review translation quality

Review may target the full `po/XX.po`, a specific commit, or changes since a
commit. When asked to review, follow the steps below.

**Workflow**: Follow steps in order. Do **NOT** use `git show`, `git diff`,
`git format-patch`, or similar to get changes—they break PO context; use **only**
`git-po-helper compare` for extraction. Without `git-po-helper`, refuse the task.
Steps 3→4→5→6→7 loop: after step 6, **always** go to step 7 (back to step 3).
The **only** ways to step 8 are when step 4 finds `po/review-todo.json` missing
or empty (no batch left to review), or when step 1 finds `po/review-result.json`
already present.

1. **Check for existing review (resume support)**: Evaluate the following in order:

- If `po/review-input.po` does **not** exist, proceed to step 2 (Extract
entries) for a fresh start.
- Else If `po/review-result.json` exists, go to step 8 (only after loop exits).
- Else If `po/review-done.json` exists, go to step 6 (Rename result).
- Else if `po/review-todo.json` exists, go to step 5 (Review the current
batch).
- Else go to step 3 (Prepare one batch).

2. **Extract entries**: Run `git-po-helper compare` with the desired range and
redirect the output to `po/review-input.po`. See "Comparing PO files for
translation and review" under git-po-helper for options.

3. **Prepare one batch**: Batching keeps each run small so the model can
complete review within limited context. **Directly execute** the script
below—it is authoritative; do not reimplement.

```shell
review_one_batch () {
min_batch_size=${1:-100}
INPUT_PO="po/review-input.po"
PENDING="po/review-pending.po"
TODO="po/review-todo.json"
DONE="po/review-done.json"
BATCH_FILE="po/review-batch.txt"

if test ! -f "$INPUT_PO"
then
rm -f "$TODO"
echo >&2 "cannot find $INPUT_PO, nothing for review"
return 1
fi
if test ! -f "$PENDING" || test "$INPUT_PO" -nt "$PENDING"
then
rm -f "$BATCH_FILE" "$TODO" "$DONE"
rm -f po/review-result*.json
cp "$INPUT_PO" "$PENDING"
fi

ENTRY_COUNT=$(grep -c '^msgid ' "$PENDING" 2>/dev/null || echo 0)
ENTRY_COUNT=$((ENTRY_COUNT > 0 ? ENTRY_COUNT - 1 : 0))
if test "$ENTRY_COUNT" -eq 0
then
rm -f "$TODO"
echo >&2 "No entries left for review"
return 1
fi

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
else
NUM=$ENTRY_COUNT
fi

BATCH=$(cat "$BATCH_FILE" 2>/dev/null || echo 0)
BATCH=$((BATCH + 1))
echo "$BATCH" >"$BATCH_FILE"

git-po-helper msg-select --json --head "$NUM" -o "$TODO" "$PENDING"
git-po-helper msg-select --since "$((NUM + 1))" -o "${PENDING}.tmp" "$PENDING"
mv "${PENDING}.tmp" "$PENDING"
echo "Processing batch $BATCH ($NUM entries out of $ENTRY_COUNT)"
}
# The parameter controls batch size; reduce if the batch file is too large.
review_one_batch 100
```

4. **Check todo file**: If `po/review-todo.json` does not exist or is empty,
review is complete; go to step 8 (only after loop exits). Otherwise proceed to
step 5.

5. **Review the current batch**: Review translations in `po/review-todo.json`
and write findings to `po/review-done.json` as follows:
- Use "Background knowledge for localization workflows" for PO/JSON structure,
placeholders, and terminology.
- If `header_comment` includes a glossary, follow it for consistency.
- Do **not** review the header (`header_comment`, `header_meta`).
- For every other entry, check the entry's `msgstr` **array** (translation
forms) against `msgid` / `msgid_plural` using the "Quality checklist" above.
- Write JSON per "Review result JSON format" below; use `{"issues": []}` when
there are no issues. **Always** write `po/review-done.json`—it marks the
batch complete.

6. **Rename result**: Rename `po/review-done.json` to `po/review-result-<N>.json`,
where N is the value in `po/review-batch.txt` (the batch just completed).
Run the script below:

```shell
review_rename_result () {
TODO="po/review-todo.json"
DONE="po/review-done.json"
BATCH_FILE="po/review-batch.txt"
if test -f "$DONE"
then
N=$(cat "$BATCH_FILE" 2>/dev/null) || { echo "ERROR: $BATCH_FILE not found." >&2; return 1; }
mv "$DONE" "po/review-result-$N.json"
echo "Renamed to po/review-result-$N.json"
fi
rm -f "$TODO"
}
review_rename_result
```

7. **Loop**: **MUST** return to step 3 (Prepare one batch) and repeat the cycle.
Do **not** skip this step or go to step 8. Step 8 is reached **only** when
step 4 finds `po/review-todo.json` missing or empty.

8. **Only after loop exits**: **Directly execute** the command below. It merges
results, applies suggestions, and displays the report. The process ends here.

```shell
git-po-helper agent-run review --report po
```

**Do not** run cleanup or delete intermediate files. Keep them for inspection
or resumption.

**Review result JSON format**:

The **Review result JSON** format defines the structure for translation
review reports. For each entry with translation issues, create an issue
object as follows:

- Copy the original entry's `msgid`, optional `msgid_plural`, and optional
`msgstr` array (original translation forms) into the issue object. Use the
same shape as GETTEXT JSON: `msgstr` is **always a JSON array** when present
(one element singular, multiple for plural).
- Write a summary of all issues found for this entry in `description`.
- Set `score` according to the severity of issues found for this entry,
from 0 to 3 (0 = critical; 1 = major; 2 = minor; 3 = perfect, no issues).
**Lower score means more severe issues.**
- Place the suggested translation in **`suggest_msgstr`** as a **JSON array**:
one string for singular, multiple strings for plural forms in order. This is
required for `git-po-helper` to apply suggestions.
- Include only entries with issues (score less than 3). When no issues are
found in the batch, write `{"issues": []}`.

Example review result (with issues):

```json
{
"issues": [
{
"msgid": "commit",
"msgstr": ["委托"],
"score": 0,
"description": "Terminology error: 'commit' should be translated as '提交'",
"suggest_msgstr": ["提交"]
},
{
"msgid": "repository",
"msgid_plural": "repositories",
"msgstr": ["版本库", "版本库"],
"score": 2,
"description": "Consistency issue: suggest using '仓库' consistently",
"suggest_msgstr": ["仓库", "仓库"]
}
]
}
```

Field descriptions for each issue object (element of the `issues` array):

- `msgid` (and optional `msgid_plural` for plural entries): Original source text.
- `msgstr` (optional): JSON array of original translation forms (same meaning as
in GETTEXT JSON entries).
- `suggest_msgstr`: JSON array of suggested translation forms; **must be an
array** (e.g. `["提交"]` for singular). Plural entries use multiple elements
in order.
- `score`: 03 (0 = critical; 1 = major; 2 = minor; 3 = perfect, no issues).
- `description`: Brief summary of the issue.


## Human translators remain in control

Git translation is human-driven; language team leaders and contributors are
responsible for maintaining translation quality and consistency.

AI-generated output should always be treated as drafts that must be reviewed
and approved by someone who understands both the technical context and the
target language. The best results come from combining AI efficiency with human
judgment, cultural insight, and community engagement.

View File

@ -159,38 +159,6 @@ It will:
and these location lines will help translation tools to locate
translation context easily.

Once you are done testing the translation (see below), it's better
to commit a location-less "po/XX.po" file to save repository space
and make a user-friendly patch for review.

To save a location-less "po/XX.po" automatically in repository, you
can:

First define a new attribute for "po/XX.po" by appending the following
line in ".git/info/attributes":

```
/po/XX.po filter=gettext-no-location
```

Then define the driver for the "gettext-no-location" clean filter to
strip out both filenames and locations from the contents as follows:

```shell
git config --global filter.gettext-no-location.clean \
"msgcat --no-location -"
```

For users who have gettext version 0.20 or higher, it is also possible
to define a clean filter to preserve filenames but not locations:

```shell
git config --global filter.gettext-no-location.clean \
"msgcat --add-location=file -"
```

You're now ready to ask the l10n coordinator to pull from you.


## Fuzzy translation

@ -229,6 +197,45 @@ git-po-helper check-commits <rev-list-opts>
```


## Preparing a "XX.po" file for commit

Once you are done testing the translation, it's better to commit a
location-less "po/XX.po" file to save repository space and make a
user-friendly patch for review.

To save a location-less "po/XX.po" automatically in the repository,
follow these steps:

First, check which filter is configured for your "po/XX.po" file:

```
git check-attr filter po/XX.po
```

The filter configuration is defined in the "po/.gitattributes" file.

Then define the driver for the filter. Most languages use the
"gettext-no-location" clean filter, which strips out both filenames and line
numbers from location comments. To set this up, run the following command:

```shell
git config --global filter.gettext-no-location.clean \
"msgcat --no-location -"
```

Some PO files use the "gettext-no-line-number" clean filter, which keeps
filenames but strips line numbers. This filter requires gettext 0.20 or
later. The only benefit is being able to locate source files from location
comments when the .po file is not updated from the POT via `make po-update`.

```shell
git config --global filter.gettext-no-line-number.clean \
"msgcat --add-location=file -"
```

You're now ready to ask the l10n coordinator to pull from you.


## Marking strings for translation

(This is done by the core developers).
@ -392,15 +399,30 @@ Git's tests are run under `LANG=C LC_ALL=C`. So the tests do not need be
changed to account for translations as they're added.


## AI-assisted translation and review

[po/AGENTS.md](AGENTS.md) describes optional workflows for AI coding assistants
that help with Git localization: updating templates and PO files, translating
`po/XX.po`, and reviewing translations. Those workflows often use git-po-helper
together with the gettext tools; see the PO helper section below for what the
program does and how to build or install it. AI assistants are optional; treat
their output as a draft and have it reviewed by contributors who know Git and
the target language well.

When you prompt a coding assistant, mention that file explicitly, for example:
"Translate po/XX.po with reference to po/AGENTS.md" (replace XX with your
language code).


## PO helper

To make the maintenance of "XX.po" easier, the l10n coordinator and l10n
team leaders can use a helper program named "git-po-helper". It is a
wrapper to gettext suite, specifically written for the purpose of Git
l10n workflow.
`git-po-helper` is a helper for Git l10n coordinators and contributors. It
automates checks that contributions follow project conventions (PO syntax,
commit messages, which paths may change, and related rules) and can work with
AI coding agents for tasks such as translating new entries, and reviewing
translations.

To build and install the helper program from source, see
[git-po-helper/README][].
Build and install instructions are in [git-po-helper/README][].


## Conventions

1628
po/bg.po

File diff suppressed because it is too large Load Diff

4065
po/ca.po

File diff suppressed because it is too large Load Diff

View File

@ -391,8 +391,8 @@ msgstr ""
#, c-format, perl-format
msgid "Apply mode change to index and worktree [y,n,q,a,d%s,?]? "
msgstr ""
"¿Aplicar cambio de modo para el índice y el árbol de trabajo [y,n,q,a,"
"d%s,?]? "
"¿Aplicar cambio de modo para el índice y el árbol de trabajo "
"[y,n,q,a,d%s,?]? "

#, c-format, perl-format
msgid "Apply deletion to index and worktree [y,n,q,a,d%s,?]? "
@ -2294,9 +2294,9 @@ msgid ""
"=<term>] [--no-checkout] [--first-parent] [<bad> [<good>...]] [--] "
"[<paths>...]"
msgstr ""
"git bisect--helper --bisect-start [--term-{new,bad}=<término> --term-{old,"
"good}=<término>] [--no-checkout] [--first-parent] [<malo> [<bueno>...]] [--] "
"[<rutas>...]"
"git bisect--helper --bisect-start [--term-{new,bad}=<término> --term-"
"{old,good}=<término>] [--no-checkout] [--first-parent] [<malo> [<bueno>...]] "
"[--] [<rutas>...]"

msgid "git bisect--helper --bisect-state (bad|new) [<rev>]"
msgstr "git bisect--helper --bisect-state (bad|new) [<rev>]"
@ -2983,11 +2983,11 @@ msgid "HEAD not found below refs/heads!"
msgstr "¡HEAD no encontrado dentro de refs/heads!"

msgid ""
"branch with --recurse-submodules can only be used if submodule."
"propagateBranches is enabled"
"branch with --recurse-submodules can only be used if "
"submodule.propagateBranches is enabled"
msgstr ""
"branch con --recurse-submodules solo se puede usar si submodule."
"propagateBranches está habilitado"
"branch con --recurse-submodules solo se puede usar si "
"submodule.propagateBranches está habilitado"

msgid "--recurse-submodules can only be used to create branches"
msgstr "--recurse-submodules solo se puede usar para crear ramas"
@ -5983,11 +5983,11 @@ msgid "protocol does not support --negotiate-only, exiting"
msgstr "el protocolo no soporta --negotiate-only, saliendo"

msgid ""
"--filter can only be used with the remote configured in extensions."
"partialclone"
"--filter can only be used with the remote configured in "
"extensions.partialclone"
msgstr ""
"--filter solo puede ser usado con el remoto configurado en extensions."
"partialclone"
"--filter solo puede ser usado con el remoto configurado en "
"extensions.partialclone"

msgid "--atomic can only be used when fetching from one remote"
msgstr "--atomic solo se puede usar cuando se busca desde un control remoto"
@ -8914,8 +8914,8 @@ msgstr "objeto esperado en el desplazamiento %<PRIuMAX> en el paquete %s"

msgid "disabling bitmap writing, packs are split due to pack.packSizeLimit"
msgstr ""
"deshabilitando escritura bitmap, paquetes son divididos debido a pack."
"packSizeLimit"
"deshabilitando escritura bitmap, paquetes son divididos debido a "
"pack.packSizeLimit"

msgid "Writing objects"
msgstr "Escribiendo objetos"
@ -9489,8 +9489,8 @@ msgstr ""
msgid ""
"\n"
"To avoid automatically configuring upstream branches when their name\n"
"doesn't match the local branch, see option 'simple' of branch."
"autoSetupMerge\n"
"doesn't match the local branch, see option 'simple' of "
"branch.autoSetupMerge\n"
"in 'git help config'.\n"
msgstr ""
"\n"

1246
po/fr.po

File diff suppressed because it is too large Load Diff

1453
po/ga.po

File diff suppressed because it is too large Load Diff

View File

@ -369,8 +369,8 @@ msgstr ""
#, c-format
msgid "Discard mode change from index and worktree [y,n,q,a,d%s,?]? "
msgstr ""
"Отменить изменения режима доступа в индексе и рабочем каталоге [y,n,q,a,"
"d%s,?]? "
"Отменить изменения режима доступа в индексе и рабочем каталоге "
"[y,n,q,a,d%s,?]? "

#, c-format
msgid "Discard deletion from index and worktree [y,n,q,a,d%s,?]? "
@ -400,8 +400,8 @@ msgstr ""
#, c-format
msgid "Apply mode change to index and worktree [y,n,q,a,d%s,?]? "
msgstr ""
"Применить изменения режима доступа к индексу и рабочему каталогу [y,n,q,a,"
"d%s,?]? "
"Применить изменения режима доступа к индексу и рабочему каталогу "
"[y,n,q,a,d%s,?]? "

#, c-format
msgid "Apply deletion to index and worktree [y,n,q,a,d%s,?]? "
@ -2966,8 +2966,8 @@ msgid "HEAD not found below refs/heads!"
msgstr "HEAD не найден в refs/heads!"

msgid ""
"branch with --recurse-submodules can only be used if submodule."
"propagateBranches is enabled"
"branch with --recurse-submodules can only be used if "
"submodule.propagateBranches is enabled"
msgstr ""

msgid "--recurse-submodules can only be used to create branches"
@ -3997,8 +3997,8 @@ msgid ""
"clean.requireForce defaults to true and neither -i, -n, nor -f given; "
"refusing to clean"
msgstr ""
"clean.requireForce установлен по умолчанию как true и ни одна из опций -i, -"
"n или -f не указана; отказ очистки"
"clean.requireForce установлен по умолчанию как true и ни одна из опций -i, "
"-n или -f не указана; отказ очистки"

msgid "-x and -X cannot be used together"
msgstr "нельзя использовать одновременно -x и -X"
@ -5890,8 +5890,8 @@ msgid "protocol does not support --negotiate-only, exiting"
msgstr ""

msgid ""
"--filter can only be used with the remote configured in extensions."
"partialclone"
"--filter can only be used with the remote configured in "
"extensions.partialclone"
msgstr ""

msgid "--atomic can only be used when fetching from one remote"
@ -8385,8 +8385,8 @@ msgstr "Каталог %s в индексе и не является подмо

msgid "Please stage your changes to .gitmodules or stash them to proceed"
msgstr ""
"Чтобы продолжить, проиндексируйте или спрячьте ваши изменения в файле ."
"gitmodules"
"Чтобы продолжить, проиндексируйте или спрячьте ваши изменения в "
"файле .gitmodules"

#, c-format
msgid "%.*s is in index"
@ -16134,8 +16134,8 @@ msgid ""
msgstr ""
"Перехватчик «%s» был проигнорирован, так как он не установлен как "
"исполняемый.\n"
"Вы можете отключить это предупреждение с помощью команды «git config advice."
"ignoredHook false»."
"Вы можете отключить это предупреждение с помощью команды «git config "
"advice.ignoredHook false»."

#, c-format
msgid "argument to --packfile must be a valid hash (got '%s')"

1345
po/sv.po

File diff suppressed because it is too large Load Diff

1362
po/tr.po

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff