Interpolation assumptions
String interpolation is used to combine variable text with a phrase.
Example of interpolation
For example, in a chatting application, you might see these phrases in an activity feed:
- Alice replied to: I'm on my way
- Bob has reacted in #social
These phrases can easily be expressed in English and German using placeholders or variables. The JSON file format "i18next" can be used to encode these placeholders using curly brackets:
{
"activity_replied": "{{name}} replied to: {{comment}}",
"activity_reacted": "{{name}} has reacted in {{channel}}"
}
{
"activity_replied": "{{name}} hat geantwortet auf: {{comment}}",
"activity_reacted": "{{name}} hat in {{channel}} reagiert"
}
Then using a JavaScript library like i18next, the variables name
, comment
, and channel
can then be interpolated using the i18next.t(key, data)
function.
const activities = [
i18next.t('activity_replied', { name: "Alice", comment: "I'm on my way" }),
i18next.t('activity_reacted', { name: "Bob", channel: "#social" })
]
Assumptions about appending and prepending text
A common mistake when internationalizing user interfaces is appending variable text to the end of a translated string.
For example, in a photo editor app, after selecting 5 photos from a list, a context menu may appear with the actions "Show 5 photos" or "Delete 5 photos".
If the user interface does not have much space to work with, a developer might simplify the wording to be "Show 5" or "Delete 5".
The developer might initially develop the UI for English, and therefore implement it in the simplest way; By appending the number of photos selected to the end of the string:
'Show ' + selected.length
'Delete ' + selected.length
Later, a naive developer who is trying to internationalize the app decides to translate the UI by converting the phrases Show
and Delete
as action_show
and action_delete
respectively. The developer is assuming the number can be appended at the end of the phrase across all languages:
t('action_show') + selected.length
t('action_delete') + selected.length
Finally, when a translator wants to translate for German, they'll immediately run into an issue. The way to phrase the button in German would normally be:
- 2 anzeigen
- 2 löschen
Because the number is hard-coded to appear at the end of the string, the translator has no way of translating the strings as they would like.
Solution
The correct way is to use string interpolation. In other words, provide the necessary context to the translation function so the translator can rearrange the text however they like.
{
"action_show": "Show {{count, number}}",
"action_delete": "Delete {{count, number}}"
}
{
"action_show": "{{count, number}} anzeigen",
"action_delete": "{{count, number}} löschen"
}
[
i18next.t('action_show', { count: selected.length }),
i18next.t('action_delete', { count: selected.length })
]
Assumptions about grammar
This example is borrowed from i18next's documentation, by Jenny Reid.
Interpolation is not always a good idea.
A developer might think themselves clever to interpolate a long sentence with a placeholder value, because most of the text is the same. In the example below, a phrase has two options for {{paymentType}}
, 'credit card'
or 'PayPal account'
:
{
"fees": "All fees will be charged to the {{paymentType}} on file for this account.",
"credit_card": "credit card",
"paypal_account": "PayPal account"
}
A German translator will struggle to translate the text neatly because the value of the {{paymentType}}
will affect how the article "the" in the {{paymentType}}
is translated in German.
Not only does the German translator need to consider the payment type's grammatical gender (masculine, feminine, neuter), but the translator also needs to know what role the object has, known as grammatical case (nominative, accusative, dative, genitive).
In the example above the grammatical gender for each payment type is:
- feminine for "Kreditkarte" (credit card)
- neuter for "PayPal-Konto" (PayPal account)
And the payment type in the sentence is the recipient of an action, which makes it dative case. This means the article for the payment types are:
- "der Kreditkarte"
- "dem PayPal-Konto"
A translator might be able to workaround the developer's poor use of interpolation by modifying the English translations for the payment types to include the article "the" …
{
"fees": "All fees will be charged to {{paymentType}} on file for this account.",
"credit_card": "the credit card",
"paypal_account": "the PayPal account"
}
… but this will not work out in the long run.
If the payment type is later used in a different phrase, like "{{paymentType}} is valid."
, this will change the grammatical case to be nominative, because it is now the subject in the sentence. That would change "der Kreditkarte" to "die Kreditkarte".
There's also the issue of capitalizing the article "the" to be "The" at the beginning of the sentence!
And that's just German… Other languages will have their own grammatical systems, so it's a very difficult problem to generalize!
Solution
The best way to handle this is to create two keys without any interpolation:
{
"fees_credit_card": "All fees will be charged to the credit card on file for this account.",
"fees_paypal_account": "All fees will be charged to the PayPal account on file for this account."
}
Which can then be easily translated into German as:
{
"fees_credit_card": "Alle Beträge werden der Kreditkarte für dieses Konto in Rechnung gestellt.",
"fees_paypal_account": "Alle Beträge werden dem PayPal-Konto für dieses Konto in Rechnung gestellt."
}
Assumptions about capitalization
In the article Date formatting assumptions: Capitalization of day names and months I demonstrate how some languages don't capitalize the day names and months, unlike English. For example, "Sunday" in English, but "dimanche" in French.
So if you had the phrase "Sunday is your birthday.", this might be internationalized and localized into English, French, and Spanish as:
"{{day}} is your birthday."
"{{day}} c'est ton anniversaire."
"El {{day}} es tu cumpleaños."
Which when interpolated would look like this:
"Sunday is your birthday."
"dimanche c'est ton anniversaire."
"El domingo es tu cumpleaños."
For this to look like a sentence, we ideally want to capitalize "dimanche" for French. We could capitalize the day
before the text gets interpolated so that "dimanche" becomes "Dimanche", but then we'd end up capitalizing "domingo" in Spanish as well which is what we don't want! Sure, the Spanish translator could compensate and just rewrite the text as: {{day}}: tu cumpleaños.
, but that doesn't look great either.
Solution
As discussed earlier in Assumptions about grammar, one solution could be to duplicate the translation 7 times, for each day of the week. But that's a lot of redundant text for the translator to work with. And if there were other events of the year we wanted to translate in addition to birthdays, this could become a large list of localization strings to maintain.
Some localization libraries have useful APIs to add custom format functions to use during interpolation. For example, i18next's formatter service:
i18next.services.formatter.add('capitalizeFirstChar', (value) => {
return value.charAt(0).toUppercase() + value.slice(1);
});
This enables the translator who has editor-access to the localized strings to choose how to format the interpolated text, in this case by capitalizing the first character of the interpolated value using capitalizeFirstChar
.
"{{day}} is your birthday."
"{{day, capitalizeFirstChar}} c'est ton anniversaire."
"El {{day}} es tu cumpleaños."
Which when interpolated now returns:
"Sunday is your birthday."
"Dimanche c'est ton anniversaire."
"El domingo es tu cumpleaños."