diff --git a/.github/workflows/translate-ja-jp.yml b/.github/workflows/translate-ja-jp.yml new file mode 100644 index 000000000..ca60e39cd --- /dev/null +++ b/.github/workflows/translate-ja-jp.yml @@ -0,0 +1,183 @@ +name: Translate ja-JP + +on: + repository_dispatch: + types: + - translate-ja-jp-release + schedule: + - cron: "47 3 * * *" + workflow_dispatch: + +permissions: + contents: write + +concurrency: + group: translate-ja-jp + cancel-in-progress: false + +jobs: + translate-ja-jp: + runs-on: ubuntu-latest + steps: + - name: Checkout publish repo + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Read source metadata + id: meta + run: | + node - <<'NODE' + const fs = require("node:fs"); + const path = ".openclaw-sync/source.json"; + const data = JSON.parse(fs.readFileSync(path, "utf8")); + if (!data.repository || !data.sha) { + throw new Error(`invalid source metadata in ${path}`); + } + fs.appendFileSync(process.env.GITHUB_OUTPUT, `repository=${data.repository}\n`); + fs.appendFileSync(process.env.GITHUB_OUTPUT, `sha=${data.sha}\n`); + NODE + + - name: Checkout source repo + uses: actions/checkout@v4 + with: + repository: ${{ steps.meta.outputs.repository }} + ref: ${{ steps.meta.outputs.sha }} + path: source + fetch-depth: 1 + + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: 22 + + - name: Setup Go + uses: actions/setup-go@v5 + with: + go-version: "1.23" + + - name: Prune stale ja-JP pages + run: | + python - <<'PY' + from pathlib import Path + root = Path("docs") + locale_root = root / "ja-JP" + if not locale_root.exists(): + raise SystemExit(0) + for path in sorted(locale_root.rglob("*"), reverse=True): + if path.is_dir(): + if not any(path.iterdir()): + path.rmdir() + continue + rel = path.relative_to(locale_root) + source = root / rel + if not source.exists(): + path.unlink() + for path in sorted(locale_root.rglob("*"), reverse=True): + if path.is_dir() and not any(path.iterdir()): + path.rmdir() + PY + + - name: Build pending docs file list + id: pending + run: | + python - <<'PY' + import hashlib + import os + import re + from pathlib import Path + + source_hash_re = re.compile(r'^x-i18n:\n(?: .*\n)*? source_hash: ([0-9a-f]{64})$', re.M) + + def stored_source_hash(path: Path) -> str: + if not path.exists(): + return "" + text = path.read_text(encoding="utf-8", errors="ignore") + match = source_hash_re.search(text) + if not match: + return "" + return match.group(1).strip() + + all_files = [] + pending_files = [] + for path in Path("docs").rglob("*"): + if not path.is_file(): + continue + if path.suffix.lower() not in {".md", ".mdx"}: + continue + rel = path.as_posix() + if rel.startswith("docs/zh-CN/"): + continue + if rel.startswith("docs/ja-JP/"): + continue + if rel.startswith("docs/.generated/"): + continue + all_files.append(str(path.resolve())) + + rel_doc = path.relative_to("docs") + locale_path = Path("docs") / "ja-JP" / rel_doc + source_hash = hashlib.sha256(path.read_bytes()).hexdigest() + if stored_source_hash(locale_path) != source_hash: + pending_files.append(str(path.resolve())) + + Path(".openclaw-sync").mkdir(exist_ok=True) + Path(".openclaw-sync/docs-i18n-ja-files.txt").write_text("\n".join(pending_files) + ("\n" if pending_files else "")) + print(f"all_docs={len(all_files)} pending_docs={len(pending_files)}") + with open(os.environ["GITHUB_OUTPUT"], "a", encoding="utf-8") as fh: + fh.write(f"all_count={len(all_files)}\n") + fh.write(f"pending_count={len(pending_files)}\n") + PY + + - name: Translate changed docs into ja-JP + if: steps.pending.outputs.pending_count != '0' + env: + OPENAI_API_KEY: ${{ secrets.OPENCLAW_DOCS_I18N_OPENAI_API_KEY }} + OPENCLAW_DOCS_I18N_PROVIDER: openai + OPENCLAW_DOCS_I18N_MODEL: gpt-5.4 + OPENCLAW_DOCS_I18N_PROMPT_TIMEOUT: 10m + run: | + if [ ! -s .openclaw-sync/docs-i18n-ja-files.txt ]; then + echo "No docs files found." + exit 0 + fi + + mapfile -t DOC_FILES < .openclaw-sync/docs-i18n-ja-files.txt + attempt=1 + max_attempts=5 + while [ "$attempt" -le "$max_attempts" ]; do + echo "docs-i18n attempt $attempt/$max_attempts" + if ( + cd source/scripts/docs-i18n + go run . \ + --docs "$GITHUB_WORKSPACE/docs" \ + --lang ja-JP \ + --src en \ + --mode doc \ + --thinking low \ + --parallel 8 \ + "${DOC_FILES[@]}" + ); then + exit 0 + fi + + if [ "$attempt" -eq "$max_attempts" ]; then + echo "docs-i18n failed after $max_attempts attempts" + exit 1 + fi + + attempt=$((attempt + 1)) + sleep 5 + done + + - name: Commit ja-JP refresh + run: | + if git diff --quiet -- docs/ja-JP docs/.i18n/ja-JP.tm.jsonl; then + echo "No ja-JP translation changes." + exit 0 + fi + + git config user.name "openclaw-docs-i18n[bot]" + git config user.email "openclaw-docs-i18n[bot]@users.noreply.github.com" + git add docs/ja-JP docs/.i18n/ja-JP.tm.jsonl + git commit -m "chore(i18n): refresh ja-JP translations" + git push origin HEAD:main diff --git a/README.md b/README.md index 1376105c6..476fae1a0 100644 --- a/README.md +++ b/README.md @@ -8,27 +8,28 @@ Source of truth lives in [`openclaw/openclaw`](https://github.com/openclaw/openc 1. English docs are authored in `openclaw/openclaw`. 2. `openclaw/openclaw/.github/workflows/docs-sync-publish.yml` mirrors the docs tree into this repo. -3. This repo stores the published docs tree plus generated zh-CN output. +3. This repo stores the published docs tree plus generated locale output. 4. `openclaw/docs/.github/workflows/translate-zh-cn.yml` runs once a day, on manual dispatch, and after release dispatches from `openclaw/openclaw` to refresh `docs/zh-CN/**`. +5. `openclaw/docs/.github/workflows/translate-ja-jp.yml` does the same for `docs/ja-JP/**`. ## Translation behavior -- zh-CN pages are generated output. +- zh-CN and ja-JP pages are generated output. - Each translated page stores `x-i18n.source_hash`. - The translate workflow computes a pending file list before calling the model. - If no English source hashes changed, the workflow skips the expensive translation step entirely. - If files changed, only the pending files are translated. - The workflow retries transient model-format failures. -- Published releases in `openclaw/openclaw` dispatch an extra zh-CN refresh so release-adjacent docs updates do not wait for the daily cron. +- Published releases in `openclaw/openclaw` dispatch extra zh-CN and ja-JP refreshes so release-adjacent docs updates do not wait for the daily cron. ## Editing rules - Do not treat this repo as the primary place for English doc edits. - Make English doc changes in `openclaw/openclaw`, then let sync copy them here. -- zh-CN pages in `docs/zh-CN/**` are generated output. +- Generated locale pages in `docs/zh-CN/**` and `docs/ja-JP/**` are generated output. - `.openclaw-sync/source.json` records which `openclaw/openclaw` commit this mirror was synced from. ## Secrets - `OPENCLAW_DOCS_SYNC_TOKEN` lives in `openclaw/openclaw` and lets the source repo push into this repo. -- `OPENCLAW_DOCS_I18N_OPENAI_API_KEY` lives in this repo and powers zh-CN translation refreshes. +- `OPENCLAW_DOCS_I18N_OPENAI_API_KEY` lives in this repo and powers locale translation refreshes. diff --git a/docs/.i18n/ja-JP.tm.jsonl b/docs/.i18n/ja-JP.tm.jsonl index e69de29bb..8b1378917 100644 --- a/docs/.i18n/ja-JP.tm.jsonl +++ b/docs/.i18n/ja-JP.tm.jsonl @@ -0,0 +1 @@ +