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: Adding a New Language

Extend action-translation to support an additional target language

This tutorial walks through adding support for a new target language — for example, adding Japanese (ja) to a project that already has Chinese (zh-cn). By the end, you’ll have:

Time: ~1 hour for configuration, plus translation time (~$0.16/lecture)

Overview

Adding a new language involves four areas:

Step 1: Create a glossary                   (glossary/ja.json)
Step 2: Add language-specific rules         (language-config.ts)
Step 3: Set up the target repository        (translate setup + init)
Step 4: Configure multi-language workflows  (.github/workflows/)

Steps 1–2 are one-time project contributions (merged into action-translation). Steps 3–4 are per-project setup.


Step 1: Create a glossary

The glossary ensures consistent translation of technical terms. Each glossary is a JSON file at glossary/{code}.json.

Start from an existing glossary

Copy an existing glossary as a template:

cd /path/to/action-translation
cp glossary/zh-cn.json glossary/ja.json

Edit the glossary

Update the file with Japanese translations. The format:

{
  "version": "1.0",
  "description": "Translation glossary for QuantEcon lectures (English to Japanese)",
  "terms": [
    {
      "en": "utility function",
      "ja": "効用関数",
      "context": "economics"
    },
    {
      "en": "Bellman equation",
      "ja": "ベルマン方程式",
      "context": "dynamic programming"
    },
    {
      "en": "steady state",
      "ja": "定常状態",
      "context": "macroeconomics"
    }
  ]
}

Each term has:

FieldDescription
enEnglish source term
{lang-code}Translation in the target language (must match the language code)
contextDomain hint for disambiguation (e.g., “economics”, “statistics”, “programming”)

Quality guidelines

The existing glossaries have 357 terms organized across:

You don’t need all 357 terms from day one. Start with the most important terms and expand over time.


Step 2: Add language-specific rules

Language rules control how Claude handles language-specific formatting in translations. They’re defined in src/language-config.ts.

What rules do

Rules are injected into every translation prompt. They tell Claude about punctuation conventions, typography, and style expectations specific to the target language. Without rules, Claude uses its general knowledge (which usually works but may be inconsistent).

Current configurations

LanguageKey rules
zh-cn (Chinese)Use full-width punctuation: (not ASCII)
fa (Farsi)Use Persian punctuation: ، ؛ ؟; keep technical terms in English; formal/academic style

Add a new language entry

Edit src/language-config.ts and add an entry to the LANGUAGE_CONFIGS object:

ja: {
  code: 'ja',
  name: 'Japanese',
  additionalRules: [
    'Use Japanese punctuation: 、(読点) for commas, 。(句点) for periods.',
    'Use full-width parentheses()and brackets【】for Japanese text.',
    'Keep mathematical notation and code in half-width ASCII characters.',
    'Use です/ます (polite) form for explanatory text, である form for formal definitions.',
    'Translate technical terms using standard Japanese academic conventions.',
  ],
},

Localization rules (optional)

If the language uses a non-Latin script and needs special fonts for matplotlib figures (like Chinese does), you may need to add font configuration to src/localization-rules.ts. Currently:

For Japanese, you would add a font entry if matplotlib’s default fonts don’t render Japanese characters correctly.

Build and test

After editing the source:

npm run build       # Compile TypeScript + bundle the action (updates dist-action/)
npm test            # Run all tests

Contribute back

If this is a generally useful language, contribute the glossary and config back:

git checkout -b add-japanese-language
git add glossary/ja.json src/language-config.ts
git commit -m "Add Japanese language support with glossary"
# Open a PR to QuantEcon/action-translation

Step 3: Set up the target repository

Now create the target repository and translate the content. This follows the same process as Tutorial: Fresh Setup, adapted for the new language.

Scaffold the target repo

npx translate setup \
  --source QuantEcon/lecture-python-intro \
  --target-language ja

This creates QuantEcon/lecture-python-intro.ja on GitHub with the scaffolding files.

Translate the content

npx translate init \
  -s ~/repos/lecture-python-intro \
  -t ./lecture-python-intro.ja \
  --target-language ja \
  --dry-run   # Preview first

