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:
- Text (in translatable form)
- HTML markup (in hard to break, but preferably flexible form)
- 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="https://wordpress.stackexchange.com/questions/165357/%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)
- simple to keep it as simple as possible
- piecemeal to prevent HTML breaks
- balanced for all other cases (likely most common)