Since decades KDE’s translation and localization framework KI18n
provides a mechanism for marking strings for message extraction and deferred translation,
I18N_NOOP prepprocessor macros. Those can be very error prone though, so
for KDE Frameworks 5.89 there is now a proposed replacement.
I18N_NOOP macro differs from the more widely used
i18n() function calls in that it
only causes a message to be extracted for translation, but it doesn’t actually perform the
translation. This is useful when the translation isn’t possible yet at this point or when
there are many possible messages of which only very few are actually needed at runtime.
Therefore those macros often appear in static message tables.
This isn’t unique to KI18n, Qt’s translation system has similar macros for example (
And while this isn’t too bad for a single message, there are many more cases to consider, such as any possible combinations of:
- Quantified messages, ie. singular/plural support.
- Messages needing a translation context for disambiguation.
- Messages containing Kuit markup.
That’s where the macros hit their limits. There we have more than one argument to pass
to the runtime translation call, so what would a macro map to? And do we trust the developer to manually
carry the context string? (see e.g.
It gets harder and harder to use this correctly the more variants we need to consider, so Albert rightfully wasn’t too happy when I recently proposed plural variants of those macros.
So what can we do instead?
KI18n has another mechanism for deferred translation,
KLocalizedString and the various
construction functions for it. Unlike the macro approach you cannot accidentally disassociate the
various strings making up a message with this, it’s all tied together by a single object. Still
you can control the time when the actual translation happens, so creating
can happen very early in the program flow.
There’s a downside though, creating a
KLocalizedString isn’t exactly cheap, it involves memory allocations
and deep copies of the strings. That isn’t a big problem for individual messages, but it does hurt
if you are dealing with larger message tables, with many entries that might never actually be needed
during a program run. The macros avoided those costs.
We can have the best of both worlds though! The macro solution goes back to the C++98 days, and might have been the best we could do at the time, but things have changed.
And that’s where the new
KLazyLocalizedString comes in. It’s a simple
constexpr container for string literals
needed for translations, and as such can be stored in static data tables. At runtime there’s only one
meaningful thing to do with it, converting it to a
KLocalizedString when needed.
KLazyLocalizedString has its own set of construction functions,
Unlike their runtime counter-parts those enforce the use of string literals. That is necessary anyway for
message extraction to work, and it avoids having to deal with runtime memory issues at all here.
Migrating away from I18N_NOOP
Let’s look at the following example which illustrates a typical use-case for the
a static table containing among other things a message that should be translated at runtime.
KLazyLocalizedString this looks very similar:
This is now no longer constrained to using the same
kli18n() variant in every entry though, we could change the second entry
to include a context for example, without having to worry about this when consuming the table entries later:
It’s also possible to have plural texts in a static message table, the API docs in the merge request contains an example illustrating that.
At this point this is still in review and scheduled for KDE Frameworks 5.89 which is due to be released early January. The old macros would be deprecated at the same time, so we’ll have a bit of porting ahead of us there.