Pseudotranslating Text in Your HTML Files

Our Zoosk products support 27 languages, so as one can imagine, the variety of translated text lengths can impact the user interface. Consider the following text in four languages:

  • Want more views? Get 75 Boost!
  • ¿Quieres recibir más vistas? ¡Obtén 75 Destacados!
  • Vous voulez plus de consultations ? Recevez 75 coups de boost !
  • Хотите больше просмотров? Поднимите свой профиль 75 раз!

When we build the UI with HTML and CSS, we must ensure the page elements flow and resize appropriately with strings in other languages. This requirement can be challenging for the following reasons:

  • Oftentimes while we’re working on new features, we only have English strings. We tend to receive translations late in the development process.
  • Even if we had our translations, when we’re rapidly iterating our UI development locally, we don’t have a system to pull translated strings into our test HTML files.

One way to test is by editing the strings manually in a browser’s inspection tools. Another method is using longer strings in the HTML file to start with. There are some issues with this:

  • Manual editing is tedious.
  • Manual editing often results in the developer typing random characters which is not helpful.

Why are random characters not helpful? They don’t reflect any kind of real-world linguistic structure whereas an educated guess on translations helps us decide how much engineering resources to invest in our work. (For fun, check out The Week’s list of really long German words.) Some layouts reflow gracefully with little extra engineering work, such as stacked elements in a mobile view. Other layouts require specific CSS techniques to maintain design specifications in other languages. Some are tricky enough that we have two options: invest significant time toward a perfect solution, or engineer them to properly reflow to a particular string length. An educated translation length helps to inform that point.

While we could add additional text like lorem ipsum passages to our HTML file to start with, this hinders us from assessing the view for our primary audience—English speakers. Because English is the most widely used language on our apps, we should develop for this audience first.

How the translation tool came about
Some time ago our then globalization manager and I were chatting about testing translations in UI development. I told him about my manual test process in Chrome DevTools and that it would be nice to click a button to magically translate the strings instead. He told me about a formula he used to create pseudotranslations. The formula uses the number of characters in a string to determine how many extra characters to add to it. I decided to practice my JavaScript training by creating a translation tool using this formula.

Expansion guidelines
To pseudolocalize a string, we expand it by adding additional wide characters to the left and right of it. We use lowercase x as the extra character. It is somewhat average in width, though you can use a wider character as well. Capital W appears to be the widest based on a very rudimentary experiment. We then enclose the final expanded string in square brackets. The first step is calculating the expansion factor which is based on one of the following guidelines:

3 or fewer characters long Triple the string length (factor: 3)
4 and fewer than 11 characters long Double the string length (factor: 2)
More than 11 characters long 1.5x the string length (factor: 1.5)

Computations and concatenations
We apply the factor in the following formula to determine the expansion string length:

expansion string length = original string length * (factor – 1)

We then take that value to determine how many instances of x to add to the left and right of a string—

left expansion length = right expansion length = round (expansion string length / 2 + 0.5 )

final expanded string = ((left expansion length – 1) * “x”) + “-” + original string + “-” + ((right expansion length – 1) * “x”)

The pseudolocalization guidelines also require a whitespace after every 8th x to allow wrapping.

If we take the string “Get more with upgrades!” and run it through the formulas, the pseudotranslation result is [xxxxx-Get more with upgrades!-xxxxx].

Additional requirements
Aside from the expansion requirements, adding this tool to a new test HTML file should be as simple as possible, and it should work right out of the box for developing both mobile touch and desktop features. As a result a UI developer can simply add a link tag to the translation tool script in their HTML file. A “Translate” button will appear in the top left of the screen. Refreshing the page returns the strings to their original form.

Below are screenshots of our subscription upgrades experience before and after pseudolocalization:


Other challenges
While I was testing the tool, the formula was applied to the Translate button. To address this, I gave the button a data attribute and used that to exclude it from the body element’s children. You can leverage this to exclude other elements on your page.

Another issue was that the formula was applied to whitespace text nodes. The result was a page full of x’s. A straightforward solution by a StackExchange user was integrated into the script to exclude all-whitespace nodes from the formula.

Future improvements and positive results
At the moment, standalone numerals are expanded the same way as text; however, it would be nice to use other numerals as expansion characters.

The translation tool has sped up UI development not only because it does away with manual editing, but it also pseudotranslates all strings at the click of a button. This has been especially helpful testing on mobile devices given limited mobile emulation in Chrome DevTools. Testing on mobile devices can find quirks not always captured in emulators as well.

Below is the full script for anyone who wants to use it in their web development projects.

document.addEventListener('DOMContentLoaded', function() {
var translateButton = document.createElement('button');
translateButton.innerHTML = 'Translate';
translateButton.addEventListener('click', processPageForTranslation);
translateButton.setAttribute("style", "position: fixed; top: 0; left: 0; opacity:.5; z-index: 1000;");
translateButton.setAttribute('data-exclude-pseduotranslation', '');
document.getElementsByTagName('body')[0].appendChild(translateButton);
}, false);
function isAllWhitespace(childNode) {
return !(/[^\t\n\r ]/.test(childNode.textContent));
}
function shouldExcludeElement(element) {
return element.hasAttribute('data-exclude-pseduotranslation');
}
function processPageForTranslation() {
var elementsToTranslate = [];
var allBodyChildren = document.getElementsByTagName('body')[0].getElementsByTagName('*');
for (var c = 0; c < allBodyChildren.length; c++) {
if (!shouldExcludeElement(allBodyChildren[c])) {
elementsToTranslate.push(allBodyChildren[c]);
}
}
for (var i = 0; i < elementsToTranslate.length; i++) {
var childNode = elementsToTranslate[i].childNodes;
for (var j = 0; j < childNode.length; j++) {
if (childNode[j].nodeType === Node.TEXT_NODE) {
if (!isAllWhitespace(childNode[j])) {
translateString(childNode[j]);
break;
}
}
}
}
function translateString(itemToTranslate) {
var factor;
itemToTranslate.textContent = itemToTranslate.textContent.trim();
// The factor is used to calculate a string's expansion for pseudotranslation
if (itemToTranslate.length <= 3) {
factor = 3;
} else if (itemToTranslate.length > 3 && itemToTranslate.length <= 11) {
factor = 2;
} else {
factor = 1.5;
}
// Expanded string length which helps determine how many extra characters to prepend and append to the original string
var expansionStringLength = itemToTranslate.textContent.length * (factor - 1);
// The number of extra characters to add to the left and right of each string
var sideExpansion = Math.ceil(expansionStringLength / 2);
// You can replace 'x' with a wider character if you'd like
var halfOfExpansionString = Array(sideExpansion).join('x');
// Add space after every 8th x
halfOfExpansionString = halfOfExpansionString.replace(/(\S{8})/g, "$1 ");
itemToTranslate.textContent = '[' + halfOfExpansionString + '-' + itemToTranslate.textContent + '-' + halfOfExpansionString + ']';
}
}
view raw translation-tool.js hosted with ❤ by GitHub