Stub Generation for p5.js Translations
I am a developer from Nepal who loves playing with tech.
Have you ever translated something, only to find out later that the original text changed while you were working? Or worse — that a whole new page existed and nobody told you?
For that, I built stub file generation. I'm writing this blog today to explain more.
A little context 🤷♀️
This summer, I'm working with the Processing Foundation as a Google Summer of Code contributor. My project is called Continued Development of Translation Tracker and my mentor is Divyansh Srivastava, who built the original tracker during GSoC 2025.
Quick background: the p5.js reference documentation lives in five languages — English as the source, plus Spanish (es), Hindi (hi), Korean (ko), and Simplified Chinese (zh-Hans). All of it is maintained by human volunteers.
Divyansh's tracker from last year already solves one problem: when an English page changes, it opens a GitHub issue telling translators what went stale.
But there was a second problem nobody was handling: what about pages that were never translated at all? When a new function is added in p5.js, a new English content page appears and... that's it. No Spanish file. No Hindi file. Simple, no idea and no signal. Translators had to notice missing files by comparing directory trees manually.
So for the last couple of weeks, I built stub file generation. Here's the PR if you want to jump straight to code: #1473
What is a stub? (important part)
A stub is scaffolding, not a translation.
I want to be super clear here because the p5.js community cares deeply about this: the tool does not translate anything. A machine can't decide how "friendly" should sound in Korean, and it shouldn't try. Humans translate. The tool just removes the boring setup work.
When the tracker finds an English reference page with no counterpart in a language folder, it generates a placeholder MDX file that contains:
The essential English frontmatter only —
title,module,submodule,file,description. Not the full API dump, just enough to orient a translator.A
needsTranslation: trueflag, so nothing ever mistakes it for finished workA short placeholder body + an HTML comment linking back to the English source file
The stub lands in the exact mirror location — example/en/foo.mdx becomes example/es/foo.mdx and so on.
How it works
The flow looks like this:
Scan the English reference pages (triggered by a push that touches
src/content/**/en/**, or manually)Find missing translations —
findMissingTranslations()checks each language folder for a matching fileGenerate stubs —
generateStubFromEnglish()builds the placeholder from the English source (MDX only for now,.yamlfiles are skipped — more on that below 😅)Open one PR per language
Why one PR per language instead of one giant PR? Because the Spanish stewards should review the Spanish batch, and the Hindi stewards should review theirs. A single (or should I say a mega) PR touching four languages at once is hard to review.
And nothing auto-merges... like ever. Maintainers and language stewards review every stub PR. The job of my script is to propose; humans decide.
Note: There's also a cap —
STUB_MAX_FILESlimits stubs to 50 per language per run (per language, not global!). Without this, a full scan of the historical backlog would flood the repo with hundreds of files in one go.
The workflow file
The stub generation runs as its own GitHub Action: .github/workflows/translation-stubs.yml. It runs with GENERATE_STUBS=true, completely separate from the issue-tracking workflow (translation-sync.yml). The two modes are mutually exclusive — when stub mode is on, the issue logic doesn't run at all. We can simply say: different jobs, different outcomes, no interference.
You can also trigger it manually with workflow_dispatch, which supports two inputs: full_scan (scan everything, not just the latest changes) and languages (target specific languages only).
For local development, I added a dry-run command:
npm run test:stubs
This previews everything into a gitignored stub-preview/ folder . It's best if we want to test locally and don't touch src/content/ files locally. I used this frequently to test.
Refactoring the tracker
While building this, the tracker's single index.js was getting way too big. After discussing with Divyansh, I split it into focused modules:
| File | What it does |
|---|---|
index.js |
Entry point and orchestration |
constants.js |
Supported languages, content types, stub frontmatter keys |
utils.js |
Path helpers, frontmatter parsing, file scanning |
github-tracker.js |
GitHub API client (issues, diffs, stub PR creation) |
workflows.js |
Translation status checks and stub generation logic |
Much easier to navigate now, and future features (auto-close, dashboard) have obvious homes.
Challenges I faced 💪
I deleted my local repo. Yes, really. Mid-project. Thankfully like 50% of changes were pushed to my fork, so "disaster recovery" was relatively less painful (I didn't crash :)
Divyansh's review caught a bug. My
generateStubFromEnglishassumed MDX frontmatter everywhere, but the scanners also pick up.yamlfiles. Fine today (stubs are reference-only), but the moment we extend to tutorials and examples, one YAML file would throw and kill the entire batch because I had no per-file try/catch. The fix was to skip.yamlexplicitly for now, and wrap each file's generation so one failure becomes a logged warning instead of a dead run.
What's next 🚀
Per the tracking issue (#1404), coming up:
Auto-close tracker issues when a translation PR merges
A progress dashboard —
translation-status.jsonwith per-language coverage, rendered as markdown progress barsExpanding stub coverage to
tutorials/andexamples/(this is where that YAML handling becomes essential)
Important links 🔗
Summary
The whole point of this script is simple: remove the friction of finding missing files, creating placeholders, opening PRs. It does keep humans in the loop while doing so.
If you speak Spanish, Hindi, Korean, or Chinese and want to contribute to p5.js translations, the stub PRs must make it easier than ever to jump in.





