Dynamic or static links and HTML in translatable strings

Last updated on
21 December 2016

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;

Breaking text out to paragraphs makes it possible for translators to work on smaller chunks, which makes it more likely that they will actually get to the end of it and submit a translation. This also makes it possible to reuse some paragraphs between different help pages, like some modules do; so translators would only translate them once.

When it comes to inline markup however, things are different. It would not be wise to split up a sentence just because you try to emphasize a part:

$DO_NOT_DO_THIS .= t('This information is ') .'<strong>'. t(' very important') .'</strong>.';

This achieves HTML and text separation but makes life hard for you and translators alike. In these simple cases, just include the inline markup, and let the translators live with the fact. It provides them more context instead of the two unique strings you'd have with the above example. Do this instead:

$output .= t('This information is <strong>very important</strong>.');

Links are the same problem, but their href also brings a few questions, so it is best to take a bit closer look at them. Let's discuss two different types of links, internal and external. Examples of their use are:

Bad examples:

$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'),

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. Some developers use the ! placeholder because the link HTML code should not be escaped. This could be a problem if your external URL is not HTML-safe (eg. it includes an ampersand) as well. 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.

The other extreme is just including the external link as-is in the text. This is bad because it does not allow you to change it in a later release without requiring translators to fix their work as well. Even for possibly static links like the Drupal Handbooks, there might be changes, so it is best to remove the target from the URL.

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. It is also a best practice to use @ placeholders for URLs, since this makes sure that proper escaping happens on them. Even for internal links, if for example clean URLs are not enabled, an ampersand in the URL will surely need to be escaped. To make this format easy to remember, make sure to use url() when constructing links for translatable strings instead of l() and that would make you use HTML inside the text.

Finally, 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 in between the two, it just does not work. More descriptive names are better for you and for translators alike.

Read more on placeholders in general.