2.2. Marking translatable strings

You are now at the point where you can begin the task of marking all appropriate strings for translation. Note that not every single string needs to be so marked. Strings that are not visible to the user should usually be left alone (since that way all your configuration files and so on are going to be locale independent). You may wish for some portions of error messages to not be translated. The user can cut and paste those messages into an email to you and you will be able to understand them and grep them out of the code. Usually, it is not necessary to translate names, although see Section 5, “Tips and Tricks” for what happens in the about box of an application.

As a general rule, strings which will be seen by a user in the normal operation of your application should be translated. Strings which are only relevant to the developer — debugging output and internal error messages, for example — should not be translated.

Care should be taken when deciding which messages to mark for translation. Leaving important visible strings untranslated looks unprofessional and inconveniences users. Marking extraneous strings for translation serves no effective purpose and only abuses the service provided by the many volunteer translators working on GNOME.

2.2.1. Source code files

As an initial attempt to ferret out all the likely strings from your C or C++ source code, run the command xgettext -a -o my-strings --omit-header *.c *.h from your source directory.

This will run through every .c and .h file in the current directory and extract all of their strings into a file caled my-strings. This file is very similar to what translators work with when translating, but the key thing at the moment is to look at the format of each line. You will see that they look like

#: slice-n-dice.c:36
msgid "Sharpening the knives and preparing for action."
msgstr ""

This tells you that the given string is in the file slice-n-dice.c at line number 36. So, if you decide that that string should be translated, open up the file and put _( before the string and a matching ) just after it. So, you may have some code that used to say

popup_display ("Sharpening the knives and preparing for action.");

and it will now read

popup_display (_("Sharpening the knives and preparing for action."));

There is one problem with this markup scheme. Since _() is a function call, you cannot use it in all circumstances. For example, if you are initialising a static array with some strings, you are not permitted to call a function in the initialiser portion of the statement. Consider code that looks like the following.

ShapeData shapes[] = {"circle", "square", "triangle", NULL};

In this case, you should use the N_() macro to mark up these strings. This macro ultimately does nothing, but when the gettext tools are scanning the code looking for translatable strings, they can be told to look for this type of markup and also extract those strings. The above example then becomes

ShapeData shapes[] = {N_("circle"), N_("square"), N_("triangle"), NULL};

Once you have marked up some strings with N_() you then need to find all the places where those strings are used in the code and wrap the code that produces the string in a _(). This is because it is the _() that performs the message lookup and translation function. Continuing the above example, we might have code (after i18n marking) that looks like this.

for (i = 0; shapes[i] != NULL; ++i)
    show_shape_name (_(shapes[i]));

Notice how the retrieval of the string from shapes[] is now passed through a call to _() so that the show_shape_name() function sees the translated string, not the original version. This part of the code markup is probably the trickiest bit to get right, since it is not uncommon to overlook a place where a static string is actually being used (although the string itself may be marked up correctly with N_()).

Once you have gone through every string in the my-strings file created earlier and determined if they should be translated or not, you can delete that file. It was just an aid to getting the process going. At this point (as always) your code should still build and run normally, although the i18n portion will still not work (all the special markup comes out as no-ops since ENABLE_NLS is undefined). It is worth testing this as you go along to avoid problems later.

2.2.1.1. Special requirement for libraries

If you are writing a library, almost everything written previously applies as with a standalone application. The only problem that arises is that a library will never really know what message domain is currently active when it is processing a string and you usually want to have the library's own domain active at that point. The simplest solution to this problem is to change one of the defintions in the i18n header file mentioned in the Section 2.1, “The i18n header file” section. Find the line that says

#define _(String) gettext(String)

and change it to read

#define _(String) dgettext(GETTEXT_PACKAGE, String)

In this way, all strings that pass through the _() function will be translated in the domain of the library all the time.

One other situation that arises very rarely may be worth knowing about. In libgnomeui, for example, there are functions that operate on arbitrary arrays of strings (menu items, in this case). Some of those strings are standard and have been translated in the library itself. Others will have been supplied by the client application and translated in that domain. So libgnomeui defines the following function and convenience macro.

Example 2. Code from libgnomeui/gnome-app-helper.c

#define L_(x) gnome_app_helper_gettext (x)

const gchar *
gnome_app_helper_gettext (const gchar *str)
{
        char *s;

        s = gettext (str);
        if ( s == str )
                s = dgettext (GETTEXT_PACKAGE, str);

        return s;
}

Strings marked with L_() are thereby translated in the currently active domain if possible and the library's domain otherwise. You may wish to duplicate this code if you have similar circumstances.

2.2.2. Desktop files

Desktop files are used in GNOME as a means to determine which menu an application should appear under in the panel menus. It also contains a descriptive string that appears as a tooltip when the application is on the panel, along with information about how to run the application and what icon to show.

From an internationalisation point of view, the interesting parts of a .desktop are the Name and Comment fields. They are the only two fields used in GNOME 2 that are defined as translatable in the desktop file specification.

