Topics

Van Google Translate API naar LLM-gebaseerde vertaling — hoe we onze meertalige aanpak hebben heringenierd met Claude API + R2 differentieel cachen

  • column

Eerder schreef ik een artikel over meertaligheid van Astro SSG-sites met Google Translate API. Dit was een systeem dat statische HTML na de build vertaalde met Google Cloud Translation API om versies in verschillende talen te genereren. Als goedkope benadering voor meertaligheid was het volkomen praktisch.

Na enige tijd merkte ik echter punten op die zowel vanuit SEO als vertaalkwaliteit zorgwekkend waren. Kijk in een ander artikel voor het verhaal over "waarom we Google Translate hebben stopgezet".

Artikel: Migratie van automatische vertaling van Google Translate naar Claude API

Dit artikel gaat over "hoe we het eigenlijk hebben herbouwd" — de implementatiedetails. We vervingen de engine van Google Translate door een LLM (Claude API) en eliminerden daarbij ook de handmatige werkzaamheden die eerder achter waren gebleven.

Vertalen met LLM tijdens de build, differentiaalbuffering in R2

De basisaanpak is hetzelfde als voorgaande keer: vertaling vindt plaats tijdens de build (aan serverzijde). We roepen Claude API niet aan vanuit de browser. Wat verschilt is de vertaalengine en waar de buffering is opgeslagen.

De buildstroom ziet er als volgt uit.

npm run build
  ├── fetch-microcms   (microCMSから記事データ取得)
  ├── astro build      (日本語HTMLを生成 → dist/)
  ├── translate        (各ロケールのHTMLを生成 → dist/{locale}/)
  └── update-xml       (sitemap更新)

Wat translate doet (binnenin translate-html-llm.mjs) gebeurt ruwweg in deze volgorde.

  1. Download het vertaalbuffer (één JSON-bestand) van Cloudflare R2
  2. Laad elk HTML-bestand onder dist/ in met cheerio en extraheer de tekst die moet worden vertaald
  3. Als het in de cache aanwezig is, gebruik dit; voor ontbrekende items stuurt u alleen naar Claude API
  4. Vervang tekst door vertaalde resultaten en schrijf naar dist/{locale}/
  5. Merge de nieuw vertaalde inhoud in de cache en upload naar R2

Het sleutelaspect is tweeledig: alleen de gewijzigde onderdelen vertalen (verschilvertaling) en de cache in R2 plaatsen.

① Hoe natuurlijke vertalingen maken wanneer inline tags voorkomen

Dit was de belangrijkste verbetering die ik graag wilde doorvoeren.

Stel bijvoorbeeld dat de brondtekst dit HTML bevat.

<p>私たちは<strong>ウェブアクセシビリティ</strong>を重視しています</p>

Als we normaal met vertaling zouden omgaan, zouden wij / webtoegankelijkheid / serieus nemen drie afzonderlijke fragmenten apart vertalen. Omdat het Nederlands en het Japans verschillende woordvolgorde hebben, zouden de vertaalde fragmenten op hun oorspronkelijke plaats niet natuurlijk overkomen en zou <strong> op verkeerde plaatsen terechtkomen. Hoe meer inline-tags in een zin, hoe meer het kapotgaat. Dit was de grootste frustratie met het oude systeem.

De verbeteringsmethode bestaat uit twee fasen.

Eerste stap: chunk de inhoud per blokelement. De minimale eenheid voor vertaling is niet de "ruimte tussen tags", maar de volledige inhoud (innerHTML) van blokelementen zoals p h1–h6 li td blockquote. We splitsen geen zinnen.

Tweede stap: vervang inline-tags door markers en geef de hele zin als één vertaaleenheid door. We vervangen de <strong> en <a> in een chunk tijdelijk door markers zoals ....

私たちは[[T1]]ウェブアクセシビリティ[[/T1]]を重視しています

We instrueren het LLM: "Dit is één zin. Vertaal het natuurlijk en plaats dezelfde markers opnieuw bij woorden die nadruk behoeven. Je mag de markerposities verplaatsen naar de woordvolgorde van de doeltaal." Nadat we de vertaling ontvangen, herstellen we de markers naar de originele <strong> en <a href="...">. Attributen (zoals href en class) behouden we ongewijzigd.

