How do you manage translation JSON files for i18n?
Title: How do you manage translation JSON files? (built a tool for my own pain points, curious if others hit the same walls)
I've been building a multilingual vocabulary app (iOS/macOS, SwiftUI + SwiftData) that ships in 30+ languages, and managing the translation JSON has been way more painful than I expected. Curious how other devs handle this, and whether the specific problems I ran into are universal or just my own workflow being weird.
The problems I kept hitting:
- Structural drift: With thousands of repeated objects like {"id": "...", "translations": {"en": "...", "ko": "...", ...}}, it's easy for one entry somewhere in the middle to silently lose a field, get a typo'd language key, or mismatch nesting — and standard JSON validators don't catch this, because the JSON is technically still valid, just structurally inconsistent with the rest of the file.
- Missing/empty language values: Standard validation says the file is fine even if half your language keys are empty strings. You don't find out until QA or a user report.
- Truncated files: If you're generating translations with an LLM and the output gets cut off mid-response, you end up with a JSON tail that's broken in very specific, recoverable ways — but a normal validator just gives you "unexpected end of input" with no path to fix it.
- camelCase ↔ snake_case round-tripping: If your app code uses camelCase but your pipeline/backend expects snake_case, converting back and forth loses information — going from snake_case back to camelCase is ambiguous (is it "userID" or "userId"?). I needed something that remembers the original casing per-project so round-trips are lossless.
- Merging new translations back in without breaking things: getting updated translations back — from a translator, an LLM, or myself editing a spreadsheet — and merging them into the existing file without silently clobbering good data or creating duplicate entries turned out to be its own problem.
What I built:
A macOS app that treats JSON translation files as a first-class problem instead of "just JSON":
- A syntax validator that recovers from smart quotes, markdown code fences, trailing commas — the common copy-paste mistakes when dealing with LLM output
- A "pattern stamp" engine that learns the structure of your repeated objects and flags entries that deviate, without caring about the actual values — just the shape
- Language coverage checking that detects missing keys and can auto-suggest which language a stray value probably belongs to
- A tail-repair engine specifically for truncated LLM output
- camelCase/snake_case conversion with per-project memory of original casing
- A paste-merge tool with conflict detection when combining translation batches
Why this changes how you use AI for translation (and saves tokens):
Most people I see doing this either paste the whole JSON file into an Xcode-integrated AI assistant, or hand-copy fragments into ChatGPT and hope the formatting survives the round trip. Both are wasteful in different ways.
Because this tool can isolate exactly the slice you need — a single language column, a single entry, a single group of entries missing a specific key — you don't have to send your whole dataset to any AI to get one thing translated or fixed. You extract just the piece you actually need, send that to whatever model you want (doesn't have to be the one tied into your IDE), and paste the result back. The merge engine handles conflict detection so pasting back in isn't a manual find-and-replace exercise — it tells you if something you're about to overwrite already differs from what's incoming, instead of silently clobbering it.
This matters more at maintenance time than at initial translation time. Say you're at 10 languages and you add 10 more. You don't need to regenerate all 20 — you extract the existing 10-language values as reference/context, send only the request for the 10 new languages, and merge the result back into the same objects without touching what's already correct. Later, if one specific language needs a wording fix across the whole file (a tone change, a terminology correction, whatever), you can pull out just that one language's values across every entry, hand that single column to an AI, and merge the corrected column back in — instead of re-sending 20 languages' worth of context just to fix one.
The net effect: the LLM call gets smaller and cheaper because you're not sending 19 languages of context to fix the 20th, and the merge step means you're not manually reconciling what came back against what you had.
Who I think this is for:
- Indie/solo devs doing i18n for their own app who don't want a full localization platform built for teams/CI pipelines (Lokalise, Localazy, etc.) when what they actually have is "a JSON file and a growing list of languages"
- Anyone generating or maintaining translations via LLM, since most of these failure modes (truncated output, inconsistent formatting, one language quietly missing, wasted context on unchanged languages) are specifically artifacts of an LLM-in-the-loop workflow
- Anyone who designed their own JSON schema for translations rather than using a framework's built-in i18n format (i18next, .strings, .arb, etc.)
Question for the thread: does this match how you handle i18n JSON and AI-assisted translation, or do you use a completely different structure/approach where these problems don't come up? Trying to figure out if I've been solving a self-inflicted problem or something more people run into.
The philosophy behind the app:
Ultimately, I wanted to build an app that gracefully handles almost every mistake and repetitive task that a beginner or indie developer encounters.
Sure, if you dig deep enough into VS Code, you can technically find all these features. But honestly, what did you think when you first opened VS Code? I remember looking at it and thinking, "Piloting a spaceship would probably be easier than navigating this." I wanted to build something that bypasses that friction entirely.
Please share your thoughts or experiences