# Translating website texts on Workers AI: four models, one weekday trap

I was comparing Workers AI models for translating website texts from Brazilian Portuguese into Spanish and English: which model to use, and what it costs.

I tested four models on the same 20 texts, then tried to fix the most promising one with a better prompt. All numbers are measured, from one to three runs per model, so read it as a practical test, not a benchmark.

## The test

**Texts:** 20 texts from a sample website for a food trailer, about 1,800 characters in total: a tagline, an "about" text with two paragraphs, an announcement, opening days, an FAQ, dish descriptions and an image alt text. Hand-made Spanish and English versions of the same texts served as the reference.

**Directions:** Portuguese → Spanish and Portuguese → English. The Spanish is meant for Argentina, so the prompt asks for *rioplatense* Spanish: "vos" instead of "tú", and Argentine words where they differ.

**LLMs:** one call per language, all 20 texts in it as numbered blocks (`### 1`, `### 2`, …), temperature 0.2, the same system prompt for every model:

```text
You translate texts from Brazilian Portuguese into Spanish as spoken in
Argentina (rioplatense): use "vos" (not "tú") when addressing the reader, and Argentine words where
they differ (for example "panchos", "papas fritas").
Rules:
- Translate faithfully. Do not add, drop or embellish anything; no new claims, prices or numbers.
- Keep the tone simple and friendly, like the original.
- Keep Brazilian food names that have no real equivalent (for example pastel, garapa, batata palha)
  as they are, in the original spelling.
- Keep units, times and numbers exactly as written (150 g, 18h, 500 ml).
- Keep line breaks inside a text.
- Answer with every text, in the same order, each starting with its own marker line exactly as given
  (### 1, ### 2, ...). Nothing else: no notes, no explanation.
```

**Dedicated translation model:** `m2m100-1.2b` takes no prompt, so it got one call per text with `source_lang: "pt"` and `target_lang`.

## The models

|Model|Kind|Neurons per M tokens (in / out)|
|-|-|-|
|`@cf/meta/llama-4-scout-17b-16e-instruct`|LLM, 17B active (MoE)|24,545 / 77,273|
|`@cf/mistralai/mistral-small-3.1-24b-instruct`|LLM, 24B|31,876 / 50,488|
|`@cf/meta/llama-3.1-8b-instruct-fp8`|LLM, 8B, quantized|13,778 / 26,128|
|`@cf/meta/m2m100-1.2b`|translation model|31,050 / 31,050|

Two models I wanted to include did not make it:

* **`indictrans2-en-indic-1B`** looks like a candidate in the catalog's translation category, but it only translates *from English into Indian languages*. It cannot do Portuguese → Spanish at all.
* **`gemma-3-12b-it`** answered `5018: This account is not allowed to access @cf/google/gemma-3-12b-it` on my (free) account.

## Results at a glance

||Scout|Mistral Small|Llama 3.1 8B|m2m100|
|-|-|-|-|-|
|Neurons, es / en|59.8 / 53.5|53.6 / 48.8|26.5 / 22.9|31.7 / 29.5|
|Time, es / en|23 s / 14 s|10 s / 12 s|33 s / 31 s|18 s / 32 s (20 calls)|
|All 20 texts back, markers intact|yes|yes|yes|n/a (one call per text)|
|Wrong facts|none|**wrong weekdays** (es + en)|**wrong weekdays**, wrong dishes|hallucinated and dropped sentences|
|Argentine Spanish|mostly|yes|no|no|

The neuron counts come from the `usage.neurons` field that the REST API returns with every response.

## The weekday trap

One text was simply *"Quarta e quinta, das 18h às 23h"*: Wednesday and Thursday, 6 to 11 pm. Portuguese counts weekdays (*segunda-feira* = Monday, *terça* = Tuesday, *quarta* = Wednesday, *quinta* = Thursday), so *quarta* looks like "fourth" and *quinta* like "fifth".

* **Scout:** *"Miércoles y jueves"* / *"Wednesday and Thursday"*. Correct.
* **Mistral Small:** *"Jueves y viernes"* / *"Thursday and Friday"*. Wrong in both languages: shifted by one day.
* **Llama 3.1 8B:** *"Cuarta y quinta"*. Not Spanish; the Portuguese words with a Spanish spelling.
* **m2m100:** *"Miércoles y Jueves"* / *"Wednesday and Thursday"*. Correct.

This is the worst kind of error for a website: the translation reads perfectly, whoever checks it may not read the target language well, and customers turn up on the wrong day. It argues for a deterministic check after the model, whatever the model: weekdays, times, prices and numbers in the source must map to the same values in the translation.

## Per model

### llama-4-scout: usable

