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.
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 initThe 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| Flag | What it does |
|---|---|
--namespace, -n (repeatable) | Namespaces to include, skipping the picker. |
--dir | Parent directory for translation files; each namespace gets <dir>/<namespace>. |
--local-lang | The language code you write source values in (defaults to the namespace's server default — see source language). |
--seed | What 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. |
--force | Overwrite 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" }
}
]
}| Field | Meaning |
|---|---|
version | Config format version (currently 1). A newer version than your CLI supports asks you to norcube upgrade. |
organization | Pins 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[].namespace | The namespace name (case-sensitive) — what --namespace refers to. |
namespaces[].dir | Directory of translation files, relative to the config file (not your cwd), one <lang-code>.json per language. |
namespaces[].format | Only flat-json exists today. |
namespaces[].default_local_language | The language your local source values are written in. |
namespaces[].language_aliases | Maps 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 nothingEach 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.
| Flag | Default | What it does |
|---|---|---|
--dry-run | off | Stop after planning; print the would-be creates/updates/deletes. |
--strategy | local | Conflict policy — see below. |
--prune | off | Delete server marks that don't exist locally. Permanent; dry-run first. |
--retranslate-on-source-change | off | When a source value changed, clear that term's other-language translations so the AI regenerates them. Off = stale translations stay. |
--wait / --wait-timeout / --poll-every | on / 5m / 1s | Whether 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. |
--config | auto | Path 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.json→cs-CZ) → attached silently and recorded inlanguage_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 webpull 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=falseA 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.
Related
- Sync jobs — the server-side lifecycle, statuses, and guarantees behind every sync.
- CLI overview — resource commands and org resolution.