Logo
By Glot Team

i18n JSON Translation: The Complete Guide for Vue, React & Nuxt

Everything you need to know about structuring, managing, and translating i18n JSON files across modern JavaScript frameworks.

If you've ever shipped an app to users who speak more than one language, you've almost certainly dealt with i18n JSON translation files. They're the backbone of internationalization in every major JavaScript framework — from Vue to React to Nuxt and Next.js. Yet despite being "just JSON," these files have a surprising number of gotchas that can slow down your team, introduce bugs, and make translators miserable.

This guide covers the real-world patterns you'll encounter, the differences between frameworks, and practical workflow tips to keep your locale files under control.

What Are i18n JSON Files?

An i18n JSON file is a simple key-value map that stores translated strings for a specific locale. Your application loads the correct file based on the user's language preference, and a runtime library swaps in the right text. A minimal example looks like this:

// en.json
{
  "greeting": "Hello, world!",
  "logout": "Log out"
}

// ja.json
{
  "greeting": "こんにちは!",
  "logout": "ログアウト"
}

Simple enough. But once your app hits a few dozen screens, you're suddenly managing thousands of keys across five, ten, or thirty locale files. That's where structure matters.

Flat vs. Nested: Choosing a JSON Structure

There are two dominant patterns for organizing JSON translation files, and each has real trade-offs.

Flat Structure

{
  "home.hero.title": "Build something great",
  "home.hero.subtitle": "Start your project today",
  "settings.profile.name": "Display name"
}

Flat keys use dot notation to encode hierarchy. They're easy to search with grep or Ctrl+F, and merging changes in Git produces fewer conflicts since each key lives on a single line. Many teams prefer this for large projects because diffs stay clean.

Nested Structure

{
  "home": {
    "hero": {
      "title": "Build something great",
      "subtitle": "Start your project today"
    }
  },
  "settings": {
    "profile": {
      "name": "Display name"
    }
  }
}

Nested JSON feels more natural and mirrors your component tree. It's the default in most framework setups. The downside: deep nesting makes merge conflicts more painful, and it's harder to spot missing keys without tooling.

In practice, we recommend nested structures for projects under ~500 keys, and flat for anything larger. If you're using Glot's editor, you can switch between visual and flat views regardless of which format your files use — so the choice becomes purely about Git ergonomics.

Framework-Specific Patterns

vue-i18n (Vue & Nuxt)

vue-i18n is the de facto standard for Vue and Nuxt projects. It supports both nested and flat JSON out of the box. A typical Nuxt project using @nuxtjs/i18n organizes locale files like this:

locales/
  en.json
  zh-TW.json
  ja.json
nuxt.config.ts  // i18n module config

In your components, you access translations with $t('key.path') in templates or t('key.path') from the useI18n() composable. Vue-i18n also supports pluralization, interpolation with named variables like {`{count}`}, and linked messages — features you'll want when your translators need flexibility.

One common pain point: vue-i18n warns at runtime when keys are missing, but only in development. In production, missing keys silently render the raw key string. That means you can ship settings.profile.bio as visible text to your users if you forget to add it to a locale file. Automated key validation before deploy is essential.

react-intl (React)

react-intl (part of FormatJS) takes a slightly different approach. It uses ICU MessageFormat syntax, which is more powerful for complex pluralization and gender rules but adds a learning curve. Your JSON files look like this:

{
  "greeting": "Hello, {name}!",
  "itemCount": "{count, plural, one {# item} other {# items}}",
  "lastSeen": "Last seen {date, date, medium}"
}

React-intl encourages flat key structures and typically uses defineMessages or useIntl() hooks. A key difference from vue-i18n: react-intl requires a defaultMessage alongside each message ID, which serves as both the English fallback and documentation for translators. This is great for developer experience but means your translation keys live in two places — your code and your JSON.

next-i18next (Next.js)

next-i18next wraps the popular i18next library for Next.js and uses namespace-based JSON files. Instead of one monolithic file per locale, you split translations by feature:

