React
Localhero.ai translates the JSON message catalogs your React i18n library already uses, whether that is i18next, next-intl or react-intl. When a pull request adds keys to your source-locale JSON, the GitHub Action translates the ones that are new or missing and commits the updated catalogs back to the same PR, so you always have on-brand translations ready to deploy with each PR.
Prerequisites
- A React app with JSON message catalogs (i18next, next-intl, react-intl or vue-i18n)
- Node 18 or later, to run the CLI through
npx - A Localhero.ai API key
Get an API key from your account.
Setup
1. Initialize the project
Run the CLI in your project root. No install step, and it detects which i18n library you use:
npx @localheroai/cli init
The defaults it proposes per library. Accept them, or point it elsewhere during init:
| Detected | Path | Pattern |
|---|---|---|
| next-intl | messages/ |
**/*.json |
| next-i18next, next-translate | public/locales/ |
**/*.json |
| i18next | public/locales/ |
**/*.json |
| react-intl | src/translations/ |
**/*.json |
| vue-i18n | src/locales/ |
**/*.json |
The last row is not a typo. Vue projects run through the same JSON path, so everything on this page applies to a vue-i18n setup as well.
2. Check the generated config
init writes a localhero.json in your project root. For an i18next layout with English as the source, it looks like this:
{
"schemaVersion": "1.0",
"projectId": "your-project-id",
"sourceLocale": "en",
"outputLocales": ["de", "fr", "sv"],
"translationFiles": {
"paths": ["public/locales/"],
"pattern": "**/*.json"
}
}
Commit this file. Fields are documented in Project Setup.
3. Add the GitHub Action workflow
init offers to create the workflow for you. Add your API key as the repository secret LOCALHERO_API_KEY on GitHub and new keys get translated on every PR. It runs localheroai/localhero-action. To set it up by hand, this is the file; the paths: filter points at the locale directory, so change it if yours is messages/ or src/translations/:
name: Localhero.ai - Automatic I18n translation
on:
pull_request:
paths:
- "public/locales/**"
- "localhero.json"
repository_dispatch:
types: [localhero-sync]
workflow_dispatch:
concurrency:
group: translate-${{ github.event.client_payload.branch || github.head_ref || github.run_id }}
cancel-in-progress: true
jobs:
translate:
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
steps:
- uses: actions/checkout@v5
with:
ref: ${{ github.event.client_payload.branch || github.head_ref || github.ref_name }}
fetch-depth: 0
- uses: localheroai/localhero-action@v1
with:
api-key: ${{ secrets.LOCALHERO_API_KEY }}
fetch-depth: 0 is important: the Action diffs against the base branch, and a shallow checkout has nothing to compare. See the GitHub Actions guide for all inputs.
How it works
- You open a pull request that adds or changes keys in your source locale file, for example
public/locales/en/common.json. - The GitHub Action diffs the branch against your base branch and translates only the keys that changed or are missing. Updated and removed keys are synced to the Localhero.ai backend, so the web interface stays in sync with your codebase and PRs.
- It commits the updated JSON files to the same PR, so the diff you review contains both the code and its translations.
- You review and edit the translations in the dashboard, and edits sync back to the branch.
Nothing in your app has to know Localhero.ai exists. The JSON catalog is the integration, so your provider setup, your t() calls and your components stay as they are.
Rather not automatically translate on PRs? Skip the Action and run npx @localheroai/cli translate locally to fill in every missing key, then commit the result yourself. See the CLI reference.
React specifics
Namespaced files and nested keys
Namespaced files like common.json and checkout.json work without configuration, as do the nested objects these catalogs normally hold. The **/*.json pattern is recursive.
How the locale of a file is worked out
The locale is read from the filename or path: basename first, then a path segment, then an _ or - suffix, then the parent directory. That covers both common layouts, public/locales/de/common.json and messages/de.json.
Matching expects lowercase xx or xx-XX codes, but a name like pt_BR.json still resolves when that locale is configured, since known locales are matched before the regex. For anything it does not reach, set translationFiles.localeRegex.
Keys you never want translated
Use ignoreKeys for internal or admin-facing copy. It takes exact keys or a trailing wildcard, and applies on both translate and push:
"translationFiles": {
"paths": ["public/locales/"],
"pattern": "**/*.json",
"ignoreKeys": ["admin.*", "internal.debugBanner"]
}
To skip whole files instead of keys, use ignore with a glob.
Renaming and removing keys
In the pull request flow this takes care of itself: keys removed in the diff are reported to the backend along with the rest of the changes. The manual push command is more conservative and never deletes, so after renaming or removing keys outside a PR, run push --prune --force to make the remote side match your files.
One file holding several locales
Everything above assumes one locale per file. If your JSON files instead hold several locales as top-level keys, turn on translationFiles.multiLanguageFiles. Beta, JSON and YAML only. A file qualifies when every top-level key is a configured locale and at least two are present.
Questions we get
Can I translate some languages myself?
Yes. Turn off Auto-translate for that language in your project settings, and Localhero.ai writes nothing for it. The CLI reports those languages as skipped, and you keep them under your own process while the rest are translated automatically.
Will it translate my whole catalog on every PR?
No, only keys that changed or are missing. On a pull request the diff is against the PR's base branch. Locally, --changed-only compares against main unless translationFiles.baseBranch says otherwise.
How do I stop it running on a specific PR?
Add the skip-translation label. The Action also skips draft PRs and its own commits. The details are in the GitHub Actions guide.
Longer walkthrough with a real codebase: localizing a React and Next.js app. If you are still choosing a library, we compared react-i18next and Lingui.
Last updated
Ready to try it?
Get setup in a couple of minutes. No credit card required.