CLI

Project sync

Pin a repository to LangSync with nrc langsync init, then reconcile local JSON translation files with sync and pull — flags, conflict strategies, and the .langsync.json reference.

Not verified yet

Project sync keeps translation files in your Git repository and LangSync in agreement. You initialise a project once (nrc langsync init), which writes a .langsync.json config; from then on nrc langsync sync pushes local changes and pulls back the complete, AI-completed state, and nrc langsync pull refreshes local files without pushing anything.

Initialise a project

Run in the project root:

nrc langsync init

The wizard resolves your organization, lists its namespaces, lets you pick one or more (or create one inline if none exist), asks where each namespace's translation files should live (default i18n/<namespace>), and fetches each namespace's default language. Then it runs a seed pass for the newly added namespaces.

Non-interactive / scripted setup:

nrc langsync init -n web -n marketing --dir i18n --seed pull
FlagWhat it does
--namespace, -n (repeatable)Namespaces to include, skipping the picker.
--dirParent directory for translation files; each namespace gets <dir>/<namespace>.
--local-langThe language code you write source values in (defaults to the namespace's server default — see source language).
--seedWhat to do right after writing the config: pull (default — download server state), push-all (upload every local <lang>.json as the seed), push-default (upload only the default-language file and AI-translate the rest), none.
--forceOverwrite an existing .langsync.json instead of merging into it.

Re-run init any time to add another namespace — existing entries are kept, and the seed pass touches only what's new.

The .langsync.json file

init writes this at the project root; commit it.

{
  "version": 1,
  "organization": {
    "id": "6a4c1f0e-…",
    "slug": "acme",
    "name": "Acme Corp"
  },
  "namespaces": [
    {
      "namespace": "web",
      "dir": "i18n/web",
      "format": "flat-json",
      "default_local_language": "en",
      "language_aliases": { "cs_cz": "cs-CZ" }
    }
  ]
}
FieldMeaning
versionConfig format version (currently 1). A newer version than your CLI supports asks you to norcube upgrade.
organizationPins the project to one org. Every langsync command run inside this tree targets this org regardless of your global active org; --org still overrides for debugging.
namespaces[].namespaceThe namespace name (case-sensitive) — what --namespace refers to.
namespaces[].dirDirectory of translation files, relative to the config file (not your cwd), one <lang-code>.json per language.
namespaces[].formatOnly flat-json exists today.
namespaces[].default_local_languageThe language your local source values are written in.
namespaces[].language_aliasesMaps on-disk filename codes to server codes (e.g. cs_cz.json ↔ server cs-CZ). Maintained automatically by sync.

Translation file format

flat-json files are plain one-level string maps, one file per language inside the namespace dir:

i18n/web/
├── en.json
├── de.json
└── cs.json
{
  "button.save": "Save",
  "welcome.title": "Welcome"
}

Rules the CLI enforces and guarantees:

  • Strings only, no nesting. Nested objects, arrays, or numbers are rejected rather than silently mangled.
  • Keys are written sorted with two-space indent and a trailing newline, and files are written atomically — so post-sync Git diffs are minimal and reviewable.
  • Empty strings are preserved as empty translations.
  • Dotfiles and subdirectories in the dir are ignored.

Sync

nrc langsync sync                # everything in .langsync.json
nrc langsync sync -n web         # one namespace
nrc langsync sync --dry-run      # plan only — see the diff, change nothing

Each namespace gets its own sync job; jobs run in parallel with a live per-namespace dashboard showing the current phase and progress. After the server finishes (push + AI translation of missing cells), the CLI writes the complete per-language state back to your local files. Failures appear in an Issues block with hints — the most common being source values that are empty, which the AI skips.

FlagDefaultWhat it does
--dry-runoffStop after planning; print the would-be creates/updates/deletes.
--strategylocalConflict policy — see below.
--pruneoffDelete server marks that don't exist locally. Permanent; dry-run first.
--retranslate-on-source-changeoffWhen a source value changed, clear that term's other-language translations so the AI regenerates them. Off = stale translations stay.
--wait / --wait-timeout / --poll-everyon / 5m / 1sWhether and how long to wait for AI translation to finish before writing files and returning. --wait=false returns right after the push; run pull later for the rest.
--configautoPath to .langsync.json (default: walk up from the current directory).

Conflict strategies

A conflict is the same key having different values locally and on the server (someone edited in the dashboard while you edited in Git):

  • local (default) — your value wins and is pushed.
  • server — nothing is pushed at all; the sync becomes a pull-only refresh.
  • interactive — the CLI walks you through each conflicting key with keep-local / keep-server choices and apply-to-all shortcuts.

Conflicts are only negotiated for the source language. For target languages, your local edits always win — a hand-fixed translation in your repo is treated as authoritative over whatever the server has.

Deletions

By default, sync is conservative: marks that exist on the server but not in your local files are left alone (maybe a teammate just added them in the dashboard). --prune inverts this — the local file set becomes authoritative and server-only marks are deleted, translations and all. --dry-run --prune shows what would go.

Language attachment pre-flight

Before pushing, sync looks at which <code>.json files exist locally and makes sure each language is attached to the namespace:

  • exact code match → attached silently;
  • separator-only or single obvious candidate (cs_cz.jsoncs-CZ) → attached silently and recorded in language_aliases;
  • ambiguous or unknown → interactive picker (with an option to create a custom language, or to skip the file).

Dropping a new pl.json into the directory and running sync is all it takes to add Polish to the namespace.

Pull

nrc langsync pull            # refresh all namespaces from the server
nrc langsync pull -n web

pull downloads the current server state into your local files and never modifies the server — no push, no AI translation triggered. Use it to pick up dashboard edits, or to collect translations that were still draining when a --wait=false sync returned.

Your source language vs. the namespace's

default_local_language doesn't have to match the namespace's default language. A Czech developer on a namespace pinned to en-US can set default_local_language: "cs", write source strings in cs.json, and sync — the server then AI-translates from Czech into every attached language, English included. Each developer's config can differ; the setting is per-project-file, not per-namespace.

CI usage

Everything works headless — pickers are replaced by flags and confirmations by --yes:

# after merging to main
- run: nrc langsync sync --strategy local --wait=false

A typical CI policy: sync on merge to main (push new source strings, kick off AI translation, don't block the pipeline waiting for it), and a scheduled pull job — or a pre-release one — that commits refreshed translation files back to the repo for review.

  • Sync jobs — the server-side lifecycle, statuses, and guarantees behind every sync.
  • CLI overview — resource commands and org resolution.

On this page