7.9 KiB
Telegram Desktop Localization
Coding Style Note
Use auto: In the actual codebase, variable types are almost always deduced using auto (or const auto, const auto &) rather than being written out explicitly. Examples in this guide may use explicit types for clarity, but prefer auto in practice.
// Prefer this:
auto currentTitle = tr::lng_settings_title(tr::now);
auto nameProducer = GetNameProducer(); // Returns rpl::producer<...>
// Instead of this:
QString currentTitle = tr::lng_settings_title(tr::now);
rpl::producer<QString> nameProducer = GetNameProducer();
String Resource File
Base user-facing English strings are defined in the lang.strings file:
Telegram/Resources/langs/lang.strings
This file uses a key-value format with named placeholders:
"lng_settings_title" = "Settings";
"lng_confirm_delete_item" = "Are you sure you want to delete {item_name}?";
"lng_files_selected" = "{count} files selected"; // Simple count example (see Pluralization)
Placeholders are enclosed in curly braces, e.g., {name}, {user}. A special placeholder {count} is used for pluralization rules.
Pluralization
For keys that depend on a number (using the {count} placeholder), English typically requires two forms: singular and plural. These are defined in lang.strings using #one and #other suffixes:
"lng_files_selected#one" = "{count} file selected";
"lng_files_selected#other" = "{count} files selected";
While only #one and #other are defined in the base lang.strings, the code generation process creates C++ accessors for all six CLDR plural categories (#zero, #one, #two, #few, #many, #other) to support languages with more complex pluralization rules.
Translation Process
While lang.strings provides the base English text and the keys, the actual translations are managed via Telegram's translations platform (translations.telegram.org) and loaded dynamically at runtime from the API. The keys from lang.strings (including the #one/#other variants) are used on the platform.
Code Generation
A code generation tool processes lang.strings to create C++ structures and accessors within the tr namespace. These allow type-safe access to strings and handling of placeholders and pluralization. Generated keys typically follow the pattern tr::lng_key_name.
String Usage in Code
Strings are accessed in C++ code using the generated objects within the tr:: namespace. There are two main ways to use them: reactively (returning an rpl::producer) or immediately (returning the current value).
1. Reactive Usage (rpl::producer)
Calling a generated string function directly returns a reactive producer, typically rpl::producer<QString>. This producer automatically updates its value whenever the application language changes.
// Key: "settings_title" = "Settings";
auto titleProducer = tr::lng_settings_title(); // Type: rpl::producer<QString>
// Key: "confirm_delete_item" = "Are you sure you want to delete {item_name}?";
auto itemNameProducer = /* ... */; // Type: rpl::producer<QString>
auto confirmationProducer = tr::lng_confirm_delete_item( // Type: rpl::producer<QString>
tr::now, // NOTE: tr::now is NOT passed here for reactive result
lt_item_name,
std::move(itemNameProducer)); // Placeholder producers should be moved
2. Immediate Usage (Current Value)
Passing tr::now as the first argument retrieves the string's current value in the active language (typically as a QString).
// Key: "settings_title" = "Settings";
auto currentTitle = tr::lng_settings_title(tr::now); // Type: QString
// Key: "confirm_delete_item" = "Are you sure you want to delete {item_name}?";
const auto currentItemName = QString("My Document"); // Type: QString
auto currentConfirmation = tr::lng_confirm_delete_item( // Type: QString
tr::now, // Pass tr::now for immediate value
lt_item_name, currentItemName); // Placeholder value is a direct QString (or convertible)
3. Placeholders ({tag})
Placeholders like {item_name} are replaced by providing arguments after tr::now (for immediate) or as the initial arguments (for reactive). A corresponding lt_tag_name constant is passed before the value.
- Immediate: Pass the direct value (e.g.,
QString,int). - Reactive: Pass an
rpl::producerof the corresponding type (e.g.,rpl::producer<QString>). Remember tostd::movethe producer or userpl::duplicateif you need to reuse the original producer afterwards.
4. Pluralization ({count})
Keys using {count} require a numeric value for the lt_count placeholder. The correct plural form (#zero, #one, ..., #other) is automatically selected based on this value and the current language rules.
-
Immediate (
tr::now): Pass afloat64orint(which is auto-converted tofloat64).int count = 1; auto filesText = tr::lng_files_selected(tr::now, lt_count, count); // Type: QString count = 5; filesText = tr::lng_files_selected(tr::now, lt_count, count); // Uses "files_selected#other" -
Reactive: Pass an
rpl::producer<float64>. Use thetr::to_count()helper to convert anrpl::producer<int>or wrap a single value.// From an existing int producer: auto countProducer = /* ... */; // Type: rpl::producer<int> auto filesTextProducer = tr::lng_files_selected( // Type: rpl::producer<QString> lt_count, countProducer | tr::to_count()); // Use tr::to_count() for conversion // From a single int value wrapped reactively: int currentCount = 5; auto filesTextProducerSingle = tr::lng_files_selected( // Type: rpl::producer<QString> lt_count, rpl::single(currentCount) | tr::to_count()); // Alternative for single values (less common): rpl::single(currentCount * 1.)
5. Custom Projectors
An optional final argument can be a projector function (like Ui::Text::Upper or Ui::Text::WithEntities) to transform the output.
- If the projector returns
OutputType, the string function returnsOutputType(immediate) orrpl::producer<OutputType>(reactive). - Placeholder values must match the projector's input requirements. For
Ui::Text::WithEntities, placeholders expectTextWithEntities(immediate) orrpl::producer<TextWithEntities>(reactive).
// Immediate with Ui::Text::WithEntities projector
// Key: "user_posted_photo" = "{user} posted a photo";
const auto userName = TextWithEntities{ /* ... */ }; // Type: TextWithEntities
auto message = tr::lng_user_posted_photo( // Type: TextWithEntities
tr::now,
lt_user,
userName, // Must be TextWithEntities
Ui::Text::WithEntities); // Projector
// Reactive with Ui::Text::WithEntities projector
auto userNameProducer = /* ... */; // Type: rpl::producer<TextWithEntities>
auto messageProducer = tr::lng_user_posted_photo( // Type: rpl::producer<TextWithEntities>
lt_user,
std::move(userNameProducer), // Move placeholder producers
Ui::Text::WithEntities); // Projector
Key Summary
- Keys are defined in
Resources/langs/lang.stringsusing{tag}placeholders. - Plural keys use
{count}and have#one/#othervariants inlang.strings. - Access keys via
tr::lng_key_name(...)in C++. - Call with
tr::nowas the first argument for the immediateQString(or projected type). - Call without
tr::nowfor the reactiverpl::producer<QString>(or projected type). - Provide placeholder values (
lt_tag_name, value) matching the usage (direct value for immediate,rpl::producerfor reactive). Producers should typically be moved viastd::move. - For
{count}:- Immediate: Pass
intorfloat64. - Reactive: Pass
rpl::producer<float64>, typically by converting anintproducer using| tr::to_count().
- Immediate: Pass
- Optional projector function as the last argument modifies the output type and required placeholder types.
- Actual translations are loaded at runtime from the API.