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::producer
of the corresponding type (e.g.,rpl::producer<QString>
). Remember tostd::move
the producer or userpl::duplicate
if 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 afloat64
orint
(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.strings
using{tag}
placeholders. - Plural keys use
{count}
and have#one
/#other
variants inlang.strings
. - Access keys via
tr::lng_key_name(...)
in C++. - Call with
tr::now
as the first argument for the immediateQString
(or projected type). - Call without
tr::now
for the reactiverpl::producer<QString>
(or projected type). - Provide placeholder values (
lt_tag_name, value
) matching the usage (direct value for immediate,rpl::producer
for reactive). Producers should typically be moved viastd::move
. - For
{count}
:- Immediate: Pass
int
orfloat64
. - Reactive: Pass
rpl::producer<float64>
, typically by converting anint
producer 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.