Unicode en tekencodering: waarom je app in andere talen kapotgaat
Unicode in zakelijke apps: hoe 'José' verandert in 'José', waarom dat gebeurt en wat UTF-8 overal in productie echt vereist.

Unicode is de standaard die aan elk teken van elk schriftsysteem, van "A" tot "あ" tot "🚀", een uniek nummer geeft. Tekencodering is de aparte stap die die nummers omzet in bytes die een computer kan opslaan en versturen. De meeste software die "kapotgaat in andere talen" faalt niet bij de vertaling. Ze faalt in die eerdere, onzichtbare laag, en de fout heeft een naam: mojibake, de verminkte tekst die je krijgt wanneer de bytes met de verkeerde codering worden teruggelezen.
We beloofden een close-up van die fout in meertalige softwareontwikkeling en in internationalisatie (i18n), uitgelegd, waar een klant met de naam José verandert in "José" op een etiket of een factuur.
Unicode en UTF-8 zijn niet hetzelfde
De twee termen worden door elkaar gebruikt, en die gewoonte ligt aan de basis van de meeste verwarring.
Unicode is een kaart. Het kent aan elk teken een code point toe: een nummer, geschreven als U+XXXX. De letter "A" is U+0041. Het Chinese teken "中" is U+4E2D. De kaart alleen zegt niet hoe je dat nummer als bytes op een schijf opslaat of over een netwerk verstuurt.
UTF-8 is één manier om die kaart in bytes te coderen. Het is de dominante manier: volgens de actuele W3Techs-meting draait 99% van alle websites waarvan de codering bekend is op UTF-8. Het stelt elk code point voor met 1 tot 4 bytes: gewone letters en cijfers zonder accent passen in 1 byte (identiek aan het oude ASCII, en daarom is UTF-8 achterwaarts compatibel ermee), Latijnse letters met accent nemen doorgaans 2 bytes, de meeste Chinese, Japanse en Koreaanse tekens nemen er 3, en emoji nemen er 4.
Oudere coderingen laten dezelfde bytewaarden overeenkomen met totaal andere tekens. Windows-1252 en Latin-1 (ISO-8859-1), beide gangbaar op oudere Windows-systemen en legacy-databases, gebruiken één byte per teken en dekken alleen West-Europese schriften. Geef UTF-8-bytes aan software die Windows-1252 verwacht, of andersom, en je krijgt uitvoer die geldig lijkt maar stilletjes fout is. Dat verschil is mojibake, en het is grotendeels mechanisch: bij een zuivere decodeerfout kunnen dezelfde oorspronkelijke bytes nog aanwezig zijn, maar een verkeerde interpretatie, foutafhandeling, transcoding of opslag kan gegevens ook vervangen, afkappen of ontoegankelijk maken; controleer daarom de volledige gegevensketen.
Mojibake, uitgelegd: hoe "José" "José" wordt
Loop de echte bytes na en de fout ziet er niet langer mysterieus uit.
In UTF-8 codeert het teken met accent "é" als twee bytes: 0xC3 0xA9. "José" in UTF-8 is dus de bytereeks voor J, o, s, dan 0xC3 0xA9, dan het slotteken.
Stel nu dat die bytereeks bij een systeem terechtkomt dat ze als Windows-1252 leest in plaats van UTF-8, een verschil dat voortdurend optreedt wanneer data tussen een moderne app en een oudere database, exporttool of partnersysteem verhuist. Windows-1252 weet niet dat die twee bytes samen één teken vormen. Het leest ze als twee losse tekens: 0xC3 verschijnt als "Ã" en 0xA9 als "©". De ene letter "é" wordt de tekenreeks van twee tekens "é", en "José" wordt "José" op het scherm, het etiket of de factuur.
Hetzelfde mechanisme levert elk ander mojibake-patroon op dat je vast al gezien hebt: typografische aanhalingstekens die veranderen in reeksen als “ en â€\x9d, of een kastlijntje dat — wordt. Elk geval bestaat uit de bytes van de ene codering, gelezen door het regelboek van een andere. Kun je de twee botsende coderingen benoemen, dan is de oplossing meestal een configuratiewijziging.
Waar coderingsbugs een bedrijf echt raken
Mojibake op het scherm is het zichtbare symptoom. De duurdere fouten spelen zich eronder af.
- Verminkte namen op klantgerichte documenten. Verzendetiketten, facturen en contracten waar de naam van een klant als "José" verschijnt in plaats van "José" ogen op zijn best slordig, en belanden in het slechtste geval op het verkeerde adres, een reëel risico bij douanedocumenten en transporteursdata in de logistiek, waar een naam of adres exact moet overeenkomen met een officiële aangifte.
- Kapotte zoekopdrachten en opvragingen. Als een naam met de ene codering wordt opgeslagen en met een andere wordt gezocht, komen de bytereeksen niet overeen, en een klantrecord dat echt bestaat geeft nul resultaten. Supportteams leren dit handmatig te omzeilen lang voordat iemand het onderliggende coderingsverschil diagnosticeert.
- Stille afkapping van data. Een databasekolom die in bytes is gedimensioneerd in plaats van in tekens kan een multibyteteken middenin een insert doormidden knippen, waardoor het laatste teken van een naam corrupt raakt of een gebroken bytereeks achterblijft die later, vaak in een heel ander deel van het systeem, de validatie niet haalt.
- Emoji en CJK-tekens die volledig verdwijnen. De historisch utf8 genoemde tekenset van MySQL sloeg altijd hooguit 3 bytes per teken op, genoeg voor de meeste Latijnse, Cyrillische en gangbare CJK-tekens, maar niet genoeg voor emoji of sommige CJK-uitbreidingstekens, die er 4 nodig hebben. Tekst met zulke tekens werd niet verminkt; ze werd geweigerd of stil afgekapt, zolang niet de echte volledige UTF-8-optie van MySQL, utf8mb4, werd gebruikt.
- Legacy-exports die moderne systemen vergiftigen. Een CSV- of EDI-feed uit een oudere ERP of een partnersysteem, die nog standaard Windows-1252 uitstuurt, importeert op het eerste gezicht netjes en corrumpeert elke naam met accent zodra iemand het bestand in een UTF-8-native tool opent.
De valstrik voor Vlaamse en Nederlandse teams is geniepiger dan hij lijkt. Nederlands zelf komt er meestal ongeschonden door: doorlopende Nederlandse tekst is grotendeels ongeaccentueerd, en Latin-1 en Windows-1252 dekken de trema's op ë, ï en ü prima, zodat een Vlaams of Nederlands systeem jaren op een legacy-codering draait zonder dat iemand iets merkt. De rekening arriveert bij de eerste klant met een trema in haar naam zoals Zoë, bij de eerste klant Öztürk of Wojciech, bij de eerste leverancier die een feed in het Cyrillisch stuurt, of bij de eerste emoji in een opmerkingenveld. Daarom worden coderingsbugs zo vaak in productie ontdekt in plaats van tijdens het testen.
De legacy-coderingen die nog steeds problemen geven
Drie coderingen verklaren bijna elk verschil dat een zakelijk systeem nog tegenkomt: puur ASCII (alleen Engelse letters, cijfers en basisleestekens, geen accenten of niet-Latijnse schriften), Latin-1 / ISO-8859-1 (één byte per teken, alleen West-Europese accenten), en Windows-1252 (de bijna-superset van Latin-1 van Microsoft, de standaard op oudere Windows-software en een frequente bron van het mojibake-patroon met typografische aanhalingstekens hierboven).
Latin-1 verdient een aparte kanttekening in een euromarkt, want het bevat het euroteken "€" niet. Voor een Vlaamse of Nederlandse offerte, factuur of catalogus is dat geen randgeval maar het teken dat je het vaakst nodig hebt. Precies daarvoor bestaat Latin-9 (ISO-8859-15), de herziening die het euroteken toevoegde. Windows-1252 bevat "€" wel. Een legacy-systeem dat op de plek van "€ 1.500" terugvalt op "EUR 1.500" of een leeg vakje toont, maakt geen typfout: het verklapt zijn codering.
Ze blijven hangen op precieze, voorspelbare plekken: databases die jaren geleden zijn aangemaakt met een standaardtekenset die niemand ooit herzag, CSV-exports uit oudere boekhoud- of ERP-systemen, EDI-berichten van logistieke en vrachtpartners die op decennia-oude formaten draaien, en PDF-generatiebibliotheken die een smalle tekenset veronderstellen tenzij je ze anders instrueert. Elk van die plekken is een grens waar data van het ene systeem naar het andere oversteekt, en grenzen zijn precies waar coderingsaannames onuitgesproken en tegenstrijdig blijven.
Wat "UTF-8 overal" echt vereist
"Gebruik gewoon UTF-8" is een juist advies dat het werk onderschat. Volledige UTF-8-ondersteuning betekent dat je het expliciet instelt op elke laag.
- Tekenset en collatie van de database. UTF-8 gedeclareerd op de database, elke tabel en elke kolom, en ook op de verbinding. Op MySQL in het bijzonder betekent dat utf8mb4, dat de legacy-alias utf8 vervangt.
- HTTP- en bestandsheaders. Elke API-respons en elke bestandsexport meldt zijn codering expliciet (Content-Type: text/html; charset=utf-8), in plaats van clients te laten gokken.
- CSV-exports met een BOM wanneer Excel de bestemming is. Met een BOM aan het begin herkent Excel het bestand als UTF-8. Zonder BOM leest Excel een kale UTF-8-CSV soms als Windows-1252, een terugkerende oorzaak van "waarom ziet mijn export er kapot uit in Excel"-vragen.
- Glyphdekking van het lettertype. Een correct gecodeerd Cyrillisch of CJK-teken verschijnt alsnog als een leeg vierkantje ("tofu") als het gekozen lettertype er geen glyph voor heeft. Dat is een designprobleem, en voor de gebruiker ziet het er identiek uit.
- Validatie op elke grens. Uploads, API-invoer en partnerdatafeeds krijgen hun codering gecontroleerd en genormaliseerd naar UTF-8 aan de ingang, zodat verminkte bytes nooit in de opslag terechtkomen.
Zet die vijf één keer goed, op het niveau van de architectuur, en tekencodering houdt niemand meer bezig. Mis er één, en het duikt weer op als supportticket telkens wanneer een naam met accent, een emoji of een niet-Latijns schrift die ene grens bereikt.
Veelgestelde vragen over Unicode en tekencodering
Wat is Unicode?
Unicode is een standaard die een uniek nummer, een code point genoemd, toekent aan elk teken van elk groot schriftsysteem. Het is een kaart van tekens naar nummers. Het definieert op zichzelf niet hoe die nummers als bytes worden opgeslagen.
Wat is het verschil tussen Unicode en UTF-8?
Unicode is de tekenkaart; UTF-8 is een codering die Unicode-code points omzet in bytes, met 1 tot 4 bytes per teken. UTF-8 is de dominante codering op het web vandaag: volgens W3Techs wordt het gebruikt door 99,0% van de sites waarvan de codering bekend is.
Wat is mojibake?
Mojibake is de verminkte tekst die ontstaat wanneer bytes die in de ene tekenset zijn gecodeerd, met een andere worden teruggelezen. Het is mechanisch: hetzelfde coderingsverschil levert altijd hetzelfde verminkte patroon op, zoals "José" dat "José" wordt.
Waarom toont mijn software rare tekens zoals "é" in plaats van letters met accent?
Een onderdeel in het systeem, vaak een database, exporttool of oudere partnerfeed, leest UTF-8-bytes als Windows-1252 of Latin-1. De oplossing is achterhalen waar het verschil optreedt en dat onderdeel expliciet op UTF-8 zetten.
Hoe los ik coderingsproblemen in bestaande software op?
Audit elke grens: tekenset en collatie van de database, bestandsexports, API-headers en partnerdatafeeds, en zet elke grens expliciet op UTF-8 (utf8mb4 als de database MySQL is). We voeren die audit uit bij het scopen van elke meertalige build; vraag een offerte met vaste prijs aan en hij zit erbij.
Laat software bouwen die elk teken correct verwerkt
Vertel ons wat je software moet ondersteunen, inclusief de namen, schriften en partnerdatafeeds die ze correct moet verwerken. We antwoorden met een vaste scope, een vaste prijs en een leverdatum.
Verder lezen
Software nodig die vanaf dag één elke markt spreekt?
Vertel ons wat je wilt laten bouwen. Je krijgt een vaste scope, een vaste prijs en een opleverdatum in weken.
