This tutorial covers connecting action-translation to a project where the target repository already exists and contains translations — but was translated manually or with a different tool, before action-translation was in place.
By the end, you’ll have:
.translate/metadata bootstrapped from the current stateHeading-maps generated for all translated files
Automated sync and review workflows configured
Time: ~30 minutes (includes sync verification) Cost: Free for status/heading-maps. ~0.16/file if resync needed.
When to use this tutorial¶
Use this when you have:
An English source repo (e.g.,
QuantEcon/lecture-python-intro)A Chinese target repo (e.g.,
QuantEcon/lecture-python-intro.zh-cn)The target already has translated
.mdfiles in a docs folderThe repos are not yet connected via action-translation (no
.translate/folder, no sync workflow)
If you’re starting from scratch with no translations, see Tutorial: Fresh Setup instead.
Overview¶
Step 1: Assess the current state (translate status)
Step 2: Check sync before bootstrapping (translate status --check-sync)
Step 3: Catch up stale translations (translate forward, if needed)
Step 4: Bootstrap .translate/ metadata (translate status --write-state)
Step 5: Fix missing heading-maps (translate headingmap)
Step 6: Configure workflows and secrets (manual GitHub setup)
Step 7: Verify the connection (translate doctor)Step 1: Assess the current state¶
Before making changes, understand what you’re working with. The status command gives a free, instant diagnostic:
npx translate status \
-s ~/repos/lecture-python-intro \
-t ~/repos/lecture-python-intro.zh-cnExample output:
Sync Status: lecture-python-intro ↔ lecture-python-intro.zh-cn (zh-cn)
File Status
──────────────────────────────── ────────────────────
intro.md ✅ ALIGNED
cobweb.md 📋 MISSING_HEADINGMAP
solow.md 📋 MISSING_HEADINGMAP
cagan_adaptive.md ⏳ OUTDATED
pv.md ⚠️ SOURCE_AHEAD
new_lecture.md ➕ SOURCE_ONLY
old_removed.md 🔸 TARGET_ONLYWhat to look for:
| Status | What it means | Action needed |
|---|---|---|
ALIGNED | Sections match and file is up to date | None |
MISSING_HEADINGMAP | No heading-map in target frontmatter | Add heading-map (Step 3) |
OUTDATED | Source has newer commits | Resync later (see Resync Tutorial) |
SOURCE_AHEAD | Source has more sections than target | Resync later |
SOURCE_ONLY | File exists only in source | Translate new file later |
TARGET_ONLY | File exists only in target | Investigate (orphaned?) |
At this stage, you’re gathering information. Pay attention to OUTDATED and SOURCE_AHEAD statuses — these indicate the translations may have fallen behind the source. We’ll address those in Step 2 before bootstrapping.
For a more comprehensive assessment including workflow and state file verification, run:
npx translate doctor \
-t ~/repos/lecture-python-intro.zh-cnThis reports configuration health across all dimensions. All items should be ✅ by the end of Step 5.
Step 2: Check sync before bootstrapping¶
Run the sync check to compare source and target content:
npx translate status \
-s ~/repos/lecture-python-intro \
-t ~/repos/lecture-python-intro.zh-cn \
--check-syncThis uses a lightweight LLM comparison to classify each file:
| Result | Meaning | Action |
|---|---|---|
IDENTICAL | Translation matches current source | Safe to bootstrap |
I18N_ONLY | Differences are localization-only (fonts, RTL, etc.) | Safe to bootstrap |
CONTENT_CHANGES | Source has content changes not in translation | Resync first (Step 3) |
TARGET_HAS_ADDITIONS | Target has extra content not in source | Run backward to capture, then resync |
If all files show IDENTICAL or I18N_ONLY, skip to Step 4. If any show CONTENT_CHANGES, proceed to Step 3. If any show TARGET_HAS_ADDITIONS, consider running translate backward first to capture improvements before resyncing.
Step 3: Catch up stale translations¶
For files flagged as CONTENT_STALE, use the forward command to bring them up to date. The forward command preserves existing localization (RTL adaptations, font configuration, # i18n code, locale-specific links) while updating the translation to match the current source content.
# Dry run first — see what would change
npx translate forward \
-s ~/repos/lecture-python-intro \
-t ~/repos/lecture-python-intro.zh-cn \
--dry-run
# Resync all stale files
npx translate forward \
-s ~/repos/lecture-python-intro \
-t ~/repos/lecture-python-intro.zh-cnFor a single file:
npx translate forward \
-s ~/repos/lecture-python-intro \
-t ~/repos/lecture-python-intro.zh-cn \
-f solow.mdReview and commit the changes:
cd ~/repos/lecture-python-intro.zh-cn
git diff lectures/ # Review all changes
git add lectures/
git commit -m "Resync translations to current source via forward"
git pushStep 4: Bootstrap .translate/ metadata¶
Now that translations are current, bootstrap the state tracking. The .translate/ folder records which source commit each translation is synced to:
npx translate status \
-s ~/repos/lecture-python-intro \
-t ~/repos/lecture-python-intro.zh-cn \
-l zh-cn \
--write-stateThis creates:
.translate/
├── config.yml # Project settings
└── state/
├── intro.md.yml # Per-file metadata
├── cobweb.md.yml
└── solow.md.ymlEach state file records:
source-sha: abc1234f # Current HEAD SHA for the source file
synced-at: "2026-03-16" # Target file's last commit date
model: unknown # Not recoverable from history
mode: RESYNC # Marked as RESYNC (not a fresh translation)
section-count: 5 # Current source section countCommit the metadata:
cd ~/repos/lecture-python-intro.zh-cn
git add .translate/
git commit -m "Bootstrap .translate/ metadata via action-translation"
git push origin mainStep 5: Fix missing heading-maps¶
Heading-maps are essential for reliable section matching. If Step 1 showed MISSING_HEADINGMAP for some files, you need to add them.
Option A: Use headingmap to generate maps (recommended)¶
The headingmap command generates heading-maps by matching source and target headings by position — no LLM calls needed, and no changes to translations:
# Generate heading-map for a single file
npx translate headingmap \
-s ~/repos/lecture-python-intro \
-t ~/repos/lecture-python-intro.zh-cn \
-f cobweb.mdThis:
Parses both files to extract section headings
Matches them by position (1st source → 1st target, etc.)
Injects the
heading-mapinto the target file’s YAML frontmatterMakes no changes to the translation content
For all files missing heading-maps:
npx translate headingmap \
-s ~/repos/lecture-python-intro \
-t ~/repos/lecture-python-intro.zh-cnPreview first with --dry-run:
npx translate headingmap \
-s ~/repos/lecture-python-intro \
-t ~/repos/lecture-python-intro.zh-cn \
--dry-runOption B: Use forward to regenerate (also adds heading-maps)¶
If you already ran forward in Step 3, heading-maps were generated as part of that process. Check if any files still need them:
npx translate headingmap \
-s ~/repos/lecture-python-intro \
-t ~/repos/lecture-python-intro.zh-cn \
--dry-runThe diff should show the heading-map added to the frontmatter. The translation content should be largely preserved (the RESYNC mode is designed to maintain existing style).
For multiple files:
# Resync all files that need attention
npx translate forward \
-s ~/repos/lecture-python-intro \
-t ~/repos/lecture-python-intro.zh-cnOption C: Add heading-maps manually¶
If you don’t want to re-translate files (e.g., they’ve been carefully reviewed by human translators), you can add heading-maps by hand.
For each translated file:
Parse the source file to find the English section headings and their IDs
Find the corresponding translated headings in the target file
Add the heading-map to the target file’s YAML frontmatter
Example:
English source (cobweb.md):
## Overview
## The Cobweb Model
### Equilibrium
## ExercisesChinese target (cobweb.md):
## 概述
## 蛛网模型
### 均衡
## 练习Add this to the target’s frontmatter:
---
title: 蛛网模型
translation:
title: 蛛网模型
headings:
Overview: "概述"
The Cobweb Model: "蛛网模型"
The Cobweb Model::Equilibrium: "均衡"
Exercises: "练习"
---Heading keys use the full heading text with :: nesting for subsections. See Heading Maps for complete format rules.
Commit the heading-maps¶
cd ~/repos/lecture-python-intro.zh-cn
git add lectures/
git commit -m "Add heading-maps to translated files"
git push origin mainStep 6: Configure workflows and secrets¶
Now set up the automated pipeline. You need a workflow in the source repo and one in the target repo.
6a: Source repo — Sync workflow¶
Create .github/workflows/sync-translations.yml in the source repository:
name: Sync Translations
on:
pull_request:
types: [closed]
paths:
- 'lectures/**/*.md'
- 'lectures/_toc.yml'
issue_comment:
types: [created]
jobs:
sync:
if: >
(github.event_name == 'pull_request' && github.event.pull_request.merged == true) ||
(github.event_name == 'issue_comment' && contains(github.event.comment.body, '\translate-resync'))
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
with:
fetch-depth: 2
- uses: QuantEcon/action-translation@v0
with:
mode: sync
target-repo: 'QuantEcon/lecture-python-intro.zh-cn'
source-language: 'en'
target-language: 'zh-cn'
docs-folder: 'lectures'
anthropic-api-key: ${{ secrets.ANTHROPIC_API_KEY }}
github-token: ${{ secrets.TRANSLATION_PAT }}6b: Source repo — Secrets¶
| Secret | Value | Purpose |
|---|---|---|
ANTHROPIC_API_KEY | Your Anthropic API key | Pays for Claude translations |
TRANSLATION_PAT | GitHub PAT with repo scope | Creates PRs in the target repo |
6c: Target repo — Review workflow¶
Create .github/workflows/review-translations.yml in the target repository:
name: Review Translations
on:
pull_request:
types: [opened, synchronize, labeled, reopened]
jobs:
review:
# Ignore `labeled` events for every other label: a sync applies its labels one call at a
# time, and each one would otherwise start a full (billed) review of the same diff.
if: >
contains(github.event.pull_request.labels.*.name, 'action-translation') &&
(github.event.action != 'labeled' || github.event.label.name == 'action-translation')
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: write
# One review per PR — supersede an in-flight review instead of running both
concurrency:
group: review-translations-${{ github.event.pull_request.number }}
cancel-in-progress: true
steps:
- uses: actions/checkout@v6
with:
fetch-depth: 2
- uses: QuantEcon/action-translation@v0
with:
mode: review
source-repo: 'QuantEcon/lecture-python-intro'
source-language: 'en'
target-language: 'zh-cn'
docs-folder: 'lectures'
anthropic-api-key: ${{ secrets.ANTHROPIC_API_KEY }}
github-token: ${{ secrets.GITHUB_TOKEN }}6d: Target repo — Secret¶
| Secret | Value |
|---|---|
ANTHROPIC_API_KEY | Your Anthropic API key |
6e: Merge order¶
Merge the target repo changes first (.translate/, heading-maps, review workflow), then the source repo sync workflow. This ensures the target is ready to receive translation PRs when the sync workflow activates.
Step 7: Verify the connection¶
Run status again to confirm everything is properly connected:
npx translate status \
-s ~/repos/lecture-python-intro \
-t ~/repos/lecture-python-intro.zh-cnWhat “ready” looks like:
All files show
✅ ALIGNEDor⏳ OUTDATED(OUTDATED is OK — it just means the source has newer changes)No
MISSING_HEADINGMAPwarnings.translate/config.ymlexists in the target repo.translate/state/*.ymlfiles exist for each translated file
Run a final health check:
npx translate doctor \
-t ~/repos/lecture-python-intro.zh-cn \
-s ~/repos/lecture-python-introAll checks should pass ✅. If some files show OUTDATED or SOURCE_AHEAD, address them with forward before considering the connection complete — otherwise those files will be silently out of date.
Common issues when connecting¶
Many files show MISSING_HEADINGMAP¶
This is expected for repos translated before action-translation. Use headingmap to fix without changing translations:
npx translate headingmap \
-s ~/repos/lecture-python-intro \
-t ~/repos/lecture-python-intro.zh-cnOr use forward if you also want to update the translations:
npx translate forward \
-s ~/repos/lecture-python-intro \
-t ~/repos/lecture-python-intro.zh-cnReview changes, commit, and push.
Section counts don’t match (SOURCE_AHEAD / TARGET_AHEAD)¶
This means the source and target have different numbers of ## sections. Common causes:
A section was added to the source but not translated yet → address with
forwardThe target was manually restructured → may need manual alignment
Subsections (
###) inside a section don’t affect the count — only##headings matter
Files exist in only one repo¶
SOURCE_ONLY— new file that hasn’t been translated yet. Useforward -f <file>or wait for the sync action to handle it when the file is changed via a PR.TARGET_ONLY— a file that was deleted from source. Consider deleting it from target too.
Next steps¶
Tutorial: Resync a Drifted Target — catch up on outdated files
Tutorial: Backward Analysis & Review — find improvements in translations worth backporting
Tutorial: Automated Maintenance — set up scheduled checks
CLI Reference — full command documentation
Heading Maps — format rules and ID generation