I18N
This section collects some notes for developers/programmers on how to correctly prepare their code for translations.
Contents
Which strings translation need
- Obvisious strings presented to the user should be translatable,
- but strings, which go into the log, should not. At least some of us have problems to read log entries in CJK writing.
How to make strings in code translatable
Strings in Glade files
In Glade files translatable strings have the form:
<property name="label" translatable="yes">Some translatable text with _mnemonic</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="use_underline">True</property>
For the mnemonic property use_underline see #Special_characters_and_other_tips.
Strings in C files
Normally, strings in C code just need to be enclosed with the _( ) function. Well, actually it is a macro expanding gettext() into a function, but this is usually just an implementation detail (For more macros search your IncludeDir for Glib and that for gi18n.h
). For example,
func("A translatable string!");
should instead be written as
func(_("A translatable string!"));
However, it is important to keep in mind that _( ) is a function; this means that in certain situations it cannot be used. For example,
gchar* array_of_strings[] = {_("first string"), _("second string"), _("third string")};
would cause a compiler error. Instead, these strings should be wrapped with the N_( ) macro, which is declared as #define N_(String) gettext_noop(String)
. Then, whenever one of the strings is actually used (as opposed to declared), it should be wrapped with the _( ) function again:
gchar* array_of_strings[] = {N_("first string"), N_("second string"), N_("third string")};
func(_(array_of_strings[0]));
Another use case for gettext_noop are strings which should be stored untranslated in some backend (configuration or GnuCash data), but displayed translated to the user.
See also: #Disambiguation Prefix
Strings in C Preprocessor Macros
Xgettext doesn't run cpp and can't see inside macros. Call gettext in the macro, not on the macro.
- Wrong
#define EXPLANATION "The list below shows ..." /* snip */ gnc_ui_object_references_show( _(EXPLANATION), list);
- Right
#define EXPLANATION _("The list below shows ...") /* snip */ gnc_ui_object_references_show(EXPLANATION, list);
Strings in C++ files
For strings in C++ files the same techniques as for C files can be used. The drawback of this is one could only work with C-style strings (char*), which means one would not be able to benefit from the many advantages of C++.
A better alternative is to use boost::locale's message translation functionality.
In order to use this, one needs to add the following near the top of the C++ source file:
#include <boost/locale.hpp>
// Optionally:
namespace bl = boost::locale;
// to be able to use bl::translate and bl::format instead of boost::locale::translate and boost::locale::format
After that using boost::locale essentially means using boost::locale::translate() instead of gettext(). This function is designed to be used in a stream and work with the locale imbued in the stream, so one would usually
- create a stringstream
- imbue the desired locale
- stream the result of boost::locale::translate into this stream
- when completely done, copy the string from the stringstream.
The link above gives some more elaborate examples and you can find some examples in the gnucash code already as well, like in filepathutils.cpp.
The biggest hurdle at the time of this writing is imbuing a locale in the stream. For most of the uses in gnucash this should simply be the global locale. However we don't set this yet in the C++ environment (where to do this still has to be determined). So the few spots in the code where we use this feature are generating a locale on the spot to imbue and that continues to be required until we set a global C++ locale.
Formatted strings
Some strings come have placeholders in that can only be completed at runtime, like a number of transactions in a selection, or the name of a file to be opened. The C world has a whole set of functions that work with such format strings (printf and all variants).
For C++ a more elegant method exists as well in the form of boost's localized text formatting. These have several advantages over the C based format functions, so in C++ it's recommended to use the boost::locale::format features.
Plural Forms
Not all languages have the same simple kind of plural forms as english. See gettexts Plural-forms for details.
Empty Strings
There is no need, to mark an empty strings as translatable. The gettext tools use the empty string for their message catalog header. So it will confuse translators to have your preceding comment in the header of their .po file.
Mask Unintended Line Breaks
In Scheme you can enter continous text over several lines like (_ "This report is useful to calculate periodic business tax payable/receivable from
authorities. From 'Edit report options' above, choose your Business Income and Business Expense accounts.
Each transaction may contain, in addition to the accounts payable/receivable or bank accounts,
a split to a tax account, e.g. Income:Sales -$1000, Liability:GST on Sales -$100, Asset:Bank $1100.")
#: gnucash/report/standard-reports/income-gst-statement.scm:43
msgid ""
"This report is useful to calculate periodic business tax payable/receivable "
"from\n"
" authorities. From 'Edit report options' above, choose your Business Income "
"and Business Expense accounts.\n"
" Each transaction may contain, in addition to the accounts payable/"
"receivable or bank accounts,\n"
" a split to a tax account, e.g. Income:Sales -$1000, Liability:GST on Sales -"
"$100, Asset:Bank $1100."
Watch all the "\n"s, which get inserted by xgettext and the leading spaces in the next line. While the newlines are ignored by the HTML renderer, the translators get confused.
Instead mask the line endings by "\": (_ "This report is useful to calculate periodic business tax payable/receivable from \
authorities. From 'Edit report options' above, choose your Business Income and Business Expense accounts. \
Each transaction may contain, in addition to the accounts payable/receivable or bank accounts, \
a split to a tax account, e.g. Income:Sales -$1000, Liability:GST on Sales -$100, Asset:Bank $1100.")
#: gnucash/report/standard-reports/income-gst-statement.scm:43
msgid ""
"This report is useful to calculate periodic business tax payable/receivable "
"from authorities. From 'Edit report options' above, choose your Business "
"Income and Business Expense accounts. Each transaction may contain, in "
"addition to the accounts payable/receivable or bank accounts, a split to a "
"tax account, e.g. Income:Sales -$1000, Liability:GST on Sales -$100, Asset:"
"Bank $1100."
When to use N_() and when to use _()
For developers, both N_() and _() seem to be used. What is the difference?
Simple English:
N_("s")
_("s")
However, some code may be of the form...
x = "A description";
alert(_(x));
In this case, the x, although is meant to be translated, will not be successfully translated because the engine had no idea the "A description" string is meant to be translated.
The gettext solution is as follows:
x = N_("A description");
alert(_(x));
where the first statement assigns string (marked as translatable to x), and the second statement takes the string, passes it as a parameter to _() which translates it, and is then passed it as a parameter to alert().
How to give the translators a clue
Sometimes it is useful to give the translators some additional information about the meaning of a string. To achieve this effect, you can insert a section of comment lines direct before the related string, where the first comment starts with "Translators:". There must not be any control statement between comment and string.
In C based files
Example: /* Translators: the following string deals about:
* The Answer to Life, the Universe and Everything
* Source:
* http://en.wikipedia.org/wiki/Phrases_from_The_Hitchhiker%27s_Guide_to_the_Galaxy */
func(_("42"));
In the pot and po files, this comment will show up as follows:
#. Translators: the following string deals about:
#. The Answer to Life, the Universe and Everything
#. Source:
#. http://en.wikipedia.org/wiki/Phrases_from_The_Hitchhiker%27s_Guide_to_the_Galaxy
#: foo/bar.c:123
msgid "42"
msgstr ""
Note: An empty comment line will end the process. If the string " Source:" were missing, the URL were not part of the output.
In SCM files
If the first expression has a translatable string and the file has a long header comment, split the line before the string and insert a translator comment. Otherwise the POT file will be flooded with file headers.
Example:;; Boston, MA 02110-1301, USA gnu@gnu.org
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(define GNC_COMMODITY_NS_CURRENCY
;; Translators: Namespaces of commodities
(gettext "CURRENCY"))
In glade files
In glade 3.16, using the edit button for a label, one can enter translatable, translation context and comment. From intltool fails to extract comments from glade-type files when not last attributes:
<property name="label" translatable="yes" context="infinitive" comments="my comment">
Introducing new terms
If you introduce new terms which are more than once used, you should include them to #The_glossary_file. The instruction is in #Terms missing or inadequate in the glossary file.
Borrowing Code
Ideally they would use their own text domain like in our use of ISOcode.
If not - like in 2.7 /borrowed/goffice - we should consider some msgcat magic with their po files to
- update our existing po files
- create new po files.
At present a bash script was written to take care of the first part: import-goffice-translations.sh
This script can be used to import translations from the goffice source directory into our po files. Note this script
will run over all of our existing po files in one go, so it can only be used to update all po files at once.
There is no code to create new po files. I think this can be done in a few steps as well, as in
- create the new po file as we would normally do for gnucash (see msginit elsewhere on this page)
- run the script mentioned above to import goffice translations
- this may change more files than needed, use git checkout to undo the changes to files you didn't want to alter
- continue as usual with translation work.
Further Reading
If you want to read more about this topic, GnomeI18nDeveloperTips might be a good starting point.
Follow the rules of the Gnome Human Interface Guide like Writing style and Typography.
More technical and historical details can be found in Gettext Manual: The Programmer’s View and Guile Reference Manual: Support for Internationalization.