gettext

Warning: This document pertains to the development of Mozilla web sites and not to the development of Gecko-based extensions or applications.

Plurals

gettext lets you define and use singular and plural forms of a string. To take advantage of this functionality, you need to use a different keyword (i.e. different gettext function name) for string which are supposed to support plurals. For instance, in PHP the default keyword for regular messages is gettext() and _(). For messages with plural support, the default keyword is ngettext().

A definition of a string with plurals takes three arguments:

  1. the singular form of the English string,
  2. the plural form of the English string, and
  3. the number basing on which the function will return the correct (singular or plural) form of the string.

Consider the following code snippet:

<?php
  $num = 1;
  printf(ngettext("%d user likes this.", "%d users like this.", $num), $num);
?>

Depending on the value of the $num variable, this code will either use the singular ("user likes) or the plural ("users like") form of the string. Notice that you need to pass the $num variable twice. First in the ngettext() call, where it will be used to determine which form of the string will be returned. In the above example, $num equals 1, so the string returned (for English) will be %d user likes this. The second time the $num variable is passed in the printf() call, which at this point of interpretation looks something like printf("%d user likes this.", $num);. The %d in the first argument (which is the string returned by gettext) will be replaced with the value of $num.

For English, the above code will produce the following output:

1 user likes this.

The string definition in the messages.po file will look like this:

#: file.php:3
#, php-format
msgid "%d user likes this."
msgid_plural "%d users like this."
msgstr[0] ""
msgstr[1] ""

Depending on the localizer's target language and its rules for creating plural forms, there might be another field for translation, e.g. msgstr[2]. gettext will return one of the msgstrs, depending on the number passed to the gettext function in the code ($num in the above example). To determine which form to return, gettext uses a plural rule, which should be declared in the PO file header. For example, for English, the plural rule is of the following form:

Plural-Forms: nplurals=2; plural=n != 1;

The first part defines the number of different plural forms (2). The second part is evaluated and the result of the evaluation is used to choose the right msgstr. In the rule above, if n equals 1, the expression is evaluated to false, or 0, so msgstr[0] will be used. Consider another example, Gaeilge (Irish):

Plural-Forms: nplurals=3; plural=n==1 ? 0 : n==2 ? 1 : 2;

If n equals 10, the expression n==1 ? 0 : n==2 ? 1 : 2 is evaluated to 2, and so a msgstr[2] should be used.

Read more about plurals in gettext at gnu.org.

Using context with msgctxt

Depending on context in which it is used, one English string might require two or more different translations. This is particularly true for short strings, like "File" or "Log in". For instance, "Log in" as a button label might be translated by a localizer as the imperative, but for a dialog title, the localizer may choose to use a different form, like gerund (much like "Logging in"). Gettext's context feature allows the developer to distinguish between two identical English strings and disambiguate the translation.

In the PO file, msgctxt looks like this:

msgctxt "Button label"
msgid "Log in"
msgstr ""

msgctxt "Dialog title"
msgid "Log in"
msgstr ""

You can add context to your strings by passing an additional argument to the gettext function call and then having xgettext extract it as the context. In PHP, however, the default gettext functions don't support passing such additional argument. To fix this, you may choose to write your own helper gettext functions. The following example illustrates this.

<?php

// Implement gettext context
if (!function_exists('pgettext')) {
    function pgettext($context, $msgid) {
        $contextString = "{$context}\004{$msgid}";
        $translation = _($contextString);
        if ($translation === $contextString) return $msgid;
        else  return $translation;
    }

    function npgettext($context, $msgid, $msgid_plural, $num) {
        $contextString = "{$context}\004{$msgid}";
        $contextStringp = "{$context}\004{$msgid_plural}";
        $translation = ngettext($contextString, $contextStringp, $num);
        if ($translation === $contextString) {
            return $msgid;
        } else if ($translation === $contextStringp) {
            return $msgid_plural;
        } else {
            return $translation;
        }
    }
}

// New gettext keyword for regular strings with optional context argument
function ___($message, $context ="") {
    if($context != "") {
        return pgettext($context, $message);
    } else {
        return _($message);
    }
}	

// New gettext keyword for plural strings with optional context argument
function n___($message, $message_plural, $num, $context ="") {
    if($context != "") {
        return npgettext($context, $message, $message_plural, $num);
    } else {
        return ngettext($message, $message_plural, $num);
    }
}


// L10N: This is a block comment one line directly above the gettext function call. It will be extracted by xgettext.
echo ___('A simple string.');

echo /* L10N: This is an inline comment. It will be extracted by xgettext. */ ___('A string with context.', 'Unique context string');

$num = 1;
// L10N: This is another block comment that will be extracted by xgettext.
printf(n___("%d user likes this.", "%d users like this.", $num), $num);

// L10N: This block comment will not be extracted by xgettext because it's two lines above the gettext function call.
$num = 4;
printf(n___("%d user likes this.", "%d users like this.", $num, 'Another unique context string'), $num);

?>

This code produces the following output:

A simple string.
A string with context.
1 user likes this.
4 users like this.

To extract the strings from the PHP file, use xgettext. You can specify the function calls to look for with the --keyword option. For example, --keyword=n___:1,2,4c tells xgettext to look for n___() calls, and extract the first argument as the singular string, the second argument as the plural string and the fourth argument as the context (the third argument is reserved for the number in this example).

$ xgettext --language=PHP \
           --add-comments=L10N \
           --keyword=___:1 \
           --keyword=___:1,2c \
           --keyword=n___:1,2 \
           --keyword=n___:1,2,4c \
           file.php 

Running xgettext will result in the following messages.po file:

# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2009-09-28 16:18+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=CHARSET\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\n"

#. L10N: This is a block comment one line directly above the gettext function call. It will be extracted by xgettext.
#: file.php:12
msgid "A simple string."
msgstr ""

#. L10N: This is an inline comment. It will be extracted by xgettext.
#: file.php:14
msgctxt "Unique context string"
msgid "A string with context."
msgstr ""

#. L10N: This is another block comment that will be extracted by xgettext.
#: file.php:18
#, php-format
msgid "%d user likes this."
msgid_plural "%d users like this."
msgstr[0] ""
msgstr[1] ""

#: file.php:22
#, php-format
msgctxt "Another unique context string"
msgid "%d user likes this."
msgid_plural "%d users like this."
msgstr[0] ""
msgstr[1] ""

Document Tags and Contributors

Contributors to this page: jswisher, stasm, kmaglione, mialy
Last updated by: mialy,