Skip to content

Translation for Developers

When writing new application code, developers should organize their code in a way which is amenable to internationalization, so that it may be localized to various languages and regions of the world.

If you are an extension developer, there is additional documentation in the Extension translation page.


  • The strings hard-coded into PHP should be wrapped in ts() function calls. For example:

    $string = ts('Hello, World!');
    $group = ['' => ts('- any group -')] + $this->_group;
  • You can also use placeholders for variables:

    $string = ts("A new '%1' has been created.", [1 => $contactType]);

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

  • If the string might be singular or plural, use the following syntax:

    $string = ts('%count item created',
      ['count' => $total, 'plural' => '%count items created']


When translating strings in an extension, ts scope needs to be declared. The CRM.ts function takes scope as an argument and returns a function that always applies that scope to ts calls:

// This closure gets a local copy of jQuery, Lo-dash, and ts
(function($, _, ts) {
  CRM.alert(ts('Your foo has been barred.'));
})(CRM.$, CRM._, CRM.ts(''));


CRM.ts is not the same as the global ts function. CRM.ts is a function that returns a function (javascript is wacky like that). Since your closure gives the local ts the same name as the global ts, it will be used instead.


Your local version of ts could be named anything, but strings in your javascript file cannot be accurately parsed unless you name it ts.

Smarty templates

  • The strings hard-coded into templates should be wrapped in {ts}...{/ts} tags. For example:

    {ts}Full or partial name (last name, or first name, or organization name).{/ts}
  • If you need to pass a variable to the localizable 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}

Best practices

The general rules for avoiding errors 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.
  • You must pass a literal string into ts(), not a variable.

Avoid variables inside strings


$string = ts("The date type '$name' has been saved.");


$string = ts("The date type '%1' has been saved.", [1 => $name]);

Avoid tags inside strings


{ts}<p>Hello, world!</p>{/ts}


<p>{ts}Hello, world!{/ts}</p>

Hyperlinks within larger blocks of text are an exception to this rule, where you should place the <a> tags within the ts. Any link parameters should be provided as arguments to the ts. For example:


{ts}Here is a block of text with a link to the <a href="" target="_blank">CiviCRM Web Site</a>.{/ts}

Less bad

{ts 1='href="" target="_blank"'}Here is a block of text with a link to the <a %1>CiviCRM Web Site</a>.{/ts}

Smarty doesn't evaluate within single quotes, so if you are capturing an URL for a link, capture it with the href=" and optionally target="_blank".

Less bad

{capture assign=something}href="{crmURL p='civicrm/admin/something' q='reset=1'}" target="_blank"{/capture}
{ts 1=$something}Here is a block of text with a link to a <a %1>specific URL in CiviCRM</a>.{/ts}

For title attributes in <a> links, within CiviCRM these usually only appear in links that aren't within a larger block of text or where there is no clickable text, such as a datepicker icon. In this situation, the title text needs to be translated:


{ts}<a href="" title="List participants for this event (all statuses)">Participants</a>{/ts}

Less bad

<a href="" title="{ts}List participants for this event (all statuses){/ts}">{ts}Participants{/ts}</a>

If there is no clickable text, just translate the title attribute:


<a title="{ts}Select Date{/ts}"><i class="crm-i fa-calendar"></i></a>

Avoid multi-line strings

Even if your code editor may not like it, long strings should be on a single line since a change in indentation might change where the line breaks are, which would then require re-translating the string.


$string = ts("Lorem ipsum dolor sit amet, consectetur adipiscing elit.
  Proin elementum, ex in pretium tincidunt, felis lorem facilisis 
  lacus, vel iaculis ex orci vitae risus. Maecenas in sapien ut velit
  scelerisque interdum.");


$string = ts("Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin elementum, ex in pretium tincidunt, felis lorem facilisis lacus, vel iaculis ex orci vitae risus. Maecenas in sapien ut velit scelerisque interdum.");

Avoid strings which begin or end with spaces


$string = $labelFormat['label'] . ts(' has been created.'),


$string = ts('%1 has been created.', [1 => $labelFormat['label']]),

Avoid escaped quotes


$string = ts('A new \'%1\' has been created.', [1 => $contactType]);


$string = ts("A new '%1' has been created.", [1 => $contactType]);

Use separate strings for plural items


$string = ts('%1 item(s) created', [1 => $count)];


$string = ts('%count item created',
  array('count' => $total, 'plural' => '%count items created')

Ensure that strings have some words in them

Another common error is to use ts() to aggregate strings or as a "clever" way of writing shorter code:


Incorrect aggregation. This will be extremely confusing to translations and might give some really bad results in some languages.

$operation = empty($params['id']) ? ts('New') : ts('Edit'));
$string = ts("%1 %2", [1 => $operation, 2 => $contactType]);

Less bad

if (empty($params['id'])) {
  $string = ts("New %1", [1 => $contactType]);
else {
  $string = ts("Edit %1", [1 => $contactType]);

Note that this still makes it difficult to use the correct gender.

Include typography in strings

Typography is different in different languages and thus must be translated along with the string. For example, in French, there must be a space before a colon.


{ts}Event Total{/ts}:


{ts}Event Total:{/ts}

Rationale for using Gettext

In most projects, strings are typically translated by either:

  • using Gettext (which is what CiviCRM does),
  • using arrays of key/string dictionaries,
  • using database lookups of strings (which is what Drupal does).

In order to be support Joomla!, WordPress, Backdrop and eventually other content management systems. Gettext is the standard way to translate strings in PHP, used by most projects.

Other guides/references

Here are the guides to other popular projects: