This documentation refers to an older version of CiviCRM (3.4 / 4.0). View latest version.

Skip to end of metadata
Go to start of metadata
This page refers to CiviCRM 3.4 and 4.0, current STABLE version.

Documentation Search

CiviCRM 3.4 and 4.0 Documentation

Support and Participation

Developer Resources

CiviCRM books!

Make sure to check out the FLOSS Manual Understanding CiviCRM as well! You can also support this project by ordering a hard copy.

Or support us by buying an eBook or hard copy of Using CiviCRM from Packt Publishing.


In-code strings

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


...should be changed like this:


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:

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:

Note that the array values should themselves be translated by your code before passing in, if appropriate.

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

In-template strings

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


...should be changed like this:


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:

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

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
Smarty example

(warning) 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.

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:

Another example would be the tagging of such construct:


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


Check what's exactly being tagged



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



...should be:


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

from the main CiviCRM directory.

You can also check particular files with the PHP extractor:

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.

  • None