Difference between revisions of "I18N"

From GnuCash
Jump to: navigation, search
m (When to use N_() and when to use _(): highlight)
(GUI Guidelines has a few more details.)
 
(74 intermediate revisions by 3 users not shown)
Line 1: Line 1:
 +
[[Category:Development‏‎]]
 +
[[Category:I18N]]
 +
[[Category:Translation]]
 +
This section collects some notes for developers/programmers on how to correctly prepare their code for [[translation]]s.
  
This section collects some notes for developers/programmers on how to correctly prepare their code for translations.
+
[[GUI Guidelines]] has a few more details.
  
=== Which strings translation need ===
+
=== General Thoughts ===
* Obvisious strings presented to the '''user''' should be ''translatable'',
+
;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.
+
: * but strings, which go into the '''log''', should ''not''. At least some of us have problems to read log entries in CJK writing.
 +
: * Pure '''numerical''' — including date, time and, monetary — strings should be ''format''ed according [[Locale Settings]], but usually ''not translated''.
 +
: * Strings containing only a '''special symbol''' like ''bullets'' usually need no translation. But in some cases there can be different typographical conventions like for quoting.
 +
: * Verify, that '''arrows''' like <tt>>></tt> are properly reverted if you run the program in a RTL written language:
 +
::<syntaxhighlight lang="sh">LANG=he_IL.utf8 gnucash</syntaxhighlight> Usually our used tools are smart enough and then no translation is required.
 +
: * ''Never'' mark an '''empty string''' 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.
 +
;Separate content and form: Avoid markups, leading and trailing spaces; use functions to generate uppercase text or other formated text, …
 +
:Often the GUI offers better options for highlighting.
 +
;Granulation: With a few exceptions like ''labels'', ''menu entries'', or ''table elements'' use '''full sentence'''s in proper '''US English'''. Because grammars are very different do ''not concat'' them, but use '''format strings'''. Usually the longest allowed element is a ''paragraph'', but it is not easy to find a change like a fixed typo in a full paragraph.
 +
: Split repeated elements for translation and compose them for output by a format string.
 +
:;Example: Msg1 = Sentence1, Sentence2
 +
::Msg2 = Sentence1, Sentence3
 +
:For the translators it is less work to translate Sentence1, Sentence2, Sentence3 each once.
 +
;Avoid Ambiguity: This can be achieved in several ways:
 +
:* Use of '''full sentence'''s, i.e. for ''tooltips''.
 +
:* Use of '''context''' for short messages like ''column headers''.
 +
:* Adding translator '''comment'''s.
 +
:* Add an entry to the '''glossary,''' if a term needs explanation and is used more than once.
 +
;Recommendations: If a message has more than one parameter, insert ordinal numbers already in the source like in C "%1$s contains %2$s." allowing the translator to write "%2$s is part of %1$s." instead.
 +
;Conventions: A few conventions are defined in [[GUI Guidelines]].
 +
:;Textual: US English instead of british English or more worse (~our vs. ~or, ~zation vs. ~sation …).
 +
::Separate sentences by one space only after the full stop, not runoff style!
 +
::;Recycle already existing strings: Try to use for the same item in
 +
