Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 39 additions & 11 deletions .claude/skills/verify-l10n-pr/SKILL.md
Original file line number Diff line number Diff line change
@@ -1,30 +1,58 @@
---
name: verify-l10n-pr
description: Use when reviewing a PR that adds or modifies a `_language-<code>.yml` localization file in `src/resources/language/`. Runs structural checks that need no translation tool, plus an optional MT spot-check when a translation MCP is connected.
description: Use when reviewing or landing a PR that adds or modifies a `_language-<code>.yml` localization file in `src/resources/language/`. Checks the file against current main, reviews the contributor's values, fills keys added since the PR was opened with reviewed machine translation, and finishes the PR.
---

# Verify a Localization PR

Most quarto-cli maintainers cannot read most of the 30+ languages `src/resources/language/*.yml` covers. This skill separates what's checkable without any language knowledge (structural correctness) from what benefits from a machine-translation second opinion (semantic plausibility) — and treats the second part as optional, since not every reviewer has a translation tool connected.
Most quarto-cli maintainers cannot read most of the 30+ languages `src/resources/language/*.yml` covers. This skill separates what is checkable without any language knowledge (structure, parity with main) from what needs a translation second opinion (the meaning of each value), and keeps the contributor's human translations as the authority: machine translation is a reviewer for their values and a filler only for keys they never saw.

## Step 1 — Structural checks (always run, no tool dependency)
Before judging any value, read `src/resources/language/README.md` and `src/resources/language/AGENTS.md`, and the translator note above each key in `_language.yml`. Those notes are the rules contributors were asked to follow.

## Step 1: Duplicates and open questions

List every open PR touching the same file:

```bash
gh pr list --repo quarto-dev/quarto-cli --state open --search "_language-<code>" --json number,title,author,createdAt
```

Several PRs for one language happen (Arabic had two, a month apart, both unreviewed). Compare them key by key and keep one: prefer the one with a native-speaker review or an engaged author, carry over any value from the other that settles an open question, and close the other with thanks and a pointer. Read the whole thread of the one you keep, inline review comments included, and note earlier maintainer questions and suggestion blocks that never got an answer.

Step 1 is done when one PR is chosen and every open question and unapplied suggestion on it is listed.

## Step 2: Structure against current main

Language PRs often sit for months while keys are added to `_language.yml`. Check against main, not the PR's base:

```bash
git fetch upstream main
git show upstream/main:src/resources/language/_language.yml > /tmp/_language-main.yml
uv run .claude/skills/verify-l10n-pr/scripts/structural_check.py \
src/resources/language/_language.yml \
/tmp/_language-main.yml \
src/resources/language/_language-<code>.yml
```

Catches: missing/extra keys vs. the base English file, `{0}`/`{1}`-style placeholder mismatches, empty values where the base has content, raw trailing whitespace on the line (what `git diff --check` flags), and a heuristic check for quarto's accessibility rule that `navigation-*-label` values must not contain the language's word for "navigation" (screen readers already announce the landmark role — see the rule comment around `src/resources/language/_language.yml:87-96`). That last check is language-agnostic: it compares each label value's words against the same file's `toggle-navigation` value, which is expected to contain the word. A shared stem is a likely violation worth a look, not a certain one — always confirm before commenting on the PR.
Catches: missing/extra keys, `{0}`/`{1}` placeholder mismatches, empty values where the base has content, trailing whitespace, and a heuristic for the rule that `navigation-*-label` values must not contain the language's word for "navigation" (it compares each label's words against the same file's `toggle-navigation` value; a shared stem is a lead to confirm, not a verdict). On Windows, set `PYTHONIOENCODING=utf-8`, or non-Latin output raises `UnicodeEncodeError`.

`tests/unit/language-parity.test.ts` is the gate CI applies: it fails on any missing key not listed for that language in `tests/unit/language-known-gaps.yml`. Every key Step 2 reports missing ends up either translated (Step 4) or in that list before merge.

## Step 3: Review the contributor's values

Needs a context-aware translation MCP (e.g. `mcp__lara-translate__translate`). If none is connected, say so in the review and rely on Steps 1-2 and native-speaker review; plain free MT fails on this content (`references/why-not-libretranslate.md`).

