Every button label, menu title, and status message you see on a VW, SEAT, Skoda, or Cupra MIB infotainment screen comes from somewhere. It's not baked into the application, but it lives in small binary files that get loaded per language, so the exact same firmware can greet a driver in Dutch, Arabic, or Bulgarian without shipping a different build for each.
Those files are the .res files in the i18n folder of each skin on the MIB1 and MIB2 platforms, and they're the subject of this post: what's inside them, and the tool I built to view and edit them.
File format details
A .res file is a translation resource for the MIB1/MIB2/MIB2.5 infotainment platform. Each one holds the UI strings for a single locale: nl_NL.res for Dutch, ar_SA.res for Arabic, and so on: as a flat table of UTF-16 strings addressed by numeric ID. The application code never contains the literal text "Bluetooth connect"; it asks the translation layer for string ID 137 in the currently active locale, and the .res file for that locale answers.
Binary structure: header, strings, index
Every .res file follows the same three-part layout:
[0x00-0x27] 40-byte fixed header
[0x28 …] String table, stringCount entries, tightly packed
[ …EOF] Index / group section, trailing metadata (I haven't fully decoded this, but it doesn't seem useful to be edited)
The header contains values like the version, type of file, the amount of strings inside.
The string table consists of string entries. Each entry is built like this:
u32 unknown (always 0)
u16 charCount
u16 reserved (always 0)
charCount*2 bytes UTF-16LE text

A string's "ID" isn't stored anywhere, but it's simply its 0-based position in this sequence. Read entry 0, that's string ID 0; read the next one, that's ID 1; and so on until stringCount is reached. Because there's no offset table and no terminator between entries, the only way to find string N is to walk every entry before it and add up their lengths. That's a deliberate trade-off: it keeps the format compact (no pointer table overhead) at the cost of requiring a full linear parse, which is fine for a file that's loaded once per boot.

After the last string comes an index section that runs to the end of the file: repeating groups of (type, count, ids[count]), mapping the UI's screen/category structure onto string IDs. This part is only partially reverse-engineered, but the good news is the editor never needs to fully understand it, it only ever changes the text of existing IDs, never adds, removes, or reorders strings, so the index section is preserved byte-for-byte, untouched, on every save. There's no benefit in editing this data.

Why the format looks like this
Nothing about .res is exotic once you see the constraints it's solving for: infotainment head units have limited flash and modest CPUs, and the string table needs to load fast at boot with minimal parsing overhead. Fixed-width headers with mostly-constant fields, tightly packed UTF-16LE entries with no padding, and ID-as-array-position addressing are all choices that favor a simple linear reader over a flexible-but-heavier format like JSON or XML. Also, contrary to what people have been writing on my blog in the past, the .res format in the MIBs isn't the standard .res file format as defined in any ISO or ANSI standard. The MIB uses the .res format for a bunch of other things, although they don't really need an editor, I guess (unless you have a good use case, let me know).
So… let's discuss the tool itself!
How to use the editor
The new Translations tool at mqbtools.nl/res walks through the format above without you needing to know any of it:

Upload a .res file. Drop in any MIB1/MIB2/MIB2.5 translation file, dumped from your system or from the app.img of a firmware distribution.

Browse and filter strings. Every parsed string shows up in a table with its numeric ID, the original text, and an editable field next to it. A filter box lets you search by ID or by text content, so you don't have to scroll through thousands of entries to find the one string you care about.

Edit translations in-browser. Click into any row and change the text. Edited rows are visually highlighted so you can see at a glance what you've touched, and your edits are tracked separately from the original, so nothing is destroyed until you explicitly save.

Download the modified file. When you're happy with your changes, save and download a new .res file with your edits baked in.
Put it back on your unit. There are many ways to do this, which I will not describe here.
I hope this tool is useful for people. If you like this type of tools, and want to support the development and keeping the server running: buy me a coffee, or check out one of the other donation methods you see when saving the edited file! I see a lot of people using my tools, and some of you are power users, probably using it for business (and some are even trying to reverse engineer my tools. please don't: just ask me instead of brute forcing my tools to be able to make your own version of it).
The tool can be found at mqbtools.nl/res. Enjoy!



Comments 8
Great tool, thanks for building it. One data point in case it's useful: it doesn't apply to Audi MIB2 High.
My unit is MHI2_ER_AU37x_P5089 (variant FM2-P-TNL-EU-AU-MQB). I dumped the full backup and went through it looking for .res files:
— No file with an I18d or I18n header anywhere — I scanned all 1472 files including the 1 GB MMX app.img, the RCC/MMX flash dumps, lsd.jxe and every .ifs/.efs/.iso.
— I also unpacked the headers of all 10,430 zip entries across every container in the backup. Zero entries with a .res extension.
— The /eso/hmi/lsd/ directory on the MMX app partition has no Resources folder at all, so no skin*/i18n/*.res path exists. Reading the QNX6 dirent straight out of app.img, that directory contains: traceConfig.properties, kzbs, hmi_startup.json.example,
lsd.sh, version.txt, libjprof.so, bundles.properties, bundles_headless.properties, lang_hmi_builtin.config, jre, DSITracer.jar, DSITracer-clientData.bin, jars, images, language_lut_hmi.txt.
On this platform the HMI is the Java/ESO one (lsd.jxe), and translations live in /eso/hmi/lsd/jars/lang_data.zip as resources/HMI<module>EvoHighScale/strings_<locale>.data — 22 modules × 25 locales, 550 files.
Different format, no magic. Big-endian:
u32 count
u32[count] cumulative end offsets (bytes)
u32 total byte length
bytes[] UTF-8 text, strings concatenated, no separators
u8[count+21] per-string flag/type bytes (0x00 / 0x01 / 0x08)
Parses cleanly and the indices are the string IDs — e.g. in HMIToneEvoHighScale/strings_ru_RU.data (897 strings): 280 = "Развлекательные системы", 281 = "MMI touch", 286 = "Audi Heartbeat".
Nice! Can you send me some examples of the .data files?
You should add an editable language pointer for the string and adding\removing strings, coz sometimes when a UI or flash doesn't have such language (like a china units or USA units) at all and you have to add a new language from a scratch (yep, nar, eu or china units have way different IDs and strings) and this tool we be swiss knife for any text modifications.
The problem would be, adding a language means you have to add it to the code that handles the UI as well. the available language selection is spread across a few files, and it isn't very flexible.
Yeah, but that's a whole different story =) The editor itself needs those options
hmmm
did anyone already perform such modifications succesfully?
Yes, but that depends on the mib3 version. There is 2 types, a usual one and android automotive one. Also people done a golden skin transfer on mib2 and they had the same procedure with complete new localization. For the early mib3s some guy currently working on it. Mib3 AAs — much easier, it is full android. All of them running under ICU including AAs so technically any can display any language. When i've done initial icu format mods, people started coming back only about 2 years after, so it will be handy later.
Sounds like there's no handy 1-solution-for-all-platforms here, but something that might be useful is a script that creates the right files within the MIB units, depending on the platforms. I'll stick to just the editor in this case, because I can't test all these other possibilities.