A methods document with a reference implementation for teams that publish open source on GitHub while also running a bilingual (Japanese/English) community website. Publish one GitHub Release, and WordPress automatically creates a paired Japanese and English article — this guide records how the pipeline is built, the release-note writing convention at its core, and the pitfalls we hit.
日本語版 README はこちら / This is the record of a setup built and operated on www.rumicar.com since August 2026. It shares its philosophy with the sibling project AutoPost-Guide (publish an article → front page → Facebook, all automatic); this one automates the release → bilingual articles leg.
How to read this document: it records one setup that works as of August 2026 — not a guarantee. The reference implementation is the actual file running on rumicar.com, site-specific values included. It is material to read and rewrite for your own site, not something to copy and run.
We publish open-source software (a simulator, libraries) on GitHub. Every release came with the same manual chore: writing an announcement article for the website — twice, in Japanese and in English — even though the release notes already existed on GitHub. Fabricating the missing language with machine translation is not acceptable for technical writing.
So the requirements were:
- After publishing a GitHub Release, zero human steps until the JA/EN pair of articles exists on the site.
- The machine must never fabricate a translation — when no translation exists, degrade honestly instead.
- Be polite to the external API (GitHub), at the request rate a small site should hold itself to.
Publish a GitHub Release (bilingual notes — see the convention below)
│
▼ wp-cron (twice daily) checks the Releases API (1 request per repo)
│
├─ new release found
│ ├─ split the body by language (mechanical <details> split)
│ ├─ convert to HTML via GitHub's /markdown API (same markup GitHub renders)
│ ├─ create the JA and EN posts, pair them via Polylang
│ └─ translation present → both published /
│ absent → EN published, JA saved as draft
│
└─ record as processed (skipped next time)
The heart of this system is not code; it is an agreement about how release
notes are written. Write the notes in English, and fold the Japanese
translation into a <details> block per section:
## What's New
- Added lap-time overlay to the simulator view.
<details><summary>日本語</summary>
- シミュレータ画面にラップタイム表示を追加しました。
</details>
## Bug Fixes
- Fixed camera jitter on corner entry.
<details><summary>日本語</summary>
- コーナー進入時にカメラが揺れる問題を修正しました。
</details>Why this format earns its keep:
- It reads well on GitHub as-is. English readers see English; Japanese readers open the fold. No tooling involved on the reading side.
- The mechanical split is trivial. English version = full text with the
<details>blocks removed. Japanese version = headings + the contents of each<details>. A regular expression plus a heading-wise scan — no translation engine anywhere. - Forgetting everything fails safe. A release with no Japanese
<details>at all produces a Japanese article containing the English text — as a draft, for a human to translate and publish. That is the "never fabricate a translation" policy, implemented. Note the limit: if only some sections lack their<details>, those parts remain English inside a Japanese article that gets published — the convention includes "if you translate, translate every section".
The split condition is the exact string <details><summary>日本語</summary>
(the reference implementation tolerates whitespace variation only). Variants
like <summary> 日本語訳</summary> are treated as English. Also, one
<details> per section is the rule — a second one in the same section is
silently dropped from the Japanese version. Keep the convention narrow and
rigid — that is what makes it dependable.
- Rate: twice daily, one request per repository (six requests per day for our three repos) — roughly 1/240th of GitHub's unauthenticated limit of 60 requests/hour (= 1,440/day). On a day a release is processed, add two /markdown calls per release; the margin barely moves.
- Failures stay quiet: an API error changes nothing and simply waits for the next run.
- First-run valve: enabling the plugin does not backfill articles for historical releases; the first run only records the existing releases (the newest five the API returns) as processed. The valve works per repository, so adding a repository to the watch list later also starts with a record-only pass for that repository.
- State is one option: records of processed releases (
repo@tagkeys), per-repository initialization markers (init:keys) and skip records, all in a single option. Delete arepo@tagentry to make the next cron run reprocess that release (an existing article with the same slug is reused — title, body and status rewritten — rather than duplicated).
Cron runs in the default-language context, so get_category_by_slug('news-en')
comes back empty — the English category is filtered away, and posts land in
the default category. The fix is get_terms( ..., 'lang' => '' ) to bypass
the language filter (see the comment in rumicar_relnotes_insert()).
In a cron context, wp_insert_post passes content through kses, which
mangles the HTML GitHub produces (<details>, <summary>, …). The reference
implementation therefore writes the post body directly via $wpdb. This
is acceptable only because the HTML originates from our own releases in our
own repositories, rendered by GitHub's /markdown API. Never reuse this
technique for untrusted sources (other people's repositories, form input);
in those cases keep kses, or sanitize the output yourself.
"Failures retry next time" turns into an infinite retry when the condition is permanent. An empty-body release was exactly that: it would have been reprocessed every 12 hours, forever — a latent path found in code review, not an incident we observed. Permanent conditions must be recorded as skipped, not failed. (Fixed. When you write retry logic, classify each failure as transient or permanent first.)
Polylang functions were called unguarded, so deactivating Polylang made the
cron run fatal. Guarded with function_exists() — the pairing is skipped,
the articles still get created. Code called from cron must survive the
morning when a dependency is switched off.
Each check fetches the five most recent releases per repository. Shipping more than five releases in twelve hours would drop some — and would also mean this polling design is the wrong tool; switch to webhooks at that pace.
What was verified at installation (2026-08-07) is article generation via dry-run (forced-draft mode): the language split, category resolution and Polylang pairing were exercised there, and pitfall #1 was found in the process. What has not happened yet is a real release flowing through the full pipeline in production — no new release has been published since installation (the first-run valve only recorded the existing ones). The next release will be the first live pass. The pre-publication fixes (pitfalls #3, #4 and others) also postdate the dry-run verification, so they too have not seen a live release yet. By design, published articles then ride the sibling pipeline described in AutoPost-Guide (front-page What's New, Facebook post).
reference/rumicar-release-notes.php —
the mu-plugin running on rumicar.com (current version including the
pre-publication fixes; one file, about 340 lines), site-specific values
included. What to substitute:
| Where | rumicar.com value | Yours |
|---|---|---|
rumicar_relnotes_repos() |
three RumiCar-group repos | repos to watch |
| category slugs | news / news-en |
your target categories |
| language pair | ja / en | match your Polylang setup |
RUMICAR_RELNOTES_AUTOPUBLISH |
true | start with false (drafts only) |
Polylang is assumed. With WPML and friends the shape — create two posts, pair them — is the same; translate the function calls.
- This document records one setup working as of August 2026 and guarantees nothing. GitHub, WordPress and Polylang change.
- The reference implementation is not meant to be executed as-is. Read pitfall #2 before adapting it to your site.
MIT. Quote, republish, adapt freely. The license covers this document and the reference implementation code; it grants no rights over the release notes or articles the pipeline processes.