Skip to main content
Your source file is the JSON locale file that contains your original-language strings. LazyLocales uses a .source.json convention to separate the annotated source from the clean runtime file.

The .source.json convention

When you run lazylocales translate for the first time, the CLI automatically renames your source file:
en.json  →  en.source.json
On every subsequent translate or push, a clean en.json is generated automatically — stripped of all annotations and ready for your i18n library.
locales/
├── en.source.json   ← your editable source (with annotations)
├── en.json           ← auto-generated clean copy (for runtime)
├── fr.json           ← AI-translated
├── de.json           ← AI-translated
└── es.json           ← AI-translated
The clean en.json is regenerated on every translate and push run. You should never edit it directly — always edit en.source.json instead.

Why two files?

Most i18n libraries (i18next, next-intl, vue-i18n) expect a plain JSON file with only key-value pairs. Annotation keys like __context__ and __lock__ would cause warnings or unexpected behavior at runtime. The .source.json convention keeps your annotations in a separate file that only the CLI reads, while the generated en.json stays clean for your app.

Annotations

Annotations are special keys you add to your source file to control how the AI translates (or doesn’t translate) specific strings. They are always stripped from the generated output files.

Context (__context__)

Provide translation context by adding a __context__ key next to any translation string. The key name format is __context__ followed by the sibling key name.
en.source.json
{
  "greeting": "Hello",
  "__context__greeting": "Informal greeting shown on the homepage hero section",

  "cta": "Get started",
  "__context__cta": "Call-to-action button, keep it short and punchy (max 2 words)"
}
Context annotations are sent to the AI model as hints. They help produce more accurate translations by telling the AI:
  • Where the text appears in your UI
  • Tone — formal, casual, technical, playful
  • Constraints — character limits, line length, abbreviation rules
  • Terminology — domain-specific terms, brand voice
Context keys work at any nesting level:
en.source.json
{
  "nav": {
    "home": "Home",
    "__context__home": "Main navigation link — single word preferred"
  }
}

Lock (__lock__)

Prevent a key from being translated by setting its __lock__ sibling to true or "true". Locked keys are copied verbatim from the source into every target locale file.
en.source.json
{
  "brand": "LazyLocales",
  "__lock__brand": true,

  "product": {
    "name": "TranslateAI Pro",
    "__lock__name": true,
    "tagline": "Fast, accurate translations"
  }
}
In the output files (de.json, fr.json, etc.), brand will always be "LazyLocales" and product.name will always be "TranslateAI Pro" — they are never sent to the AI. Lock annotations are useful for:
  • Brand names and product names
  • Technical terms that must stay in the source language
  • Constants — error codes, identifiers, placeholder strings
  • Legal text that requires exact wording

Combining annotations

You can use both __context__ and __lock__ on the same key. For example, you might lock a key now but leave context for future reference:
en.source.json
{
  "errorCode": "ERR_AUTH_FAILED",
  "__lock__errorCode": true,
  "__context__errorCode": "Internal error code, never translate"
}

How annotations affect the diff

When you add, remove, or change a __context__ or __lock__ annotation, the CLI treats the associated key as modified — even if the value itself didn’t change. This means:
  • Adding __context__greeting will re-translate greeting in all target locales (with the new context).
  • Adding __lock__brand will replace the translated brand with the source value in all target locales.
  • Removing a __lock__ will cause the key to be translated by AI on the next run.

Full example

Here’s a realistic source file with mixed annotations:
en.source.json
{
  "common": {
    "appName": "LazyLocales",
    "__lock__appName": true,

    "cancel": "Cancel",
    "save": "Save",
    "__context__save": "Primary action button in forms — keep it short",

    "greeting": "Welcome back, {name}!",
    "__context__greeting": "Shown on dashboard after login. {name} is the user's first name."
  },
  "errors": {
    "notFound": "Page not found",
    "__lock__notFound": true,
    "__context__notFound": "HTTP 404 — locked because we want the English string everywhere",

    "serverError": "Something went wrong. Please try again.",
    "__context__serverError": "Generic fallback error — friendly, not technical"
  }
}
After running lazylocales translate, the generated de.json would look like:
de.json
{
  "common": {
    "appName": "LazyLocales",
    "cancel": "Abbrechen",
    "save": "Speichern",
    "greeting": "Willkommen zurück, {name}!"
  },
  "errors": {
    "notFound": "Page not found",
    "serverError": "Etwas ist schiefgelaufen. Bitte versuchen Sie es erneut."
  }
}
Notice:
  • appName and notFound are locked — copied verbatim from the source.
  • save and greeting are translated with their context hints.
  • All annotation keys are stripped — the output is clean JSON.