On Windows, run via PowerShell or Git Bash with `PYTHONIOENCODING=utf-8` set — `uv run` scripts printing non-Latin script (Cyrillic, Arabic, CJK, etc.) hit `UnicodeEncodeError` on Windows' default console codepage otherwise.
Read `references/mt-spot-check.md` before running it: it covers the `context` parameter, how to tell a meaning difference from translator variation, and the `navigation-*-label` false positive. Back-translate any value a reviewer or Copilot questioned too, and settle terminology questions with evidence the reader can check (how other language files handle the same pair of keys, an encyclopedia article in that language), not with MT alone.

## Step 2 — MT spot-check (optional, only if a translation MCP is connected)
Step 3 is done when every value is either accepted or listed with a concrete question.

Check whether `mcp__lara-translate__translate` (or an equivalent translation MCP) is available in this session. If not, **say so plainly in the review and stop here** — the structural checks above still stand on their own; don't attempt MT verification with tools known to be unreliable for this (see `references/why-not-libretranslate.md`).
## Step 4: Fill missing keys (when maintainers finish the PR)

If Lara (or similar) is available, read `references/mt-spot-check.md` before running it — it covers the `context` parameter, how to read the results, and a real false-positive this technique produces (`navigation-*-label` again) that you must not miss.
Only for keys the contributor never had the chance to translate, typically ones added to `_language.yml` after the PR was opened. Follow `references/fill-missing-keys.md`: translation with each key's translator note as context, two blind reviewers, and only values both accept get committed.

## Step 3 — Writing up findings
## Step 5: Finish the PR

If this produces PR review comments, use the `gh-issue-style` skill for voice before drafting. Don't reference this skill, its scripts, or any MCP/tool name in the public comment — describe findings by what they are (a missing placeholder, a possible accessibility-rule conflict), not how you found them.
- Maintainers push the fixup to the contributor branch (maintainer edits are usually allowed): merge main, resolve `README.md` conflicts by taking main's table and adding the language row, add the Step 4 values in `_language.yml` key order and any known-gaps entries, and add the changelog entry to the `news/changelog-<version>.md` matching `QUARTO_VERSION` in `configuration`, crediting the author (`(author: @login)`). Run `unit/language-parity.test.ts` before pushing.
- A native speaker's suggestion from the review goes into that fixup, with a `Co-authored-by:` trailer for the reviewer and a reply in its thread.
- The PR comment lists every machine-translated value in a table, with a plain statement that they are AI-generated and how they were checked. Write it in the maintainer's voice (`gh-issue-style` skill if available) and describe findings by what they are, without naming this skill, its scripts, or any MCP.
- Questions to a silent author do not block the merge. Answer what evidence can settle, keep the contributor's value otherwise, and invite a follow-up PR for anything else.
- A community review of an existing file gets a short approval; the maintainer moves its changelog entry to the current version file if needed.
42 changes: 42 additions & 0 deletions .claude/skills/verify-l10n-pr/references/fill-missing-keys.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# Fill Missing Keys with Reviewed Machine Translation

The procedure used for the per-key translation PRs of #14822, applied to one language. Goal: commit only values two independent reviewers accept; everything else stays a known gap for a native speaker.

## 1. Gather context per key

For each missing key, collect:

- The translator note above it in `_language.yml` (sense of ambiguous words, verb or noun, singular or plural, visible text or screen-reader only, rules such as "no word for navigation").
- Related strings from the target file that set vocabulary and grammatical form: for buttons, the file's `search-clear-button-title`, `search-detached-cancel-button-title`, `tools-share`; for links, `back-to-top`, `repo-action-links-edit`; for landmarks, `toggle-navigation`, `crossref-sec-prefix`, `toc-title-website`; for code labels, `code-summary`.

Keys sharing one note (the five `navigation-*-label`, the two `scrollable-*-label`) go in one batch with one context.

## 2. Translate

One `mcp__lara-translate__translate` call per batch, `source: en-US`, `target: <code>`, `no_trace: true`, and a `context` written from step 1 that names the use, the rule, and two or three of the file's related strings with their English. Per-key context, not blanket `instructions`: a blanket instruction over-applies (it turned "Slide menu" into "Open the slide menu" in some languages).

The Lara free tier is 10,000 characters a month; one language's missing keys are a few hundred.

## 3. Review blind

Write one self-contained prompt: every key with its English value, use and rule; the file's related strings; the translated values as a table. Ask for exactly one line per key:

```
K# OK|FLAG <confidence high|medium|low> <if FLAG: short reason and one better candidate>
```

