Why I started
fixops.pro has four languages: English as the main one, plus German, Greek and Ukrainian. Translating every page, every form label and every category name by hand takes a long time, and the plugin's own automatic translation often reads dry and imprecise. I decided to try a different approach: hand the routine work to an AI agent and keep control myself.
This research note covers how it works, what came out of it, and the mistakes I caught along the way.
An experiment with Claude
I have used AI in my work for a long time, and I wanted to see how far I could go with Claude from Anthropic on one concrete task: a multilingual WordPress site. What mattered to me was not "ask a model to translate a paragraph", but to build it into a real process, so that it finds untranslated strings on its own, writes the result where the plugin expects it, and does not break the site while doing it.
How it works technically
TranslatePress itself registers only a few settings actions in WordPress (for example, the list of languages), and by default they are not open to agents over MCP. It has no actions for reading and writing the translations themselves. I wrote that missing part as a small plugin, TranslatePress AI Bridge. It adds "abilities" to the site for working with the dictionary, and agents connect to them from there.
Site layer: WordPress Abilities
Since WordPress 6.9 the core has an Abilities API: a plugin declares small operations with a description of their inputs and outputs. My plugin declares seven of them:
| Ability | What it does |
|---|---|
get-strings | Returns dictionary strings of a chosen language, page by page. Untranslated only by default. |
register-page-strings | Loads a page in the target language so TranslatePress adds its strings to the dictionary. |
save-translations | Writes "id and translation" pairs through TranslatePress' own query layer. |
get-excluded-selectors and update-excluded-selectors | Read and change the "Do not translate these selectors" list. |
purge-strings | Deletes junk untranslated strings by id or regular expression. |
translate-via-api | Translates untranslated strings with Claude, using the API key set in the plugin. |
Connection layer: MCP
Model Context Protocol connects such abilities to an AI agent as tools. The site runs the MCP Adapter, which publishes the flagged abilities. The agent first asks the site what it can do and what parameters each action takes, and only then calls them.
So in the chat I tell Claude "translate the strings into Greek", and it reads the dictionary, translates and writes the result by itself. I do not have to open the visual TranslatePress editor and wait for its admin-ajax request queue.
Autonomous layer: my own Anthropic API key
Working through a chat means I have to sit there and watch. So that the site can translate on its own, without a client and without an open MCP session, I added Anthropic API support to the plugin, using my own key (Anthropic bills the key owner). There are three ways to start a run:
- a button in the plugin settings;
- a WP-CLI command:
wp trpab translate --lang=de_DE --dry-run, then the same without--dry-run; - the
translate-via-apiability, or the built-in "Claude (Anthropic API)" engine in TranslatePress automatic translation.
By default the plugin uses the claude-haiku-4-5 model, sends 40 strings per request and at most 200 strings per run. A glossary and style notes can be set in the settings, for example "write Website, not Webseite". The key can live in wp-config.php, in an environment variable, or encrypted in the database. The last option never shows the key again.
Trust model
I set up trust so that the agent can make mistakes without big consequences:
- Permissions. The same as the TranslatePress editor (
manage_optionsby default). - Machine status.
save-translationsstores translations with the "machine" status and by default never overwrites strings reviewed by a human. - Dry run. Writing, deleting and API translation run as dry runs by default, so they show what would happen without changing anything.
- Own site only.
register-page-stringsonly requests URLs on the site's own domain. - Backup. Before bulk operations you need a copy of the
wp_trp_dictionary_*tables.
The principle is simple: the agent proposes and writes, while I set the rules, review the result and make the decisions.
What went wrong
Junk in the dictionary
As soon as the agent registered the first page, the dictionary filled up with a lot of noise:
- icon names (
check_circle,gpp_bad,volunteer_activism). This is the Material Symbols font, where an icon is set as text, and the plugin honestly took it for words; - Divi template text such as lorem ipsum and "Your content goes here";
- test posts such as "Hello world!";
- the typing line in the header: it prints letter by letter, and every intermediate version (
checking client-c.io … res,… resp,… respo) became a separate string; - numbers and a time zone such as
18msandGMT+2.
Each language collected about fifty such strings. I removed them with a regular expression, first as a dry run that shows exactly what would be deleted, and only then for real.
But cleaning the dictionary treats the symptom. The cause is elsewhere, so I did two things:
- I added the containers that hold dynamic content to the plugin's exclusions (
#tickerText,[data-count-to],.notranslate); - I moved icons out of text and into CSS: instead of a word in the markup, the icon is now drawn with
contentin the stylesheet. The plugin does not see that text, so the strings no longer appear.
IDs are not shared
The same English phrase has a different number in each language. My Greek dictionary was offset from the German one by dozens of ids. If you apply one language's list of ids to another, the translation is written to the wrong strings. So the agent fetches a fresh list for every language.
The cache shows the old page
I saved the translations, opened the page and saw English. The cause was the LiteSpeed cache serving a stored copy. To confirm the translations were in place, I requested the page with an arbitrary parameter in the URL, which bypasses the cache. After purging the cache everything fell into place.
Pages without posts are not translated
The "Off the Grid" category had been created but had no posts yet, and its page returned a 404. Strings with its name only appeared in the dictionary once the category started showing up on other pages.
Where the AI got it wrong
This is the most important part. The translations looked smooth, but when I reread them carefully I found real errors.
In Greek:
- a non-existent word instead of "I started" (the text had "Ύργισα" instead of "Ξεκίνησα");
- a phrase meaning "sites that get lost because of hackers" instead of "sites that get hacked";
- list items ended with a semicolon. In Greek that is a question mark, so the list read like a chain of questions;
- the word for "program" instead of "company" where an agency was meant;
- several typos and agreement errors.
In Ukrainian: one word had a Latin letter i inside Cyrillic. It is invisible to the eye, but a text search will not find such a word.
I fixed about thirty Greek strings and one Ukrainian string. I did not reread the German by hand, and that is an honest weak spot.
The conclusion: an AI translation works as a draft, not as a final result. This matters most for languages you do not read yourself.
How I control quality
- String status. Every translation is saved with the "machine" status. Strings a human has reviewed get the "reviewed" status, and by default the agent leaves them alone.
- Dry run. Any bulk action, especially deletion, first runs in a mode that changes nothing and only shows a list.
- A native reader. For languages you do not know, you need at least one reader. For Greek, that is the step I would take earlier next time.
- Checking the live page. I do not trust a write to the dictionary until I see the translation on the live page, with the cache bypassed.
What to check before you repeat this
- License and terms. My plugin is built for the free TranslatePress and works through its own query layer. Paid versions and add-ons may have their own terms of use, so before connecting an AI agent to them, read those terms and ask support if in doubt.
- Whose texts they are. The strings go to a model. For your own site that is fine, but you must not send client texts this way without consent.
- Whose bill it is. With the API, every run is billed to your key. Start with a dry run: it shows how many strings and tokens a run would take.
- A backup. Before bulk operations, make a copy of the dictionary tables.
Summary
In one day I got the post, the interface and the categories translated into three languages and put the dictionary in order. Doing it by hand would have taken much longer.
But the main result is a different one. The agent speeds up the routine, while responsibility for accuracy stays with the human. Without a review, a word that does not exist would have stayed in the Greek text.
Let's work together
If you need a multilingual WordPress site, help with TranslatePress, or a careful way to bring AI into such processes without losing quality, get in touch. I reply fast and without unnecessary calls.