Localization
We use FastGettext in YaST for translating the strings. The PO/MO file backend is used so the translation process is the same as translating any other program which uses the GNU gettext.
Collecting the Translatable Strings
Run this command (in a GIT checkout):
rake pot
The rake command scans all sources for translatable strings and creates
the <textdomain>.pot
files. Usually only one file is created per YaST module.
Notes for Developers
Translation Marks
All texts which should be translated needs to be wrapped in _()
translation
function.
Notes for Translators
Currently YaST copies the complete comment which precedes the translated string.
# This comment is part of the pot file
# TRANSLATORS: this comment is part of the pot file
# as well.
_("Something to translate.")
However it is recommended to use the TRANSLATORS:
keyword to be compatible with
GNU gettext which copies only the text after this keyword. This would allow us
to skip the comments not relevant for translators.
Plural Forms
Use n_()
for plural forms to have a nice looking text with numbers or lists:
n_("%s package", "%s packages", packages.size) % packages.size
The first string contains singular form, the second one plural form.
Note: it is also possible to use a word (like a or one) instead of a number for the singular case:
n_("One package", "%s packages", packages.size) % packages.size
But be careful when using more format specifiers (e.g. %s
), this will not work
in singular case correctly (will use wrong parameter):
n_("One package from %s", "%s packages from %s", packages.size) %
[ packages.size, repo ]
In that case you need to use named parameters:
n_("one package from %{repo}", "%{size} packages from %{repo}", packages.size) %
{ :repo => repo, :size => packages.size }
See the GNU gettext manual for more details about plural forms.
Using Named Parameters
The named parameters mentioned in the previous section allow translators to
change the order of parameters in translations (which might be necessary in
some languages in some cases), so it makes sense to use them also in simple _()
case.
Named parameters can also help to understand the meaning of the value
(%{package}
is easier to understand than %s
), they can help to translate
the string properly.
Translating Constants
Use N_()
for translating Ruby constants, it does not translate the string at runtime
but it will be found when creating the pot file. Then use _()
when it needs to be
translated later:
class Foo
# ERROR_MSG will not be translated, but the string will be found by gettext
# when creating the .pot file
ERROR_MSG = N_("Something failed")
end
# translate the string
puts _(Foo::ERROR_MSG)
change_language
# here it will correctly use the new language
puts _(Foo::ERROR_MSG)
Interpolations
Be careful when mixing translations and string (or regular expression) interpolations, this usually does not work.
Interpolations in Translated Text
Example
_("Current time: #{time}")
Problems
This does not work at all. There are two problems:
- Ruby gettext cannot extract the text with interpolations correctly, the generated POT file contains a broken string.
- At runtime the string will not be found in the translation database as gettext receives the already expanded string.
Note: In some cases the text can be included in the generated POT (e.g.
_("Current time is #{Time.now}")
) but the POT file will contain the expanded
string which is very likely useless
(msgid "Current time is 2017-05-22 13:28:30 +0200"
) and will never match at
runtime.
Solutions
As already mentioned earlier, use the %
operator or the format
method on the
translated string.
_("Current time is %s") % [ time ]
format(_("Current time is %s"), time)
Translated Text in Interpolation
Example
"<li>#{_("Item")}</li>"
# the same problem exists with regular expression literals
/#{_("Item")}/
Problem
This should work at runtime but the problem is that Ruby gettext cannot extract the text into the POT file. That means unless the very same text is used somewhere else the translators will not translate it.
Solutions
Move the translatable text outside the interpolation so it can be found when collecting the translatable strings.
- Use the
+
operator instead of the interpolation - Use a helper variable
Examples
"<li>" + _("Item") + "</li>"
item = _("Item")
"<li>#{item}</li>"
Gettext Format Tags
The GNU Gettext supports several language format tags which describe the format for the positional arguments. This enures that the translated strings contain the same placeholders as the original string.
In the YaST Ruby code we basically use two kinds of formats, the old YCP format
("%1"
) and new Ruby variants ("%s"
and "%{foo}"
). The YCP format is
natively supported by Gettext, it works out of box. Unfortunately the Ruby
format is not supported at all.
But for the "%s"
style we can use the c-format
and for the "%{foo}"
format we can use the perl-brace-format
. That is not exactly the same as
in Ruby (Perl uses simpler "{foo}"
format without the percent sign at the
beginning) but is close enough and should avoid most of the usual translation
bugs.
Ruby supports an additional format with angled braces ("%<foo>s"
) but because
there is no suitable equivalent in any Gettext format this should not be used
in the YaST code.
The Gettext language format tags are added by a post-processing script, after creating the POT file.
YaST Pot File Auto-Generation
To automate the task of updating POT files on code changes, there is a script that is run as a Jenkins task called yast-POT-updater (internal link only).
It runs every day at midnight a script called update-tool-cron.sh
which is [documented at yast-translations github page]
(https://github.com/yast/yast-translations/blob/master/tools/README.md). Since the full checkout of all yast repositories
is really time consuming, we reuse the checkout. That means, it can only be run on a specific worker.
We are using the label yast-pot
for them.
A collection of ideas, how this could be improved is collected in a TODO list.