Dashboard > CiviCRM Documentation > ... > 7. Develop > Internationalisation guide
Internationalisation guide Log In | Sign Up   View a printable version of the current page.

 Contents
  Documentation Home

TABLE OF CONTENTS

In-code strings

The strings hard-coded into PHP should be wrapped in ts() function calls. Thus, the following example...

CRM/Contact/Form/Search.php
...
$group = array('' => ' - any group - ') + $this->_group;
...

...should be changed like this:

CRM/Contact/Form/Search.php
...
$group = array('' => ts(' - any group - ')) + $this->_group;
...

This will send the " - any group - " string to our localisation engine, which will in turn call Gettext and return the proper translation (or the string itself, if there's no translation available in the currently-used locale).

Sometimes you might need to pass a variable to localisable string. Consider situation like this:

$string = "This is a kind of dynamic string containing " . $something . " in the middle";

Of course yoy can tag both strings separately, but in order to make translation easier (you are keeping the whole context of the sentence), you should pass the variable as an argument to ts() function. Second argument is an array containing numbered values. So the ts() function call should look like this:

$string = ts("This is a kind of dynamic string containing %1 in the middle", array( 1 => $something ) );

Respectively, if you want to pass more variables, you just add new elements to the array:

$string = ts("This is %1 and this is %2.", array( 1 => $firstVar, 2 => $secondVar ) );

In-template strings

The strings hard-coded into templates should be wrapped in {ts}...{/ts} tags. Thus, the following example...

templates/CRM/Contact/Form/Search/BasicCriteria.tpl
...
Full or partial name (last name, or first name, or organization name).
...

...should be changed like this:

templates/CRM/Contact/Form/Search/BasicCriteria.tpl
...
{ts}Full or partial name (last name, or first name, or organization name).{/ts}
...

Currenty we're simply passing the contents and arguments of the {ts} tags to the ts() function.

If you need to pass a variable to the localisable string, you should use the following pattern:

<div class="status">{ts 1=$delName}Are you sure you want to delete <b>%1</b> Tag?{/ts}</div>

Consistently, every replaceable variable should be named with number and called as %number in the string, like:

{ts 1=$firstVar 2=$secondVar}This is %1 and this is %2.{/ts}

The span of the {ts} tags should be as broad as possible, so the translators can get as much context as possible. I.e., if there are several consecutive paragraphs tagged with <p>, the whole sequence should be tagged with one {ts} block.

The HTML inside the {ts} tags should be left as-is.

The Plurals Issue - number-based strings conjugation

There are situations when we want not only to pass a number to a string, but also make it different based on the number in question; example: "Found 1 contact" / "Found 2 contacts". In English this is easy, there are two plural forms ("contact" vs. "contacts"), but in Polish this is quite different - we say "Znaleziono 1 kontakt", "Znaleziono 2, 3, 4 kontakty", "Znaleziono 5, 6, ..., 10, 11, 12, 13, ... 20, 21 kontaktów", "Znaleziono 22, 23, 24 kontakty", "Znaleziono 25, ..., 101 kontaktów", etc.

Gettext handles this perfectly, but has to be told what the count is, and what are the basic singular and plural English strings. This is realised by the ts() function and the {ts} tags as follows:

PHP exapmple
$theString = ts('Found %count conact', array('count' => $count, 'plural' => 'Found %count contacts'));
Smarty example
{ts count=$count plural='Found %count contacts'}Found %count contact{/ts}

Note: The use of a named variable (%count) is a special case here. Normally only numbered variables can be used (%1, %2, etc.).

Good practices

Avoid splitted strings

Due to language differences, sometimes certain template tricks should be avoided. E.g. when you have this kind of construct, you cannot properly internationalise it.

<legend>{if $action eq 1}New{else}Edit{/if} Relationship(s)</legend>

When you tag New word separately, string extractor will add this word to strings bundle. This means, there will be only one translation of this word available. However, depending on the context, this word can be translated in many ways, e.g. in Polish, it can be "Nowy", "Nowa" or "Nowe". So in order to avoid such situations, please try to use following constructs:

<legend>{if $action eq 1}{ts}New Relationship{/ts}{else}{ts}Edit Relationship(s){/ts}{/if}</legend>

Another example would be the tagging of such construct:

templates/CRM/Contact/Page/View/Contact.tpl
Group Memberships{if $group.totalCount GT 3} (3 of {$group.totalCount}){/if}

This should be rewritten, as the final tagging looks like this:

templates/CRM/Contact/Page/View/Contact.tpl
{if $group.totalCount GT 3}
    {ts 1=$group.totalCount}Group Memberships (3 of %1){/ts}
{else}
    {ts}Group Memberships{/ts}
{/if}

Check what's exactly being tagged

Example:

CRM/Contact/Form/Individual.php
$genderOptions[] = HTML_QuickForm::createElement('radio', 'gender', 'Gender', Female', 'Female');

The first 'Female' is the visible label (and should be translated - thus, ts()-tagged), and the second 'Female' is the button's value (and must remain untranslated/untagged, as the form's logic depends on it).

Popular errors

Examples:

Wrong
ts('Total Selected Contact(s): %1',$valid+$invalid+$duplicate)

ts("New relationship record(s) created: $valid.<br />");

ts("Cannot find class \"$className\"");

ts("Cannot find method \"$methodName\" for class \"$className\"");

ts($someValue);

...should be:

Right
ts('Total Selected Contact(s): %1', array(1 => $valid+$invalid+$duplicate))

ts('New relationship record(s) created: %1.', array(1 => $valid)) . '<br />';

ts('Cannot find class "%1"', array(1 => $className));

ts('Cannot find method "%1" for class "%2"', array(1 => $methodName, 2 => $className));

$someValue;   // this is a variable, so it should be already translated before

The general rules for avoiding erros may be summed up like this:

  • if the string needs to be parsed, i.e. is in double quotes, then there's probably an error there
  • no string concatenation in the ts() calls
  • the second parameter of the ts() call must be an array

To check for errors in the ts() tagging of the whole project, run

$ bin/extractor.php > /dev/null

from the main CiviCRM directory.

You can also check particular files with the PHP extractor:

$ bin/php-extractor.php CRM/History/Page/Activity.php > /dev/null

Why don't we use Drupal's locale module?

Because we wanted to be CMS-agnostic. We have to support our own method of localisation (via the Gettext's .po file) anyway, and Drupal uses it's own reimplementation of the Gettext ideas basing on a database solution.

That said, Drupal can use the Gettext standard as an alien format, importing and exporting the database contents to and from .po files. Moreover, Drupal's t() and format_plural() functions seem quite compatible with Gettext's gettext() and ngettext(), so in the future we'll investigate the optional use of Drupal's localisation mechanisms in place of our own Gettext calls.


Added by Piotr Szotkowski , last edited by David Greenberg on Apr 03, 2007  (view change)
Labels: 
(None)

Recently Updated  |  Documentation Credits

Powered by a free Atlassian Confluence Open Source Project License granted to CiviCRM . Evaluate Confluence today.
Powered by Atlassian Confluence 2.7.1, the Enterprise Wiki. Bug/feature request - Atlassian news - Contact administrators