Skip to content

TranslatableMarkup & t()

When to Use

When translating UI strings in PHP code — form labels, system messages, error messages, navigation text.

Decision: Which Translation Function

If you need... Use... Why
Translate static string in class $this->t('Text') via StringTranslationTrait Best practice, testable, DI-friendly
Translate in function (no class) new TranslatableMarkup('Text') Explicit object creation
Translate in legacy code t('Text') global function Acceptable but avoid in new code
Format without translation new FormattableMarkup() Placeholder replacement only, no translation
Plural forms \Drupal::translation()->formatPlural($count, '1 item', '@count items') Handles singular/plural correctly per language

Pattern: Using t() Correctly

Basic usage:

use Drupal\Core\StringTranslation\StringTranslationTrait;

class MyClass {
  use StringTranslationTrait;

  public function myMethod() {
    $message = $this->t('Hello, world!');
    return $message;
  }
}

With placeholders:

// @variable — sanitized with Html::escape()
$message = $this->t('Welcome, @name!', ['@name' => $account->getDisplayName()]);

// %variable — sanitized and wrapped in <em>
$message = $this->t('File %filename uploaded.', ['%filename' => $file->getFilename()]);

// :variable — sanitized for use in href attributes (URL-safe)
$message = $this->t('Visit <a href=":url">the site</a>.', [':url' => $url]);

Placeholder types (Drupal 8+): - @variable — use for user input, entity labels, filenames (auto-escaped) - %variable — use for emphasized values (auto-escaped + <em>) - :variable — use for URLs in href attributes (auto-escaped, safe for URLs)

Note: The !variable placeholder from Drupal 7 is NOT supported in t() or TranslatableMarkup. For pre-sanitized markup, use FormattableMarkup instead of t().

Pattern: String Context for Disambiguation

Problem: English word "May" translates differently as month vs permission verb.

Solution: Add context parameter.

// Month name
$month = $this->t('May', [], ['context' => 'Long month name']);

// Permission
$verb = $this->t('May', [], ['context' => 'Verb: permission']);

In .po file:

msgctxt "Long month name"
msgid "May"
msgstr "Mayo"

msgctxt "Verb: permission"
msgid "May"
msgstr "Puede"

When to use context: - Short strings with multiple meanings (days, months, verbs) - Ambiguous single words - Technical terms that vary by domain

Pattern: Plural Forms

$count = 5;
$message = \Drupal::translation()->formatPlural(
  $count,
  '1 item',              // Singular
  '@count items',        // Plural
  ['@count' => $count],  // Replacements
  ['context' => 'Shopping cart']  // Optional context
);
// Returns: "5 items"

Language-specific plural rules: - English: 1 vs other - French: 0-1 vs other - Russian: complex rules with 3+ forms - Drupal handles this automatically based on language

Pattern: TranslatableMarkup in Render Arrays

$build['#markup'] = $this->t('Translatable text');

// Or
$build['message'] = [
  '#markup' => $this->t('Welcome, @name!', ['@name' => $user->getDisplayName()]),
];

Delay translation: Don't cast to string early. Let rendering system translate.

// GOOD — delays translation
$build['#title'] = $this->t('Page title');

// BAD — forces early translation, breaks caching
$build['#title'] = (string) $this->t('Page title');

Common Mistakes

  • Translating user input$this->t($user_input) is XSS vulnerability. NEVER translate variables. Only translate literal strings
  • Using variables as source string$this->t($string) where $string is a variable. Static analysis can't extract for translation. Always use literal strings
  • Wrong placeholder type → Using !variable for user input allows XSS. Use @variable for user data
  • Concatenating translated strings → Don't do $this->t('Hello') . ' ' . $this->t('world'). Languages reorder words. Use placeholders: $this->t('Hello @name', ['@name' => 'world'])
  • Casting to string too early(string) $this->t('Text') breaks language-based caching. Let rendering system handle
  • Missing context for ambiguous strings → "Order", "May", "Second" need context to translate correctly

See Also

  • Interface Translation — .po file management
  • Twig Translation — translating in templates
  • Anti-Patterns & Common Mistakes — security issues
  • Reference: core/lib/Drupal/Core/StringTranslation/TranslatableMarkup.php
  • Reference: https://www.drupal.org/docs/8/api/translation-api/overview
  • Reference: https://drupalize.me/tutorial/drupal-code-standards-t-function