Internationalization options in templates

From Koha Wiki
Jump to navigation Jump to search

Koha has several options for helping to isolate strings in templates for translation, ways to add context information to translatable strings, and handling for plural forms. Although most strings can be pulled from the templates by the translation tools without these functions, in many cases these functions will make the translator's job easier.

When we want to ensure that a string is picked up for translation we can wrap them in in function calls. Ex:

[% t('A translatable string') %]

That way, translated strings are clearly identified and can be extracted more easily using standard tools (xgettext). Strings wrapped in t() calls will be retrieved during run-time instead of during creation of translated template files.

Plural forms

Please read this first: https://www.gnu.org/software/gettext/manual/gettext.html#Plural-forms

Previously, if we wanted to handle plural forms in Koha we would do something like this

[% IF items.count == 1 %]
    There is 1 item
[% ELSE %]
    There are [% items.count %] items
[% END %]

Or, easier:

Items: [% items.count %]

The 1st example assumes all languages have only one singular form and one plural form, which is wrong. The 2nd example does not give us much flexibility to write messages.

This alternative tells the string extractor which strings are singular and which are plural. It can be done by wrapping strings like this:

[%# assumes that we have at least 1 item %]
[% tn('There is an item', 'There are several items', items.count) %]

The generic form is

[% tn( msgid, msgid_plural, n) %]

What happens here is that we tell gettext that:

  • the singular form is msgid ('There is an item')
  • the plural form is msgid_plural ('There are several items'),

This will result in a .po file entry like this:

msgid "There is an item"
msgid_plural "There are several items"
msgstr[0] "Here goes the translation for the singular form"
msgstr[1] "Translation for the 1st plural form"
msgstr[2] "Translation for the 2nd plural form, if any"
msgstr[X] "Translation for the Xth plural form, if any"

The n parameter of tn() is an integer that Gettext will use to determine which form should be used, depending of the Plural-Forms PO header (which is different for each language, see https://localization-guide.readthedocs.io/en/latest/l10n/pluralforms.html). For instance, for Slovak, Plural-Forms is

nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;

which means the result of tn() will be:

  • msgstr[0] if n is 1
  • msgstr[1] if n is 2, 3 or 4
  • msgstr[2] for any other value of n

Context

Context is simply an additional string to help disambiguate words in certain situations. For instance, "item" is a very generic term and has several translations in other languages. We can help the translator by attaching context to a string

[% tp('Bibliographic record', 'item') %]

The resulting PO entry will be:

msgctxt "Bibliographic record"
msgid "item"
msgstr ""

The context will appear in translators' translation interface so that they will know what kind of item they are dealing with.

The context will not appear in Koha interface

The same string with different context can have different translations. For instance:

 [% tp('email', 'subject') %]
 [% tp('bibliographic record', 'subject') %]

will result in two different entries in the .po file:

msgctxt "bibliographic record"
msgid "subject"
msgstr ""
msgctxt "email"
msgid "subject"
msgstr ""

It can also help in some cases where it is not clear if the string to translate is a verb, a noun, or something else. For instance:

[% tp('verb', 'Order') # to order %]
[% tp('noun', 'Order') # an order %]

Variable substitution in translation

Imagine you want to translate the following message

Item checked out on [% date %]

You could do something like this

[% t('Item checked out on') %][% issue.issuedate %]

But this has some disadvantages:

  • The message is not complete: The translator can't be sure if it's a date after the message, and the translation might change if it's something other than a date.
  • It is impossible to put the date elsewhere than the end of the message, which could be a problem in some languages.

To fix that, you can use variable substitutions.

[% tx('Item checked out on {date}', { date = issue.issuedate }) %]

What we have done here:

  • Append an 'x' to the function name (every translation function has an 'x' variant)
  • Add a {date} string in msgid parameter, where date is the name of a key inside the last parameter of the function
  • Add an hashref as last parameter that contain the string substitutions to be done after the retrieval of the translated message

You can add as many variable substitutions as you want. For instance

[% tx("Hi {foo}! I'm {bar}", { foo = foo_value, bar = bar_value }) %]

Translations will have to keep those {NAME}.

msgid "Hi {foo}! I'm {bar}"
msgstr "Salut {foo}! Je suis {bar}"

Combine them all

You can use plural forms, context and variable substitution at the same time

[% tnpx(context, msgid, msgid_plural, n, vars) %]
[% tnpx('bibliographic record', 'there is {count} item', 'there are {count} items', items.count, { count = items.count }) %]