Skip to main content
Glama
shreyas411

Translation Sync MCP Server

by shreyas411
README.md
# Translation Sync MCP Server

A general-purpose MCP server that keeps per-language JSON locale files in sync with an English source file, using the free [MyMemory](https://mymemory.translated.net/) translation API (no API key required). Works with any project that stores locales as `src/assets/i18n/<lang>/<lang>.json` (or a custom path you configure).

## Features

- 🌍 Translates to any of MyMemory's ~111 supported languages, not just a fixed list
- πŸ†“ No API key, no signup β€” free MyMemory API
- πŸ”„ Two ways to detect changes: a local cache diff, or a JSON-based git diff (immune to the "comma shift" false positives of text-based git diff parsing)
- 🧩 Validates that `{{placeholder}}` / `%s` / `%1$s` tokens survive translation β€” a mismatch is never written to disk
- πŸ€– Agent fallback β€” if MyMemory can't produce a usable translation (API error, quota, bad language code, or a token mismatch), the server does **not** write English text or a broken translation. It reports the failure and asks the calling agent to translate it directly; you feed the result back via `manual_translations`
- πŸ—‘οΈ Prunes target-language keys that no longer exist in the source (including add-then-delete-in-the-same-diff via orphan detection)
- πŸ“ Preserves each target file's existing JSON indentation
- ⚑ Retries transient MyMemory failures (429/5xx) with backoff; bounded concurrency so it doesn't hammer the API

## Requirements

- Node.js 18+ (uses the native `fetch`)
- A git repository (only required for `preview_git_changes` / `diffGitChanges`; the cache-based tools work without git)

## Installation

```bash
cd .mcp/translation-sync
npm install
```

Add it to your project's `.mcp.json` (paths are resolved relative to this file, not hardcoded β€” see Configuration below):

```json
{
  "mcpServers": {
    "translation-sync": {
      "command": "node",
      "args": ["./.mcp/translation-sync/index.js"]
    }
  }
}
```

Restart your MCP client (Claude Code, etc.) to pick it up.

## Configuration

The server has no hardcoded project path. It resolves things in this order:

| Setting | Resolution order |
|---|---|
| Project root | `TRANSLATION_SYNC_ROOT` env var β†’ nearest enclosing git repo root (`git rev-parse --show-toplevel`) β†’ two directories above this script |
| Locale folder | `TRANSLATION_SYNC_I18N_PATH` env var (relative to project root) β†’ `src/assets/i18n` |

Set these as env vars in `.mcp.json` if your project's locale folder lives somewhere else:

```json
{
  "mcpServers": {
    "translation-sync": {
      "command": "node",
      "args": ["./.mcp/translation-sync/index.js"],
      "env": {
        "TRANSLATION_SYNC_I18N_PATH": "src/locales"
      }
    }
  }
}
```

Expected folder layout under the locale path β€” one subfolder per language code, containing a file whose name mirrors the source file's with `en` replaced by that code:

```
src/assets/i18n/
β”œβ”€β”€ en/
β”‚   β”œβ”€β”€ en.json
β”‚   └── en-b2b.json
β”œβ”€β”€ es/
β”‚   β”œβ”€β”€ es.json
β”‚   └── es-b2b.json
└── fr/
    └── fr.json
```

A language folder that doesn't exist yet is created automatically the first time you sync to it.

## MCP Tools

### `list_available_languages`

```js
list_available_languages({ scope: "project" }) // this project's configured locale folders (default)
list_available_languages({ scope: "all" })     // every language MyMemory supports, project-agnostic
```

### `preview_changes`

Cache-based diff: compares the source file against the last time `sync_translations` ran (stored in `.translation-cache.json`). No git required.

```js
preview_changes({ source_file: "en.json" })
```

### `preview_git_changes`

Git-based diff against a ref (defaults to `HEAD`). Use this to see what's changed but not yet synced/committed.

```js
preview_git_changes({ source_file: "en.json", base_ref: "HEAD" })
```

### `translate_text`

One-off translation utility, independent of any locale file.

```js
translate_text({ text: "Collection Progress", target_language: "es" })
```

If MyMemory can't translate it, the response asks you (the calling agent) to translate it yourself instead of returning English or a broken string.

### `sync_translations`

The main tool. Syncs added/modified keys into every target language file and removes keys no longer in the source.

```js
// Sync everything that changed, across all of this project's configured languages
sync_translations({ source_file: "en.json" })

// Only specific languages
sync_translations({ source_file: "en.json", target_languages: ["es", "fr", "de"] })

// Only specific keys
sync_translations({ source_file: "en.json", keys: ["text.collection.progress"] })

// Re-translate keys even if the target already has a value
sync_translations({ source_file: "en.json", force_update: true })

// Supply your own translations for keys MyMemory couldn't handle
// (from a prior response's "needs_agent_translation" list)
sync_translations({
  source_file: "en.json",
  manual_translations: {
    es: { "text.collection.progress": "Progreso de la colecciΓ³n" }
  }
})
```

**When MyMemory fails a key** (API error, exhausted retries, or a placeholder/token mismatch), that key/language pair is **not written** to the target file. It's listed in the response under `needs_agent_translation` with the reason. Translate those yourself and resync with `manual_translations` to fill them in β€” `manual_translations` entries are written as-is, no API call.

## Recommended workflow

1. Edit `src/assets/i18n/en/en.json`.
2. Call `preview_git_changes` (or `preview_changes`) to see what changed.
3. Call `sync_translations` for the languages you need.
4. If the response lists `needs_agent_translation`, translate those yourself and call `sync_translations` again with `manual_translations`.
5. Review the diff in target language files, then commit.

## How it works

- **Change detection**: `preview_changes`/`sync_translations` diff the current source JSON against a cached snapshot from the last sync. `preview_git_changes` instead diffs parsed JSON against a git ref β€” both compare parsed objects, never raw text, so a key deletion elsewhere in the file never gets misread as an edit to its neighbor.
- **Translation**: each changed value is sent to MyMemory individually, 5 requests in flight at a time, with retry/backoff on 429/5xx. MyMemory's own errors (e.g. "invalid target language", quota messages) come back as HTTP 200 with the error text *as* `translatedText` β€” the server checks `responseStatus` in the body to catch these rather than trusting the field's mere presence.
- **Token safety**: after translation, `{{...}}` and `%s`/`%1$s`-style tokens in the source and translated text are compared; a mismatch is treated as a failure, not written.
- **Pruning**: any key present in a target file but absent from the current source is deleted, including keys that were added and removed again within the same diff (orphan detection).

## Known limitations

- **MyMemory free tier**: ~5,000 words/day per IP. Heavy use may hit quota; failures surface via `needs_agent_translation` rather than failing silently.
- **Translation quality**: MyMemory is a free machine-translation/translation-memory service, not professional-grade. Always review target-language diffs before committing, especially for domain-specific terminology.
- **Quota**: hitting the daily MyMemory word limit surfaces as a clear `needs_agent_translation` reason ("daily free-tier word quota exhausted") rather than a generic API error.

## Troubleshooting

| Symptom | Fix |
|---|---|
| Server not listed as a tool | Check `.mcp.json`, confirm Node 18+, restart the MCP client |
| `ENOENT ... scandir .../i18n` | `TRANSLATION_SYNC_I18N_PATH` / project root didn't resolve to your actual locale folder β€” set the env vars explicitly |
| A key keeps appearing in `needs_agent_translation` | MyMemory can't translate it (bad language code, quota, or the translation keeps dropping a `{{token}}`/`%s`) β€” translate it manually and pass it via `manual_translations` |
| Translations look wrong/literal | MyMemory is machine translation β€” use `force_update` after fixing the source text, or supply a manual translation |

## License

MIT

TDQS

A3.8/5.0

Scored across 5 tools

Disambiguation5/5

Each tool has a clearly distinct role: sync performs writes, two previews use different comparison baselines (cache vs git ref), list reports locales, and translate_text handles ad hoc translation. The most similar pair, preview_changes and preview_git_changes, is explicitly differentiated by baseline and description.

Naming Consistency5/5

All tool names use snake_case with a verb-first pattern: sync_translations, preview_changes, preview_git_changes, list_available_languages, translate_text. The convention is predictable and readable throughout.

Tool Count5/5

Five tools is well-scoped for a translation synchronization server. Each tool supports a distinct part of the workflow without obvious bloat or missing core operations.

Completeness4/5

The tool set covers the core lifecycle: syncing translations, previewing changes from cache and git, listing languages, and translating individual text. Minor gaps exist around cache/config management or per-key editing, but agents can work around them.

Maintenance

ActivityMaintained
ResponsivenessNo issues