Wordpress - How to print translation supported text with HTML URL

Since esc_html_e will escape HTML link (hence will show the HTML anchor as plain text), you'll need to segment the text and escape the non-HTML part with esc_html_e or esc_html__, and print the HTML LINK part without HTML escaping.

Method-1 (just for your understanding):

You may do it in parts, like this:

esc_html_e( 'Dear Guest, with Your information we could not find any room at the moment.', 'text-domain' );
echo "<br><br>";

printf(
    esc_html__( '%1$s %2$s', 'text-domain' ),
    esc_html__( 'Please contact us on our', 'text-domain' ),
    sprintf(
        '<a href="%s">%s</a>',
        esc_url( 'http://www.example.com/contact-us/' ),
        esc_html__( 'Contact Page', 'text-domain' )
    )
);

printf(
    ' or <a href="%s">%s</a>',
    esc_url( 'mailto:[email protected]', array( 'mailto' ) ),
    esc_html__( 'Email', 'text-domain' )
);

Method-2 (just for your understanding):

Obviously, different languages will have different ordering of texts, so to give translators more flexibility (with escaping and ordering of text), you may do it in the following way instead:

printf(
    esc_html__( '%1$s%2$s%3$s%4$s%5$s', 'text-domain' ),
    esc_html__( 'Dear Guest, with Your information we could not find any room at the moment.', 'text-domain' ),
    nl2br( esc_html__( "\n\n", 'text-domain' ) ),
    sprintf(
        esc_html__( '%1$s %2$s', 'text-domain' ),
        esc_html__( 'Please contact us on our', 'text-domain' ),
        sprintf(
            '<a href="%s">%s</a>',
            esc_url( 'http://www.example.com/contact-us/' ),
            esc_html__( 'Contact Page', 'text-domain' )
        )
    ),
    esc_html__( ' or ', 'text-domain' ),
    sprintf(
        '<a href="%s">%s</a>',
        esc_url( 'mailto:[email protected]', array( 'mailto' ) ),
        esc_html__( 'Email', 'text-domain' )
    )
);

This way of doing it will:

  1. Escape all the necessary translated texts.

  2. Allow the HTML for link, email (with mailto: syntax) etc.

  3. Allow translators to have all sorts of different ordering of texts for different languages. The argument swapping notation (%1$s, %2$s etc.) is used so that the translators may reorder the translated text where needed.


Method-3 (Updated & Recommended):

As @shea rightly pointed out, Method-2 above works fine, but it may be difficult for the translators to add support for different languages. So we need a solution that:

  1. Keeps the sentences intact (doesn't break sentences).

  2. Does proper escaping.

  3. Provides ways to have different ordering for contact & email links (or anything similar) within the translated sentence.

So to avoid the complication of method-2, the solution below keeps the translatable sentences intact and does the proper URL escaping & argument swapping at the same time (more notes within CODE comments):

// sample contact url (may be from an unsafe place like user input)
$contact_url = 'http://www.example.com/contact-us/';
// escaping $contact_url
$contact_url = esc_url( $contact_url );

// sample contact email (may be from an unsafe place like user input)
$contact_email = '[email protected]';
// escaping, sanitizing & hiding $contact_email.
// Yes, you may still need to sanitize & escape email while using antispambot() function
$contact_email = esc_url( sprintf( 'mailto:%s', antispambot( sanitize_email( $contact_email ) ) ), array( 'mailto' ) );

esc_html_e( 'Dear Guest, with Your information we could not find any room at the moment.', 'text-domain' );
echo "<br><br>";

printf(
    esc_html__( 'Please contact us on our %1$s or per %2$s.', 'text-domain' ),
    sprintf(
        '<a href="%s">%s</a>',
        $contact_url,
        esc_html__( 'Contact Page', 'text-domain' )
        ),
    sprintf(
        '<a href="%s">%s</a>',
        $contact_email,
        esc_html__( 'Email', 'text-domain' )
        )
    );

This way of doing it will give translators two full sentences & two separate words to translate. So a translators will only have to worry about the following simple lines (while the CODE takes care of the rest):

esc_html_e( 'Dear Guest, with Your information we could not find any room at the moment.', 'text-domain' );
// ...
esc_html__( 'Please contact us on our %1$s or per %2$s', 'text-domain' )
// ...
esc_html__( 'Contact Page', 'text-domain' )
// ...
esc_html__( 'Email', 'text-domain' )

That's it, simple structure and does proper escaping and argument swapping as well.


Read more about internationalization for themes & internationalization for plugins.


I think the answer by @Fayaz is very good; they are definitely on the right path in that you should use sprintf() wrapped around translations to include things such as HTML and links.

However, I don't believe that it is a good idea to split sentences into parts for translation, as many languages have different sentence structures that are not at all compatible with English. By splitting out individual words, a great deal of the context in which a specific word is translated is lot, potentially leading to ambiguity and translation errors.

Instead, I recommend translating phrases and sentences as a whole, and then using sprintf or different esccaping functions where appropriate.

For your text, the first part can simply be translated by itself with esc_html_e:

esc_html_e( 'Dear Guest, with your information we not find any room at the moment. ', 'text-domain' );

The second part is slightly harder. Now, I assume that you are retrieving the page URL and email from the database somehow, probably using get_the_permalink() or get_option() or some other function. So I'm going to assume they are stored in $contact_page_url and $contact_email variables.

The first step is to translate the string, without the links. Note that we need to use __() without escaping at this point - that will come later.

__( 'Please contact us on our <a href="%1$s">contact page</a> or by email %2$s.', text-domain' );

This way the translator is free to word the sentence as necessary and place the link URL and email where they need to.

The next step is to use sprintf() to insert the link URL and email. Note that we use esc_url() here to escape the URL, and also the antispambot() to encode the email address to provide a minimal protection against scrapers:

$text = sprintf(
    __( 'Please contact us on our <a href="%1$s">contact page</a> or by email %2$s.', 'text-domain' );
    esc_url( $contact_page_url ),
    sprintf( '<a href="mailto:%1$s">%1$s</a>', antispambot( $contact_email ) );
);

Finally, rather than using esc_html() , we need to use wp_kses() to only allow link elements in the translated HTML:

echo wp_kses( $text, array( 'a' => array( 'href'  => array() ) ) );

And that's it! The final code is:

esc_html_e( 'Dear Guest, with your information we not find any room at the moment. ', 'text-domain' )

$text = sprintf(
    __( 'Please contact us on our <a href="%1$s">contact page</a> or by email %2$s.', 'text-domain' );
    esc_url( $contact_page_url ),
    sprintf( '<a href="mailto:%1$s">%1$s</a>', antispambot( $contact_email ) );
);

echo wp_kses( $text, array( 'a' => array( 'href'  => array() ) ) );