icui18n
icui18n layers polymorphic resource bundle inheritance and locale-change events on top of ICU’s ResourceBundle API. It shares the same design philosophy as the Java libraries — bundles follow the class hierarchy, locale changes fire to listeners automatically, and every object manages its own locale independently — implemented in C++20.
Requires: C++20 · ICU4C · CMake 3.21+ · View on GitHub →
Integration
Section titled “Integration”Add the following to your project’s CMakeLists.txt. ICU must be installed on the build machine; set ICU_ROOT if it is not on the default search path.
include(FetchContent)
FetchContent_Declare( icui18n GIT_REPOSITORY https://github.com/clydegerber/icui18n.git GIT_TAG v1.0.0)FetchContent_MakeAvailable(icui18n)
target_link_libraries(my_target PRIVATE icui18n::icui18n)Core abstractions
Section titled “Core abstractions”Localizable
Section titled “Localizable”The bundle chain owner and locale-event source. Holds the current icu::Locale, an ordered chain of ICU ResourceBundle nodes (most-derived class → hierarchy root), and a map of locale-change listeners. Key lookup walks the chain; ICU handles per-bundle locale fallback (en_US → en → root) automatically within each node. All public methods are thread-safe after construction.
LocalizableFor<Self, Parent>
Section titled “LocalizableFor<Self, Parent>”CRTP mixin that wires a concrete class into the bundle chain. Declare two public static constexpr std::string_view members:
bundle_root— filesystem path to the directory containing the compiled.resfiles.bundle_name— relative bundle path (e.g.com/example/ServiceBundle).
Two specialisations cover the two usage patterns:
| Specialisation | When to use |
|---|---|
LocalizableFor<Self> | Self is the root of a localizable hierarchy |
LocalizableFor<Self, Parent> | Self extends a localizable parent class |
LocaleSubscription
Section titled “LocaleSubscription”Move-only RAII handle returned by addLocaleListener(). The listener is automatically deregistered when the subscription is destroyed or move-assigned over.
Resource
Section titled “Resource”Non-owning (Localizable&, key) handle. Lazy accessors — getString(), getInteger(), getDouble(), getTable() — always reflect the source’s current locale with no caching.
Resourceful
Section titled “Resourceful”Mixin for objects that display content from an external Localizable. Manages a Resource binding and an optional callback that fires when the source changes locale. Move-only; copy is deleted to prevent lambda capture bugs.
Polymorphic inheritance
Section titled “Polymorphic inheritance”Bundles follow the class hierarchy. A subclass only needs the entries it overrides — everything else falls through to the parent bundle.
#include <icui18n/Localizable.hpp>
// Root of the hierarchyclass Service : public icui18n::LocalizableFor<Service>{public: static constexpr std::string_view bundle_root = "/usr/share/myapp/i18n"; static constexpr std::string_view bundle_name = "com/example/ServiceBundle";};
// Extends Service — inherits bundle_root, overrides "greeting"class UserService : public icui18n::LocalizableFor<UserService, Service>{public: static constexpr std::string_view bundle_name = "com/example/UserServiceBundle";};A cross-library subclass that must store its bundles under its own install path redeclares bundle_root independently:
class ExtService : public icui18n::LocalizableFor<ExtService, Service>{public: static constexpr std::string_view bundle_root = "/usr/lib/ext/i18n"; static constexpr std::string_view bundle_name = "com/ext/ExtServiceBundle";};Bundle file format
Section titled “Bundle file format”ICU resource bundles are plain-text .txt files compiled to binary .res files by genrb. The root bundle provides the fallback for all locales:
root { greeting { "Hello" } farewell { "Goodbye" }}fr { greeting { "Bonjour" } farewell { "Au revoir" }}Compile with:
genrb -d /usr/share/myapp/i18n/com/example/ServiceBundle root.txt fr.txtReading values
Section titled “Reading values”Service svc;svc.setBundleLocale(icu::Locale::getFrench());
std::optional<icu::UnicodeString> greeting = svc.getString("greeting"); // "Bonjour"std::optional<std::int32_t> retries = svc.getInteger("max_retries");std::optional<double> ratio = svc.getDouble("threshold"); // stored as stringstd::optional<icu::ResourceBundle> errors = svc.getTable("errors");Locale-change events
Section titled “Locale-change events”auto sub = svc.addLocaleListener( [](const icu::Locale& prev, const icu::Locale& next) { // re-render, reformat, etc. });
svc.setBundleLocale(icu::Locale::getGerman()); // listener fires here
// sub goes out of scope → listener automatically deregisteredResourceful
Section titled “Resourceful”Objects that display content from an external Localizable without owning a bundle:
#include <icui18n/Resourceful.hpp>
class GreetingLabel : public icui18n::Resourceful{public: void bind(const Service& svc) { setResource({svc, "greeting"}, [this](const icu::Locale&, const icu::Locale&) { refresh(); }); }
void refresh() { if (auto r = getResource()) display(r->getString()); }};Thread safety
Section titled “Thread safety”getString,getInteger,getDouble,getTable,getBundleLocale,addLocaleListener— shared lock; safe to call concurrently with each other.setBundleLocale— exclusive lock during bundle installation; listeners are fired outside the lock. Not safe to call concurrently with itself.