dp_intl 1.0.0 rethinks C++ internationalization without locale dependency
dp_intl version 1.0.0 presents itself as a Gettext-compatible internationalization library for C++ with a specific mission: to improve upon the standard Gettext runtime, also known as libintl. The project addresses several pain points that have persisted in C++ internationalization workflows for years, starting with the library's relationship with the locale system.
No dependency on the locale
The default Gettext workflow hinges on calling setlocale() from the C standard library to establish the language. This function reaches far beyond message catalog selection—it alters the behavior of other locale-dependent C routines, which can introduce subtle bugs that surface only in specific locales. For instance, invoking setlocale() with LC_ALL changes all locale categories, including LC_NUMERIC, which governs the decimal separator in functions like snprintf() and strtof(). A number serialized with a period as decimal separator may not parse correctly under a locale that expects a comma, and the reverse is true as well. Even when application code does not directly use these functions, third-party libraries including the C++ standard library may invoke them; as of C++26, std::stof and std::stod rest on top of strtof and strtod, and prior to that standard, std::to_string relied on sprintf(). To preserve LC_NUMERIC intact, developers must avoid LC_ALL and set only LC_CTYPE and LC_MESSAGES—the two categories Gettext requires. However, LC_CTYPE still reaches into functions from ctype.h and strings.h such as tolower() and strcasecmp(), and code can break with the Turkish locale, where the uppercase of "i" is "İ" and the lowercase of "I" is "ı". dp_intl sidesteps this entirely by not depending on the locale. Instead, it determines the preferred language through the system interface language settings, keeping numeric formatting untouched.
No platform-specific number types for plural forms
Gettext routines for translating plural form messages use the unsigned long type for numbers. The size of this type varies by platform: 64 bits wide on 64-bit Unix-like systems (Linux, macOS) but 32 bits wide on 64-bit Windows. This variability makes cross-platform code difficult to write without either capping numbers at 2³² (4,294,967,295) or applying workarounds, because unsigned wrap-around (modulo 2³²) on systems with a 32-bit unsigned long can produce incorrect plural forms. dp_intl uses a 64-bit integer type (uint64_t) on all platforms, eliminating this class of bugs and ensuring consistent plural logic whether the code runs on Windows, Linux, or macOS.
Full control over file layout and loading
Gettext can load message catalogs only from the local file system, and the catalogs must reside in the directory structure mandated by bindtextdomain():
Any language in source strings through configurable plural rules
Gettext's plural form messages are limited to English and several other languages that have exactly two plural forms: "one" for the number 1 and "many" for all other numbers. This constraint makes it impossible to use languages whose plural systems do not fit this pattern. French, for example, also has two plural forms, but "one" applies to both 0 and 1, which does not align with Gettext's hardcoded scheme. dp_intl supports source strings in any language because its plural rules are configurable rather than baked into the API. This flexibility means the library can handle languages with three, four, or more plural forms, as well as languages where the categorization of numbers differs from the binary "one/many" model.
Integration pathways: source tree, FetchContent, or system installation
dp_intl is designed for straightforward integration into C++ projects. The library has no external dependencies and can be included directly in a project's source tree. Using CMake, developers can add it with add_subdirectory(), specifying the path to the dp_intl directory and linking the target dp_intl::dp_intl. To keep the source tree lean, only the include/, src/, CMakeLists.txt, and license.txt files need remain in the dp_intl directory. Alternatively, CMake's FetchContent can download dp_intl on demand, either from an archive URL or directly from the Git repository. For a traditional installation, running cmake with Release build type, building, and then installing with strip produces a system-wide library, with CMAKE_INSTALL_PREFIX available to change the default installation directory. Regardless of method, a C++ compiler supporting C++17 and CMake version 3.22 or newer are required.
CMake configuration and workflow compatibility
Library options can be set via CMake. For libraries added through add_subdirectory() or FetchContent, the set() command before those calls configures options such as DP_INTL_BUILD_SHARED_LIB to produce a shared library instead of the default static build. For installation-time builds, the -D flag during CMake configuration enables the same option: cmake -DDP_INTL_BUILD_SHARED_LIB=YES -B build. Running cmake -LH -B build reveals all configurable options, including dp_intl-specific ones starting with DP_INTL_. Since dp_intl only implements the runtime portion of Gettext (libintl), the overall translation workflow remains identical to any Gettext-based project, and the GNU Gettext manual provides full documentation for that workflow. The dp_intl API documentation, available both online and in the library headers, recommends starting with its two main entities for newcomers.
UTF-8 exclusivity and message catalog compatibility
A deliberate design choice in dp_intl is that it only works with UTF-8. The Gettext library historically supported various 1-byte encodings to accommodate languages before UTF-8 became ubiquitous, but that approach is now largely impractical. Even the msgfmt utility from the GNU Gettext project, starting with version 0.22, creates MO files in UTF-8 by default regardless of the source PO file encoding. If existing MO message catalogs use 1-byte encodings, they can be converted to UTF-8 either from the original PO files (if available) or recovered via msgunfmt from the GNU Gettext project. dp_intl fully supports the MO file format used by Gettext, including complete support for plural rule formulas defined in the "Plural-Forms" header entry, and message catalogs must use UTF-8 encoding.
A natural ending
By removing locale dependency, standardizing 64-bit integer handling for plural forms, enabling archive-aware catalog loading, and supporting configurable plural rules for any language, dp_intl offers a pragmatic upgrade path for C++ projects that need robust internationalization without the constraints of traditional Gettext. The library maintains compatibility with existing workflows and message catalog formats while addressing specific technical debt that has accumulated over decades of Gettext usage. Teams familiar with Gettext can transition with minimal friction, and new projects benefit from a cleaner API surface and fewer platform-specific surprises.