Introduction
A .mo file is the compiled form of a gettext catalogue: the message
tables that a program loads at run time to find the translation of a string. The
letters stand for Machine Object. msgfmt makes it from a
.po file, and
msgunfmt turns it back into one. The C library of GNU systems, GNU
libintl and the gettext modules of Python, PHP and other languages read it.
Catalogues usually sit in LOCALEDIR/LANG/LC_MESSAGES/DOMAIN.mo.
The format was made to be read fast: two sorted tables, one of the original strings and one of the translations in the same order, and an optional hash table that finds an original without searching. All numbers are 32 bits. The file is written in the byte order of the machine that made it, and a reader tells the order from the first four bytes.
A file dropped on any page opens in the explorer. It shows the header, both string tables, the hash table (checking that every original is found through it) and every entry decoded with the encoding that the catalogue’s own header names.
Structure
The manual describes the layout with a picture; this is the same, with the offsets
that the header gives. The tables and strings may lie anywhere in the file, but
msgfmt writes them in this order.
0x00Header the magic number, the revision, the number of strings N, the offsets of the two tables, and the size and offset of the hash table28 bytes0x1CRevision 1 fields the system-dependent segments and strings: five more numbers20 bytes, only in revision 1OOriginal table N pairs of length and offset, one for each original string, sorted8 × N bytesTTranslation table N pairs of length and offset, the translation of the original with the same number8 × N bytesHHash table S numbers, each 0 or the number of an original plus one4 × S bytes, optional…System-dependent tables the segment table, the two offset tables and the descriptors of the system-dependent stringsonly in revision 1…Strings every original, then every translation, each ended by a NUL byte that its length does not countto the end
Telling the two byte orders apart
The magic number is 0x950412de. A file written on a little-endian
machine starts with the bytes DE 12 04 95, and one written on a big-endian machine with
95 04 12 DE. A reader that reads the first four bytes in its own byte
order gets either 0x950412de (the order of the file is its own) or
0xde120495 (every number in the file must be swapped). Only the numbers
are swapped; the strings are bytes.
Worked example
The smallest useful catalogue, with the header entry and one message, as
msgfmt 1.0 wrote it on a little-endian machine, with no
untranslated or fuzzy
entries:
00000000 DE 12 04 95 00 00 00 00 02 00 00 00 1C 00 00 00 ................
00000010 2C 00 00 00 03 00 00 00 3C 00 00 00 00 00 00 00 ,.......<.......
00000020 48 00 00 00 04 00 00 00 49 00 00 00 28 00 00 00 H.......I...(...
00000030 4E 00 00 00 04 00 00 00 77 00 00 00 01 00 00 00 N.......w.......
00000040 02 00 00 00 00 00 00 00 00 52 65 61 6C 00 43 6F .........Real.Co
00000050 6E 74 65 6E 74 2D 54 79 70 65 3A 20 74 65 78 74 ntent-Type: text
00000060 2F 70 6C 61 69 6E 3B 20 63 68 61 72 73 65 74 3D /plain; charset=
00000070 55 54 46 2D 38 0A 00 56 72 61 69 00 UTF-8..Vrai.
| Offset | Value | Meaning |
|---|---|---|
0x00 | DE 12 04 95 | The magic number in a little-endian file. |
0x04 | 0 | Revision 0: major 0, minor 0. |
0x08 | 2 | N: two strings, the header entry and “Real”. |
0x0C | 0x1C | O: the original table starts straight after the 28-byte header. |
0x10 | 0x2C | T: the translation table, 2 × 8 bytes later. |
0x14 | 3 | S: three entries in the hash table. |
0x18 | 0x3C | H: the hash table. |
0x1C | 0, 0x48 | Original 0: length 0 at offset 0x48, the empty string of the header entry. |
0x24 | 4, 0x49 | Original 1: “Real”, 4 bytes at 0x49. |
0x2C | 0x28, 0x4E | Translation 0: the 40 bytes of the header fields at 0x4E. |
0x34 | 4, 0x77 | Translation 1: “Vrai”, 4 bytes at 0x77. |
0x3C | 1, 2, 0 | The hash table: slot 0 holds string 1 (the header entry), slot 1 holds string 2 (“Real”), slot 2 is empty. |
0x48 | 00 | The NUL after the empty original. |
Sources
The layout is from the GNU gettext manual’s chapter on MO files (the texinfo source,
read in November 2026) and from gettext’s own source code: gmo.h for the
header and the system-dependent structures, hash-string.c and
hash-string.h for the hash function, dcigettext.c for the
lookup, loadmsgcat.c for how a loader checks and reads a file, and
write-mo.c for how msgfmt sizes and fills the hash table and
writes the file. The worked examples were written by msgfmt 1.0.
Not described
Known, not yet covered
- Reference: the pointer entries that the manual says may follow offset 24, for later extensions such as flag bits and a charset indication, are not defined by anything read, and no revision uses them.
- Reference: the exact meaning of major revision 1, which the
source names for the
Isystem-dependent segment; that page says what was read. - Viewer: the Java
.classand .NET outputs ofmsgfmtare other formats and are not read; only the MO file is.
Variants
- gettext PO message catalogue – the text catalogue a translator edits
- gettext POT template – the template a catalogue is made from, with every translation empty
- gettext MO binary catalogue – the compiled catalogue that programs load
Links
Software
- GNU gettext – msgfmt compiles a catalogue and msgunfmt turns it back into text (Linux, macOS, Windows)
- Poedit – translation editor that writes the MO file when it saves a catalogue (Windows, macOS, Linux)
Developers
- gettext – Python’s module that reads MO files
- polib – reads and writes PO and MO files (Python)
- gettext-parser – parses and compiles PO and MO files (JavaScript)
- GNU C Library – its gettext functions load MO files directly (C)
Reference
- The Format of GNU MO Files – the GNU gettext manual’s description, with its layout picture
- gettext source code – the repository, with
gmo.h,write-mo.candloadmsgcat.c