How do I connect GitHub and sync translations?
GitHub sync imports the translation JSON files that live in your repository into your Better i18n project.
Worth being precise about what it does, because the name suggests more: it reads files, not code. It does not scan your source for t() calls — that is the CLI's job (better-i18n scan / sync), which runs on your machine or in CI.
How GitHub sync works #
You trigger a sync (dashboard or CLI)
→ Better i18n reads the translation JSON files on the configured branch + path
→ Language comes from the file layout
→ Keys and translations are imported into the project
→ The result is written to the CDN and a sync job is loggedSetting up GitHub sync #
Step 1: Connect your GitHub account #
- Go to Settings → Integrations → GitHub
- Click "Connect GitHub"
- Authorize the Better i18n GitHub App
- Select which repositories it may read
Step 2: Point it at your translation files #
- Pick the repository
- Pick the branch (usually
main) - Set the base path where your translation files live (e.g.
locales/orsrc/messages/)
The connect screen inspects the repository tree and pre-fills the path and format for you. Check what it detected rather than accepting it blindly — a wrong path is the usual cause of a first sync failing with "Source language files not found".
Step 3: Confirm the locale bindings #
Better i18n records which file belongs to which language and shows that mapping on the connect screen and in integration settings. If a language is bound to the wrong file, fix it there before the first sync.
How the language is detected #
| Layout | Example | Language from |
|---|---|---|
| Flat | en.json, tr.json | the filename |
| Namespaced | en/common.json, tr/nav.json | the first directory |
A file whose name is not a locale (messages.json, strings.json) will not bind to a language on its own — put it under a locale directory.
Sync is triggered, not automatic #
There is no push webhook and no background polling: a git push on its own does not start a sync. You start one from the dashboard, or from the terminal:
better-i18n syncIf you want sync on every push, run that command as a step in your CI workflow with BETTER_I18N_API_KEY in the environment. That is the honest way to get "automatic" — and it puts the trigger somewhere you can see it fail.
Sync history #
Every run is a job you can inspect:
better-i18n syncs list
better-i18n syncs get <syncId>
better-i18n syncs cancel <syncId>The same history is in the dashboard, with the per-file activity log — how many files were parsed and how many keys came out of each. That log is the first place to look when a sync "worked" but a language is short of keys.
A job whose worker died is closed automatically after 15 minutes, so a run stuck at "in progress" resolves itself rather than blocking the next sync forever.
Concurrent edits #
Sync imports values from your files, and translations edited in the dashboard are not silently overwritten by a stale value: when a value has moved on the server since the copy you started from, Better i18n reports the conflict instead of picking a winner behind your back. Resolve it and re-run.
If your repository is the single source of truth for source strings, keep the editing in the repo and treat the dashboard as review. If the dashboard is the source of truth, use better-i18n pull to bring translations back down rather than hand-editing files that the next sync will overwrite.
Monorepos #
Each Better i18n project connects to one repository, branch and base path, so a monorepo with two apps means two projects — one per app — each pointed at its own path (apps/web/locales, apps/mobile/locales). Both can live in the same repository.
Better I18N