Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

Tutorial: Connect an Existing Target

Add action-translation to a repo that was already translated

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:

Time: ~30 minutes (includes sync verification) Cost: Free for status/heading-maps. ~0.03/fileforsynccheck. 0.03/file for sync check. ~0.16/file if resync needed.

When to use this tutorial

Use this when you have:

It also fits a partial edition: one that has translated some lectures, may hold English copies of others, and takes the rest one reviewed PR at a time, as in One reviewed lecture at a time. Notes in Steps 2, 4, 5, 6 and 7, and under Common issues, cover what differs for it.

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

Example 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_ONLY

What to look for:

StatusWhat it meansAction needed
ALIGNEDSections match and file is up to dateNone
MISSING_HEADINGMAPNo heading-map in target frontmatterAdd heading-map (Step 3)
OUTDATEDSource has newer commitsResync later (see Resync Tutorial)
SOURCE_AHEADSource has more sections than targetResync later
SOURCE_ONLYFile exists only in sourceTranslate new file later
TARGET_ONLYFile exists only in targetInvestigate (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-cn

This 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-sync

This uses a lightweight LLM comparison to classify each file:

ResultMeaningAction
IDENTICALTranslation matches current sourceSafe to bootstrap
I18N_ONLYDifferences are localization-only (fonts, RTL, etc.)Safe to bootstrap
CONTENT_CHANGESSource has content changes not in translationResync first (Step 3)
TARGET_HAS_ADDITIONSTarget has extra content not in sourceRun 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-cn

For a single file:

npx translate forward \
  -s ~/repos/lecture-python-intro \
  -t ~/repos/lecture-python-intro.zh-cn \
  -f solow.md

Review 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 push

Step 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-state

This creates:

.translate/
├── config.yml              # Project settings
└── state/
    ├── intro.md.yml        # Per-file metadata
    ├── cobweb.md.yml
    └── solow.md.yml

Each 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 count

Commit the metadata:

cd ~/repos/lecture-python-intro.zh-cn
git add .translate/
git commit -m "Bootstrap .translate/ metadata via action-translation"
git push origin main

Step 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.

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.md

This:

  1. Parses both files to extract section headings

  2. Matches them by position (1st source → 1st target, etc.)

  3. Injects the heading-map into the target file’s YAML frontmatter

  4. Makes 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-cn

Preview first with --dry-run:

npx translate headingmap \
  -s ~/repos/lecture-python-intro \
  -t ~/repos/lecture-python-intro.zh-cn \
  --dry-run

Option 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-run

The 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-cn

Option 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:

  1. Parse the source file to find the English section headings and their IDs

  2. Find the corresponding translated headings in the target file

  3. Add the heading-map to the target file’s YAML frontmatter

Example:

English source (cobweb.md):

## Overview
## The Cobweb Model
### Equilibrium
## Exercises

Chinese 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 main

Step 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]
    branches: [main]
    paths:
      - 'lectures/**/*.md'
      - 'lectures/_toc.yml'
  issue_comment:
    types: [created]

jobs:
  sync:
    # The issue_comment path requires all three: a comment on a PR (not a bare
    # issue), the command, and a trusted author — otherwise any account could
    # fire a secrets-bearing run (Anthropic spend plus the PAT) from any comment.
    if: >
      (github.event_name == 'pull_request' && github.event.pull_request.merged == true) ||
      (github.event_name == 'issue_comment' &&
       github.event.issue.pull_request &&
       startsWith(github.event.comment.body, '\translate-resync') &&
       contains(fromJSON('["OWNER", "MEMBER", "COLLABORATOR"]'), github.event.comment.author_association))
    runs-on: ubuntu-latest

    # The action authenticates with TRANSLATION_PAT; the ambient GITHUB_TOKEN
    # is unused beyond checkout, so keep it read-only.
    permissions:
      contents: read

    steps:
      - uses: actions/checkout@v7
        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 }}

On a partial edition, add sync-scope: existing (a temporary input; see Partial editions) to the action’s inputs. A sync then updates only the lectures that have a state file on the edition’s default branch, and never translates a lecture the edition has not taken:

- uses: QuantEcon/action-translation@v0
  with:
    mode: sync
    # ... the inputs above
    sync-scope: existing

Switch only once every lecture the edition has translated has its state file on the default branch (Step 4). Under existing, a lecture without one is not synced, so an edition with no state files would sync no lecture at all.

Sync keeps _toc.yml a mirror of the source’s under existing too, so the first sync of a source PR that changes it replaces the edition’s table of contents with one that lists lectures the edition does not have yet. Before this workflow merges, make the edition’s build prune it (see Prune the table of contents at build time).

6b: Source repo — Secrets

SecretValuePurpose
ANTHROPIC_API_KEYYour Anthropic API keyPays for Claude translations
TRANSLATION_PATGitHub PAT with repo scopeCreates 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@v7
        with:
          fetch-depth: 2

      - uses: QuantEcon/action-translation@v0
        with:
          mode: review
          source-repo: 'QuantEcon/lecture-python-intro'
          source-language: 'en'
          docs-folder: 'lectures'
          anthropic-api-key: ${{ secrets.ANTHROPIC_API_KEY }}
          github-token: ${{ secrets.GITHUB_TOKEN }}

There is no target-language input here: review mode detects the language from the repository-name suffix (lecture-python-intro.zh-cn → zh-cn). A repo without a language suffix logs a warning and reviews without a glossary.

6d: Target repo — Secret

SecretValue
ANTHROPIC_API_KEYYour 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-cn

What “ready” looks like:

Run a final health check:

npx translate doctor \
  -t ~/repos/lecture-python-intro.zh-cn \
  -s ~/repos/lecture-python-intro

All 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.

On a partial edition, some of this reads differently:


Common issues when connecting

Many files show MISSING_HEADINGMAP

This is expected for repos translated before action-translation.

On a partial edition, the English copies that have sections show MISSING_HEADINGMAP too, and should keep it. Run either command below with -f for each translated lecture, or with --exclude for the copies: without either, forward rewrites each copy the source has moved on from and gives it a state file, which puts it in sync’s scope (see the note in Step 5).

Use headingmap to fix without changing translations:

npx translate headingmap \
  -s ~/repos/lecture-python-intro \
  -t ~/repos/lecture-python-intro.zh-cn

Or use forward if you also want to update the translations:

npx translate forward \
  -s ~/repos/lecture-python-intro \
  -t ~/repos/lecture-python-intro.zh-cn

Review 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:

On a partial edition, an English copy can show SOURCE_AHEAD too; leave it (see Step 7).

Files exist in only one repo

Next steps