Translation for Developers¶
Developers should write their code 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.
PHP¶
-
The strings hard-coded into PHP should be wrapped in
ts()
function calls. For example:$string = ts('Hello, World!'); $options = [ 1 => ts('One'), 2 => ts('Two'), 3 => ts('Three'), ];
-
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 needs to be pluralized, use the singular form as the main string, and provide the count (integer) and plural (string) in the 2nd argument along with any placeholder values:
$string = ts('%count item was created by %1', [ 'count' => $total, 'plural' => '%count items were created by %1', 1 => $userName, ]);
Javascript¶
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('foo.bar.myextension'));
Note
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.
Important
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} </div>
Best practices¶
The general rules for avoiding errors may be summed up as:
- The first argument to
ts()
must be a single, literal string. - The string must not contain variables, concatenation, line-breaks, or leading/trailing spaces.
- The second parameter of the
ts()
call is an array of variables to swap for placeholders in the string.
Use placeholders instead of variables inside strings¶
Bad
$string = ts("The date type '$name' has been saved.");
Good
$string = ts("The date type '%1' has been saved.", [1 => $name]);
Avoid tags inside strings¶
Bad
{ts}<p>Hello, world!</p>{/ts}
Good
<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:
Bad
{ts}Here is a block of text with a link to the <a href="https://www.civicrm.org" target="_blank">CiviCRM Web Site</a>.{/ts}
OK
{ts 1='href="https://www.civicrm.org" 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"
.
OK
{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:
Bad
{ts}<a href="https://www.example.org/civicrm/something?reset=1" title="List participants for this event (all statuses)">Participants</a>{/ts}
Good
<a href="https://www.example.org/civicrm/something?reset=1" 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:
Good
<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.
Bad
$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.");
Good
$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¶
Bad
$string = $labelFormat['label'] . ts(' has been created.'),
Good
$string = ts('%1 has been created.', [1 => $labelFormat['label']]),
Avoid escaped quotes whenever possible¶
Bad
$string = ts('A new \'%1\' has been created.', [1 => $contactType]);
Good
$string = ts("A new '%1' has been created.", [1 => $contactType]);
Use separate strings for plural items¶
Bad
$string = ts('%1 item(s) were created by %2', [1 => $count, 2 => $userName]);
Good
$string = ts('%count item was created by %1', [
'count' => $total,
'plural' => '%count items were created by %1',
1 => $userName,
]);
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:
Bad
Incorrect aggregation. This will be extremely confusing to translators 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]);
OK
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.
Bad
{ts}Event Total{/ts}:
Good
{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: