Build a Message Catalogue with gencat

gencat turns a plain-text message source into a binary GNU message catalogue plus a C header with matching set and message numbers. It ships with glibc, here as Ubuntu package libc-dev-bin version 2.39-0ubuntu8.9. Give it about fifteen minutes if you already have a small set of messages to compile.

You need a shell, a writable working directory and the development package that provides /usr/bin/gencat. Everything here creates ordinary files in the current directory. It does not need sudo, touch your locale configuration or install a service.

1. Confirm the installed command

Check which executable will actually run, and record its version before it goes anywhere near a build script:

$ command -v gencat
/usr/bin/gencat
$ gencat --version
gencat (Ubuntu GLIBC 2.39-0ubuntu8.9) 2.39

The local manual documents gencat [OPTION...] -o OUTPUT-FILE [INPUT-FILE].... It also lists an older positional form, where the output file comes before the inputs. Use -o in anything new: it keeps the output visually distinct from the inputs.

Checkpoint: gencat --version should name the glibc release you actually intend to support. Different implementation or release? Keep whatever gets generated and run the checks below before trusting details like header naming.

2. Write a message source file

Every message belongs to a numbered or named set. Named sets and messages make the generated C header useful to your source code, and gencat assigns the numeric values behind them. Create messages.msg with this content:

$quote "
$set Main
Hello "Hello, %s!"
Ready "The file is ready."
$set Network
Unavailable "Network unavailable."

$quote makes double quotes delimit message text. $set Main opens a set called Main; the names that follow are message identifiers until the next set. A message can carry a format marker such as %s, but gencat never substitutes it: your program supplies that value when it calls catgets.

Keep set and message names stable once code depends on them. The generated numbers are the interface between your program and its catalogue. Changing the text is safe; reusing an identifier for an unrelated message just makes a mismatched catalogue harder to diagnose later.

3. Generate the catalogue and header

Run gencat with one output path for the binary catalogue and one for the header:

$ gencat -H messages.h -o messages.cat messages.msg
$ ls -l messages.cat messages.h
-rw-r--r-- 1 user user ... messages.cat
-rw-r--r-- 1 user user ... messages.h

A clean run normally prints nothing. The catalogue is binary, so do not expect it to open sensibly in a text editor. The header should hold definitions like these, with your own source path and line numbers:

#define NetworkSet 0x2        /* messages.msg:5 */
#define NetworkUnavailable 0x1 /* messages.msg:6 */
#define MainSet 0x1           /* messages.msg:2 */
#define MainHello 0x1         /* messages.msg:3 */
#define MainReady 0x2         /* messages.msg:4 */

Order and spacing are generated output, so do not chase them. Do check that every set and message you expect actually appears. An empty header means the input did not have symbolic names in the form this GNU implementation expects.

Checkpoint: verify both files before compiling anything against them:

$ test -s messages.cat && test -s messages.h && echo 'catalog and header are non-empty'
catalog and header are non-empty
$ grep -E '^#define (Main|Network)' messages.h
#define NetworkSet 0x2 ...
#define MainSet 0x1 ...

4. Use standard input or several source files

If a generator produces the source on the fly, pass - as the input file; the same dash as an output file sends the binary catalogue to standard output instead. That is handy in a pipeline, but redirect binary output to a file or another program rather than letting it splatter across your terminal:

$ printf '%s\n' '$set Extra' '1 "Generated input."' | gencat -o extra.cat -
$ test -s extra.cat && echo 'extra.cat created'
extra.cat created

You can pass several input files too. gencat treats them as one combined stream, so set and message identifiers must not collide by accident: a duplicate pair is an error even if a later file looks like it should win. Keep the full source list in your build rule so a clean rebuild stays reproducible.

5. Know what happens on rebuild

When the named output catalogue already exists, this GNU implementation merges the newly generated messages into it. Matching set/message pairs get replaced; unrelated older messages stay put. Convenient for incremental updates, but it can also leave stale messages behind and surprise a clean-build review.

Warning: --new throws away the old catalogue and writes only what the current input produces. Do not point it at a shared or production catalogue until you have checked the output path and kept a recoverable copy.

$ cp --preserve=all messages.cat messages.cat.bak
$ gencat --new -H messages.h -o messages.cat messages.msg
$ test -s messages.cat && test -s messages.h && echo 'rebuilt from current source'
rebuilt from current source

Recovery: if the rebuild fails, restore the previous catalogue with mv messages.cat.bak messages.cat. If it succeeds but looks wrong, compare the files before touching anything else. The backup step is reversible; deleting it with rm is permanent, so only do that once you have actually tested the result.

6. Diagnose failures without reaching for root

A missing input file gives a non-zero status and names the path. Check that path and its read permission first:

$ test -r messages.msg && echo readable
readable
$ gencat -H messages.h -o messages.cat messages.msg
$ status=$?
$ printf 'gencat status: %s\n' "$status"
gencat status: 0

A non-zero status is not a reason to run this as root: a writable build directory is enough. Fix the filename, source syntax or destination permissions instead. If a program reads a stale-looking catalogue, check it is opening the one you just generated and not a copy sitting under a system locale directory.

For a genuinely fresh result, build into a new temporary or versioned filename, inspect it, then install it through your normal deployment process. Keep the source and its generated header together: a header from one revision paired with a catalogue from another can map a perfectly good-looking identifier to the wrong text.

Done means