Skip to content

Internationalization (i18n)

The framework supports internationalization based on gettext. Translations are organized by domains, allowing you to separate localization between application modules.

Configuration

Translations are configured in the translations section of config.json:

json
{
    "translations": [
        {
            "domain": "identity",
            "path": "backend/identity/locale"
        },
        {
            "domain": "shop",
            "path": "backend/shop/locale"
        }
    ]
}

domain string

Translation domain name. Used to reference a specific set of translations in code.

path string

Path to the locale directory.

Translation file structure

Translation files should be located at the following path:

<path>/<lang>/LC_MESSAGES/<domain>.mo

For example, for domain identity and language ru:

backend/identity/locale/ru/LC_MESSAGES/identity.mo
backend/identity/locale/en/LC_MESSAGES/identity.mo

Creating translation files

1. Create PO file

Create an identity.po file for each language:

po
# English translations for identity module
msgid ""
msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Language: en\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"

msgid "Welcome"
msgstr "Welcome"

msgid "Invalid credentials"
msgstr "Invalid credentials"

# Plural forms
msgid "error"
msgid_plural "errors"
msgstr[0] "error"
msgstr[1] "errors"

2. Compile to MO file

bash
msgfmt -o identity.mo identity.po

Usage in code

Include the header file:

c
#include "translation.h"

Simple translation

The tr function returns a translation by message identifier:

c
const char* message = tr(ctx, "identity", "Welcome");
// Result: "Welcome" (for en) or localized string (for other languages)

Warning

Do not free the memory returned by tr. The string is managed by the translation system.

Translation with placeholders

The trf function replaces {key} placeholders with provided values:

c
// PO file: msgid "Hello, {name}!" msgstr "Hello, {name}!"

char* message = trf(ctx, "identity", "Hello, {name}!", "name", username, NULL);
// Result: "Hello, John!"

free(message); // Must free the memory

Argument list

Arguments are passed as "key", "value" pairs and terminated with NULL.

Plural forms

The trn function selects the correct form based on the count:

c
const char* message = trn(ctx, "identity", "error", "errors", error_count);
// 1 -> "error"
// 2 -> "errors"

Plural forms with placeholders

The trnf function combines plural forms and placeholder substitution:

c
char count_str[16];
snprintf(count_str, sizeof(count_str), "%d", count);

char* message = trnf(ctx, "identity", "{n} error found", "{n} errors found",
                     count, "n", count_str, NULL);
// 1 -> "1 error found"
// 3 -> "3 errors found"

free(message);

Language detection

Language is determined automatically in the following order of priority:

  1. Query parameter lang?lang=ru
  2. Accept-Language header — parsed to get the preferred language
  3. Default languageen

Example

GET /api/users?lang=ru
Accept-Language: en-US,en;q=0.9

In this case, Russian (ru) will be used since the query parameter has the highest priority.

API reference

tr

c
const char* tr(httpctx_t* ctx, const char* domain, const char* msgid);
ParameterDescription
ctxHTTP context for language detection
domainTranslation domain
msgidMessage identifier
ReturnsTranslated string (do not free)

trf

c
char* trf(httpctx_t* ctx, const char* domain, const char* msgid, ...);
ParameterDescription
ctxHTTP context for language detection
domainTranslation domain
msgidMessage identifier with placeholders
..."key", "value" pairs terminated with NULL
ReturnsTranslated string (caller must free)

trn

c
const char* trn(httpctx_t* ctx, const char* domain, const char* singular,
                const char* plural, unsigned long n);
ParameterDescription
ctxHTTP context for language detection
domainTranslation domain
singularSingular form
pluralPlural form
nCount for form selection
ReturnsTranslated string (do not free)

trnf

c
char* trnf(httpctx_t* ctx, const char* domain, const char* singular,
           const char* plural, unsigned long n, ...);
ParameterDescription
ctxHTTP context for language detection
domainTranslation domain
singularSingular form with placeholders
pluralPlural form with placeholders
nCount for form selection
..."key", "value" pairs terminated with NULL
ReturnsTranslated string (caller must free)

Usage example

c
#include "http.h"
#include "translation.h"

void get_profile(httpctx_t* ctx) {
    // Simple translation
    const char* title = tr(ctx, "identity", "Profile");

    // Translation with placeholder
    char* greeting = trf(ctx, "identity", "Welcome back, {name}!",
                         "name", user->name, NULL);

    // Plural form
    char count_str[16];
    snprintf(count_str, sizeof(count_str), "%d", notification_count);
    char* notifications = trnf(ctx, "identity",
        "You have {n} new notification",
        "You have {n} new notifications",
        notification_count, "n", count_str, NULL);

    // ... build response ...

    free(greeting);
    free(notifications);
}

Released under the MIT License.