npx translate init \
  -s ~/repos/lecture-python-intro \
  -t ./lecture-python-intro.ja \
  --target-language ja

Push the translated content

cd lecture-python-intro.ja
git add .
git commit -m "Initial Japanese translation via translate init"
git push origin main

Step 4: Configure multi-language workflows

If your source repo already syncs to one language, add the new language as a parallel job.

Source repo — Multi-language sync workflow

Update .github/workflows/sync-translations.yml in the source repository:

name: Sync Translations

on:
  pull_request:
    types: [closed]
    paths:
      - 'lectures/**/*.md'
      - '_toc.yml'
  issue_comment:
    types: [created]

jobs:
  sync-to-chinese:
    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@v4
        with:
          fetch-depth: 2
      - uses: QuantEcon/action-translation@v0
        with:
          mode: sync
          target-repo: 'QuantEcon/lecture-python-intro.zh-cn'
          target-language: 'zh-cn'
          docs-folder: 'lectures'
          anthropic-api-key: ${{ secrets.ANTHROPIC_API_KEY }}
          github-token: ${{ secrets.TRANSLATION_PAT }}

  sync-to-japanese:
    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@v4
        with:
          fetch-depth: 2
      - uses: QuantEcon/action-translation@v0
        with:
          mode: sync
          target-repo: 'QuantEcon/lecture-python-intro.ja'
          target-language: 'ja'
          docs-folder: 'lectures'
          anthropic-api-key: ${{ secrets.ANTHROPIC_API_KEY }}
          github-token: ${{ secrets.TRANSLATION_PAT }}

The jobs run in parallel — each language is translated independently.

Target repo — Review workflow

Create .github/workflows/review-translations.yml in the new target repository:

name: Review Translations

on:
  pull_request:
    types: [opened, synchronize]

jobs:
  review:
    if: contains(github.event.pull_request.labels.*.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@v4
        with:
          fetch-depth: 2
      - uses: QuantEcon/action-translation@v0
        with:
          mode: review
          source-repo: 'QuantEcon/lecture-python-intro'
          source-language: 'en'
          target-language: 'ja'
          docs-folder: 'lectures'
          anthropic-api-key: ${{ secrets.ANTHROPIC_API_KEY }}
          github-token: ${{ secrets.GITHUB_TOKEN }}

Secrets

Add these secrets to the relevant repos:

RepoSecretPurpose
Source repoANTHROPIC_API_KEYClaude translations
Source repoTRANSLATION_PATCross-repo PR creation (needs repo scope for all target repos)
New target repoANTHROPIC_API_KEYTranslation reviews

The TRANSLATION_PAT in the source repo needs access to the new target repo. If you’re using a fine-grained PAT, update its repository scope.


Step 5: Verify the setup

Check sync status

npx translate status \
  -s ~/repos/lecture-python-intro \
  -t ~/repos/lecture-python-intro.ja \
  -l ja

All files should show ✅ ALIGNED.

Health check

For a comprehensive verification that covers configuration, state, workflows, and heading-maps:

npx translate doctor \
  -t ~/repos/lecture-python-intro.ja

Any ❌ items should be addressed before moving to production.

Test the pipeline

  1. Make a small edit in the source repo

  2. Open and merge a PR

  3. Check both target repos — you should see translation PRs in both .zh-cn and .ja repos


Working without a glossary or language config

Both the glossary and language config are optional. The system works with any language code — you just get fewer guardrails:

ComponentWithout itImpact
GlossaryClaude uses its own terminology choicesLess consistent technical terms
Language configNo language-specific prompt rulesClaude uses general knowledge (usually fine)

You can add both later and run forward to bring existing translations into alignment with the new glossary.


Maintaining multiple language targets

With multiple target languages, the CLI tools work the same way — just specify the language:

# Status for Japanese target
npx translate status -s ~/source -t ~/target-ja -l ja

# Backward analysis for Japanese
npx translate backward -s ~/source -t ~/target-ja -l ja

# Forward resync for Japanese
npx translate forward -s ~/source -t ~/target-ja -l ja

Each target repository maintains its own .translate/state/ independently.


Checklist

Next steps