Faithful, all numbers and times right, line breaks kept, vos used (*"armá"*, *"hacé"*, *"vos pagás"*). Weak spots: it slipped into Spain's plural *vosotros* three times (*"Mirad"*), and picked *cola* and *dinero* where Argentina says *fila* and *efectivo*. English was fine, a bit literal (*"combined menu"*; *limão* became *lemon*, which in Brazil is a lime).

### mistral-small-3.1-24b: the best Spanish, one serious error

The most natural Argentine Spanish of the four: *pancho*, *papas fritas*, *vos pagás*, *retirás*, and *ustedes* forms (*"vean"*, *"hagan"*) for the plural, which is correct in Argentina. Also the fastest. But it moved the opening days by one day, in both languages. Without the weekday error it would have been my pick; see the second round below.

### llama-3.1-8b-instruct-fp8: cheapest, not good enough

Roughly half the neurons of Scout, but in Spanish it changed facts: *cachorro-quente* and even *salsicha* (sausage) became *choripán* (a chorizo sandwich), *batata palha* (potato sticks) became *papas arrugadas* (a dish from the Canary Islands), the trailer became a *camión*, the weekdays came out as *"Cuarta y quinta"*, and it used *usted* instead of *vos*. It also turned a section name ("Onde nos encontrar", *Where to find us*) into a question. The English was acceptable but stiff (*"fried potato"*, *"the point of each day"*). It is also the 8B variant without JSON mode on Workers AI, which matters if you want structured output.

### m2m100-1.2b: a cautionary tale

Cheap per call and it got the weekdays right, but:

* For *"Salsicha, molho de tomate, milho, batata palha, ketchup e mostarda"* it returned, in English: *"This is the most important part of the history of the world, and the most important part of the world."*
* *Garapa* (sugarcane juice) became *"Calabaza molida… congelada"* (ground pumpkin, frozen) in Spanish and *"Melted garlic, frozen"* in English.
* It **dropped whole sentences**: the FAQ answer *"See Where to find us for each day's spot. If it rains…"* lost its first sentence; another text lost its second.
* Spain's Spanish (*patata*, *perro caliente*), and there is no way to ask for a variant: the model takes no instructions.

Longer text blocks were no better. The two-paragraph "about" text came back as one paragraph, and with errors a reader notices at once:

|Portuguese|m2m100, Spanish|m2m100, English|
|-|-|-|
|*feito na chapa* (made on the griddle)|*hecha en la hoja* (made on the leaf)|*made on the plate*|
|*um trailer de lanches que roda Arapoti* (a food trailer that moves around Arapoti)|*un trailer de aperitivos que corre Arapoti*|*a snack trailer that runs Arapoti*|
|*carne moída por nós* (meat we grind ourselves)|*carne molida por nosotros*|*meat milled by us*|
|*retire no trailer* (pick it up at the trailer)|*retire en el trailer*|*remove it in the trailer*|

A dedicated translation model sounds like the safe, cheap choice; for short, idiomatic, food-heavy texts it was the least safe of the four. Why, in short:

* **It is a 2020 model.** M2M-100 was a strong research model when it came out, but recent LLMs have caught up with and overtaken this generation of translation models, especially on informal and uncommon text.
* **It was trained on whole sentences from the web.** A list like *"Salsicha, molho de tomate, milho, batata palha…"* is not a sentence; on input unlike its training data a model of this type can produce fluent text that has nothing to do with the source. Several sentences in one input invite it to drop one.
* **Rare words get guessed.** *Garapa* and *batata palha* are too rare in its training data; an LLM knows them from everything else it has read.
* **No instructions.** No language variant, no "keep this name", no "keep the line breaks".

It did get the weekdays right, and on plain, complete sentences in a common language pair it is cheap and decent. Sending it one sentence at a time would probably stop the dropped sentences, but not the guessing or the Spain Spanish.

### Why not combine models?

An obvious idea is to mix: one model for short texts, another for longer blocks, or the cheapest model that is good enough for each kind of text. I decided against it:

* **Mixed tone on one page.** A tagline in Argentine Spanish with *vos* directly above an "about" text in Spain's Spanish is noticeable to exactly the visitors the translation is for.
* **Two sets of failure modes.** Every model fails differently (weekdays here, hallucinations there), so each needs its own checks and its own prompt or pre-processing, plus a rule for which text goes where.
* **Nothing to gain.** Translating a whole website costs a few hundred neurons once (see below). Saving part of that does not pay for the extra code.

## A rule in my own prompt was wrong

I told the models to keep Brazilian food names "that have no real equivalent" untranslated, and named *batata palha* and *garapa* as examples. The models obeyed, and the result read worse than the hand translations, which say *papas pay* / *potato sticks* and *jugo de caña* / *sugarcane juice*. Better rule: translate when there is a common equivalent, keep only names without one (*pastel*), and never send names that must not change (dish names, the business name, addresses) to the model at all.

## Second round: can a better prompt fix it?

