What is the best practice for writing a link to the screen through t()? In Drupal 7, one could do the following one line::

t('Visit !mylink', ['!mylink' =&gt; 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' =&gt; $mylink-&gt;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' =&gt; render($mylink-&gt;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:


https://www.drupal.org/docs/security-in-drupal/sanitizing-output
https://www.drupal.org/docs/security-in-drupal/writing-secure-code-for-drupal


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()).
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 &lt;a href=":url"&gt;content types&lt;/a&gt; page', array(
  ':url' =&gt; Url::fromRoute('entity.node_type.collection')-&gt;toString(),
));


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

BlockContentController::add()

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


ListingEmpty::render()

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


Alternatively, Drupal core uses the following code.

block_help()

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


template_preprocess_forums()

    if ($variables['tid'] != $topic-&gt;forum_tid) {
      $variables['topics'][$id]-&gt;moved = TRUE;
      $variables['topics'][$id]-&gt;title = $topic-&gt;getTitle();
      $variables['topics'][$id]-&gt;message = Link::fromTextAndUrl(t('This topic has been moved'), Url::fromRoute('forum.page', ['taxonomy_term' =&gt; $topic-&gt;forum_tid]))-&gt;toString();
    }