High-Performance, Zero-Lock Internationalization (i18n) & Localization (l10n) Plugin for the Drogon C++ Web Framework
drogon_i18n is a modern, thread-safe, and lightning-fast internationalization plugin designed specifically for the Drogon C++ web framework. Built from the ground up for high-concurrency systems, it leverages a cold-start memory preloading pattern to deliver Zero-Lock
-
Zero-Lock Runtime Execution: Dictionaries are loaded once during application bootstrap (
initAndStart). Once running, memory data structures become immutable, enabling lock-free, concurrent$O(1)$ read operations across all event-loop worker threads. -
Modern C++20 Architecture: Fully compliant with C++20 standards, utilizing modern filesystem features, UTF-8 path conversions (
generic_u8string), and string views. -
Flexible Dictionary Providers:
-
JSON Loader (
JsonProvider): Supports both flat key-value pairs and deeply nested JSON objects with automatic Dot-Notation Key Flattening (e.g.,{"auth": {"login": {"title": "Welcome"}}}$\rightarrow$ "auth.login.title"). -
GNU Gettext Loader (
MoProvider): Direct binary parsing of compiled GNU Gettext.mofiles with automatic endianness detection/swapping and Zero-Heap buffer allocations. -
Extensible Provider Architecture: Implement the
I18nProviderinterface to add support for YAML, XML, or database-driven dictionary sources.
-
JSON Loader (
-
Smart Dynamic Locale Resolution: Automatically detects client language preference from incoming
HttpRequestPtrusing a customizable multi-tier fallback pipeline:- Request Attributes (e.g., injected by custom filters/middlewares)
- HTTP Session Data (
req->getSession()) - URL Query Parameters (e.g.,
?lang=ar) - HTTP
Accept-LanguageHeader (with short-code fallback matching likear-EG$\rightarrow$ ar) - Configured Default / Fallback Locale
-
Thread-Safe Localized Formatting: Manages pre-constructed system
std::localeinstances per registered language. Enables localized formatting for numbers, currencies, and dates without modifying global process locale state (std::setlocale). -
Seamless CSP (Drogon Server Pages) Integration: Provides concise, zero-cost helper macros (
tr,trl,vtr,vtrd,vtrl) and theViewHelperresponse builder to easily translate text directly within C++.cspviews. -
Deterministic Namespace Hierarchy: Subdirectories and file names automatically map into explicit namespaces (e.g.,
locales/ar-eg/erp/accounting.json$\rightarrow$ ns = "erp::accounting"). -
Enterprise Security & Reliability:
- Path Traversal Protection: Guarded directory scanning using canonical path verification.
-
Stack Overflow Recursion Guards: Prevents JSON depth-based Denial of Service (DoS) attacks (
MAX_JSON_DEPTH = 32). - Buffer Overflow Protection: Strict boundary checks for binary Gettext offsets.
- Cross-Platform Compatibility: Fully tested on Linux (GCC / Clang), macOS (Apple Clang), and Windows (MSVC).
drogon_i18n expects a structured locale directory (configurable via locales_directory). Each locale folder contains a metadata file named _info_.json alongside translation files.
locales/
├── ar-eg/
│ ├── _info_.json
│ ├── main.json
│ ├── gettext.mo
│ └── erp/
│ └── accounting.json
└── en-us/
├── _info_.json
└── main.json
Every language folder must include an _info_.json metadata file:
{
"status": true,
"key": "ar-eg",
"longCode": "ar_EG",
"shortCode": "ar",
"rtl": true,
"title": "العربية",
"mainDictionary": "main"
}| Field | Type | Description |
|---|---|---|
status |
bool |
Enables or disables the locale. Disabled locales are skipped during boot. |
key |
string |
Unique identifier key used by the application (e.g., "ar-eg", "en-us"). |
longCode |
string |
POSIX/OS language code used for std::locale construction (e.g., "ar_EG"). |
shortCode |
string |
ISO 639-1 two-letter code for HTTP Accept-Language fallback matching. |
rtl |
bool |
Layout direction flag (Right-To-Left). |
title |
string |
Display name of the language (e.g., "العربية"). |
mainDictionary |
string |
Default dictionary file stem used when no explicit namespace is specified. |
-
locales/en-us/main.json$\rightarrow$ Namespace:"main"or"" -
locales/en-us/gettext.mo$\rightarrow$ Namespace:"gettext" -
locales/en-us/erp/accounting.json$\rightarrow$ Namespace:"erp::accounting"
- C++ Compiler: C++20 compliant (GCC 11+, Clang 13+, MSVC 2019+)
- CMake: Version 3.15 or higher
- Drogon Framework: v1.9.0 or higher
git clone https://github.com/STCodePro/Drogon-I18n.git
cd Drogon-I18n
mkdir build && cd build
cmake .. -DCMAKE_BUILD_TYPE=Release -DBUILD_EXAMPLES=ON
make -j$(nproc)
sudo make installfind_package(Drogon CONFIG REQUIRED)
find_package(drogon_i18n REQUIRED)
add_executable(my_drogon_app main.cpp)
target_link_libraries(my_drogon_app PRIVATE drogon_i18n Drogon::Drogon)add_subdirectory(third_party/Drogon-I18n)
add_executable(my_drogon_app main.cpp)
target_link_libraries(my_drogon_app PRIVATE drogon_i18n)drogon_i18n can be initialized programmatically or via Drogon’s config.json configuration file.
#include <drogon/drogon.h>
#include <drogon/i18n/I18nPlugin.h>
int main()
{
Json::Value pluginConfig;
pluginConfig["locales_directory"] = "./locales";
pluginConfig["default_locale"] = "en-us";
pluginConfig["fallback_locale"] = "en-us";
// Configurable Key Resolutions
pluginConfig["locale_attribute_key"] = "lang";
pluginConfig["locale_session_key"] = "lang";
pluginConfig["locale_parameter_key"] = "lang";
pluginConfig["locale_view_data_key"] = "lang";
pluginConfig["request_view_key"] = "req";
pluginConfig["default_main_dictionary"] = "main";
drogon::app().addPlugin("drogon::i18n::I18nPlugin", {}, pluginConfig);
drogon::app().addListener("127.0.0.1", 8080).run();
return 0;
}{
"plugins": [
{
"name": "drogon::i18n::I18nPlugin",
"dependencies": [],
"config": {
"locales_directory": "./locales",
"default_locale": "en-us",
"fallback_locale": "en-us",
"locale_attribute_key": "lang",
"locale_session_key": "lang",
"locale_parameter_key": "lang",
"locale_view_data_key": "lang",
"request_view_key": "req",
"default_main_dictionary": "main"
}
}
]
}You can translate messages using the Translator class or the shorthand tr and trl macros.
#include <drogon/drogon.h>
#include <drogon/i18n/Translator.h>
using namespace drogon;
using namespace drogon::i18n;
void handleWelcome(const HttpRequestPtr &req,
std::function<void(const HttpResponsePtr &)> &&callback)
{
std::string name = req->getParameter("name");
if (name.empty()) name = "Guest";
// 1. Shorthand macro with automatic HTTP Request locale resolution & template variable interpolation
std::string welcomeMsg = tr(req, "welcome", {{"name", name}});
// 2. Explicit namespace translation ("erp::accounting")
std::string invoiceMsg = tr(req, "invoice_created", "erp::accounting", {{"id", "1001"}});
// 3. Explicit locale translation ignoring request context
std::string arMsg = trl("ar-eg", "goodbye");
auto resp = HttpResponse::newHttpResponse();
resp->setBody(welcomeMsg + "\n" + invoiceMsg + "\n" + arMsg);
callback(resp);
}LocaleManager handles pre-constructed std::locale instances without modifying the global process state (std::setlocale), guaranteeing thread-safety.
#include <drogon/i18n/LocaleManager.h>
#include <drogon/i18n/Translator.h>
#include <sstream>
void handleFormat(const HttpRequestPtr &req,
std::function<void(const HttpResponsePtr &)> &&callback)
{
std::string activeLang = Translator::getLocaleFromRequest(req);
// Retrieve pre-constructed system std::locale for active language
const std::locale &sysLoc = LocaleManager::instance().getSystemLocale(activeLang);
std::ostringstream ss;
ss.imbue(sysLoc); // Apply locale safely to stream
ss << "Formatted Number: " << std::fixed << 1234567.89;
auto resp = HttpResponse::newHttpResponse();
resp->setBody(ss.str());
callback(resp);
}drogon_i18n provides ViewHelper and dedicated CSP helper macros (vtr, vtrd, vtrl) to render localized templates cleanly.
#include <drogon/i18n/ViewHelper.h>
void handleProfile(const HttpRequestPtr &req,
std::function<void(const HttpResponsePtr &)> &&callback)
{
HttpViewData data;
data.insertAsString("name", "Ahmed");
// Automatically injects resolved locale and req pointer into HttpViewData
auto resp = ViewHelper::newI18nHttpViewResponse(req, "ProfileView", data);
callback(resp);
}<%inc
#include <drogon/i18n/ViewHelper.h>
%>
<%c++
auto req = @@.get<drogon::HttpRequestPtr>("req");
auto name = @@.get<std::string>("name");
%>
<!DOCTYPE html>
<html>
<head>
<title>{% vtrd(@@, "profile"); %}</title>
</head>
<body>
<h1>{% vtr(req, "welcome", {{"name", name}}); %}</h1>
<p>{% vtr(req, "invoice_created", "erp::accounting", {{"id", "505"}}); %}</p>
</body>
</html>- Create a Gettext source file (
locales/ar-eg/gettext.po):
msgid ""
msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
msgid "hello_gettext"
msgstr "مرحباً بك من ملف Gettext الثنائي!"- Compile the
.pofile into a binary.mofile usingmsgfmt:
msgfmt -o locales/ar-eg/gettext.mo locales/ar-eg/gettext.po- Call the translated string in your code (mapped to namespace
"gettext"):
std::string msg = tr(req, "hello_gettext", "gettext");You can extend drogon_i18n by implementing the I18nProvider interface and registering it with the plugin.
#include <drogon/i18n/I18nProvider.h>
#include <drogon/i18n/I18nPlugin.h>
#include <drogon/i18n/Translator.h>
class CustomYamlProvider : public drogon::i18n::I18nProvider
{
public:
bool load(const std::string &filePath,
const std::string &langKey,
const std::string &ns) override
{
// Parse custom file format...
std::string key = "custom_key";
std::string value = "Custom Value";
// Store into global dictionary
drogon::i18n::Translator::addTranslation(langKey, ns, key, value);
return true;
}
};
// Register in main.cpp or startup code:
auto plugin = drogon::app().getPlugin<drogon::i18n::I18nPlugin>();
if (plugin)
{
plugin->registerProvider(".yaml", std::make_shared<CustomYamlProvider>());
}| Macro | Signature | Context / Usage |
|---|---|---|
tr(req, key, ...) |
translate(HttpRequestPtr, key, ns, args) |
Main translation shorthand using HTTP Request |
trl(locale, key, ...) |
localeTranslate(locale, key, ns, args) |
Translation shorthand using explicit locale string |
vtr(req, key, ...) |
Translator::translate(req, key, ns, args) |
View shorthand for HTTP Request context |
vtrd(data, key, ...) |
ViewHelper::translate(HttpViewData, key, ns, args) |
View shorthand for HttpViewData (@@) context |
vtrl(locale, key, ...) |
Translator::localeTranslate(locale, key, ns, args) |
View shorthand for explicit locale string |
Traditional i18n libraries suffer from runtime lock contention or file I/O latency. drogon_i18n solves this using an immutable memory model:
[ Server Initialization Phase (Single-Threaded) ]
1. I18nPlugin::initAndStart() runs.
2. Scans locales directory and parses _info_.json metadata.
3. Providers (JsonProvider, MoProvider) parse files.
4. Pre-constructs std::locale objects in LocaleManager.
5. Populates global dictionary map: "langKey::ns::key" -> "value".
[ Server Runtime Phase (Multi-Threaded Event Loops) ]
1. Thousands of incoming HTTP requests arrive simultaneously across worker threads.
2. Translator performs direct O(1) lock-free std::unordered_map lookup.
3. Zero lock acquisition, zero atomic counter bouncing, zero file I/O.
Contributions, bug reports, and feature requests are welcome!
- Fork the repository on GitHub.
- Create a feature branch (
git checkout -b feature/AmazingFeature). - Commit your changes (
git commit -m 'Add some AmazingFeature'). - Push to the branch (
git push origin feature/AmazingFeature). - Open a Pull Request.