·6 min read

Stop Editing JSON Translation Files Manually

i18njsontranslationsdeveloper-tools

Stop Editing JSON Translation Files Manually

If you've worked on a multilingual website, you know the drill. Open en.json, find the key you need, make the change, then open fr.json, de.json, ja.json, and repeat for every locale. It's tedious, error-prone, and a terrible use of developer time.

Yet most teams still do it this way. The translation files sit in a locales/ or messages/ directory, and every content update means hand-editing raw JSON. Let's talk about why this is a problem and what to do instead.

The anatomy of i18n JSON files

Most i18n libraries (next-intl, react-i18next, vue-i18n) use a similar structure. Your translation files are JSON objects with nested keys:

{
  "common": {
    "buttons": {
      "save": "Save",
      "cancel": "Cancel",
      "delete": "Delete"
    },
    "errors": {
      "required": "This field is required",
      "email": "Please enter a valid email address",
      "minLength": "Must be at least {{count}} characters"
    }
  },
  "dashboard": {
    "title": "Dashboard",
    "welcome": "Welcome back, {{name}}",
    "stats": {
      "totalUsers": "Total Users",
      "activeProjects": "Active Projects",
      "pendingReviews": "Pending Reviews"
    }
  }
}

This is fine when your app has ten keys. But real-world apps have hundreds or thousands. A production en.json can easily be 2,000+ lines of deeply nested JSON.

The five most common mistakes

1. Trailing commas

JSON doesn't allow trailing commas, but every developer's muscle memory says otherwise. One misplaced comma and your entire locale file is unparseable:

{
  "settings": {
    "theme": "Dark",
    "language": "English",
  }
}

Your app won't throw a helpful error. It'll fail silently or crash at runtime with something like SyntaxError: Unexpected token }.

2. Mismatched keys across locales

You add a new feature and update en.json with five new keys. Then you copy-paste to fr.json and start translating. Except you miss one key. Or you accidentally nest it at the wrong depth:

// en.json
{
  "checkout": {
    "shipping": {
      "title": "Shipping Address",
      "subtitle": "Where should we deliver?"
    }
  }
}

// fr.json (oops, "subtitle" is missing)
{
  "checkout": {
    "shipping": {
      "title": "Adresse de livraison"
    }
  }
}

This creates a gap your i18n library might fill with a fallback, or might leave as a blank string in production.

3. Broken interpolation variables

Most i18n libraries use template variables like {{name}} or {count}. When translating, it's easy to accidentally rename the variable, forget a brace, or remove it entirely:

// en.json
"welcome": "Welcome back, {{name}}"

// de.json (wrong variable name)
"welcome": "Willkommen zurück, {{username}}"

This renders as a literal {{username}} string in the UI because the variable username was never passed. Only name was.

4. Invalid JSON structure

Large nested JSON files are fragile. One missing bracket collapses the entire tree. Diffs become unreadable when you accidentally re-indent half the file. And merge conflicts in JSON are nightmarish because you can't just accept both changes without validating the structure.

{
  "nav": {
    "home": "Home",
    "about": "About",
    "contact": "Contact"
  // missing closing brace, everything below is broken
  "footer": {
    "copyright": "2026 Acme Inc."
  }
}

5. Stale translations

The English file gets updated regularly. The other locales drift. After a few months, your ja.json is 30 keys behind en.json, and nobody knows which ones are missing without writing a script to diff them.

Why a visual editor changes everything

The root cause of all these problems is the same: developers are editing a data format (JSON) as if it were source code. But translation files aren't code. They're structured content. And structured content deserves a structured editor.

A visual translation editor shows you:

  • All locales side by side. You see en, fr, de in columns. Missing keys are highlighted immediately. No manual diffing.
  • Validation in real time. Trailing commas, broken brackets, mismatched interpolation variables: caught before you save.
  • Nested key navigation. Instead of scrolling through 2,000 lines of JSON, you browse a tree. Click dashboard.stats.totalUsers and edit the value.
  • Search and filter. Find the key you need in seconds, across all locales at once.
  • Safe saves. The editor writes valid JSON. Always. You can't accidentally break the file structure.

The Git-based approach

Some editors require migrating your translations to a hosted platform or database. That means vendor lock-in, an extra build step, and a deployment pipeline that now depends on a third-party API.

A better approach: keep your translation files exactly where they are, in your Git repo, and layer a visual editor on top. You edit in the UI, preview the changes, and push a pull request. Your existing review workflow stays the same.

This is the approach tools like SkyBlobs take. It connects to your GitHub repository, reads your i18n files (JSON, YAML, or nested key-value formats), and gives you a side-by-side translation editor. Changes go through your normal PR process.

Setting up a better workflow

Here's a practical workflow that eliminates most manual JSON editing mistakes:

1. Organize your translation files consistently

Pick one structure and stick with it. Group by feature, not by component:

locales/
  en/
    common.json
    dashboard.json
    checkout.json
  fr/
    common.json
    dashboard.json
    checkout.json

2. Use a flat key convention for large files

Deeply nested JSON is harder to maintain. Consider flat keys with dot notation:

{
  "common.buttons.save": "Save",
  "common.buttons.cancel": "Cancel",
  "dashboard.title": "Dashboard",
  "dashboard.welcome": "Welcome back, {{name}}"
}

This makes diffs cleaner, reduces indentation errors, and makes search easier.

3. Add a CI check for missing keys

Write a simple script (or use an existing tool) that compares your locale files and fails the build if keys are missing:

import fs from "fs";
import path from "path";

function getKeys(obj: Record<string, unknown>, prefix = ""): string[] {
  return Object.entries(obj).flatMap(([key, value]) => {
    const fullKey = prefix ? `${prefix}.${key}` : key;
    if (typeof value === "object" && value !== null) {
      return getKeys(value as Record<string, unknown>, fullKey);
    }
    return [fullKey];
  });
}

const localesDir = path.join(process.cwd(), "locales");
const baseLocale = JSON.parse(
  fs.readFileSync(path.join(localesDir, "en.json"), "utf-8")
);
const baseKeys = new Set(getKeys(baseLocale));

const locales = fs.readdirSync(localesDir).filter((f) => f !== "en.json");

for (const file of locales) {
  const locale = JSON.parse(
    fs.readFileSync(path.join(localesDir, file), "utf-8")
  );
  const localeKeys = new Set(getKeys(locale));
  const missing = [...baseKeys].filter((k) => !localeKeys.has(k));
  if (missing.length > 0) {
    console.error(`${file} is missing ${missing.length} keys:`);
    missing.forEach((k) => console.error(`  - ${k}`));
    process.exit(1);
  }
}

4. Use a visual editor for day-to-day changes

For any content change (fixing a typo, adding a new string, updating a CTA), use a visual editor instead of opening the JSON file directly. It's faster, safer, and accessible to non-developers on your team.

5. Keep translations in version control

Never move your translations to a platform that owns the data. Your translation files should live in Git, version-controlled alongside your code. This gives you full history, rollback capability, and no vendor lock-in.

The bottom line

Editing JSON translation files by hand is a solved problem. The tooling exists to give you a visual, validated, side-by-side editing experience without leaving your Git workflow.

If your team is still opening JSON files in VS Code and hunting for nested keys across five locale files, it's time to reconsider. Your translations are content, not code. Treat them accordingly.

Try SkyBlobs free

Connect your GitHub repo and start editing content visually. No setup, no config files.