Mistral Small wrote the best Spanish, so I tried to fix its weekday error with the prompt instead of dropping the model. The improved prompt adds four rules:

```text
- Portuguese weekdays: domingo = Sunday, segunda(-feira) = Monday, terça = Tuesday, quarta = Wednesday,
  quinta = Thursday, sexta = Friday, sábado = Saturday. Translate each day by this list, never by counting.
- In Brazil "limão" is a lime.
- Translate food names when the target language has a common equivalent (batata palha = papas pay /
  potato sticks, garapa = jugo de caña / sugarcane juice); keep only names without one (pastel).
- Argentine usage: never vosotros forms ("mirad", "haced"); use vos for one reader and ustedes for
  several. Say "fila" (not "cola") and "efectivo" for cash.        (Spanish prompt only)
```

Both Mistral Small and Scout ran it **three times** per language, to see whether a fix holds or was luck.

||Mistral Small|Scout|
|-|-|-|
|Weekdays right|**3 of 3**, es and en|3 of 3, es and en|
|*vosotros* ("Mirad")|none|**still there, 3 of 3**|
|*fila*, *efectivo*, lime|yes|yes|
|*papas pay* / *jugo de caña*|yes|yes|
|*papas pay* in the **English** text|**3 of 3**|no (*potato sticks*)|
|Neurons, es / en|57.7 / 51.4|62.8 / 55.4|
|Time, es / en|15 to 19 s / 9 to 14 s|16 to 29 s / 12 to 18 s|

What this shows:

* **The weekday list fixed Mistral completely.** Given the meaning instead of having to count, it got the days right every time, in both languages.
* **Scout ignored an explicit rule.** "Never vosotros forms (*mirad*)" is in the prompt, and Scout still wrote *"Mirad abajo…"* in all three runs. Its output was identical in every run at temperature 0.2, so a retry would not have helped either.
* **Examples in a prompt leak.** I gave the food examples as *"papas pay / potato sticks"*; Mistral used the Spanish *papas pay* in its **English** text every time. Examples belong in the prompt for that target language only.
* **Mistral mixes forms of address.** It used *ustedes* for the instructions (*"Armen el pedido… envíen"*) and *vos* elsewhere (*"vos pagás"*), sometimes in one sentence. Both are correct in Argentina, but one form throughout reads better; the prompt needs to pick one.
* **A prompt only fixes the traps you know about.** Months, dates, *meio-dia*, *quinzena*: the next one is not in the list. The deterministic check after the model stays.

## What it costs

The 20 texts were about 1,800 characters. A complete small website is two to three times that, so translating one into two languages with Mistral Small or Scout costs roughly **300 neurons, once**. After that only new or changed texts are sent. On the free plan's 10,000 neurons a day that is not the bottleneck; even at paid rates it is a fraction of a cent.

So the price difference between the models hardly matters here. Correctness does.

## What I chose

**Mistral Small 3.1 24B**, with the improved prompt and three more measures. All of it is now built into the website editor's "translate" button:

1. **Prompt examples per target language only** (no *papas pay* in English), and **one form of address** for Argentine Spanish: *vos* everywhere (*armá, hacé, pedí, vos pagás*), never *tú*, *usted*, *ustedes* or *vosotros* for the reader.
2. **Names that must not change are never sent.** Only the fields that are meant to be translated go to the model; dish names, the business name, addresses and prices stay out. The business name is also in the prompt with "never translate that name".
3. **A deterministic check after the model** compares weekdays, times, prices and numbers between source and translation. A translation that loses one or brings in a new one is sent once more (at a slightly higher temperature); if it fails again it is left out and flagged, and the person translates that text by hand. The translations only go into a draft: the person reads them (a filter shows the texts the AI just translated), can correct them, and only then publishes.

With that final prompt, the same 20 texts ran three times into each language: **120 of 120 translations passed the check the first time**, none needed the retry, there were no *vosotros*/*tú*/*usted* slips, and a batch of 20 cost about 55 neurons.

The model name is a setting, so switching back to Scout (or to the next model) is one line, and the 20 texts are the regression test that shows whether the switch is safe.

## What comes next

A prompt only fixes the traps I already know about. The rest will show up in real use, when people correct the suggestions before publishing. How those corrections turn into prompt rules, through a weekly job that proposes changes as a pull request instead of editing the prompt itself, is the next part of this series.

## Caveats

* One run per model and language in the first round, three in the second; 20 texts, temperature 0.2.
* The second-round table is the prompt shown above; the final prompt (examples per target language, *vos* only) was measured separately, three runs per language (see "What I chose").
* Prices and model availability are those of Workers AI in October 2026, on a free account.

---

*Co-authored with Claude.*

If this was useful, [you can buy me a coffee](https://ko-fi.com/p47k0).

New articles: [follow via RSS](https://blog.koorevaar.com/feed.xml).
