12

I've seen different ways of doing making a text with a hyperlink translatable. However, I have been unable to find a single best practice.

So, here are some of the solutions I found:

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

// METHOD 2
echo '<a target="_blank" href="' . esc_url( 'https://goo.gl' ) . '">';
_e( 'Please read this.', 'tacoverdo-example-domain' );
echo '</a>';

// METHOD 3
sprintf( __( 'Please read <a href="%s">this</a>.', 'tacoverdo-example-domain' ), esc_url( 'https://goo.gl' ) );

// METHOD 4
sprintf( __( 'Please read %sthis%s.', 'tacoverdo-example-domain' ), '<a target="_blank" href="' . esc_url( 'https://goo.gl' ) . '">', '</a>' );

// METHOD 5
_e( 'Please read <a target="_blank" href="' . esc_url( 'https://goo.gl' ) . '">this</a>', 'tacoverdo-example-domain' );

My first thought would be that method 1 would be best. It does not require your translators to know HTML. But it also doesn't allow the ones that do to mess with it. It is also quite DRY (Don't Repeat Yourself) since you don't have to translate the whole HTML-part over and over again.

However, when posting this question on twitter, people replied that method 3 would be best as you can see here.

So, how should I make a text with hyperlink translatable in WordPress?

15

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)
  • +1. I like the "Balanced" option, maybe can be improved adding context using _x instead of __ – gmazzap Oct 14 '14 at 21:06
  • I intended snippets as basic examples rather than comprehensive ones. There will probably be variations in each due to sheer number of scenarios and translation functions. – Rarst Oct 14 '14 at 21:11
  • The balanced option have also the problem, that translators can create wrong markup. For a sure and secure translation is it better, that you exclude html. Also I vote for translation functions with context. That give the translators the chance to understand the how without a knowledge about position, context in the plugin, theme. – bueltge Oct 14 '14 at 21:22
  • I've heard the 'translators need context'-argument before, but it makes me wonder. What do they need context for in this example? I think it's good practice to provide context when needed, but do translators really need it in this case? Do they need to know whether %s stands for <strong> or <a href target="#">? Does it influence their translation? – Taco Verdo Oct 15 '14 at 15:22
  • @TacoVerdo if link is broken out I would assume text without it might very well be ambiguous, in that case the context should tell what destination the text is for. Context serves to pad information until it is complete and unambiguous. As you break text into smaller standalone pieces, each of them needs more context for where it fits in the whole. – Rarst Oct 15 '14 at 15:56
6

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.

2

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.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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