[Development] format-like tr()

Volker Hilsheimer volker.hilsheimer at qt.io
Thu Oct 24 13:23:39 CEST 2024


> On 24 Oct 2024, at 12:29, Ivan Solovev <ivan.solovev at qt.io> wrote:
> 
> Hi Volker,
> 
>> From my understanding, the idea behind the std::format framework would be
>> to have a “cardinality" type for which we can implement a special formatter,
>> allowing “whatever” to be in the format specification.
> 
> True, but the custom formatter specialization is called when the argument id
> is already parsed.
> In your example:
> 
>  std::format("{0:Cardinality}", Cardinality{42});
> 
> the std implementation parses the "{0:" part, extracting the argument id == 0,
> and then passes the rest of the string to the std::formatter<Cardinality>
> specialization.
> 
> IIUC, Thiago suggested to use 'n' instead of the argument id.


What we need is a way to signal to translators: this placeholder in the string represents the value that decides about which plural form to use. We can call it whatever we want. Today we do that by putting a `%n` into the translatable string; the tr() function replaces that with the number passed, but also selects the appropriate plural form from the translation. So in Engineering English

tr(“You have %n unread message(s)!”, “”, total);

the translator sees that %n is where the number goes and can position the %n appropriately in the target languages, e.g. in German

Null: Du hast keine ungelesenen Nachrichten!
Singular: Du hast eine ungelesene Nachricht!
Plural: Du hast %n ungelesenen Nachrichten!

If we want to make that information available to the translator, then we need to be able to do something in the source string. A very liberal formatter would allow anything to appear between the `:` and the closing brace, e.g.:

std::format(“You have {:number of messages goes here} unread message(s)”);

is possible (updated my godbolt sandbox to do that).


Btw, where does the “tr” go? Is it going to be tr(std::format(…)) or std::vformat(tr(…))?

In the former, we’d have to pass the value twice (once for std::format to substitute, once for tr() to pick the right translation; the benefit is compile time checks.

With the latter, we get no compile time services from std::format, but don’t have to pass the value twice. Use tr()’s plural functionality and %n for it as before. Otherwise, both sources and translations can muck around with std::format-features as much as they like (it’s equivalent to tr(…).arg()).


Volker



More information about the Development mailing list