Using Translation Placeholders

Objectives
  • Learn about the three types of placeholders you can use in translatable text.
  • Research the translation service and the class that provides it using the reference documentation.
  • Add a new route and controller and use the service to demonstrate the different replacement placeholder types.
Last revision:

Replacement Placeholders

Placeholders are like variables inside the text you want to translate. They are substituted with the values you provide, but:

  • The replaced values themselves are not translated.
  • The replaced values are processed and, depending on the placeholder type, will be processed and/or sanitized (made safe) before being displayed.

Let's look at some examples.

Type 1: @variable

Usage:

  • Use this placeholder as the default choice for anything displayed on the site.
  • However, do not us it within HTML attributes, JavaScript, or CSS: doing so is a security risk.

Processing:

  • Special characters are converted to HTML entities via HTML::escape

Example 1

Image your source text is something like "You have x new messages", where 'x` can be any number. This is a typical example of a dynamic string

Without placeholders, you would only be able to translate the entire string literally: "Vous avez x messages" in French, which is not very useful.

With the placeholder system you can do the following:

Code
$counter = 17;
$message = t("You have @number messages.", [ '@number' => $counter ]);

Someone who adds a French translation for that string would enter:
'Vous avez @counter messages.'

Example 2

Code
// In this example we present a user's comment in a preview mode and ask them
// if they really want to publish it. We must consider all user input to be UNSAFE, 
// because we never know what malicious users may try to save into our database.

// Retrieve the comment the user just submitted.
// This is a line of imaginary code. The point here is not to show how to retrieve comments,
// but how to deal with potentially unsafe data.
$submitted_comment = retrieve_comment_that_was_just_submitted();

$message = t("Are you sure you want to publish the following comment? @comment", 
  [ '@comment' => $submitted_comment ]
);

Type 2: %variable

Usage: 

Use when the replacement value is to be wrapped in <em> tags. However, do not us it within HTML attributes, JavaScript, or CSS: doing so is a security risk.

Processing: 

Other than the addition of <em> tags to display the text as italic, no processing happens.

Example

Code
$title = 'Why most plants are green';
$message = t("Your page %title has been saved.", ['%title' => $title]);

Output: Your page Why most plants are green has been saved.


Type 3: :variable

Usage: 

Use when dangerous protocols may cause a security issue.

Processing: 

Special characters are converted to HTML entities via HTML::escape. Dangerous protocols are removed via UrlHelper::stripDangerousProtocols.

Example

Code
// The following example fits into a scenario where someome submits the title and link 
// of a blog post to your Drupal site.

// The code uses 2 imaginary functions to retrieve user-submitted content.
// Again, the focus is not on these 2 functions, but how to turn them into a link in a safe
// way, knowing you should never trust user-submitted data.

$link_text = retrieve_blog_title();
$link_address = retrieve_blog_link;

$link = t('<a href=":url">@link_text</a>', [
  ':url' => $link_address,
  '@link_text' => $link_text,
]);

Activity

In this task you will create a new controller and route to demonstrate the use of the three types of placeholder in the t() function.

  • Add a new '/translation' route linked to a controller method called TranslationDemo.
  • Add a new TranslationController with a TranslationDemo method.
  • Make the method do the following:
  • Create 3 variables ($msg1, $msg2, $msg3).
  • Use the t() function to assign a value to each of the three variables, using a @ placeholder for the first, a % placeholder for the second, and a : placeholder for the third
  • Print the 3 messages on screen.
Hints
  • At the end of your new controller method, you can combine the three message variables and return them like this:
return [
 '#markup' => "<p>$msg1</p><p>$msg2</p><p>$msg3</p>",
];

Activity

  • Add a new /placeholders route.
  • Add a new method to the TranslationController
  • This new method should:
    • Use t() to translate a dynamic string that says "You have x unread messages and y read messages", replacing x and y with appropriate placeholders.
    • Return the translated message.
Hints
  • You can use as many placeholders as you want, as long as you provide a value for each of the placeholders.
  • The " Type 3: :variable" section in this unit demonstrates how to work with more than one placeholder. In that example both placeholders were of a different type, but you can use placeholders of the same type as well.

Activity

  • Which of the three placeholders is the most common one - the one you're likely to use most often?

Activity

  • Why is it important that user-provided input gets sanitized (also referred to as escaped)?

Summary

  • t() offers three types of replacement placeholders.
  • Each type of placeholder results in different processing and/or sanitisation of the provided value.
  • All user-generated input must be sanitised before you attempt to display it. If you don't, you're creating a security issue.
  • You can use as many placeholders as you want in a translatable string.