I'm 19, in Tokyo, and I build mostly alone. Over the last few months I shipped a lot of my own projects — and almost all of them have basically zero users. That gap taught me something that took a while to admit: building was never the hard part. Getting one stranger to trust the thing was.
So I changed what I optimize for. Stars you can buy. Follower counts you can inflate. But a maintainer on someone else's repository typing "merged" on your pull request — that one you cannot fake. It lives in a third party's git history forever. That's the only credential I trust now, and it's the one this post is about.
This week I had four pull requests merged into open-source projects that other people actually run in production, all of them Japanese localization (i18n). Here's exactly what they were, what the diffs looked like, and the surprisingly fiddly things i18n teaches you.
The four PRs (all merged, all verifiable)
Every one of these is a public PR page you can open right now — that's the whole point of using merged PRs as proof. None of this is "trust me."
-
medusajs/medusa #15839 — filled 511 missing keys in the admin dashboard's
ja.json.+838 / −26, one file. -
usememos/memos #6048 — filled 24 missing Japanese keys.
+30 / −4, one file. -
janhq/jan #8349 — completed
common.jsonand fixed terminology.+145 / −18, one file. -
janhq/jan #8348 — updated Japanese translations across six locale files.
+82 / −10.
Three different projects, three different maintainers, three different review styles. Medusa is a commerce platform, Jan is a local-LLM desktop app, Memos is a note-taking server. I picked them for a boring reason: I opened each project's locales/ja.json (or its equivalent), diffed it against the English source of truth, and found real gaps — keys that existed in English but were missing, empty, or still in English in the Japanese file. A missing-key list is the cleanest possible first contribution: it's objectively needed, it's scoped to one file, and it doesn't touch anyone's logic.
What the diffs actually looked like
If you've never done an i18n PR, the work is not "translate a wall of prose." Most of these files are flat JSON dictionaries of tiny UI strings. Here's the shape of it, from the real Jan diff:
{
"manageProviders": "プロバイダーを管理",
"hiddenProviders": "{{count}}件を無効化中",
"searchThreads": "スレッドを検索...",
"settingUpJan": "Janを起動しています…",
"claude_code": "Claude Code"
}
Five lines, five different lessons. That's i18n.
The traps nobody warns you about
1. Interpolation placeholders are sacred. Look at "{{count}}件を無効化中". The {{count}} is an ICU/i18next variable the app fills at runtime. If you "helpfully" translate or reorder it, you break the app — and Japanese word order is different from English, so the temptation to move it is constant. The rule: the placeholder is code, not text. It stays byte-for-byte identical; only the words around it change. Japanese also has no plural inflection, which actually makes the grammar easier here, but it makes it easy to forget that the English source had {{count}} disabled and the placeholder has to survive intact.
2. Some "words" must stay in English. "claude_code": "Claude Code" — I left it untranslated on purpose. Product names, protocol names, and established technical proper nouns (Claude Code, MTP, llama.cpp, DeepSeek) read as wrong when forced into katakana. Maintainers notice over-translation immediately. Knowing what not to translate is half the skill, and it's the half a machine-translation pass usually gets wrong.
3. Punctuation has a nationality. "searchThreads": "スレッドを検索..." keeps the trailing ellipsis because the original was a placeholder hint. But elsewhere Japanese UI conventionally uses the full-width ellipsis … and full-width punctuation (、 。) rather than ASCII , .. Mixing half-width and full-width in the same file is the single most common thing a native reviewer flags. Pick the convention the file already uses and be ruthlessly consistent — consistency matters more than which one you pick.
4. Terminology has to match the rest of the app, not the dictionary. In the Jan PR I didn't just fill blanks; I fixed existing terms (+145 / −18 — note the deletions). "Integrations" → 連携, "experimental" → 実験的, "Remote access" → リモートアクセス. The job isn't word-by-word correctness, it's making the whole surface feel like one person wrote it. If three different contributors translated "settings" three different ways, the app feels machine-made. A glossary pass that unifies terms is often more valuable than net-new translation.
5. The "boring" key is the dangerous one. "settingUpJan": "Janを起動しています…" — a loading message. Easy to skim past. But these short status strings are where tone lives: too formal and the app sounds like a tax form, too casual and it sounds unprofessional. Japanese has multiple politeness registers; matching the app's existing register across hundreds of micro-strings is the actual craft.
How I actually worked (reproducible)
Nothing here needs a framework. The loop that produced four merges:
-
Find the source of truth. Usually
en.json/en-US. That's the canonical key set. -
Diff it against
ja.json. Missing keys, empty values, and not-yet-translated English are your worklist. You can do this with a five-line script or just by eye for small files. This is objective — you're not arguing taste, you're filling holes the project already declared it wants filled. -
Read the surrounding code for context. A key called
dismisscould be a button ("閉じる") or a verb depending on where it renders. Grep for where the key is used before you guess. - Preserve every placeholder and proper noun. (See the five traps above.)
- Match the file's existing conventions — punctuation width, politeness register, term glossary.
- Keep the PR scoped to one concern. "Fill missing ja keys" is reviewable in five minutes. "Refactor the whole i18n system" is not.
- Write the PR description for a maintainer who doesn't read Japanese. Say what changed (N keys filled, terms unified) and why it's safe (no logic touched, placeholders preserved). Reviewers merge what they can verify quickly.
That last point is underrated. Two of these maintainers couldn't personally audit my Japanese — so I made the PR's safety obvious instead: one file, additive, no behavior change, every interpolation intact. A scoped, low-risk diff with a clear description is far more likely to merge than a brilliant-but-sprawling one.
Why localization specifically
If you're looking for a way in, i18n is one of the friendliest first-contribution lanes that exists, and it's chronically under-served. English-default projects accumulate translation debt constantly — every new feature ships English strings first, and the locale files fall behind. If you're fluent in a language the project doesn't have a maintainer for, you have a real, scarce, verifiable skill the repo needs. The bar to start is low; the bar to do it well (the five traps) is where you earn the merge.
It also produces exactly the credential I was after: a specific human, in public, accepting your work into a tool real people use.
The honest part
I won't pretend four PRs is a portfolio. It's a start, and I'm being precise about the number on purpose — four merged, with more in review, all i18n. The reason I'm precise is the same reason the credential works: the moment you round up or imply more than happened, you've traded the one unfakeable thing for the kind of inflation everyone's learned to discount. The whole appeal of a merged PR is that it's checkable. So I keep it checkable.
If you build a lot and feel invisible — and if you've shipped things that died for lack of users, I see you — try pointing some of that energy at someone else's repo for a week. Find the missing keys. Fix the terminology. Write the boring, safe, scoped PR. Let a stranger merge it. It compounds in a way a launch tweet never does.
— greymoth · Tokyo · github.com/greymoth-jp
Top comments (0)