Zo werkt het: <strong> verwijst nu naar het juiste woord ook aan de Engelse kant, en de zin klinkt natuurlijk.

② Differentiaalbepaling van vertalingen en R2-cachebeheer

Als we alles elke keer vertaalden, zouden we nooit genoeg budget hebben. We gebruikten eerder al een cache met de naam translate-cache.json, maar deze keer hebben we de sleutelstructuur en opslaglocatie herzien.

Wanneer je de prompt aanpast, worden de vertalingen opnieuw gegenereerd

De cachesleutel wordt samengesteld uit "originele Japanse tekst + landinstelling + inhoud van het vertaalprompt". Op deze manier wordt automatisch alleen het overeenkomstige item als "niet in cache" behandeld als de Japanse tekst verandert, en worden alle items opnieuw vertaald als de vertaalinstructies of terminologierichtlijnen (prompt) wijzigen.

Het doel is om ongelukken te voorkomen waarbij oude vertalingen in de cache blijven hangen nadat we de prompt hebben verbeterd. In de praktijk gebruiken we hiervoor strings die zijn gehasht met sha256 als sleutel.

De cachestructuur ziet er zo uit:

{"<sha256のキー>":{"value":"翻訳結果(マーカー込み)","locale":"en","model":"claude-haiku-4-5-20251001","translatedAt":"2026-06-12T..."}}

Cache in R2 gezet, handmatig werk geëlimineerd

Dit is het opruimen van de vorige achterstand.

Vorige keer beheerden we de cache via JSON-bestanden in de repository. Daarom, wanneer we artikelen toevoegden in het CMS en via deploy hooks bouwden, kon de server alleen de oude cache op Git zien. We werkten rond dit probleem met de regel: 'als je een artikel toevoegt, build dan lokaal en push het bijgewerkte cachebestand naar Git'.

Deze keer plaatsten we de cache in een enkele JSON blob op Cloudflare R2. Bij elke build halen we deze uit R2 op en schrijven hem terug wanneer klaar. Dit zorgt ervoor dat de cache persistent blijft, zelfs bij builds via webhooks, en het handmatige lokale build→push-werk is volledig verdwenen. Voeg een artikel toe en push het, dan worden alleen de nieuwe items vertaald en wordt de cache automatisch bijgewerkt.

Vertaalprioriteitstabel

Inconsistenties in vertalingen (zoals schommelingen in het merk Liberogic) waren ook vorige keer een pijnpunt. Dit keer hanteren we vier niveaus.

  1. Handmatige overschrijving (data-i18n-key) ― HTML-elementen met data-i18n-key gebruiken handgeschreven vertalingen die van tevoren zijn voorbereid. Geen LLM, geen woordenboek — dit garandeert dat 'deze vertaling exact dit moet zijn'.
  2. Termenlijst (glossary) ― Voor herhaalde vaste labels zoals navigatie en paginatitels fixeren we vertalingen in een JSON woordenboek. Update het woordenboek en herbouw — de wijzigingen zijn onmiddellijk zichtbaar.
  3. R2 cache ― Als geen van beide bovenstaande niveaus van toepassing is, controleren we de cache.
  4. Claude API ― Alleen items die nergens anders voorkomen, gaan naar het LLM.

Consistentie van termen in de tekst kan niet volledig worden bereikt met puntgerichte woordenboeken (die geen gedeeltelijke overeenkomsten ondersteunen), dus gebruiken we termijnhints aan de systeempromptkant om ze losjes af te stemmen.

Omgaan met de 'gewoonten' van LLM's