public/
  locales/
    en/
      common.json
      dashboard.json
      settings.json
    de/
      common.json
      dashboard.json
      settings.json

Each namespace maps to a JSON file. You load only the namespaces a page needs via serverSideTranslations, which keeps bundle sizes small. The downside is managing dozens of small files across every locale. Adding a new namespace means touching every language directory, and keeping them in sync requires discipline or tooling.

Common Pain Points (And How to Solve Them)

1. Missing Keys Across Locales

The most frequent bug in i18n translation workflows: you add a key to en.json, ship the feature, and forget to add it to three other locale files. The fix is straightforward — run a validation script in CI that compares all locale files against your source language and fails the build on mismatches. Tools like i18n-check or a simple Node script can handle this.

2. Stale Translations

You rename a key or change the English copy, but the translations for other languages still reflect the old version. This is harder to catch automatically. Some teams add a _updatedAt metadata key or use checksums to flag translations that may be outdated. Others re-translate from scratch whenever the source changes — which is where AI translation becomes practical.

3. Context Loss for Translators

A JSON key like "save" could mean "Save" (a button), "save" (as in save money), or "a save" (in sports). Without context, translators guess wrong. The best practice is to use descriptive keys — "editor.toolbar.saveButton" instead of "save" — and provide comments or screenshots alongside your locale files.

4. Merge Conflicts in Large Teams

When multiple developers add keys to the same JSON file in the same sprint, merge conflicts are inevitable. Flat structures help. Alphabetical key sorting helps more. Some teams even use a locking mechanism where each developer works on a specific namespace. Regardless of your approach, having a single source of truth for key management — whether that's a spreadsheet, a SaaS tool, or a JSON editor like Glot — dramatically reduces friction.

How AI Translation Changes the Workflow

Traditional i18n translation workflows involve exporting JSON files, sending them to a translation agency, waiting days or weeks, importing the results, and fixing formatting issues. For startups and indie developers, this cycle is too slow and too expensive.

AI-powered translation offers a compelling alternative for initial drafts. Modern language models understand context, handle pluralization rules, and produce translations that are significantly better than the machine translation of five years ago. The workflow becomes:

  1. Write your source locale (usually en.json) as you build features.
  2. Run AI translation to generate all target locales instantly.
  3. Have native speakers review and refine — they're editing, not translating from scratch.
  4. Ship faster, iterate on translation quality over time.

This review-first approach is especially effective because editing a mostly-correct translation is dramatically faster than translating from zero. Your bilingual team members can review a hundred keys in the time it would take to translate twenty.

Glot is built around exactly this workflow. You paste or upload your i18n JSON file, use AI to translate it into any target language, review the results in a side-by-side editor, and export clean JSON ready to drop into your locales/ directory. No accounts, no vendor lock-in, and your data stays in your browser. You can also edit JSON files online with full syntax validation to catch formatting issues before they hit production.

Quick Reference: File Structures by Framework

Here's a cheat sheet for how each major framework expects its JSON translation files to be organized:

  • vue-i18n / @nuxtjs/i18n — One JSON per locale at the project root or in a locales/ folder. Nested or flat. Configured in nuxt.config.ts or i18n.config.ts.
  • react-intl — Flat JSON with ICU MessageFormat values. Typically in src/lang/ or src/translations/. Loaded via IntlProvider.
  • next-i18next — Namespace-based JSON split by feature. Lives in public/locales/{lang}/. Loaded per-page with serverSideTranslations.
  • i18next (generic) — Same namespace model as next-i18next. Works with any bundler. Supports lazy loading via backends.

Managing i18n JSON translation files doesn't have to be painful. Pick a consistent key structure, validate your locales in CI, and use AI to eliminate the bottleneck of manual translation. Your future self (and your translators) will thank you.

Ready to Translate Your i18n JSON Files?

Paste your JSON, pick a target language, and get AI-powered translations in seconds. Free, local-first, no sign-up required.

Try Glot Editor Now