Drupal7 translation, localization, and the t() function

Keywords

Some useful external links concerning Drupal7's strategy for registering translatable strings.

At the time of writing, that strategy is not easy to marry with OO encapsulation of strings. For the OOE = Object Oriented Examples = One Of Each module this means that it is difficult to apply translation policy switches to encapsulated strings as used in the Render classes.

Some key points to keep in mind are (as quoted 8 Aug 2014).

To understand dynamic placeholder options read function format_string($string, array $args = array()):

Formats a string for HTML display by replacing variable placeholders.

This function replaces variable placeholders in a string with the requested values and escapes the values so they can be safely displayed as HTML. It should be used on any unknown text that is intended to be printed to an HTML page (especially text that may have come from untrusted users, since in that case it prevents cross-site scripting and other security problems).

In most cases, you should use t() rather than calling this function directly, since it will translate the text (on non-English-only sites) in addition to formatting it.

Parameters

$string: A string containing placeholders.

$args: An associative array of replacements to make. Occurrences in $string of any key in $args are replaced with the corresponding value, after optional sanitization and formatting. The type of sanitization and formatting depends on the first character of the key:

- @variable: Escaped to HTML using check_plain(). Use this as the default choice for anything displayed on a page on the site.

- %variable: Escaped to HTML and formatted using drupal_placeholder(), which makes it display as emphasized text.

- !variable: Inserted as is, with no sanitization or formatting. Only use this for text that has already been prepared for HTML display (for example, user-supplied text that has already been run through check_plain() previously, or is expected to contain some limited HTML tags and has already been run through filter_xss() previously).

All drupal_placeholder($text) does is run it through check_plain($text) anyway after wrapping in <em>:

function drupal_placeholder($text) {
  return '<em class="placeholder">' . check_plain($text) . '</em>';
}

There is a list of recommendations at How to write Drupal translatable interfaces, but in reality, especially when trying to employ OO coding respecting the Don't Repeat Yourself (DRY Principle) principle, they are not so easy to achieve:

1. Do not include variables into t() like t($example) if there this string is dynamically or configurable.
2. Explain some more bad style examples (and what POTX don't like and what's wrong).
3. Explain why strings should not use other string variables (see intro here), context sensitive translations and explain the possible problems
4. t() should whenever possible - do not contain HTML (except content sensitive translations with links)
5. Do not add line breaks inside t().
6. Do not split t() strings over multiple lines separated with DOT's. Example:

t('adsasd adasdasda asdadssa'.
  'asad asad adssdada')
For example, a direct side-effect of 4., 5., and 6. is massively long text strings longer than the 80 characters recommended by Coder !

Here's an example initially taken from OoePageController:

    $tagB = $this->tf()->newTag(XHTML::B);
    // A reusable tag for wrapping render objects !
 
    $umlwarn = $this->rf()->newR(
        t('Unless you study the UML diagrams and the policy notes linked to them you will') . '<br/>'
        . t('probably not fully understand this tutorial module and why it is coded as it is !'),
        $tagB
         // This will "wrap" the markup using the
         // Drupal render array prefix/suffix.
    );

Breaking the line explicitly slightly destroys the context especially of the second string: t('probably not fully ..').

Compare this with (from Dynamic or static links and HTML in translatable strings):

The general rule of thumb for translatable strings is that we keep inline markup for translation but avoid block tags (but do read the whole story before you make this your mantra). For example, when you make several paragraphs available for translation, you should generally do the following:

function example_help($path, $arg) {
  switch ($path) {
    case 'admin/example';
      $help = '<p>' . t('This example module should hopefully help you understand best practices for Drupal modules.') . '</p>';
      $help .= '<p>' . t('If you have multiple paragraphs in help text for example, break them down to different translatable strings. This also helps translators focus on shorter strings. Because these are paragraphs, enough context is available to translate them properly.') . '</p>';
      return $help;
  }
}

