Difference between revisions of "Coding for translation"

From Gramps
Jump to: navigation, search
m (Textual reports)
m (How it works)
(28 intermediate revisions by the same user not shown)
Line 42: Line 42:
 
  <property name="label" translatable="no">&lt;b&gt; - &lt;/b&gt; </property>
 
  <property name="label" translatable="no">&lt;b&gt; - &lt;/b&gt; </property>
  
Note:
+
====Non ASCII characters====
 +
 
 
If you plan to use non ASCII characters in a string, that shall be translated,
 
If you plan to use non ASCII characters in a string, that shall be translated,
 
do not use escape sequences:
 
do not use escape sequences:
Line 53: Line 54:
  
 
In this case note the special characters for deg, min, sec.
 
In this case note the special characters for deg, min, sec.
 +
 +
====Accessibility====
 +
 +
In addition to [http://developer.gnome.org/gtk/2.24/GtkWidget.html#id1294298 accelerators], ''[http://developer.gnome.org/gtk/2.24/GtkWidget.html GtkWidget]'' also support a custom <accessible> element, which supports actions and relations. Properties on the accessible implementation of an object can be set by accessing the internal child "accessible" of a ''[http://developer.gnome.org/gtk/2.24/GtkWidget.html GtkWidget]''. See [http://developer.gnome.org/gtk/2.24/GtkWidget.html#GtkWidget-BUILDER-UI GtkBuilder UI].
 +
 +
* Gtk label
 +
 +
''A [http://developer.gnome.org/gtk/2.24/GtkLabel.html GtkLabel]'' '''with mnemonic support''' will automaticaly generate accessibility keys on linked ''[http://developer.gnome.org/gtk/2.24/GtkEntry.html GtkEntry]'' and ''UndoableEntry'' fields. Remember that Gramps also uses custom widgets like ''StyledTextEditor'' and ''ValidatableMaskedEntry'', which do not always have relation with a ''GtkLabel''.
 +
 +
* Toggle buttons and Icons on toolbar
 +
 +
Gramps often uses ''[http://developer.gnome.org/gtk/2.24/GtkToggleButton.html GtkToggleButtons]'' and alone ''[http://developer.gnome.org/gtk/2.24/GtkImage.html GtkImage]'' (image without label), this excludes blind people and generates a poor interface for accessibility.
 +
 +
See [[Accessibility]].
  
 
===Into addons plugins===
 
===Into addons plugins===
Line 63: Line 78:
 
==How it works==
 
==How it works==
  
[http://www.gnu.org/software/gettext/manual/gettext.html GNU gettext] and [http://live.gnome.org/TranslationProject/DevGuidelines/Localize%20using%20gettext%20and%20intltool Gnome] provide utilities and a [http://www.gnome.org/~malcolm/i18n/build-changes.html translation framework] (''previously [http://gramps.svn.sourceforge.net/viewvc/gramps/branches/maintenance/gramps20/gramps2/src/build_po build_po] and [http://gramps.svn.sourceforge.net/viewvc/gramps/branches/maintenance/gramps20/gramps2/src/get_strings get_strings]''):
+
We need at least [http://www.gnu.org/software/gettext/manual/gettext.html GNU gettext], then [http://www.gnu.org/software/autoconf/manual/gettext/msginit-Invocation.html msginit] will generate a standard gettext header.
* [http://www.gnu.org/software/autoconf/manual/gettext/msginit-Invocation.html msginit] will generate a standard gettext header.
 
* intltool-update will manage template and translations.
 
* intltool-extract will extract translation strings on ''.glade'' and ''.xml'' files, by generating files with ''.h'' extension.
 
 
 
# Generates a new template (gramps.pot), into ''/po'' directory :
 
intltool-update -p
 
 
 
* intltool-merge will merge cached translations into .in files
 
  
# Merges translated strings into desktop file, ''root'' directory :
+
Gramps has used different environments according to versions for retrieving strings to translate:
intltool-merge -d po/ data/gramps.desktop.in data/gramps.desktop
 
  
# Merges translated strings into xml file, ''root'' directory :
+
* [[Translation_environment20|2.0.x]]
intltool-merge -x po/ data/gramps.xml.in data/gramps.xml
+
* [[Translation_environment22|2.2.x to Gramps 3.4.x]]
 +
* [[Translation_environment4|Trunk]]
  
# Merges translated strings into key file, ''root'' directory :
+
There are two stages to getting a translation to work.  
intltool-merge -k po/ data/gramps.keys.in data/gramps.keys
 
  
 
===Files and directory===
 
===Files and directory===
  
There are two stages to getting a translation to work. Translations are stored in a <code>.po</code> file that contains the mappings between the original strings and the translated strings, see [[Translating GRAMPS]].  
+
Translations are stored in a <code>.po</code> file that contains the mappings between the original strings and the translated strings, see [[Translating GRAMPS]].  
  
 
Translators use a generic file <code>gramps.pot</code> to generate their <code>.po</code> file.
 
Translators use a generic file <code>gramps.pot</code> to generate their <code>.po</code> file.
GRAMPS uses a utility that extracts the strings from the source code to build the <code>.po</code> file. This utility (a perl script called by the command <code>make</code>) examines the source files for strings that have been marked as translatable. In the python source, these are the strings enclosed in the <code>_()</code> function calls.
+
Gramps uses a utility that extracts the strings from the source code to build the <code>.po</code> file. This utility examines the source files for strings that have been marked as translatable. In the python source, these are the strings enclosed in the <code>_()</code> function calls.
 
 
If you want this script to take your translatable strings into account, you must add your source file path in the file : <code>po/POTFILES.in</code>. For this report example, you should add:
 
 
 
...
 
# plugins directory
 
src/plugins/AncestorChart2.py
 
src/plugins/AncestorReport.py
 
...
 
src/plugins/FindDupes.py
 
src/plugins/Leak.py
 
src/plugins/MediaManager.py
 
src/plugins/Myreport.py                # <------
 
src/plugins/NarrativeWeb.py
 
src/plugins/PatchNames.py
 
...
 
 
 
In this file, the sources are sorted within each directory or category.
 
  
 
Note that because strings are extracted by a script from the source file, string constants and not variables must be enclosed in the <code>_()</code> call. In the following example, the extraction script will not extract the string.
 
Note that because strings are extracted by a script from the source file, string constants and not variables must be enclosed in the <code>_()</code> call. In the following example, the extraction script will not extract the string.
Line 116: Line 105:
 
At run time, the <code>_()</code> calls will translate the string by looking it up in the translation database (created from the <code>.po</code> files) and returning the translated string.
 
At run time, the <code>_()</code> calls will translate the string by looking it up in the translation database (created from the <code>.po</code> files) and returning the translated string.
  
You can check missing references (not on <code>POTFILES.in</code> and <code>POTFILES.skip</code>) with the command
+
===Add the reference to the file===
/intltool-update -m
+
 
into <code>/po</code> directory.
+
We need to also add a reference to this file for generating the translation template.
 +
 
 +
* [[Translation_environment22#Files_and_directory|2.2.x to Gramps 3.4.x]]
 +
* [[Translation_environment4#Files_and_directory|Trunk]]
  
 
==Tips for writing a translatable Python module==
 
==Tips for writing a translatable Python module==
Line 156: Line 148:
 
Plurals are handled differently in various languages. Whilst English or German have a singular and a plural form, other languages like Turkish don't distinguish between plural or singular and there are languages which use different plurals for different numbers, e.g. Polish.
 
Plurals are handled differently in various languages. Whilst English or German have a singular and a plural form, other languages like Turkish don't distinguish between plural or singular and there are languages which use different plurals for different numbers, e.g. Polish.
  
Gramps provides a [[Translating_GRAMPS#Plural_forms|plural forms]] support, useful for locales with multiples plurals according to a number (''often slavic based languages'') or for Asian family languages (''singular = plural'').
+
Gramps provides a [[Translating_GRAMPS#Plural_forms|plural forms]] support, useful for locales with multiples plurals according to a number (''often slavic based languages'') or for Asian family languages (''singular = plural'').
 +
 
 +
Note, some locales need singular form with [http://en.wikipedia.org/wiki/Plural#Zero zero] and plural form might be also used in this case.
  
 
We need to call module :
 
We need to call module :
Line 175: Line 169:
  
 
We need to call module :
 
We need to call module :
  from TransUtils import sgettext as _
+
  from gen.ggettext import sgettext as _
 
or
 
or
  from TransUtils import sngettext as _
+
  from gen.ggettext import sngettext as _
(if you use ngettext)
+
(if you use ngettext) # not implemented
  
 
Translation string will use context, but this will be hidden on user interface.
 
Translation string will use context, but this will be hidden on user interface.
Line 202: Line 196:
 
  See ''the person'' details # or See ''the family, the event, etc...'' details
 
  See ''the person'' details # or See ''the family, the event, etc...'' details
 
  Make ''the person'' active
 
  Make ''the person'' active
 +
 +
===Genitive form===
 +
 +
Genitive (and some other) forms need to modify the name itself into some locales, like Finnish or Swedish.
 +
 +
Instead of "free form" text that talks about
 +
e.g.
 +
son '''of %s'''
 +
better would be for example some tabulated format like this:
 +
  son: %s
 +
  daughter: %s
 +
which doesn't require genitive.
  
 
==Textual reports==
 
==Textual reports==

Revision as of 12:58, 3 October 2012

Coding guidelines to enable easy and correct translation of strings on the User Interface.

Introduction

GRAMPS has always been internationalized (see http://gramps-project.org/2006/04/looking-back-over-5-years). Therefore, all strings meant for the user should always be flagged for translation.

In order to be considered for inclusion in the offical GRAMPS release, any piece of code must support internationalization. What this means is that the Python module must support translations into different languages. GRAMPS provides support to make this as easy as possible for the developer. For enabling, a language code must be set on configure.in file into ALL_LINGUAS section.

How to allow translations

GRAMPS provides a simple interface (based on the gettext interface) to mark strings as being translatable. First, import the gettext function from the intl library.

from gen.ggettext import gettext as _

This statement imports the sgettext function under the name of _. This is the function that both marks the strings for translation and performs the actual translation at runtime. Strings that should be translated should be enclosed as an argument to the function.

Example 1:

print "Hello world!"

In this example, the string will always be printed as specified.

Example 1 internationalized:

print _("Hello world!")

In this example, GRAMPS will attempt to translate the string. If a translation exists, the call to the function will return the translation. If a translation does not exist, the original string is returned.

All strings meant for the user should be always be preceeded with the _ function.

If you use non ASCII characters in a string, that shall be translated, the string must be unicode. Example:

print _(u"Eg, valid values are 12.0154, 50° 52′ 21.92″N")


Into glade file

Just enable the translatable attribute on an XML element.

<property name="label" translatable="yes">_Family:</property>
<property name="tooltip" translatable="yes">Abandon changes and close window</property>
<property name="label" translatable="no"><b> - </b> </property>

Non ASCII characters

If you plan to use non ASCII characters in a string, that shall be translated, do not use escape sequences:

Eg, valid values are 12.0154, 50&#xB0; 52' 21.92"N

use in stead:

Eg, valid values are 12.0154, 50° 52′ 21.92″N

In this case note the special characters for deg, min, sec.

Accessibility

In addition to accelerators, GtkWidget also support a custom <accessible> element, which supports actions and relations. Properties on the accessible implementation of an object can be set by accessing the internal child "accessible" of a GtkWidget. See GtkBuilder UI.

  • Gtk label

A GtkLabel with mnemonic support will automaticaly generate accessibility keys on linked GtkEntry and UndoableEntry fields. Remember that Gramps also uses custom widgets like StyledTextEditor and ValidatableMaskedEntry, which do not always have relation with a GtkLabel.

  • Toggle buttons and Icons on toolbar

Gramps often uses GtkToggleButtons and alone GtkImage (image without label), this excludes blind people and generates a poor interface for accessibility.

See Accessibility.

Into addons plugins

from TransUtils import get_addon_translator
_ = get_addon_translator().gettext

See Addon developpement.

How it works

We need at least GNU gettext, then msginit will generate a standard gettext header.

Gramps has used different environments according to versions for retrieving strings to translate:

There are two stages to getting a translation to work.

Files and directory

Translations are stored in a .po file that contains the mappings between the original strings and the translated strings, see Translating GRAMPS.

Translators use a generic file gramps.pot to generate their .po file. Gramps uses a utility that extracts the strings from the source code to build the .po file. This utility examines the source files for strings that have been marked as translatable. In the python source, these are the strings enclosed in the _() function calls.

Note that because strings are extracted by a script from the source file, string constants and not variables must be enclosed in the _() call. In the following example, the extraction script will not extract the string.

mystring = "Hello World!"
print _(mystring)

The correct method would be to use one of the following:

mystring = _("Hello World!")
print mystring

At run time, the _() calls will translate the string by looking it up in the translation database (created from the .po files) and returning the translated string.

Add the reference to the file

We need to also add a reference to this file for generating the translation template.

Tips for writing a translatable Python module

Use complete sentences

Don't build up a sentence from phrases. Because a sentence is ordered in a particular way in your language does not mean that it is ordered the same way in another. Providing the entire sentence as a single unit allows the translator to make a meaningful translation. Do not concatenate phrases or terms as they will then show up as separate phrases or terms to be translated and the complete sentence may then show up incorrectly, especially in right-to-left languages (Arabic, Hebrew, etc.).

Use named %s values

Python provides a powerful mechanism that allows the reordering of %s values in a string. A translator may need to rearrange the structure of a sentence, and it may not match the order you chose. For example:

print "%s was born in %s" % ('Joe','Toronto')

In some languages it may make more sense to say:

print "%s is the city in which %s was born" % ('Toronto', 'Joe')

The problem is that this requires a change to the order of the arguments. Python provides a solution for this. By using named operators and dictionaries, we can say:

print "%(male_name)s was born in %(city)s" % {
           'city' : 'Toronto', 'male_name' : 'Joe'}

In this case, the order of the %s formatters is not important, since the values will be looked up in the dictionary at run time to resolve the value. The translator can reorder the %s formatters, or even remove them without causing any problems.

Note that Python also allows a variation which some people find easier to read:

print "%(male_name)s was born in %(city)s" % dict(
           city = 'Toronto', male_name = 'Joe')

Provide separate strings for masculine and feminine.

Many languages have the concept of gender, while others don't. A sentence may need to be phrased differently depending on whether the subject is male or female. By using the named %s values along with a bit of code, this problem can be solved.

if person.getGender() == Person.male:
       print _("%(male_name)s was born in %(city)s\n") % {
               'male_name' : name, 'city' : city }
else:
       print _("%(female_name)s was born in %(city)s\n") % {
               'female_name' : name, 'city' : city }

This allows languages with gender differences to map nicely into your sentence.

Provide support for plural forms.

Plurals are handled differently in various languages. Whilst English or German have a singular and a plural form, other languages like Turkish don't distinguish between plural or singular and there are languages which use different plurals for different numbers, e.g. Polish.

Gramps provides a plural forms support, useful for locales with multiples plurals according to a number (often slavic based languages) or for Asian family languages (singular = plural).

Note, some locales need singular form with zero and plural form might be also used in this case.

We need to call module :

from gen.ggettext import ngettext

and code like this :

 ngettext("singular %d", "plural %d", n) %n

Sample:

msg = ngettext('Import Complete: %d second',
               'Import Complete: %d seconds', t ) % t

Provide a context support.

A translator needs context for a good translation. Keep in mind you can help him/her, by using context on translation string.

We need to call module :

from gen.ggettext import sgettext as _

or

from gen.ggettext import sngettext as _

(if you use ngettext) # not implemented

Translation string will use context, but this will be hidden on user interface.

_("context|string")

Translator will see the translation string and a help string without loading program. Program will only display the string in English or with another locale.

Object classes

Gramps often displays names of primary objects (Person, Family, Event, etc ...), for being consistent on displayed strings (also in english!), there is a trans_objclass(objclass_str) function on TransUtils module.

So, when we need to display the primary object name in lower case into a sentence, we can use this function.

ex:

from gen.ggettext import sgettext as _
from TransUtils import trans_objclass
_("the object|See %s details") % trans_objclass(objclass)
_("the object|Make %s active") % trans_objclass('Person')

will display:

See the person details # or See the family, the event, etc... details
Make the person active

Genitive form

Genitive (and some other) forms need to modify the name itself into some locales, like Finnish or Swedish.

Instead of "free form" text that talks about e.g.

son of %s

better would be for example some tabulated format like this:

 son: %s
 daughter: %s

which doesn't require genitive.

Textual reports

Since Gramps-3.2 we are able to select the language for textual reports, see this feature.

Currently only available on Ancestor report (3.2.x) and detailed reports (3.3.x).

For providing this option:

  1. import EnumeratedListOption
  2. import libtranslate
from gen.plug.menu import EnumeratedListOption 
import TransUtils
from libtranslate import Translator, get_language_string

Sample of code:

language = menu.get_option_by_name('trans').get_value()
       translator = Translator(language)
       self._ = translator.gettext
       self.__narrator = Narrator(self.database, self.verbose, use_call, 
                                  use_fulldate, empty_date, empty_place, 
                                  translator=translator,
                                   get_endnote_numbers=self.endnotes)
       self.__get_date = translator.get_date
       self.__get_type = translator.get_type
self._("")
self.__get_date(event.get_date_object())
self.__get_type(event.get_type())