::::;<tt>glade</tt> and <tt>schema</tt> files: the same ''label'' and ''tooltip'' —[[GUI_Guidelines#Tooltips_and_Descriptions_of_Preferences]]
 +
::::;different dialogs: like editor and overview
 +
:::To get a dictionary <syntaxhighlight lang="sh">
 +
cd $BUILDDIR
 +
ninja pot
 +
# Either with location lines:
 +
msgcat -s -o po/gnucash-dict.pot po/gnucash.pot
 +
# or without location lines:
 +
msgcat --no-location -s -o po/gnucash-dict.pot po/gnucash.pot
 +
# Don't forget to move your dict to a save place now!
 +
</syntaxhighlight>
 +
:::See <syntaxhighlight lang="sh" inline>msggrep --help</syntaxhighlight> to query that dictionary.
 +
 
 +
=== String Manipulation ===
 +
On ''translated strings'' you must not use standard C(++) functions as defined in <tt>ctype.h</tt> or <tt><cstring> (string.h)</tt>! They assume ASCII characters, which have a size of 1 byte/char, but UTF-8 chars can have up to 4 byte/char. Instead use [https://developer.gnome.org/glib/stable/glib-Unicode-Manipulation.html GLib's Unicode Manipulation].
  
 
=== How to make strings in code translatable ===
 
=== How to make strings in code translatable ===
Line 11: Line 52:
 
In '''Glade''' files translatable strings have the form:
 
In '''Glade''' files translatable strings have the form:
 
<syntaxhighlight lang="xml" highlight="1,5">
 
<syntaxhighlight lang="xml" highlight="1,5">
   <property name="label" translatable="yes">Some translatable text with _mnemonic</property>
+
   <property name="label"
 +
      translatable="yes"
 +
      context="infinitive"
 +
      comments="Additional infos for the translator">Some translatable text with _mnemonic</property>
 
   <property name="visible">True</property>
 
   <property name="visible">True</property>
 
   <property name="can_focus">True</property>
 
   <property name="can_focus">True</property>
Line 17: Line 61:
 
   <property name="use_underline">True</property>
 
   <property name="use_underline">True</property>
 
</syntaxhighlight>
 
</syntaxhighlight>
 +
:;Note:The linebreaks between property attibutes were inserted for legability.
 +
;Attributes:
 +
:;translatable: required for translation,
 +
:;context: Optional to restrict the context,
 +
:;comments: Optional for additional infos.
 +
 
For the mnemonic property ''use_underline'' see [[Translation#Special_characters_and_other_tips]].
 
For the mnemonic property ''use_underline'' see [[Translation#Special_characters_and_other_tips]].
 +
 +
;Attention:<syntaxhighlight lang="xml">
 +
  <property name="label" translatable="yes">
 +
Some translatable text
 +
with multiple lines.
 +
</property>
 +
</syntaxhighlight> will result in <SyntaxHighlight lang="po" highlight="">
 +
#: gnucash/gtkbuilder/foo.glade:11
 +
msgid ""
 +
"\n"
 +
"Some translatable text\n"
 +
"with multiple lines.\n"
 +
msgstr ""
 +
</SyntaxHighlight>, but the first and last newline are usually undesired. To avoid them write <syntaxhighlight lang="xml">
 +
  <property name="label" translatable="yes">Some translatable text
 +
with multiple lines.</property>
 +
</syntaxhighlight>
  
 
==== Strings in C files ====
 
==== Strings in C files ====
Normally, strings in '''C''' code just need to be enclosed with the function <tt>_( )</tt>. Well, actually it is a macro expanding into a function <tt>gettext()</tt>, but this is usually just an implementation detail. For example,
+
===== Preparation =====
 +
Each C module
 +
:* having translated C strings or
 +
:* containing <tt>printf()</tt>/<tt>fprintf()</tt>/... calls with a format string that could be a translated C string
 +
should contain the line: <syntaxhighlight lang="C">
 +
#include <libintl.h>
 +
</syntaxhighlight>
 +
:;Source: {{URL:Gnu}}software/gettext/manual/html_node/Importing.html.
 +
If you want to use the 1-letter+underline abbreviations as we do, in lieu of insert<syntaxhighlight lang="C">
 +
#include <glib/gi18n.h>
 +
</syntaxhighlight>
 +
:;Details: {{URL:GTK-docs}}glib/i18n.html
 +
 
 +
===== Instant translation =====
 +
Normally, strings in '''C''' code just need to be enclosed with the ''function'' '''<tt>_( )</tt>'''. Well, actually it is a '''macro''' declared as <syntaxhighlight lang="C" inline>#define _(String) gettext (String)</syntaxhighlight>, but this is usually just an implementation detail. For example,
 
<syntaxhighlight lang="C">
 
<syntaxhighlight lang="C">
 
  func("A translatable string!");
 
  func("A translatable string!");
Line 29: Line 110:
 
</syntaxhighlight>
 
</syntaxhighlight>
  
However, it is important to keep in mind that <tt>_( )</tt> is a function; this means that in certain situations it cannot be used. For example,
+
;When not  to use instant translation:
 +
:* It is important to keep in mind that <tt>_( )</tt> is a function; this means that ''in certain situations it cannot be used''. For example, <syntaxhighlight lang="C">
 +
gchar* array_of_strings[] = {_("first string"), _("second string"), _("third string")};
 +
</syntaxhighlight> would cause a compiler error.
 +
:* Static variables are created at program initialization before main so there's no locale or gettext text_domain. That means that these strings won't get translated. The fix is to use the N_(x) macro to mark them for xgettext and then call gettext (via the _(x) macro) when you pass them to the GtkWidget for display.
 +
 
 +
===== Delayed Translation =====
 +
Instead, these strings should be wrapped with the '''<tt>N_( )</tt> macro''', which is declared as <syntaxhighlight lang="C" inline>#define N_(String) gettext_noop(String)</syntaxhighlight>. Then, whenever one of the strings is actually ''used'' (as opposed to ''declared''), it should be wrapped with the <tt>_( )</tt> function again:
 
<syntaxhighlight lang="C">
 
<syntaxhighlight lang="C">
gchar* array_of_strings[] = {_("first string"), _("second string"), _("third string")};
+
gchar* array_of_strings[] = {N_("first string"), N_("second string"), N_("third string")};
 +
:
 +
func(_(array_of_strings[0]));
 
</syntaxhighlight>
 
</syntaxhighlight>
would cause a compiler error. Instead, these strings should be wrapped with the <tt>N_( )</tt> macro, which is declared as <syntaxhighlight lang="C" inline>#define N_(String) gettext_noop(String)</syntaxhighlight>. Then, whenever one of the strings is actually ''used'' (as opposed to ''declared''), it should be wrapped with the <tt>_( )</tt> function again:
+
Another use case for <tt>gettext_noop()</tt> are strings which should be stored untranslated in some backend (log files, configuration or GnuCash data), but displayed translated to the user.<syntaxhighlight lang="C">
<syntaxhighlight lang="C">
+
gchar* msg = N_("Something went wrong!");  /* Tell xgettext to insert the string into gnucash.pot */
gchar* array_of_strings[] = {N_("first string"), N_("second string"), N_("third string")};
+
PWARN (msg);   /* Record it untranslated in the trace file */
func(_(array_of_strings[0]));
+
/* pop up a window with ... */
 +
  _(msg)   /* Inform the user with the translated message */
 
</syntaxhighlight>
 
</syntaxhighlight>
Another use case for <tt>gettext_noop()</tt> are strings which should be stored untranslated in some backend (log files, configuration or GnuCash data), but displayed translated to the user.
 
  
For ''more macros'' search your '''IncludeDir''' for ''Glib'' and that for <code>gi18n.h</code>.
+
===== Using Context =====
 +
Whenever there are ambiguities, define a context. We often use
 +
:# one letter '''abbreviations'''
 +
:## as the column header and
 +
:## as a status flag
 +
:# '''samples''' to determinate the wide of a column or an input field.
 +
:# Another use case would be ''Invoice'':
 +
:## in general,
 +
:## from supplier,
 +
:## to client.
 +
There a 2 functions to tell the translator and gettext the context:
 +
:;Ancient: <SyntaxHighlight lang="C">
 +
  Q_("Column header for 'Reconciled'|R");
 +
</SyntaxHighlight> and
 +
:;Recent: <SyntaxHighlight lang="C">
 +
  C_("Column header for 'Reconciled'", "R")
 +
</SyntaxHighlight> Both will result in
 +
<SyntaxHighlight lang="po" highlight="2-3">
 +
#: gnucash/gnome/reconcile-view.c:425
 +
#: gnucash/register/ledger-core/split-register-layout.c:691
 +
#: gnucash/register/ledger-core/split-register-model.c:306
 +
msgctxt "Column header for 'Reconciled'"
 +
msgid "R"
 +
msgstr ""
 +
</SyntaxHighlight>
 +
The benefit of C_(): also a NC_() macro ([[#Delayed Translation]]) exists, but no NQ_().
 +
A NC_() macro will be translatable by using the form:
 +
<SyntaxHighlight lang="C">
 +
C_("navigation","back")  // must be string literals
  
See also: [[Translation#Disambiguation Prefix]]
+
const char* domain = NULL;
 +
const char* context = "navigation";
 +
const char* msgid = "back";
 +
g_dpgettext2 (domain, context, msgid)
 +
</SyntaxHighlight>.
 +
;Context form: Please use similar forms of contexts like the existing entries. Use e.g.  <syntaxhighlight lang="sh">
 +
grep -n 'msgctxt' po/de.po | sort -fk2.1
 +
</syntaxhighlight> for a list.
 +
;Note: Before GnuCash 3.7 there was another selfmade form, which got replaced as it was error prone. [https://bugs.gnucash.org/show_bug.cgi?id=797349 Bug 797349 - "A"ssociate header badly translated]
  
 
==== Strings in C Preprocessor Macros ====
 
==== 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.
+
Xgettext doesn't run cpp and can't see inside macros. Call gettext ''in'' the macro, not ''on'' the macro.
 
;Wrong:<syntaxhighlight lang="C">#define EXPLANATION "The list below shows ..."
 
;Wrong:<syntaxhighlight lang="C">#define EXPLANATION "The list below shows ..."
 
/* snip */
 
/* snip */
Line 57: Line 183:
 
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++.
 
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''' [http://www.boost.org/doc/libs/1_56_0/libs/locale/doc/html/messages_formatting.html message translation] functionality.
+
A better alternative is to use '''boost::locale's''' [{{URL:Boost}}doc/libs/{{Boost_Version}}/libs/locale/doc/html/messages_formatting.html message translation] functionality.
  
 
In order to use this, one needs to add the following near the top of the C++ source file:
 
In order to use this, one needs to add the following near the top of the C++ source file:
Line 65: Line 191:
 
// Optionally:
 
// Optionally:
 
namespace bl = boost::locale;
 
namespace bl = boost::locale;
// to be able to use bl::translate and bl::format instead of boost::locale::translate and boost::locale::format
+
/* to be able to use bl::translate
 +
* and               bl::format
 +
* instead of boost::locale::translate
 +
* and       boost::locale::format   */
 
</syntaxhighlight>
 
</syntaxhighlight>
  
 
After that using boost::locale essentially means using <tt>boost::locale::translate()</tt> instead of <tt>gettext()</tt>. This function is designed to be used in a stream and work with the locale imbued in the stream, so one would usually
 
After that using boost::locale essentially means using <tt>boost::locale::translate()</tt> instead of <tt>gettext()</tt>. 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
+
# create a stringstream,
* imbue the desired locale
+
# <tt>imbue</tt> the desired locale,
* stream the result of boost::locale::translate into this stream
+
# stream the result of <tt>boost::locale::translate</tt> and <tt>boost::locale::format</tt> into this stream,
* when completely done, copy the string from the stringstream.
+
# 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 [https://github.com/Gnucash/gnucash/blob/06ad55cae981111e8858238229e2605f4c05b0d2/libgnucash/core-utils/gnc-filepath-utils.cpp#L602 in filepathutils.cpp].
 
The link above gives some more elaborate examples and you can find some examples in the gnucash code already as well, like [https://github.com/Gnucash/gnucash/blob/06ad55cae981111e8858238229e2605f4c05b0d2/libgnucash/core-utils/gnc-filepath-utils.cpp#L602 in filepathutils.cpp].
  
Line 79: Line 208:
 
===== Formatted strings =====
 
===== 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).
+
Some strings can 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 (<tt>printf</tt> and all variants).
 +
 
 +
For C++ a more elegant method exists as well in the form of [{{URL:Boost}}doc/libs/{{Boost_Version}}/libs/locale/doc/html/localized_text_formatting.html boost's localized text formatting]. These have several advantages over the C based format functions, so in C++ it's recommended to use the <tt>boost::locale::format</tt> features.
  
For C++ a more elegant method exists as well in the form of [http://www.boost.org/doc/libs/1_56_0/libs/locale/doc/html/localized_text_formatting.html boost's localized text formatting]. These have several advantages over the C based format functions, so in C++ it's recommended to use the <tt>boost::locale::format</tt> features.
+
==== Strings in SCM files ====
 +
The general form is <SyntaxHighLight lang='scheme'>
 +
;; enable I18N:
 +
(use-modules (ice-9 i18n))
 +
;; General Form:
 +
(gettext msg [domain [category]])
 +
;; The abbreviation changed with Guile 3:
 +
(G_ "Some translatable string")
 +
</SyntaxHighLight>.
 +
:;Source: [{{URL:guile-manual}}Gettext-Support.html Guile Manual: Gettext Support]
 +
:;See also: [{{URL:guile-manual}}Formatted-Output.html Guile Manual: Formatted Output]
  
 
==== Plural Forms ====
 
==== Plural Forms ====
  
Not all languages have the same simple kind of plural forms as english. See [http://www.gnu.org/s/hello/manual/gettext/Plural-forms.html gettexts Plural-forms] for details.
+
Not all languages have the same simple kind of plural forms as english. See [{{URL:Gnu}}s/hello/manual/gettext/Plural-forms.html gettexts Plural-forms] for details.
 +
 
 +
==== Avoid underline ====
 +
Because Underline <code>_</code>marks mnemonics in the GUI, do not use it for other purposes. It would confuse <tt>msgfmt -c --check-accelerators="_"</tt> else. Use the normal dash <code>-</code> instead.
  
==== Empty Strings ====
+
==== Avoid Markups and Other Formating ====
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.
+
It is annoying, if you have to translate <tt><nowiki>"number", "number:", "number: ", "<b>number</b>", "Number", "NUMBER"</nowiki></tt> all separate. In many cases you can move the formating elements outside of the translatable string or use functions for case conversion.
  
 
==== Mask Unintended Line Breaks ====
 
==== Mask Unintended Line Breaks ====
In Scheme you can enter continous text over several lines like <SyntaxHighlight lang="SCM">
+
In some languages like Scheme you can enter continous text over several lines like <SyntaxHighlight lang="SCM">
 
   (_ "This report is useful to calculate periodic business tax payable/receivable from
 
   (_ "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.
 
  authorities. From 'Edit report options' above, choose your Business Income and Business Expense accounts.
Line 127: Line 271:
 
</SyntaxHighlight>
 
</SyntaxHighlight>
  
=== When to use N_() and when to use _() ===
+
=== Add Infos for Translators ===
  
For developers, both N_() and _() seem to be used. What is the difference?
+
Often it is useful to give the translators some additional information about the meaning of specific words or full messages.
  
Simple English:
+
* Each '''financial term''' should have a [https://github.com/Gnucash/gnucash/blob/maint/po/glossary/gnc-glossary.txt glossary] entry because the meaning can be slightly differ by language or country.
 +
* Each term with a '''GnuCash specific''' meaning like "Bill", too.
 +
* If the same explanation is required '''more than once''', consider to add a section to the glossary.
  
<SyntaxHighLight lang='c'>N_("s")</SyntaxHighLight> will mark string "s" as translatable, but will ''not translate'' in code.
+
==== Glossary ====
<SyntaxHighLight lang='c'>_("s")</SyntaxHighLight> will mark string "s" as translatable, and will be translated on the spot.
+
If you introduce new terms which are more than once used, you should include them to [[Translation#The_glossary_file]]. The instruction is in [[Translation#Terms missing or inadequate in the glossary file]].
  
However, some code may be of the form...
+
==== Translator Comments for Single Messages ====
<SyntaxHighLight lang='c'>
+
You can insert a section of comment lines ''direct'' before the related string, where the first comment starts with "Translators:".
x = "A description";
+
* There must not be any control statement (if, case, while …) between comment and string.
alert(_(x));
+
* Using '''block comments''' instead of line comments will allow the gettext tools to do their own ''wrapping''.
</SyntaxHighLight>
+
* If the ''same message appears several times'' in the source files, the comment should only be attached to the '''first occurrence'''.
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.
+
 
+
===== In C based files =====
The gettext solution is as follows:
 
<SyntaxHighLight lang='c'>
 
x = N_("A description");
 
alert(_(x));
 
</SyntaxHighLight>
 
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: <SyntaxHighLight lang='c'>
 
Example: <SyntaxHighLight lang='c'>
 
  /* Translators: the following string deals about:
 
  /* Translators: the following string deals about:
  * The Answer to Life, the Universe and Everything
+
    The Answer to Life, the Universe and Everything
  * Source:
+
    Source:
  * http://en.wikipedia.org/wiki/Phrases_from_The_Hitchhiker%27s_Guide_to_the_Galaxy */
+
    https://en.wikipedia.org/wiki/Phrases_from_The_Hitchhiker%27s_Guide_to_the_Galaxy */
 
  func(_("42")); </SyntaxHighLight>
 
  func(_("42")); </SyntaxHighLight>
 
  
 
In the pot and po files, this comment will show up as follows:
 
In the pot and po files, this comment will show up as follows:
Line 168: Line 301:
 
#. The Answer to Life, the Universe and Everything
 
#. The Answer to Life, the Universe and Everything
 
#. Source:
 
#. Source:
#. http://en.wikipedia.org/wiki/Phrases_from_The_Hitchhiker%27s_Guide_to_the_Galaxy
+
#. https://en.wikipedia.org/wiki/Phrases_from_The_Hitchhiker%27s_Guide_to_the_Galaxy
 
#: foo/bar.c:123
 
#: foo/bar.c:123
 
msgid "42"
 
msgid "42"
 
msgstr "" </SyntaxHighLight>
 
msgstr "" </SyntaxHighLight>
  
 +
;Notes:
 +
:An empty comment line will end the process. If the string " Source:" were missing, the URL were not part of the output.
 +
: Do ''not decorate'' translator comments by a frame of stars:<syntaxhighlight lang="C">
 +
/* Translators: the following string deals about:                                    *
 +
  * The Answer to Life, the Universe and Everything                                  *
 +
  * Source:                                                                          *
 +
  * https://en.wikipedia.org/wiki/Phrases_from_The_Hitchhiker%27s_Guide_to_the_Galaxy */
 +
</syntaxhighlight> After its reformatting by xgettext, it would look terrible in the message cataloges.
  
'''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 =====
 
 
==== 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.
 
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:<SyntaxHighLight lang='scheme'>
+
;Example:<SyntaxHighLight lang='scheme'>
 
;; Boston, MA  02110-1301,  USA      gnu@gnu.org
 
;; Boston, MA  02110-1301,  USA      gnu@gnu.org
 
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
Line 188: Line 327:
 
</SyntaxHighLight>
 
</SyntaxHighLight>
  
==== In glade files ====
+
===== In XML =====
 +
Some variants like [[#Strings in Glade files|Glade]] have their specific tags. Others like [{{URL:GTK-docs}}gio/class.Settings.html GSettings] use simple XML comments: <syntaxhighlight lang="xml" highlight="1-3">
 +
<!-- Translators: A list of words which are not allowed to be typed, in
 +
      GVariant serialization syntax.
 +
      See: https://developer.gnome.org/glib/stable/gvariant-text.html -->
 +
<default l10n='messages' context='Banned words'>['bad', 'words']</default>
 +
</syntaxhighlight>
  
In glade 3.16, using the edit button for a label, one can enter translatable, translation context and comment.
+
===== In HTML + PHP =====
From [https://bugs.launchpad.net/intltool/+bug/705420 intltool fails to extract comments from glade-type files when not last attributes]:
+
Here it is important, that the comment is not in the HTML, but the PHP part:<SyntaxHighLight lang='html+php' highlight="2">
<SyntaxHighLight lang='xml'>
+
<img class="featureimage" src="<?=$top_dir?>/images/features/<?PHP
<property name="label" translatable="yes" context="infinitive" comments="my comment">
+
//Translators: If you already translated the guide, you can copy the image into /images/features/ as e.g. basics_AccountRelationships.ll.png
</SyntaxHighLight> should work.
+
echo T_("basics_AccountRelationships.C.png");
 
+
?>" alt="[Basic account relationship]" />
=== Introducing new terms ===
+
</SyntaxHighLight>
If you introduce new terms which are more than once used, you should include them to [[Translation#The_glossary_file]]. The instruction is in [[Translation#Terms missing or inadequate in the glossary file]].
 
  
 
=== Borrowing Code ===
 
=== Borrowing Code ===
Line 207: Line 351:
  
 
At present a bash script was written to take care of the first part:
 
At present a bash script was written to take care of the first part:
[https://raw.githubusercontent.com/Gnucash/gnucash/maint/contrib/import-goffice-translations.sh import-goffice-translations.sh]
+
[https://raw.githubusercontent.com/Gnucash/gnucash/maint/contrib/import-goffice-translations.sh import-goffice-translations.sh] in contrib/.
  
  
 
This script can be used to import translations from the goffice source directory into our po files. Note this script
 
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.
+
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
 
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 <tt>msginit</tt> elsewhere on this page)
+
# create the new po file as we would normally do for gnucash (see <tt>msginit</tt> elsewhere on this page)
* run the script mentioned above to import goffice translations
+
# 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
+
# this may change more files than needed, use <tt>git checkout</tt> to undo the changes to files you didn't want to alter
* continue as usual with translation work.
+
# continue as usual with translation work.
 +
 
 +
=== Using Other Programming Languages ===
 +
# Adapt https://www.gnu.org/software/gettext/manual/gettext.html#Sources for your source language.
 +
#;Note: The initialization is done in [https://github.com/Gnucash/gnucash/blob/maint/gnucash/gnucash-bin.c gnucash/gnucash-bin.c], search <tt>#ifdef HAVE_GETTEXT</tt>.
 +
# Add the file extensions of the source files, which contain translatable strings, to the list of <tt>make_gnucash_potfiles</tt> in [https://github.com/Gnucash/gnucash/blob/maint/po/CMakeLists.txt po/CMakeLists.txt].
 +
#:Verify the <tt>${path}</tt> in the next section is still correct.
 +
# Are changes required in [https://github.com/Gnucash/gnucash/blob/maint/po/gnucash-pot.cmake po/gnucash-pot.cmake], i.e. additional <tt>--keyword</tt> or <tt>--flag</tt> options for xgettext?
 +
# If there are files in the search path, which should be skipped, add them with the reason to [https://github.com/Gnucash/gnucash/blob/maint/po/POTFILES.skip po/POTFILES.skip]
 +
# Write down your experience as a subsection here.
 +
 
 +
==== Python ====
 +
Python gettext seems to need a separate initialization in addition to that in [https://github.com/Gnucash/gnucash/blob/maint/gnucash/gnucash-bin.c gnucash/gnucash-bin.c]. There is a developmental Pull Request dealing with that: [https://github.com/Gnucash/gnucash/pull/506].
  
 
=== Further Reading ===
 
=== Further Reading ===
  
If you want to read more about this topic, [http://live.gnome.org/GnomeI18nDeveloperTips GnomeI18nDeveloperTips] might be a good starting point.  
+
If you want to read more about this topic, [https://wiki.gnome.org/GnomeI18nDeveloperTips GnomeI18nDeveloperTips] might be a good starting point.  
  
 
Follow the rules of the [https://developer.gnome.org/hig/stable/ Gnome Human Interface Guide] like [https://developer.gnome.org/hig/stable/writing-style.html Writing style] and [https://developer.gnome.org/hig/stable/typography.html Typography].
 
Follow the rules of the [https://developer.gnome.org/hig/stable/ Gnome Human Interface Guide] like [https://developer.gnome.org/hig/stable/writing-style.html Writing style] and [https://developer.gnome.org/hig/stable/typography.html Typography].
  
More technical and historical details can be found in [http://www.gnu.org/software/gettext/manual/gettext.html#Programmers Gettext Manual: The Programmer’s View],
+
More technical and historical details can be found in [{{URL:Gnu}}software/gettext/manual/gettext.html#Programmers Gettext Manual: The Programmer’s View],
;Guile/Scheme: [https://www.gnu.org/software/guile/manual/html_node/Internationalization.html#Internationalization Guile Reference Manual: Support for Internationalization],
+
;Guile/Scheme: [{{URL:Gnu}}software/guile/manual/html_node/Internationalization.html#Internationalization Guile Reference Manual: Support for Internationalization],
 
;Python: [https://docs.python.org/3/library/i18n.html The Python Standard Library: Internationalization].
 
;Python: [https://docs.python.org/3/library/i18n.html The Python Standard Library: Internationalization].
 
[[Category:Development‏‎]]
 
[[Category:Translation]]
 

Latest revision as of 22:59, 13 April 2024

This section collects some notes for developers/programmers on how to correctly prepare their code for translations.

GUI Guidelines has a few more details.

General Thoughts

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.
* Pure numerical — including date, time and, monetary — strings should be formated according Locale Settings, but usually not translated.
* Strings containing only a special symbol like bullets usually need no translation. But in some cases there can be different typographical conventions like for quoting.
* Verify, that arrows like >> are properly reverted if you run the program in a RTL written language:
LANG=he_IL.utf8 gnucash
Usually our used tools are smart enough and then no translation is required.
* Never mark an empty string 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.
Separate content and form
Avoid markups, leading and trailing spaces; use functions to generate uppercase text or other formated text, …
Often the GUI offers better options for highlighting.
Granulation
With a few exceptions like labels, menu entries, or table elements use full sentences in proper US English. Because grammars are very different do not concat them, but use format strings. Usually the longest allowed element is a paragraph, but it is not easy to find a change like a fixed typo in a full paragraph.
Split repeated elements for translation and compose them for output by a format string.
Example
Msg1 = Sentence1, Sentence2
Msg2 = Sentence1, Sentence3
For the translators it is less work to translate Sentence1, Sentence2, Sentence3 each once.
Avoid Ambiguity
This can be achieved in several ways:
  • Use of full sentences, i.e. for tooltips.
  • Use of context for short messages like column headers.
  • Adding translator comments.
  • Add an entry to the glossary, if a term needs explanation and is used more than once.
Recommendations
If a message has more than one parameter, insert ordinal numbers already in the source like in C "%1$s contains %2$s." allowing the translator to write "%2$s is part of %1$s." instead.
Conventions
A few conventions are defined in GUI Guidelines.
Textual
US English instead of british English or more worse (~our vs. ~or, ~zation vs. ~sation …).
Separate sentences by one space only after the full stop, not runoff style!
Recycle already existing strings
Try to use for the same item in
glade and schema files
the same label and tooltipGUI_Guidelines#Tooltips_and_Descriptions_of_Preferences
different dialogs
like editor and overview
To get a dictionary
cd $BUILDDIR
ninja pot
# Either with location lines:
msgcat -s -o po/gnucash-dict.pot po/gnucash.pot
# or without location lines:
msgcat --no-location -s -o po/gnucash-dict.pot po/gnucash.pot
# Don't forget to move your dict to a save place now!
See msggrep --help to query that dictionary.

String Manipulation

On translated strings you must not use standard C(++) functions as defined in ctype.h or <cstring> (string.h)! They assume ASCII characters, which have a size of 1 byte/char, but UTF-8 chars can have up to 4 byte/char. Instead use GLib's Unicode Manipulation.

How to make strings in code translatable

Strings in Glade files

In Glade files translatable strings have the form:

   <property name="label"
      translatable="yes"
      context="infinitive" 
      comments="Additional infos for the translator">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>
Note
The linebreaks between property attibutes were inserted for legability.
Attributes
translatable
required for translation,
context
Optional to restrict the context,
comments
Optional for additional infos.

For the mnemonic property use_underline see Translation#Special_characters_and_other_tips.

Attention
   <property name="label" translatable="yes">
Some translatable text
with multiple lines.
</property>
will result in
#: gnucash/gtkbuilder/foo.glade:11
msgid ""
"\n"
"Some translatable text\n"
"with multiple lines.\n"
msgstr ""
, but the first and last newline are usually undesired. To avoid them write
   <property name="label" translatable="yes">Some translatable text
with multiple lines.</property>

Strings in C files

Preparation

Each C module

  • having translated C strings or
  • containing printf()/fprintf()/... calls with a format string that could be a translated C string
should contain the line:
#include <libintl.h>
Source
https://www.gnu.org/software/gettext/manual/html_node/Importing.html.
If you want to use the 1-letter+underline abbreviations as we do, in lieu of insert
#include <glib/gi18n.h>
Details
https://docs.gtk.org/glib/i18n.html
Instant translation

Normally, strings in C code just need to be enclosed with the function _( ). Well, actually it is a macro declared as #define _(String) gettext (String), but this is usually just an implementation detail. For example,

 func("A translatable string!");

should instead be written as

 func(_("A translatable string!"));
When not to use instant translation
  • 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.
  • Static variables are created at program initialization before main so there's no locale or gettext text_domain. That means that these strings won't get translated. The fix is to use the N_(x) macro to mark them for xgettext and then call gettext (via the _(x) macro) when you pass them to the GtkWidget for display.
Delayed Translation

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 (log files, configuration or GnuCash data), but displayed translated to the user.
gchar* msg = N_("Something went wrong!");   /* Tell xgettext to insert the string into gnucash.pot */
PWARN (msg);   /* Record it untranslated in the trace file */
/* pop up a window with ... */
   _(msg)   /* Inform the user with the translated message */
Using Context

Whenever there are ambiguities, define a context. We often use

  1. one letter abbreviations
    1. as the column header and
    2. as a status flag
  2. samples to determinate the wide of a column or an input field.
  3. Another use case would be Invoice:
    1. in general,
    2. from supplier,
    3. to client.

There a 2 functions to tell the translator and gettext the context:

Ancient
  Q_("Column header for 'Reconciled'|R");
and
Recent
  C_("Column header for 'Reconciled'", "R")
Both will result in
#: gnucash/gnome/reconcile-view.c:425
#: gnucash/register/ledger-core/split-register-layout.c:691
#: gnucash/register/ledger-core/split-register-model.c:306
msgctxt "Column header for 'Reconciled'"
msgid "R"
msgstr ""

The benefit of C_(): also a NC_() macro (#Delayed Translation) exists, but no NQ_(). A NC_() macro will be translatable by using the form:

C_("navigation","back")  // must be string literals

const char* domain = NULL;
const char* context = "navigation";
const char* msgid = "back";
g_dpgettext2 (domain, context, msgid)
.
Context form
Please use similar forms of contexts like the existing entries. Use e.g.
grep -n 'msgctxt' po/de.po | sort -fk2.1
for a list.
Note
Before GnuCash 3.7 there was another selfmade form, which got replaced as it was error prone. Bug 797349 - "A"ssociate header badly translated

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

  1. create a stringstream,
  2. imbue the desired locale,
  3. stream the result of boost::locale::translate and boost::locale::format into this stream,
  4. 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 can 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.

Strings in SCM files

The general form is
;; enable I18N:
(use-modules (ice-9 i18n))
;; General Form:
(gettext msg [domain [category]])
;; The abbreviation changed with Guile 3:
(G_ "Some translatable string")
.
Source
Guile Manual: Gettext Support
See also
Guile Manual: Formatted Output

Plural Forms

Not all languages have the same simple kind of plural forms as english. See gettexts Plural-forms for details.

Avoid underline

Because Underline _marks mnemonics in the GUI, do not use it for other purposes. It would confuse msgfmt -c --check-accelerators="_" else. Use the normal dash - instead.

Avoid Markups and Other Formating

It is annoying, if you have to translate "number", "number:", "number: ", "<b>number</b>", "Number", "NUMBER" all separate. In many cases you can move the formating elements outside of the translatable string or use functions for case conversion.

Mask Unintended Line Breaks

In some languages like 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.")
This will result in gnucash.pot as
#: 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.")
and the translators will get:
#: 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."

Add Infos for Translators

Often it is useful to give the translators some additional information about the meaning of specific words or full messages.

  • Each financial term should have a glossary entry because the meaning can be slightly differ by language or country.
  • Each term with a GnuCash specific meaning like "Bill", too.
  • If the same explanation is required more than once, consider to add a section to the glossary.

Glossary

If you introduce new terms which are more than once used, you should include them to Translation#The_glossary_file. The instruction is in Translation#Terms missing or inadequate in the glossary file.

Translator Comments for Single Messages

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 (if, case, while …) between comment and string.
  • Using block comments instead of line comments will allow the gettext tools to do their own wrapping.
  • If the same message appears several times in the source files, the comment should only be attached to the first occurrence.
In C based files
Example:
 /* Translators: the following string deals about:
    The Answer to Life, the Universe and Everything
    Source:
    https://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:
#. https://en.wikipedia.org/wiki/Phrases_from_The_Hitchhiker%27s_Guide_to_the_Galaxy
#: foo/bar.c:123
msgid "42"
msgstr ""
Notes
An empty comment line will end the process. If the string " Source:" were missing, the URL were not part of the output.
Do not decorate translator comments by a frame of stars:
 /* Translators: the following string deals about:                                    *
  * The Answer to Life, the Universe and Everything                                   *
  * Source:                                                                           *
  * https://en.wikipedia.org/wiki/Phrases_from_The_Hitchhiker%27s_Guide_to_the_Galaxy */
After its reformatting by xgettext, it would look terrible in the message cataloges.
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 XML
Some variants like Glade have their specific tags. Others like GSettings use simple XML comments:
 <!-- Translators: A list of words which are not allowed to be typed, in
      GVariant serialization syntax.
      See: https://developer.gnome.org/glib/stable/gvariant-text.html -->
 <default l10n='messages' context='Banned words'>['bad', 'words']</default>
In HTML + PHP
Here it is important, that the comment is not in the HTML, but the PHP part:
<img class="featureimage" src="<?=$top_dir?>/images/features/<?PHP 
//Translators: If you already translated the guide, you can copy the image into /images/features/ as e.g. basics_AccountRelationships.ll.png
echo T_("basics_AccountRelationships.C.png");
?>" alt="[Basic account relationship]" />

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 in contrib/.


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

  1. create the new po file as we would normally do for gnucash (see msginit elsewhere on this page)
  2. run the script mentioned above to import goffice translations
  3. this may change more files than needed, use git checkout to undo the changes to files you didn't want to alter
  4. continue as usual with translation work.

Using Other Programming Languages

  1. Adapt https://www.gnu.org/software/gettext/manual/gettext.html#Sources for your source language.
    Note
    The initialization is done in gnucash/gnucash-bin.c, search #ifdef HAVE_GETTEXT.
  2. Add the file extensions of the source files, which contain translatable strings, to the list of make_gnucash_potfiles in po/CMakeLists.txt.
    Verify the ${path} in the next section is still correct.
  3. Are changes required in po/gnucash-pot.cmake, i.e. additional --keyword or --flag options for xgettext?
  4. If there are files in the search path, which should be skipped, add them with the reason to po/POTFILES.skip
  5. Write down your experience as a subsection here.

Python

Python gettext seems to need a separate initialization in addition to that in gnucash/gnucash-bin.c. There is a developmental Pull Request dealing with that: [1].

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,

Guile/Scheme
Guile Reference Manual: Support for Internationalization,
Python
The Python Standard Library: Internationalization.