To mark these fields as translatable, copy your desktop file, slice-n-dice.desktop, to slice-n-dice.desktop.in (the .in suffix is traditional). Then put an underscore before the Name and Comment tags. So, after markup, the file might look like this.

Example 3. slice-n-dice.desktop.in

[Desktop Entry]
Encoding=UTF-8
_Name=Slice 'n' Dice
_Comment=Chop things up into shapes
Exec=slice-n-dice
Icon=slice-n-dice.png
Terminal=false
Type=Application
Categories=GNOME;Application;

Tip

Everytime you change a file like this to acommodate translations, keep a note of the filename. This applies both for this section and for the file types mentioned in the next few sections as well. Later, in Section 3.3, “Makefile changes” you will need to make some build changes to utilise these new files, so having a list will save you from missing any of them.

Once you make the alterations in the Section 3, “Incorporating i18n into the package's build infrastructure” section, you should then remove slice-n-dice.desktop, leaving only the slice-n-dice.desktop.in file. The build process will merge any translations into this template file and build a desktop file containing strings for all the available locales.

2.2.3. Server files

Applications that are called by the bonobo-activation-server to activate components on demand have a file with an extension of .server that describes how they are activated and contains some descriptive strings. So you need to mark up these files for translation as well.

Similarly to the process for desktop files, rename GNOME_slice.server to GNOME_slice.server.in. Then open this file and edit any <oaf_attribute> tags that have a type="string" attribute. These tags will have a value="..." attribute and you should change this to be _value="...". So, after markup, you will have some lines that look similar to

<oaf_attribute name="name" type="string" _value="Slicing factory"/>
<oaf_attribute name="description" type="string"
               _value="Factory for slicing and dicing"/>

and so on.

In some (indeed, most) projects, your server file will already be a template, since it will contain some variables that are subsituted by the configure script — things such as where the component is ultimately going to be installed. So the file will already be called GNOME_slice.server.in. In this case, you just add another level of .in extensions and create a GNOME_slice.server.in.in file. Note that the intltool application is run before configure does its substitutions. So, in the section about Section 3, “Incorporating i18n into the package's build infrastructure” you need to just remember that intltool will be converting GNOME_slice.server.in.in to GNOME_slice.server.in.

2.2.4. Glade files

If you are using Glade to construct your user interface, then no special markup is required to have the strings in a glade file be recognised as translatable. The intltool application, which we will set up in the next section, knows which parts of a glade-format file are translatable (widget labels, messages, accessibility strings, and so forth) and will automatically extract them.

The only thing you need to do when using Glade is to not bother turning on the Save translatable strings option under the LibGlade tab in the Options box. The files containing marked up strings will instead be generated automatically by Glade.

2.2.5. XML files

If your application comes with arbitrary format XML files, then you can mark up portions of those files for translation as well. Rename the file as previously, so slice.xml, say, becomes slice.xml.in. Then you can go through the file and mark any elements whose content should be translated with an initial underscore. So, for example,

<type level="safe">
   <shape>blunt triangle</shape>
   <description>Creates a triangle without sharp corners.</description>
</type>

might be marked up as

<type level="safe">
   <shape>blunt triangle</shape>
   <_description>Creates a triangle without sharp corners.</_description>
</type>

Notice how the <description> tag has been marked for translation. Note also that the closing tag has been similarly changed, so that the file is still well-formed XML (although probably no longer conforming to the same DTD as it may have originally).

Note

  • FIXME: What happens when an element is marked for translation and it contains another element. Should the inner element be marked?

  • If doc-i18n-tool is ever fixed up, that should replace this section, since it's less intrusive.

2.2.6. Gconf schema files

The schema files used by Gconf may contain short and long descriptions of any key; obviously these strings are candidates for translation. A key in a schema file may also have a default value which is translatable in the sense that it should be localised to be more appropriate. For example, a financial application may use "New York" as the default for the Stock Exchange" key in the C locale. However, "Frankfurt" may be a better default for this key in the de (German) locale.

The way to have the appropriate keys pulled out of schema files is to wrap them in <locale> elements and specify the locale of the original file as C. So, you would mark the financial example above like in this example (some lines removed for brevity).

Example 4. financial.schemas

<schema>
   <key>/schemas/apps/finance/preferences/current_market</key>
   ...
   <locale name="C">
      <default>New York</default>
      <short>Current stock exchange</short>
      <long>This key holds the name of the stock exchange that
      we are currently querying. It can have any string value 
      that corresponds to a world market.</long>
   </locale>
</schema>

Keys which do not have their descriptions and/or default values put inside a <locale name="C"> element will not be translated. You will usually always want to translate short and long descriptions, but only occasionally want to translate the default values.

2.2.7. User and API documentation

Unfortunately, at the present time, there is no easy way to convert user documentation or API documentation (such as that generated by gtk-doc) into a format that is consistent for translators. User documentation essentially has to be translated by manually going through the source document. API documentation currently does not support internationalisation.