My GSoC work and journey
I am a developer from Nepal who loves playing with tech.
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). Human volunteers maintain all of it.
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.
As a GSoC contributor, I am working to add 4 features: improving issue quality, stub file generation, auto-closing issues on PR merge, and analyzing the translation progress (coverage) of different languages. All of this is publicly tracked in this GitHub issue:
https://github.com/processing/p5.js-website/issues/1404
1. Details Dropdown
Divyansh's tracker already told translators that an English page had changed. It didn't tell people what changed.
So an issue would say "Spanish translation may be outdated", and the translator had to leave the issue, open a compare view, find the right file among everything else in that commit, and read the diff over there. Most people just... didn't.
My first task was to bring the diff into the issue itself.
What it does
When the tracker builds an issue, getRecentDiffForFile() walks back through that file's last two commits, compares them, and pulls out the patch for just that one file. The issue body gets a Recent English Diff section with a link to the full compare, plus a collapsible <details> block holding the patch.
This block is collapsed by default. The issue body already carries the language list, the stewards to cc, and the action checklist. Dropping 200 raw lines of diff into the middle of that would bury everything useful. Snippets are capped at 50 lines too, with a note pointing at the full compare when truncated.
If the patch can't be generated for some reason, the section falls back to a plain link instead of disappearing. The translator always gets something link to click and preview.
https://github.com/processing/p5.js-website/pull/1424
2. Stub Generation for p5.js Translations
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.
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.
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 or in Hindi, and it shouldn't try. Humans are the ones who 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)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.
https://github.com/processing/p5.js-website/pull/1473
3. Auto-close issue
The tracker was great at opening issues and not good on closing them.
Every stale translation got an issue. But when a translator actually did the work and their PR was merged, the issue just... sat there. Someone had to close it by hand. Multiply that across four languages and it can become a few hundred pages.
So the third feature closes the loop. translation-auto-close.yml runs when a merged PR touches translated content, works out which issues it resolved, and updates them.
Finding the right issue
GitHub already closes issues when you write Fixes #123 in a PR, so my first version just relied on that. It barely worked. My mentor told me that real contributors don't always read contributing guides. Some wrote nothing at all in their PR description. Some wrote "translated the color page." Some mentioned the issue number in a review comment days later.
So I stopped looking for keywords like Fixes or Resolves and started looking for any # followed by a number. The action collects candidates from everywhere: the PR title, the body, conversation comments, review comments, and any issue that cross-references the PR from its own thread.
I did cast a wide net on purpose. Code blocks and inline code are parsed first (the p5.js repo is full of hex colors like #336699, and that's a color, not issue 336699). Cross-repo references like owner/repo#123 get ignored. Every valid issue has to actually exist, has to not be a pull request, and has to carry the needs-translation label. Everything else gets logged and skipped.
One issue, four languages
A tracker issue usually covers several languages for the same English file, so "resolved" is rarely all-or-nothing. The action reads which languages the PR actually touched from its file paths (src/content/examples/es/... means Spanish), removes that language's lang-es label, strikes that line in the issue body, and only closes the issue once no lang-* labels remain.
This surfaced a problem I didn't expect. GitHub closes an issue the instant a Fixes #123 PR merges, even if the PR only handled one of three languages. My workflow ran seconds later, found a closed issue, and skipped it. So now the action reopens the issue and comments with what's still pending.
https://github.com/processing/p5.js-website/pull/1582
4. Translation Coverage
What if we can calculate and visualize how much of p5.js is actually translated?
So the last feature (translation coverage) walks every English file, checks whether each language has a counterpart, and publishes the percentages as JSON plus a public status page.
Due to the above features, I had to adapt to the counting rule. A stub counts as untranslated, same as if the file hadn't existed at all. So needsTranslation: true became a real signal instead of a note only for humans.
https://github.com/processing/p5.js-website/pull/1582
Reflection on my GSoC journey
The mentorship I received was invaluable. My mentor did the code reviews, and we brainstormed the features and bugs together.
The edge cases and normal limitations of JavaScript, where I could get a TypeError or nullability. I had missed these. Pointing this out and helping me resolve it was the biggest thing. I learned about the best software development principles. My mentor provided information and insights on how to estimate time for features and tasks, how to get reviews, and how to test.
More than that, I had amazing peers. Giving feedback on my tasks and held bi-weekly standups to share our task progress and get feedback and help from the entire cohort. I loved how helpful and collaborative the Processing Foundation team was. Super welcoming from the beginning. It was great.
Some 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 :)
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.





