This isn't limited to WPML (or even WordPress), of course, as it's about translating dynamic strings in general. But SEO best practices say you must have a very specific keyword focus in your title and whatnot or your words will never be seen by human eyeballs ⸺ that your efforts will be for naught but to train some unknown but all-knowing AIs that will never pay you for your contribution.
Suppose for a moment that you're constructing some kind of search results page. To assist in keeping the template markup clean, you stash some strings in variables early in your code.
$title = ($count === 1) ? 'Search Result' : 'Search Results';
$shown = sprintf('Showing %s of %s Results', $shown, $total);
Assume that the numbers are already calculated and that this is some Ajax-doing search page that tacks on the next set of results instead of paging through them. That particular "Showing…" string doesn't make much sense otherwise; it just so happened that I was making an Ajax-doing search page when I wrote this draft.
You then use those variables when marking up your template.
<h1 class="page-title"><?= __($title) ?></h1>
<p class="page-meta"><?= __($shown) ?></p>
Assume that there would be a text domain in this call to the translation system.
Simple enough, it seems, but you've committed a grave error: Your $shown string is not being translated in a dynamic way. The end result, the whole string, is instead being considered statically and uniquely ⸺ e.g., "Showing 10 of 42 Results".
Every combination of results string possible will be translated separately as users (and bots) cause them to be generated by searching for things. This will (a) delay automated translation efforts behind the scenes and (2) cost more money as you burn through your stack of translation credits at an increased rate.
It won't be a massive rate hike; it's nothing compared to, say, writing a new post that's 2,000 words long. But money is money and times is dimes. As people (and bots) use your site, this search-results page of yours will call for more and more combos of numbers to be translated:
- Showing 1 of 1 Result
- Showing 2 of 2 Results
- […]
- Showing 10 of 11 Results
- Showing 10 of 12 Results
- […]
- Showing 10 of 21 Results
- Showing 10 of 22 Results
- […]
- Showing 20 of 21 Results
- Showing 20 of 22 Results
The total number of strings here isn't infinite because there isn't infinite content on your site. But based on your site's "posts per page" setting and the number of posts you have, there may be hundreds if not thousands of these unique strings. And your translation system will be tasked to deal with them one by one as they're uncovered by your users (and bots).
There's definitely a formula for the number of unique strings possible here, and I've tried to guess at one at the bottom of the post. I'm no mathematician despite the minor in math I got from school.
The fix is simple. Dynamic strings need to be translated early, not on output, so those strings keep their placeholders intact. We want placeholders rather than numbers sent to the translator. The system will leave 'em be and only translate one string instead of potentially thousands: Showing %s of %s Results.
$shown = sprintf(__('Showing %s of %s Results'), $shown, $total);
Instead of waiting until output, we're translating the placeholder'd string when setting $shown. It's a one-and-done deal. The numbers get filled in after the translation process rather than before.
Simple static strings can be translated early or on output. It doesn't matter because, for instance, "Search Result" and "Search Results" are the only two options ⸺ and most pages would only have one possible title. It's really down to the aesthetics of it and how much it bothers you to have your markup not match.
<h1 class="page-title"><?= __($title) ?></h1>
<p class="page-meta"><?= $shown ?></p>
If that feels "off" to you because $title is translated on output while $shown is not, call for translating $title while you're setting it just like $shown. I dunno, man; sometimes prettier code is nicer than slightly-less-pretty code. 🤷♀️
Silly formula stuff
I may be very off on this, because I'm mostly math dumb, but the results feel pretty alright to me. Below, paging is your "posts per page" setting while total is the total number of published and searchable posts and pages on your site. strings, then, is the total number of strings you might be translating over time should you call for the translation of results strings with numbers rather than placeholders.
// Doesn't work and irrelevant if total <= paging!
strings = (total - paging) * 2 + (total - paging * 2) + 1
With 20 posts and 10 per page, that's 21 strings to be translated. With 30 posts it jumps up to 51 strings and at 40 posts it's 81 strings. Lorde forbid you have 200 searchable posts because now you're up to 561 possible unique strings to be translated. At least it's not exponential growth, I guess!