I Let Codex CLI Handle Translation File Sync — How I Built an Automated i18n Missing Key Detection Pipeline
Anyone who has run a multilingual service has experienced this nightmare at least once. The morning after deploying a new feature, a report comes in from a Japanese user: "There's weird text on the button." On investigation, a key added only to en.json is missing from ja.json and being displayed as the raw key name with no fallback. When I first encountered this problem, I thought, "This has to be solved with tooling — humans can't inspect this manually."
When supported locales grow alongside thousands of strings, the item count multiplies. At that scale, manually tracking which file contains which t() call and which locale file is missing it is practically impossible, and linters or type generation alone often can't cover it all.
This post summarizes a pipeline I've run with a real team — scanning the codebase with Codex CLI's codex exec to report missing keys, batch-syncing translation files via an MCP server, and blocking at the PR stage with GitHub Actions. Since our team hasn't been running it long enough to share statistics, I'll focus on the real traps we fell into rather than numbers.
Why This Combination, and Why Now
How Codex CLI Became a Tool for Repetitive Work
Codex CLI is a terminal-based AI coding agent provided by OpenAI. It's open source (repo: github.com/openai/codex, TypeScript-based) and handles file reading, editing, and command execution via natural-language prompts. The key point for this article is that it provides codex exec, a non-interactive execution command, which allows it to run in CI/CD pipelines without human intervention. The model is specified with the --model flag; you can attach GPT-4o (general-purpose) or reasoning models like o1 and o3 as needed. The two families have different characteristics, which I'll revisit later.
What MCP Changes
Codex CLI alone has limits for i18n automation. MCP (Model Context Protocol) is a protocol that lets AI agents call external tools and data sources in a standardized way. That standardization means you can build translation file scanning, TMS integration, and auto-translation logic once as an MCP server and reuse it across any MCP-compatible client — Claude Code, Cursor, Codex CLI, and more. Rather than a "game changer," the practical improvement is eliminating the glue code that had to be rewritten for every client.
As of September 2026, most i18n-related MCP servers are distributed as community open-source projects (see the table later). For TMS vendors' official support status, check each TMS's release notes directly; this article focuses on community servers.
Overall Pipeline Structure
One thing to emphasize upfront: the approval step after the dry-run is not inside codex exec — it's a flow where a human opens the report file and applies it with a separate command. I'll explain why in Step 3.
Step-by-Step Setup
Step 1: Encode Project Conventions in AGENTS.md
For Codex to understand a project's translation structure, it needs explicit instructions. Create an AGENTS.md file in the project root and describe your translation naming conventions and file locations.
# AGENTS.md
## i18n Conventions
### File Structure
Translation files are stored as JSON in the `src/locales/{locale}/` directory.
- Source locale: `en` (source of truth)
- Supported locales: `ko`, `ja`, `zh-CN`, `fr`, `de`, `es`, `pt-BR`
### Key Naming Rules
- Use dot notation for namespace separation: `namespace.section.key`
- Examples: `common.button.submit`, `auth.error.invalid_credentials`
### Translation Function Patterns
- React: `t()` calls from the `useTranslation` hook
- Files: `*.tsx`, `*.ts` (excluding test files)
- Patterns: `t('key.name')`, `t('key.name', { variable })`
### Placeholder Rules
- Use ICU message format: `{variable}`, `{count, plural, one {# item} other {# items}}`
- Placeholders must be preserved exactly after translation.
## Audit Task
When reporting missing keys, follow this format:
- List of missing keys per locale
- Include source file path
- Coverage ratio compared to source localeWriting AGENTS.md initially is the most labor-intensive part. The more complex your project structure, the more this upfront investment determines the quality of your automation downstream.
Step 2: Run the Audit with codex exec
Once AGENTS.md is ready, run the codebase scan with codex exec. The --sandbox read-only flag is important — it guarantees that no files will be modified at this stage.
codex exec \
--model gpt-4o \
--sandbox read-only \
"Audit the missing keys in all locale files using src/locales/en.json as the baseline, \
and save the coverage ratio and list of missing keys per locale to audit-report.json. \
Also scan the source code for t() calls and include any keys not defined in the locales files \
as a separate section."One thing to watch out for here. LLM output is non-deterministic, so the same schema won't come out every time from the prompt alone. If the next step will consume this file programmatically, you need to explicitly embed the schema in the prompt (e.g., attach a JSON Schema) and run it through a validator like zod or ajv in a post-processing script. The following is a conceptual example of roughly what you get when the schema has stabilized.
{
"coverage": {
"ko": { "ratio": 0.94, "total": 2000, "present": 1880, "missing": 120 },
"ja": { "ratio": 0.87, "total": 2000, "present": 1740, "missing": 260 },
"pt-BR": { "ratio": 0.72, "total": 2000, "present": 1440, "missing": 560 }
},
"missingKeys": {
"ko": ["feature.dashboard.new_widget", "settings.privacy.cookie_banner"],
"ja": ["feature.dashboard.new_widget", "auth.mfa.setup_prompt"]
},
"undefinedInSource": [
{ "key": "legacy.old_flow.button", "file": "src/components/OldModal.tsx", "line": 42 }
]
}The numbers and file paths are just examples; they vary per project. You also need a defense line at the parser level — if the schema doesn't match the prompt's expectation, fall back to a retry or human intervention.
The undefinedInSource section is particularly useful. It catches code that calls t() with keys not defined in any locale file. TypeScript type generation can catch this too, but because Codex reads the source context alongside, it can additionally surface candidates like "this key can safely be deleted as a dead key" — though the final call still belongs to a human.
Step 3: Separate Draft Generation from Application via MCP Server
With the audit report in hand, it's time to fill the missing keys through the MCP server. There are several community MCP servers for i18n, and the right choice depends on your project requirements. All are open source; verify official status and maintenance activity in each repo before using.
| MCP Server | Key Features | Best For |
|---|---|---|
gtrias/i18next-mcp-server |
Health check, missing key detection, translation draft generation | i18next-based projects |
Ret2Hell/i18n-mcp |
Dry-run patch output, dead-key detection | Prioritizing batch-processing safety |
dalisys/i18n-mcp |
Hardcoded string analysis, file watching | Legacy migration |
reinier-millo/i18n-mcp-server |
Specialized for JSON i18n files | Simple JSON structures |
Because Codex CLI's config file format is actively being updated across repos, consult the docs/config.md (or README) of your specific Codex CLI version for how to register MCP servers. Rather than format specifics, what matters conceptually is that you need three pieces: "MCP server command + environment variables + server-specific options." Running the server itself is usually this simple:
LOCALES_DIR=./src/locales SOURCE_LOCALE=en \
npx -y i18next-mcp-serverNow let me correct a common misconception about the draft flow. codex exec is, as the name implies, non-interactive — "show a dry-run and get approval" cannot be realized inside a single prompt. This step should honestly be split into two commands.
The first command runs read-only and produces only the draft file.
codex exec \
--model o3 \
--sandbox read-only \
"Generate translation drafts for the missing keys in audit-report.json using the i18n MCP server, \
and save the results to translations.dryrun.json. \
Placeholders like {variable} and ICU plural syntax must be preserved as-is."After review, the second command grants write permission.
codex exec \
--model gpt-4o \
--sandbox workspace-write \
"Merge the contents of translations.dryrun.json into the corresponding locale files in src/locales. \
Do not overwrite existing key values — only add missing keys."For a simple merge where you don't need Codex's judgment involved, handling this second step with a plain Node script is far more predictable. I actually ended up pulling this part out into a script in practice. The more you delegate to an LLM, the more room there is for unpredictable edits to creep in.
Step 4: Block and Auto-Fill at the PR Stage with GitHub Actions
Local automation alone isn't enough. For the whole team to uphold translation completeness standards, CI must enforce them. Split this into two jobs: an audit job that runs read-only and checks the coverage gate, and a fill job that triggers when the audit fails and pushes a translation commit to a separate branch. This separation is what lets you implement the "audit → parallel translation → commit → re-check" flow the diagram promises in actual YAML.
# .github/workflows/i18n-check.yml
name: i18n Coverage Check
on:
pull_request:
paths:
- 'src/**/*.tsx'
- 'src/**/*.ts'
- 'src/locales/**'
jobs:
audit:
runs-on: ubuntu-latest
outputs:
needs_fill: ${{ steps.audit.outputs.needs_fill }}
steps:
- uses: actions/checkout@v4
- name: Audit missing i18n keys (read-only)
id: audit
uses: openai/codex-action@v1
with:
prompt: |
Check the coverage of all locale files against src/locales/en.json as the baseline.
If any locale is below 90%, output the missing keys as GitHub Actions annotations,
write needs_fill=true to GITHUB_OUTPUT, and exit with exit 1.
sandbox: read-only
model: o3
env:
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
fill:
needs: audit
if: failure() && needs.audit.outputs.needs_fill == 'true'
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.head_ref }}
- name: Generate translation drafts and commit
uses: openai/codex-action@v1
with:
prompt: |
Generate translation drafts for missing keys using the i18n MCP server
and merge them into the corresponding locale files in src/locales.
Preserve all placeholders as-is.
sandbox: workspace-write
model: gpt-4o
env:
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
- name: Commit changes
run: |
git config user.name "codex-bot"
git config user.email "codex-bot@users.noreply.github.com"
git add src/locales
git diff --cached --quiet || git commit -m "chore(i18n): fill missing translations"
git pushCheck the Codex GitHub Action official documentation for the exact input spec and supported options, and adjust for the version you're using. The YAML above is a skeleton to illustrate the job-separation structure; in a real environment you'll need to layer on signed commits, review requests, labeling, and other team-specific rules.
One more thing. Make sure to add branch protection rules so that auto-committed translations must go through human review and approval before merging. Leaving an open path for LLM-generated strings to land directly in production is exactly the condition under which translation incidents happen.
What You Gain and What You Give Up
Real Tradeoffs
| Item | What You Gain | What You Give Up / Cost to Accept |
|---|---|---|
| Repetitive work | Key extraction and file sync are automated, freeing developer time | New maintenance burden for AGENTS.md, prompts, and schemas |
| Translation quality | LLM reflects UI context, more natural than naive dictionary substitution | Domain-specific vocabulary and tone consistency still require human review |
| Audit safety | read-only sandbox ensures no filesystem changes during scanning | workspace-write step and CI commit permissions require separate risk management |
| CI integration | Can be integrated by adding jobs to existing pipelines | API costs accumulate per PR run; incremental execution strategy needed |
| MCP reusability | One server reused across multiple clients | Still mostly community projects, so maintenance continuity is a variable |
| Determinism | Faster than humans and never gets tired | LLM output non-determinism makes schema and placeholder defenses mandatory |
Traps That Frequently Appear in Practice
LLM output non-determinism: Both the Step 2 report and the Step 3 draft can produce different results from the same prompt. If downstream steps consume this output programmatically, always attach a schema validator, and set a retry limit plus a human-intervention fallback path for validation failures.
Accumulating API costs: Processing all translation keys on every PR run adds up fast. You need an incremental execution strategy that only processes changed keys. Extracting changed source and locale files with git diff --name-only origin/main...HEAD and passing them to Codex is effective.
Network isolation: Even in the workspace-write sandbox, network access is restricted by default. MCP servers that need to call external TMS APIs require a separate network allowlist configuration. Not knowing this leads to wasted hours wondering "why can't the MCP server reach the TMS." Read the sandbox options documentation for your Codex CLI version first.
Placeholder corruption: ICU syntax like {name} and {count, plural, one {# item} other {# items}} occasionally breaks after translation. Even with it spelled out in AGENTS.md, it's not foolproof — it's safer to have a separate placeholder-matching validation step in CI. A short script that checks whether the set of {...} tokens in the source value matches the set in the translated value catches the majority of these cases.
Dead key cleanup: Removing translation keys that correspond to deleted UI is not handled automatically without extra configuration. Use the dead-key detection feature of the Ret2Hell/i18n-mcp server, or add "also include a separate section listing keys no longer referenced anywhere in the source code" to your audit prompt to surface them for review. Auto-deletion is risky — the practical approach is to generate the report and let humans decide on deletion.
Model selection: GPT-4o is fast and relatively inexpensive for general instruction-following, making it good for bulk report generation. Reasoning models like o1 and o3 are better suited for complex judgment (e.g., dead key candidate selection, context-based translation nuance evaluation). Rather than using the same model for both audit and draft, splitting by stage gives better cost-quality balance.
Two-Layer Defense: Build-Time Detection + Runtime Fallback
Relying solely on the Codex CLI pipeline is less robust than a two-layer defense.
The structure is: detect at build time with i18next-cli's status --ci option, and fall back at runtime using saveMissing + fallbackLng. Even when the Codex CLI pipeline covers most cases, it's practically safer to keep a fallback layer in case keys slip through on unexpected paths at runtime.
What This Pipeline Doesn't Cover
Once this is set up, the time spent manually hunting "which key is missing where" clearly disappears. But the following remain in human territory.
- Tone and brand consistency: Even when an auto-translated draft is grammatically correct, it may clash with the service's voice. Tone guides belong in a separate document and are upheld by a reviewer's eye.
- Legal, medical, and financial domain expressions: These are areas where LLM translations are hard to ship to production without human review. Use the pipeline only as the front end of a "auto-generate draft + expert review" workflow.
- Cultural localization: Non-text elements like dates, currencies, images, and colors are outside this pipeline's scope. A separate resource management system per locale is needed.
- Strings under A/B testing: Exposing experiment copy to an auto-translate and auto-commit pipeline risks locking unwanted variants into locale files. Separate experiment strings into their own namespace and exclude them from the pipeline.
- High-determinism boundaries: Text where wording is close to contractual — release notes, marketing copy — is better excluded from automation to avoid incidents.
The pipeline is a tool for reducing repetitive labor, not an entity that takes on translation responsibility. Establishing this boundary clearly within the team ensures that ownership of translation quality doesn't blur after automation is introduced.
References
- GitHub - openai/codex
- Codex GitHub Action official documentation
- Model Context Protocol official site
- GitHub - Ret2Hell/i18n-mcp
- GitHub - gtrias/i18next-mcp-server
- GitHub - dalisys/i18n-mcp
- Missing Translations in i18next: Fallbacks, Detection & Fixes
- Automating i18next Translations with saveMissing and Locize AI
- i18next-cli documentation