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. Hier ontleden we precies wat er gebeurt, waar coderingsbugs een bedrijf echt geld kosten, en wat "UTF-8 overal" in een echt systeem vereist.
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 wordt UTF-8 gebruikt door 99,0% van de websites waarvan de tekencodering bekend is; dit is een webmeting en geen algemene statistiek over alle bedrijfssoftware. UTF-8 codeert Unicode-tekens uit het bereik U+0000..U+10FFFF met reeksen van één tot vier octets en behoudt de volledige US-ASCII-reeks. 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. Wanneer geldige UTF-8-bytes als Windows-1252 worden gelezen, kunnen meerdere tekens ontstaan waar oorspronkelijk één teken stond; het concrete patroon hangt af van de gebruikte coderingen. Als de oorspronkelijke bytes behouden zijn en de fout uitsluitend bij de interpretatie zit, kan een configuratie- of decodeerwijziging volstaan; bij eerdere vervanging of afkapping kan herstel of migratie nodig zijn.
Waar coderingsbugs een bedrijf echt raken
Mojibake op het scherm is het zichtbare symptoom. De duurdere fouten spelen zich eronder af. Een coderingsfout kan namen, zoekresultaten, imports of documentuitvoer onjuist maken; de concrete gevolgen, zoals een verkeerd adres of financiële schade, hangen af van de validaties en processen van het betreffende systeem.
- 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. In MySQL ondersteunt utf8mb4 zowel tekens uit het Basic Multilingual Plane als aanvullende Unicode-tekens en gebruikt het maximaal vier bytes per multibyte-teken; utf8mb3 (de legacy utf8-alias) kan zulke aanvullende tekens niet opslaan.
- 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 Nederlands- en Vlaamstalige 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 Nederlands of Vlaams 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. Daardoor kunnen coderingsbugs onopgemerkt blijven wanneer tests slechts een beperkte tekenset gebruiken.
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 Nederlandse of Vlaamse 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, en niet aanneemt dat het vanzelf doorsijpelt.
- Tekenset en collatie van de database. UTF-8 gedeclareerd op de database, elke tabel en elke kolom, niet alleen op de verbinding. Op MySQL in het bijzonder betekent dat utf8mb4, niet de legacy-alias utf8.
- 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. Voor CSV-bestanden die met bepaalde Excel-versies of configuraties worden geopend, kan een BOM de herkenning van UTF-8 verbeteren; test dit per doelomgeving. 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, geen coderingsprobleem, maar 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. Unicode beschrijft de tekenset en UTF-8 is een bytegerichte coderingsvorm van Unicode; UTF-8 is niet de enige Unicode-representatie, maar wel een ASCII-compatibele vorm. 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, niet willekeurig: 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, niet de data opnieuw invoeren.
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.