Machine translation produceert vreemde vertalingen, maar dat was eerder ook al het geval. LLM's hebben hun eigen gewoonten. Hier zijn enkele ervan die in de praktijk naar voren zijn gekomen.

  • Technische zinnen blijven helemaal in het Engels achter. Door de lijst met 'niet-te-vertalen termen' zoals API, React en Vue te veel toe te passen, retourneert het LLM soms hele zinnen in het Engels. We pakken dit aan door in de gebruikersprompt sterker te benadrukken 'uitvoer in de doeltaallocale'.
  • De positie van markers draait semantisch om. Wanneer het LLM de te benadrukken woorden verkeerd beoordeelt, kan <strong> over een vreemde reeks gaan. Dit wordt opgelost door de betrokken invoer te verwijderen en opnieuw te vertalen.
  • Reacties in CJK-talen worden halverwege afgekapt. Elk Chinees/Japans teken verbruikt meer tokens; wanneer je er veel tegelijk invoert, bereikt de uitvoer de limiet en wordt JSON beschadigd. We lossen dit op door max_tokens te verhogen en het aantal items per batch te verminderen.
  • Verlies van letterlijke en vervormde CJK-datums. Regelbreaks kunnen als tekenreeks binnenslopen, en CJK-datums kunnen onnodig spaties krijgen, zoals 22 mei 2026. Deze worden in na-verwerkingsscripts opgeschoond.

Een operationeel inzicht dat hielp was dat problematische onderdelen één voor één verwijderen en opnieuw vertalen het hoogste succespercentage heeft. Wanneer je probeert meerdere dingen tegelijk op te lossen, gaat het LLM dezelfde structuur vaak op dezelfde manier verkeerd beoordelen. Haast je langzaam.

Over kosten

We gebruiken Claude Haiku 4.5 als standaard. Met kostenfocus verbeteren we eerst via prompts en woordenboeken als kwaliteitsproblemen ontstaan. We gebruiken prompt caching op de systeempromptkant om de vaste delen telkens opnieuw in te voeren zonder kosten.

Hier is een indicatie van de werkelijke kosten.

Inhoud

Kosten

Eerste volledige vertaling van één locale

ongeveer $2,50-3

Eerste volledige vertaling van alle locales

ongeveer $20

Standaard deployment (alleen cache hits)

vrijwel $0

Eén artikel toevoegen (enkele vertalingen)

ongeveer $0,01

Dagelijkse implementaties zijn vrijwel gratis, en het toevoegen van artikelen kost slechts 1 tot enkele yen. Hoewel de initiële heraanleg aanzienlijke kosten met zich mee bracht, zijn de bedrijfskosten daarna juist lager geworden.

Samenvatting

Door over te schakelen van Google Translate naar LLM-vertaling zijn de bedrijfskosten beperkt gebleven en is de vertaalkwaliteit aanzienlijk verbeterd.

Omdat het LLM is, is niet alles automatisch en perfect. We blijven geduldig aanpassingen doorvoeren, ongebruikelijke patronen opsporen en verhelpen. Omdat we nu alleen de prompt en het woordenboek hoeven aan te passen, is de verbeteringscyclus veel sneller geworden.

Auteur van dit artikel

Vanuit DTP de wereld van het web in gestapt en merkte al snel dat hij markering, frontend, directie en accessibility allemaal beheerst — een echte 'meester van techniek'. Sinds de oprichting van Liberogic een multitalent en inmiddels een levend naslagwerk in het bedrijf. Tegenwoordig is hij geïnteresseerd in de vraag "Kunnen we accessibility-implementatie meer aan AI overlaten?" en experimenteert hij graag met efficiëntie via prompts. Zowel technisch als mentaal nog volop in ontwikkeling.

Ayumi Futamata

IAAP-gecertificeerd webtoegankelijkheidsspecialist (WAS) / Opmaakingenieur / Frontend-ingenieur / Webdirecteur

Artikelen van deze medewerker bekijken

Ons sterke punt is ons betrouwbare teamstructuur en snelle responsiviteit

Bij Liberogic worden ervaren teamleden actief ingezet voor projectvoering, wat door klanten zeer wordt gewaardeerd.
We wijzen vakbekwaam projectmanagers en directors aan en streven ernaar projecten soepel te laten verlopen. We voorkomen onnodig kostenverhogingen door volledig inzet te vermijden en wijzen middelen toe waar ze het meest geschikt zijn. Onze snelheid bij taakanalyse en bij het opmaken en indienen van offertes is goed bekend.

* Wij voeren niet actief SES-achtige permanente werkzaamheden uit, dus graag van tevoren dank voor uw begrip.

U kunt vrijwel alle grote projectmanagementtools en chattoolsgebruiken, zoals Slack, Teams, Redmine, Backlog, Asana, Jira, Notion, Google Workspace, Zoom en Webex.

Neem contact met ons op voor advies over uw webvragen.

Casestudies