Tell the reviewer to flag a wrong meaning for the use, a broken rule, a grammatical form inconsistent with the file's existing strings (imperative verb vs verbal noun is the common one), and keys built inconsistently with their siblings; and to leave acceptable synonyms alone.

Run it through two reviewers that cannot see each other's output, for example `codex exec -s read-only --skip-git-repo-check -o verdict.txt - < prompt.md` and a fresh subagent told to read only the prompt file.

## 4. Apply the confidence rule

- Both reviewers OK → commit the translated value.
- Both FLAG with the same candidate → commit the candidate.
- Anything else → the value stays out; add the (key, language) pair to `tests/unit/language-known-gaps.yml` and list the candidates and the reason in the PR comment, asking a native speaker to pick.

A second MT engine agreeing is not a confidence signal: Google Translate repeated Lara's errors in the #14822 spike.

## 5. Record

Insert committed values in `_language.yml` key order. The PR comment gets a table of every value added this way and states they are AI-generated, translated with the translator notes and checked by two independent AI reviewers. Ask any native speaker already on the thread to look at them.
4 changes: 2 additions & 2 deletions .claude/skills/verify-l10n-pr/references/mt-spot-check.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,9 @@ Only flag something when the MT output and the contributor's value diverge in **

## The false positive you will hit: `navigation-*-label` keys

Quarto's accessibility convention (`src/resources/language/_language.yml:87-96`) requires the five `navigation-*-label` values (site/section/toolbar/page/breadcrumbs landmark labels) to **avoid** the word for "navigation" in that language — screen readers already announce the landmark role, so repeating it is redundant. A plain MT engine doesn't know this rule. Translating "Breadcrumbs" forward without context will often produce something like "navigation path," because that's a perfectly reasonable generic translation — and it will look like a mismatch against a contributor who correctly wrote something like "page path" instead to follow the project's rule.
Quarto's accessibility convention (the translator note above the `navigation-*-label` keys in `src/resources/language/_language.yml`) requires the five `navigation-*-label` values (site/section/toolbar/page/breadcrumbs landmark labels) to **avoid** the word for "navigation" in that language — screen readers already announce the landmark role, so repeating it is redundant. A plain MT engine doesn't know this rule. Translating "Breadcrumbs" forward without context will often produce something like "navigation path," because that's a perfectly reasonable generic translation — and it will look like a mismatch against a contributor who correctly wrote something like "page path" instead to follow the project's rule.

**Do not flag a `navigation-*-label` mismatch as a translation error without first checking whether the contributor's value avoids the word for "navigation" and the MT suggestion doesn't.** If that's the pattern, the contributor is right and the MT suggestion is the one to discard. The structural check's navigation-landmark heuristic (Step 1) is the more reliable signal for this specific rule — use the MT spot-check to corroborate general meaning, not to police this particular convention.
**Do not flag a `navigation-*-label` mismatch as a translation error without first checking whether the contributor's value avoids the word for "navigation" and the MT suggestion doesn't.** If that's the pattern, the contributor is right and the MT suggestion is the one to discard. The structural check's navigation-landmark heuristic (Step 2) is the more reliable signal for this specific rule — use the MT spot-check to corroborate general meaning, not to police this particular convention.

## Terminology ambiguity vs. genuine error

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@ Tested against quarto-cli PR #14878 (Azerbaijani localization). Self-hosted Libr

Root cause is structural, not a configuration mistake: Argos's models are trained mostly on full-sentence parallel corpora and have no way to disambiguate a bare single word with no surrounding context, and low-resource language pairs (fewer than a widely-spoken pair like French or Spanish) make it worse. Longer, sentence-length strings translated reasonably; most quarto UI-label keys are exactly the short, context-free shape this fails on.

If no context-aware/LLM-assisted translation MCP is available in a session, don't fall back to a plain free MT API expecting it to work on this content — it demonstrably doesn't, for this specific and common shape of string. Structural checks (Step 1 in `SKILL.md`) still catch real issues without needing any translation quality tool at all; rely on those and skip the MT step rather than trust a plain MT engine's short-string output.
If no context-aware/LLM-assisted translation MCP is available in a session, don't fall back to a plain free MT API expecting it to work on this content — it demonstrably doesn't, for this specific and common shape of string. Structural checks (Step 2 in `SKILL.md`) still catch real issues without needing any translation quality tool at all; rely on those and skip the MT step rather than trust a plain MT engine's short-string output.
Loading