Following that rule, the OoePageController example would become:

        t('Unless you study the UML diagrams and the policy notes linked to them you will<br/>probably not fully understand this tutorial module and why it is coded as it is !'),

On Drupal translation recommendations for links

Also from Dynamic or static links and HTML in translatable strings comes examples of using links.

Bad examples:

// DO NOT DO THESE THINGS
$BAD_EXTERNAL_LINK = t('Look at Drupal documentation at !handbook.', array(
  '!handbook' => '<a href="http://drupal.org/handbooks">'. t('the Drupal Handbooks') .'</a>',
));
$ANOTHER_BAD_EXTERNAL_LINK = t('Look at Drupal documentation at <a href="http://drupal.org/handbooks">the Drupal Handbooks</a>.');
$BAD_INTERNAL_LINK = t('To get an overview of your administration options, go to !administer in the main menu.', array(
  '!administer' => l(t('the Administer screen'), 'admin'),
);

Good examples:

// Do this instead.
$external_link = t('Look at Drupal documentation at <a href="@drupal-handbook">the Drupal Handbooks</a>.', array(
  '@drupal-handbook' => 'http://drupal.org/handbooks',
));
$internal_link = t('To get an overview of your administration options, go to <a href="@administer-page">the Administer screen</a> in the main menu.', array(
  '@administer-page' => url('admin'),
));

I find the Good examples rather ugly, what with all of that complex and error prone HTML markup in them. the argument is that:

We see a lot of bad examples in existing modules with links. A typical problem is that people try to remove HTML from the text and with it they of course also remove the link text itself. The link text then becomes isolated, not part of the flow of the sentence at all. This is a big problem for translation. ..

Finally, the translator has no idea that a link is happening behind the placeholder, let alone the text of the link, so translation combinations from such solutions tend to be funny instead of professional.

I'm not convinced. Firstly, what's wrong with using placeholders like !link-adminster-page ? Then it's rather clear that it's a link. Also, if the link title has already been passed through translation it will mostly make sense. Like this:

    $lApi = l(t('OOE API'), 'api/ooe');
    $page->addItem(
        'api', $this->rf()->newP(
            t('The automatically extracted API documentation with code is under !link-api.',
                array('!link-api' => $lApi))
            )
    );
 
    $lUml = l(t('OOE UML'), 'node/47');
    $page->addItem(
        'uml', $this->rf()->newP(
            t('The graphical Unified Modeling Language (UML) analysis and design(ed) models are under !link-uml.',
                array('!link-uml' => $lUml))
            )
    );

Nobody can tell me that's not clearer and tidier, or that the translation will be any harder.

I find this argument more compelling:

Our good examples keep the link markup in the text, so that translators are aware of what is happening, and the link text is also there to translate in the sentence flow. This lets them move the link around, if the language at hand requires it, move out text from the link or vice versa.

But I still find the "good example" rather ugly and error prone. I can't help thinking that there must be a better 3rd way, perhaps a new placeholder system/indicator dedicated to links (after all the WWW uses a lot of them).

Also from https://www.drupal.org/node/322774:

.. another best practice is naming the placeholders properly. The bad examples use !handbook and !administer which gives no clear indication of what is going behind them. Better examples are @drupal-handbook and @administer-page. If you have multiple links in one string, just don't do @url1, @url2 and so on, since when translators move things around, or when you need to add a new link inbetween the two, it just does not work. More descriptive names are better for you and for translators alike.

Then why not use @url-drupal-handbook and @url-administer-page ?

Join the discussion at Support » Translations: Concerning recommendations for internal links using placeholders.


Interface vs. Content translation (Content Translation or Entity Translation) in Drupal7

It's important to understand the difference between interface translation (performed via the t() function) and content translation, which in Drupal7 can be done 2 ways: older translation-node-set-based Content Translation and the newer Entity Translation: there's a good overview article for Drupal7 here.


Visit also