Skip to content

Extensions Translation

For developing a CiviCRM extension in a way that can be translated, all the best practices described in the Internationalisation for Developers page apply. This page describes special considerations that need to be taken into account for extensions.

See also: "Extension translation" wiki in the Translation project.

For translators: Translating strings on Transifex

There is a separate project on Transifex to translate extensions. Each extension has its own "resource". Therefore, when a translator joins a translation team, they can translate all extensions. We didn't see a need to separate each extension in a separate project, because each extension should have only one translation (.po) file.


Translation strings are sent to Transifex when the extension is "ready for automatic distribution". See: Publishing Extensions.

For administrators: Download translation files for extensions

The easiest way to download translations for extensions is to use the l10nupdate extension.

For developers: Correct usage of the E::ts() function

In PHP, Smarty, and JS code, the convention is to perform translations using the E::ts() helper function. This is the same as in core code — with the additional requirement that one must specify the "domain" so that the translation engine can use the correct dictionary (.mo file) at run-time.

New in civix 17.08

E::ts() was added to civix 17.08. The civix file may need to be regenerated. You can read more about it in the civix upgrade notes. Extensions may still use the old syntax using ts() with the domain argument.


use CRM_Myextension_ExtensionUtil as E;

class CRM_Myextension_Form_Example {
  function example() {
    $string = E::ts('Hello, %1', array(
      1 => $display_name,

Smarty templates:

{crmScope extensionKey='org.example.myextension'}
  <p>{ts 1=$display_name}Hello, %1{/ts}</p>


(function ($, ts){
  $('.foo').click(function greet(display_name) {
    window.alert(ts('Hello, %1', {1: display_name}));
}(CRM.$, CRM.ts('org.example.myextension')));


$scope.ts = CRM.ts('org.example.myextension');

Angular HTML templates:

<p>{{ts('Hello, %1', {1: display_name})}}</p>

For developers: Generate .po and .mo files for your extension

Sometimes you will want to generate the .po and .mo files for your extension during the development cycle, for testing (or for internal extensions, for production use).

Once your extension is stable you may want to consider publishing it on the Extensions Directory and getting it approved for in-app distribution you can then benefit from extension translation via Transifex where the Civi community can translate your extension and these translations can automatically be installed!

Here are the steps you'll need to follow:

Tools needed.

To generate the .po and .mo files you will need:

  1. A po editor examples include PoEdit and Virtaal
  2. The GetText utilities
  1. Download civistrings using the instructions in the readme.

  2. From the root directory of your extension do the following:

  3. Create an l10n directory: mkdir l10n

  4. Run CiviStrings civistrings -o "l10n/<Extension Shortname>.pot" . Note that the civistrings command format is:

  5. Once you have your pot file copy this into the expected directory structure, for example to translate your extension into French copy the pot file to <Extension Directory>/l10n/fr_FR/LC_MESSAGES/<Extension Shortname>.po (Replacing the parts denoted by <> with the relevant information and noting carefully the filename change from pot to po.)

  6. Use your po editor to translate the strings in the po file you copied.

  7. If your editor has not generated the corresponding mo binary file you can do this manually using the msgfmt command provided by GetText, as follows:

msgfmt <Extension Directory>/l10n/<Language>/LC_MESSAGES/<Extension Shortname>.po -o <Extension Directory>/l10n/<Language>/LC_MESSAGES/<Extension Shortname>.mo

Again, replacing the parts denoted by <> as appropriate.

Updating Strings

If you change your extension to add more E::ts or similar calls you will need to go through the same process of generating the pot file then copying it to a po file and editing that to then generate the mo file. Essentially start this process again!