In Drupal 7, one could do the following one line::

t('Visit !mylink', ['!mylink' => l('my website', 'http://example.com')]);

But that no longer works in Drupal 8 or 9 😞. So far, I've tried this:

First I create a link, Drupal 8 style:

$mylink = Link::fromTextAndUrl('my website', Url::fromUri('http://example.com'));

Then try to create a string that can be printed. I've tried toString():

t('Visit @mylink', ['@mylink' => $mylink->toString()]);

However, we're not supposed to do this. The API docs says:

Do not use this method to render a link in an HTML context. In an HTML context, self::toRenderable() should be used so that render cache information is maintained.

So I try to use toRenderable():

t('Visit @mylink', ['@mylink' => render($mylink->toRenderable()]);

But PHP 7.2 doesn't like it. The call to t() produces the following Notice:

Notice: Only variables should be passed by reference ...

Fair enough - but how is this done?

I could not find how to do this described in any of the obvious docs, such as:

So my question is:
What is the best practice for writing a link to the screen in Drupal 8?

(Environment: Ubuntu 20.04 LTS, Drupal 8.8.6 (core + devel), procedural code - e.g. hook_help()).

2 Answers 2


For a link enclosed in translatable text, Drupal core suggests to use t() and embed the HTML anchor tag directly in the translated string. In LinkGeneratorInterface::generate(), the example code for this case is the following.

$text = t('Visit the <a href=":url">content types</a> page', array(
  ':url' => Url::fromRoute('entity.node_type.collection')->toString(),

Searching for :url in the Drupal core code finds code like the following.


if (count($types) === 0) {
  return [
    '#markup' => $this
      ->t('You have not created any block types yet. Go to the <a href=":url">block type creation page</a> to add a new block type.', [
      ':url' => Url::fromRoute('block_content.type_add')->toString(),


  $element = [
    '#markup' => $this->t('Add a <a href=":url">custom block</a>.', [
      ':url' => Url::fromRoute('block_content.add_page')->toString(),
    '#access' => $access_result->isAllowed(),
    '#cache' => [
      'contexts' => $access_result->getCacheContexts(),
      'tags' => $access_result->getCacheTags(),
      'max-age' => $access_result->getCacheMaxAge(),

Alternatively, Drupal core uses the following code.


$themes = \Drupal::service('theme_handler')->listInfo();
$output = '<p>' . t('Block placement is specific to each theme on your site. Changes will not be saved until you click <em>Save blocks</em> at the bottom of the page.') . '</p>';
$output .= '<p>' . Link::fromTextAndUrl(t('Demonstrate block regions (@theme)', [
  '@theme' => $themes[$demo_theme]->info['name'],
]), Url::fromRoute('block.admin_demo', ['theme' => $demo_theme]))->toString() . '</p>';


    if ($variables['tid'] != $topic->forum_tid) {
      $variables['topics'][$id]->moved = TRUE;
      $variables['topics'][$id]->title = $topic->getTitle();
      $variables['topics'][$id]->message = Link::fromTextAndUrl(t('This topic has been moved'), Url::fromRoute('forum.page', ['taxonomy_term' => $topic->forum_tid]))->toString();
  • Very enlightening. A big thanks to both MrD and you! Commented Jun 1, 2020 at 13:12
  • 1
    As a note, the quote from the API about cache data still applies. If you have a URL that is dynamically generated and that has additional cache requirements to the rest of the page, you should be injecting them into the render array because they will be lost otherwise. Most of the time this won't apply, though. Commented Jun 1, 2020 at 15:23

It will easy if you convert link to url and use translate like this.

$url = Url::fromUri('http://example.com')->toString();
t('Visit <a href=":href">link</a>', array(':href' => $url));

Example drupal core use translation with link.

$requirements['online'] = array(
      'title' => t('Internet'),
      'value' => t('The translation server is offline.'),
      'severity' => REQUIREMENT_ERROR,
      'description' => t('The installer requires to contact the translation server to download a translation file. Check your internet connection and verify that your website can reach the translation server at <a href=":server_url">@server_url</a>.', array(
        ':server_url' => $server_url,
        '@server_url' => $server_url,

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.