Wordpress - How to make a text with hyperlink translatable in WordPress?

This is a very faceted issue. It combines inherent HTML content issues with whole new load of translation challenges, such as scanning for strings, translation process itself, and its verification.

So we have to combine:

  1. Text (in translatable form)
  2. HTML markup (in hard to break, but preferably flexible form)
  3. URL destination (in secure and preferably translatable form, might be specific to language!)

We also need to consider familiarity and prior art, in other words — what would core do. Ok, it doesn't help that from quick check the core does this in most (if not all) of these ways.

From these factors and surrounding discussion I would say there are three buckets of approaches, depending on needs and priorities.

Simple — one string with everything

__( 'Please read <a href="https://goo.gl">this</a>', 'example-domain' );
  • easy to follow in code
  • allows to translate all parts (text, markup, URL) as a whole
  • URL hardcoded
  • prone to HTML breaks
  • popular in core

Balanced — URL broken out

sprintf( 
    __( 'Please read <a href="%s">this</a>', 'example-domain' ), 
    esc_url( 'https://goo.gl' ) 
);
  • easy to follow
  • can translate all parts, but URL(s) needs separate translation call
  • URL can be dynamic and escaped
  • prone to HTML breaks
  • popular in core
  • anecdotally popular with developers/translators

Piecemeal — markup broken out

sprintf( 
    __( 'Please read %1$sthis%2$s.', 'example-domain' ), 
    '<a href="' . esc_url( 'https://goo.gl' ) . '">',
    '</a>' 
);

or via concatenation

'<a href="' . esc_url( 'https://goo.gl' ) . '">' 
. __( 'Please read this.', 'example-domain' );
. '</a>';
  • less readable
  • more resilient to HTML breaks
  • less popular in core
  • needs more context provided for translation

Rule of a thumb (as far as I see it)

  1. simple to keep it as simple as possible
  2. piecemeal to prevent HTML breaks
  3. balanced for all other cases (likely most common)

These days I work with many multilanguages websites, and I have to say:

  1. URLs of links often needs to be translatable.
  2. Trust translation function output is bad. I pretty much never use __() but always esc_html__() / esc_attr__(). Which means that string to be translated can't contain HTML of any sort.
  3. Writing in english we often don't realize that in other languages a word might be written in different ways depending on the contex. So when the text contain 1 or few words, is always better to use the *_x() variant of translating functions.
  4. to include more than one placeholder for each translatable string may be "dangerous". If the translator is not a developer, they will easily break the page rendering. In the example %1$sthis%2$s a non technical translator does not understand that the s just before this is necessary for proper rendering, and may also think that the developer wanted to type this but had a typo and wrote sthis.

For all this reasons "properly" translating text that contains links is hard. The only viable solution that takes into account all said above is:

$anchor = esc_html_x( 'Google', 'link text for google.com', 'txt-domain' );
$domain = esc_url( __( 'google.com', 'txt-domain' ) );  
$link   = sprintf( '<a href="https://%s">%s</a>', $domain, $anchor );

 /* translators: 1 is a link with text "Google" and URL google.com */
echo sprintf( esc_html__( 'Use %1$s to search.', 'example-domain' ), $link );

Which is safe and easy to be translated by non-technical people, but also verbose and a PITA to implement, especially if this has to be done for several links...

However, for a code released in the wild with thousand of (real or potential) users speaking many languages, this is the way I would take.

This is actually the way I take even for internal-use-only code, but that's me.


Note:

It may appear too much to make the word "Google" translatable separately, and that's probably true for this specific case. But sometimes even assume brand names is wrong. Just to name an example, in Italy we have the brand "Algida" which is known with ~30 different names around the world.

In such case, hardcoding the URL and/or the brand name, will cause issues.

Provide context for the translation will help the translators decide if they need to translate the brand name or not.


You have to keep the markup in the translatable string, because the translators have to know there is a link. In some languages the resulting link text might span multiple words, there could even be a comma (or an equivalent) inside or other markup that needs to be nested correctly.

It is also important to build a speaking link text, not this or here.

2 or 3 are the only translatable options, but you should make the URL translatable too.