11

In Drupal 8, it appears that using the ! (exclamation mark) placeholder with the t() function has been removed.

I have a variable that contains HTML:

<span class="fullname_wrapper"><span class="first_name">John</span> <span class="last_name">Hancock</span> <span class="account_name_wrapper">(@JohnH)</span></span>

I want to keep this HTML, as it will be styled specifically wherever it appears on the site.

In Drupal 7, I was able to do this:

t('Your name is !name', array('!name' => $name));

In which $name contains the HTML shown above. However, in Drupal 8, this doesn't work since the exclamation mark pass-through placeholder has been removed.

I tried this:

t('Your name is :name', array(':name' => $name));

But the HTML is still escaped.

The @ mark also escapes the HTML.

Does anyone know how HTML can be passed through the t() function in D8?

UPDATE:

According to No Sssweat's comment, it seems that my the ! mark does seem to work in t(). So here is my actual code:

drupal_set_message(
  $this->t(
    "@amount has been transferred to !account",
    array(
      '@amount' => '¥' . $form_state->getValue('amount'),
      '!account' => $this->accountService->formatAccountName($account)
    )
  )
);

And I get this error stack:

User error: Invalid placeholder (!account) in string: @amount has been transferred to !account in Drupal\Component\Render\FormattableMarkup::placeholderFormat() (line 235 of core/lib/Drupal/Component/Render/FormattableMarkup.php).

Drupal\Component\Render\FormattableMarkup::placeholderFormat('@amount has been transferred to !user', Array) (Line: 204)
Drupal\Core\StringTranslation\TranslatableMarkup->render() (Line: 15)
Drupal\Core\StringTranslation\TranslatableMarkup->__toString() (Line: 451)
drupal_set_message(Object) (Line: 128)

The user error is what made me think the ! mark is not allowed, but I must have misdiagnosed it. Does anyone know what is happening here?

16
  • 1
    In D8, testing with Devel Execude PHP block $test = "<b>test</b>"; drupal_set_message(t('Something !var just happened.', array('!var' => $test)), 'warning'); I get the warning message, the word test gets bolded and not escaped. Therefore, using ! seems to work fine in D8.
    – No Sssweat
    Commented Nov 21, 2016 at 2:55
  • 1
    See picture
    – No Sssweat
    Commented Nov 21, 2016 at 3:04
  • 1
    Thanks, I've updated the original post with my actual code, and the error message I'm seeing.
    – Jaypan
    Commented Nov 21, 2016 at 3:05
  • 1
    try using '!account' => '<b>test</b>' for testing purposes. See if you still get that error. Cause perhaps $this->accountService->formatAccountName($account) is not a valid code. Also, note on my testing message, I do not use $this->t(
    – No Sssweat
    Commented Nov 21, 2016 at 3:12
  • 2
    This isn't inserting HTML into the translation system - the string is "@amount has been transferred to @account" - there is no HTML in there.
    – Jaypan
    Commented Nov 22, 2016 at 0:07

2 Answers 2

29

It appears that any arguments passed to t() function are escaped unless they implement MarkupInterface. So that you need to represent the name as an object.

use Drupal\Component\Render\FormattableMarkup;

$formatted_name = new FormattableMarkup(
  '<span class="fullname-wrapper">
    <span class="first-name">@first_name</span>
    <span class="last-name">@second_name</span>
    <span class="account-name-wrapper">(@user_name)</span>
   </span>',
  [
    '@first_name' => 'John',
    '@second_name' => 'Hancock',
    '@user_name' => '@JohnH',
  ]
);

drupal_set_message(t('Your name is @name', ['@name' => $formatted_name]));
6
  • 1
    Thank you, that's exactly what I needed. It worked perfectly.
    – Jaypan
    Commented Nov 21, 2016 at 9:34
  • 2
    I think it's bad practice to use the t function like that.
    – Eyal
    Commented Nov 21, 2016 at 21:56
  • 4
    No, it's exactly how the t() function is supposed to be used (I've been using Drupal since D5).
    – Jaypan
    Commented Nov 22, 2016 at 0:06
  • 2
    @Eyal Why do you think it's bad practice? It is being used to translate '@amount has been transferred to @account' which is a normal string with placeholders. You don't use t() like in $amount . t(' has been transferred to ') . $account. That is bad practice.
    – avpaderno
    Commented Nov 22, 2016 at 6:22
  • 1
    SQL injection, It's the reason the ! placeholder was dropped in D8
    – Eyal
    Commented Nov 22, 2016 at 6:27
5

Yes, the user error you are seeing means the placeholder you are using is not recognized from t(). FormattableMarkup::placeholderFormat(), the method that does the job behind the scene, recognizes just 3 placeholder-types: @variable, %variable, and :variable. If the placeholder starts with a different character, it executes the following code, which causes the behavior you are seeing.

  default:
    // We do not trigger an error for placeholder that start with an
    // alphabetic character.
    // @todo https://www.drupal.org/node/2807743 Change to an exception
    //   and always throw regardless of the first character.
    if (!ctype_alpha($key[0])) {
      // We trigger an error as we may want to introduce new placeholders
      // in the future without breaking backward compatibility.
      trigger_error('Invalid placeholder (' . $key . ') in string: ' . $string, E_USER_ERROR);
    }
    elseif (strpos($string, $key) !== FALSE) {
      trigger_error('Invalid placeholder (' . $key . ') in string: ' . $string, E_USER_DEPRECATED);
    }
    // No replacement possible therefore we can discard the argument.
    unset($args[$key]);
    break;

The description for @variable says that the value of the variable could be:

In your case, I would just use the following code.

use Drupal\Component\Render\FormattableMarkup;

drupal_set_message(
  $this->t(
    "@amount has been transferred to @account",
    [
      '@amount' => '¥' . $form_state->getValue('amount'),
      '@account' => new FormattableMarkup($this->accountService->formatAccountName($account), [])
    ]
  )
);

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.