How to allow single quote with esc_html__() without sprintf()

Hmmm, a different way to output you say?

<?php if ( 'en_US' === get_locale() ) : ?>
    Wasn't your favorite color red?
<?php endif; ?>

:laughing:

But seriously, I mean anything can be done if you want to do it. If you look at esc_html__ – you will see that it’s just calling esc_html, which is filterable after the safe text has been determined by _wp_specialchars. Looking at _wp_specialchars you can see that it’s simply doing a string replace on single quotes to the encoded entity. If you want to undo what it has done, just do the inverse logic to replace the entity with a single quote. Something like this would work to achieve what you want:

add_filter( 'esc_html', function( $safe_text ) {
    return str_replace( '&#039;', "'", $safe_text );
}, PHP_INT_MAX );

The real question is should you do it? Some people often refer to ' and " as dumb quotes. Ideally these should be used in code, whereas and are sometimes referred to as smart quotes, fancy quotes, or typographic quotes. These are commonly thought as being displayable quotes, or quotes that belong to your content. They are meant to look prettier, and more accurately display the opening and closing of quoted text you want to display to a user/visitor. As a general rule of thumb, if you’re displaying the text for someone to read – it’s usually better to use smart quotes – which in part is why _wp_special_chars does that conversion. This is also done in the wptexturize filter throughout WordPress for displayed content – things like content, comments, nav_menu titles, link_name, term_description, bloginfo values, and widget_titles to name just a few places.

Obviously there isn’t a huge amount of risk involved by using dumb quotes for HTML output – but there are some scenarios where it could cause an issue if something was poorly written/overlooked. By filtering that value, you would need to be more responsible than the snippet I provided, and do something to ensure you’re only filtering values that you are adding via plugin/theme code to ensure that you don’t cause open vectors from someone else’s code. Ideally you’d want to check that the text being filtered belongs to your textdomain before tampering. This is another reason why it’s not a good idea to change the filtered value here as it’s extremely uncommon to use this filter – and VERY common for plugins and themes to have code that is ran through it.

So should you do it? No, but if you have an absolutely necessary need for this behavior – sure there’s ways to make it happen, even your example shows a way to annoyingly work around it. I’ve even seen people convert it back via JS when the page is loaded, which seems a bit drastic. In modern times though, it’s not being done so much for the security aspect, but more for the display value it provides to people reading content. Just don’t be silly and think that allowing it in user input forms, or doing the same technique esc_attr is a good idea though.