Compile ICU Resource Bundles with genrb
genrb compiles a text resource file into the binary .res bundle ICU actually loads. It names the output after the locale declared inside the file, not the filename you typed, which catches people out constantly. This walkthrough builds a test bundle in a temporary directory in about fifteen minutes, using genrb from Debian package icu-devtools version 74.2-1ubuntu3.1 (ICU 74.2).
The route
Jump straight to the step you need, or tick off Done means at the end.
You need a shell, the ICU development tools package, and a writable build directory. Nothing here installs the result or touches system ICU data, so no elevated privileges are needed.
1. Check the Installed Tool
Confirm which binary will actually run before you trust anything it tells you:
$ command -v genrb
/usr/bin/genrb
$ genrb --version
genrb version 56 (ICU version 74.2).
$ dpkg-query -W -f='\${Package} \${Version}\n' icu-devtools
icu-devtools 74.2-1ubuntu3.1
The first number in that version line is genrb's own tool version. The number you actually care about for compatibility is 74.2, the ICU version. The installed binary can have options that are newer or more detailed than the local manual page, so run genrb --help before reusing this recipe against a different ICU package.
Checkpoint
If command -v finds nothing, install the distribution's ICU development-tools package through the normal package manager. Do not work around a missing command by copying a binary from another host.
2. Write a Minimal Source Bundle
A resource source file is plain text. Its filename is only an input label: genrb reads the locale name declared inside the resource and uses that for the output name. This example declares the root bundle with one string and one integer:
$ work=/tmp/genrb-example
$ mkdir -p "$work/src" "$work/out"
$ printf '%s\n' 'root:table { greeting:string { "Hello from genrb" } count:int { 3 } }' > "$work/src/example.txt"
$ sed -n '1p' "$work/src/example.txt"
root:table { greeting:string { "Hello from genrb" } count:int { 3 } }
Switch to a real project tree rather than /tmp once the syntax works. Keep the source under version control and treat the destination as generated output. The quoted shell variable stops a future path containing spaces from splitting the command's arguments.
Tip
For a locale bundle, put the locale in the resource declaration itself, for example en_GB:table { ... }. Naming the file en_GB.txt does not force an en_GB.res result: genrb names the output from the locale it finds inside the file, not the filename.
3. Compile to a Binary Bundle
Pass the source and destination directories explicitly. That keeps the command reproducible and stops it accidentally writing into the system ICU data directory:
$ genrb --sourcedir "$work/src" --destdir "$work/out" example.txt
$ find "$work/out" -maxdepth 1 -type f -printf '%f\n'
root.res
$ file "$work/out/root.res"
/tmp/genrb-example/out/root.res: data
The output is binary, so file will not show your strings. What you are actually checking is the exact filename, that it exists, and a zero exit status from genrb. ICU can read the resulting root.res directly, and pkgdata can fold it into a larger ICU data archive.
Checkpoint
You should have exactly one generated file, root.res. A different base name means the locale declaration is wrong, not that the file needs renaming by hand.
4. Make the Input Encoding Explicit
Without an encoding option, the manual describes the default as the system invariant codepage. A UTF-8, UTF-16BE or UTF-16LE byte-order mark is detected automatically. If your source is known to be UTF-8 but has no BOM, say so on the command line instead of trusting the host default:
$ genrb --encoding UTF-8 \
--sourcedir "$work/src" \
--destdir "$work/out" \
example.txt
Encoding only affects how genrb reads the source. It does not change the fact that the normal output is still a binary .res file. If a non-ASCII string fails to compile, or shows up wrong downstream, check the source file's actual encoding and the encoding option before touching the resource syntax.
5. Watch a Compile Without Drowning in Output
Turn on verbose mode when a bundle has several resources, or you need to see exactly which file is being parsed:
$ genrb --verbose --sourcedir "$work/src" --destdir "$work/out" example.txt
Processing file "/tmp/genrb-example/src/example.txt"
parsing table (null) at line 1
resource greeting at line 1
string greeting at line 1
resource count at line 1
integer count at line 1
The wording and line numbers are diagnostic output, not a stable file format, so do not parse them in scripts. For a quiet build, the installed binary also accepts --quiet, but only reach for it once ordinary warnings have been dealt with. A warning you have suppressed is still a warning you may need to chase down.
6. Generate Java Only When ICU4J Needs It
The installed tool can also emit a Java ListResourceBundle source file with --write-java. Its optional encoding argument is easy to trip over: if the next token is not another option, genrb treats it as the encoding. Skip the encoding value by putting another option straight after -j:
$ mkdir -p "$work/java"
$ genrb -j -s "$work/src" -d "$work/java" example.txt
$ find "$work/java" -maxdepth 1 -type f -printf '%f\n'
LocaleElements.java
The default Java output uses ASCII plus Unicode escapes such as \u0041. To pick an encoding instead, put it directly after -j, then the source-directory option after that:
$ mkdir -p "$work/java-utf8"
$ genrb -j UTF-8 -s "$work/src" -d "$work/java-utf8" example.txt
$ test -s "$work/java-utf8/LocaleElements.java" && echo 'Java source written'
Java source written
The default bundle name in this mode is LocaleElements. That is a Java naming convention, not a sign the input locale changed. Confirm the generated package and class suit the ICU4J code that will compile them before you commit the file.
7. Diagnose Failures Before You Change Directories
A missing source file shows up as a path error. Reproduce it on purpose with a filename that does not exist:
$ genrb --sourcedir "$work/src" --destdir "$work/out" missing.txt
couldn't open file /tmp/genrb-example/src/missing.txt
$ printf 'exit status: %s\n' "$?"
exit status: 4
The temporary path in the message will differ on your machine, so check the source directory, filename and case first. The destination directory has to exist and be writable too. Do not reach for sudo as a first move: it can mask a wrong path and leaves root-owned build artefacts behind.
For repeatable builds, keep --sourcedir and --destdir explicit. Omit them and genrb falls back to ICU data locations, including the ICU_DATA environment variable. Some ICU tools expect a trailing slash there, so check it with printf '%s\n' "$ICU_DATA" before relying on the default. A missing or wrong data directory matters most when you are processing collation overrides.
8. Clean Up the Test Output
The example only ever touched the temporary directory. Once you have checked the files, remove that specific directory, not a broad path:
$ rm -rf -- "$work"
$ test ! -e "$work" && echo 'temporary build removed'
temporary build removed
Warning
This is the one destructive command in the guide. Confirm work holds only disposable test output before running it. For a real project build, delete generated files through that project's documented clean target, and keep the source bundle.
Done means
- Version confirmed.
genrb --versionmatched the installed ICU release. - Bundle compiled. A resource source file produced a
.resfile in an explicit destination directory. - Filename checked. The output name matched the locale declared inside the resource, not the input filename.
- Defaults understood. Input encoding and the
ICU_DATAfallback are deliberate choices, not accidents. - Java handled carefully. Any Java output used an explicit encoding placed right after
-j. - Failures diagnosed calmly. A missing-input error was treated as a path problem, not a reason to reach for
sudo.