We say it all the time, but we'll say it again: localization is much more than simply translating words — it involves handling grammar, context, and cultural nuances. 👀 For developers, this means that relying on string concatenation or naïve interpolation can lead to issues that make your app sound awkward or even produce incorrect messages in certain languages.
In this article, we discuss common risks and how to solve them.
✚ Avoiding string concatenation 🔗
Building sentences by concatenating strings (e.g., "Welcome, " + username
) forces a fixed word order that may work in one language (like English) but will break in languages with different syntax. Splitting sentences into fragments also deprives translators of context and produces less natural-sounding translations.
Solution 🔗
Always externalize the entire sentence as a single translatable string with placeholders. This way, translators can reorder placeholders as needed.
Example 🔗
en.json
{
"greeting": "Welcome, {username}."
}
code.js
// Assume getString is a function that retrieves and formats the translation.
const username = "Alice";
const greeting = getString("greeting", { username });
console.log(greeting); // "Welcome, Alice."
👬 Dealing with plurals effectively 🔗
Different languages have varied pluralization rules. A naïve approach like const message = count + " file" + (count !== 1 ? "s" : "");
works in English, but fails for languages with a different number of plural forms. The Czech language has three of them, Arabic six, and in Japanese, there is only one form as they don't distinguish between singular and plural in language syntax.
Solution 🔗
Define plural resources in your localizable files and let the localization engine select the correct variant.
Example 🔗
en.json
{
"file_count": {
"one": "{count} file",
"other": "{count} files"
}
}
code.js
// Assume getPluralizedString is a function that retrieves and formats the pluralized translation.
const count = 3;
const fileMessage = getPluralizedString("file_count", count, { count });
console.log(fileMessage); // "3 files" (assuming English rules)
🪢 Handling multiple plurals in one string 🔗
Some sentences contain more than one pluralized element (e.g., {count1} apples and {count2} oranges
). Managing multiple plural forms in one sentence without bloating your code can be challenging.
Solution 🔗
Define separate plural objects for each element and then combine them within a template string.
Examples 🔗
en.json
{
"num_apples": {
"one": "{count} apple",
"other": "{count} apples"
},
"num_oranges": {
"one": "{count} orange",
"other": "{count} oranges"
},
"fruit_summary": "{apples} and {oranges}."
}
code.js
const appleCount = 2;
const orangeCount = 5;
const applesText = getPluralizedString("num_apples", appleCount, { count: appleCount });
const orangesText = getPluralizedString("num_oranges", orangeCount, { count: orangeCount });
const summary = getString("fruit_summary", { apples: applesText, oranges: orangesText });
console.log(summary); // "2 apples and 5 oranges."
✍️ Formatting variable lists 🔗
Sometimes, you need to display a list of items that can vary in length — for example, "Alice, Bob, and Charlie"
, or "Alice and Bob"
(if only two names), or just "Alice"
(if one name). Constructing such lists in a localization-friendly way is challenging. Different languages have different conventions for list formatting: the separator (comma, semicolon, etc.) and the word for “and” or “or” might change, and some languages don’t use the Oxford comma or use a different conjunction.
Solution 🔗
The safest approach is to use localization libraries or APIs that know how to format lists for each locale.
If such an option is unavailable, building a list using patterns is another possible solution:
two
= “{0} and {1}” (for exactly two items)start
= “{0}, {1}” (for the beginning of a list)middle
= “{0}, {1}” (for the middle of a list)end
= “{0}, and {1}” (for the end of a list)
Example 🔗
en.json
{
"listPattern": {
"two": "{0} and {1}",
"start": "{0}, {1}",
"middle": "{0}, {1}",
"end": "{0}, and {1}"
}
}
code.js
// Helper function to replace placeholders {0} and {1} in a pattern.
function formatWithPattern(pattern, a, b) {
return pattern.replace("{0}", a).replace("{1}", b);
}
// Formats an array of items into a localized list string.
// Uses different patterns based on the number of items.
function formatLocalizedList(items, locale = "en") {
const patterns = listPattern[locale] || listPattern["en"];
const n = items.length;
if (n === 0) return "";
if (n === 1) return items[0];
if (n === 2) return formatWithPattern(patterns.two, items[0], items[1]);
// For three or more, we add start, all the middle items and then the last one.
let result = formatWithPattern(patterns.start, items[0], items[1]);
for (let i = 2; i < n - 1; i++) {
result = formatWithPattern(patterns.middle, result, items[i]);
}
result = formatWithPattern(patterns.end, result, items[n - 1]);
return result;
}
📋 Handling overflow counts in variable lists 🔗
Often, you may display a preview of items followed by an overflow count (e.g., “You have A, B, C and two more”). While in English “more” is uniform, other languages may require different forms depending on the number.
Solution 🔗
Separate the preview list from the overflow count. Define a plural resource for the overflow message so that plural logic is handled externally.
Example 🔗
en.json
{
"overflow_list": "You have {items} and {more}.",
"x_more": {
"one": "{count} more",
"other": "{count} more"
}
}
cs.json
{
"overflow_list": "Máte {items} a {more}.",
"x_more": {
"one": "{count} další",
"few": "{count} další",
"other": "{count} dalších"
}
}
code.js
const itemsList = "A, B, C"; // Pre-formatted - see "Formatting Variable Lists" above.
const extraCount = 5;
const moreText = getPluralizedString("x_more", extraCount, { count: extraCount });
const overflowMessage = getTranslation("overflow_list", { items: itemsList, more: moreText });
console.log(overflowMessage);
// English: "You have A, B, C and 5 more."
// Czech: "Máte A, B, C a 5 dalších."
🤝 Gender and grammatical agreement 🔗
Languages with grammatical gender require different word forms based on the subject’s gender. For example, a message like “{user} has updated his profile” must change if the user is female or if a gender-neutral term is needed. Simple concatenation does not allow for these variations.
Solution 🔗
Define gender-specific messages in your translation JSON. Use separate keys for each gender variant (e.g., male
, female
, other
), and choose the correct one based on the context provided in your code.
Example 🔗
en.json
{
"profile_message": {
"male": "{user} has updated his profile.",
"female": "{user} has updated her profile.",
"other": "{user} has updated their profile."
}
}
code.json
// Function to get gender-specific translation based on a user's gender.
function getProfileMessage(user, gender) {
// Assume getStringByGender is a helper that selects the right message
return getStringByGender("profile_message", gender, { user });
}
// Example usage:
const user = "Jordan";
const gender = "female"; // Could be 'male', 'female', or 'other'
const profileMessage = getProfileMessage(user, gender);
console.log(profileMessage); // "Jordan has updated her profile."
💬 A few extra tips 🔗
Ordering and positional placeholders 🔗
Different languages may require dynamic parts in a different order. Use named placeholders so that translators can reorder them without losing context. If named, the placeholders carry the context.
Inconsistent formatting of numbers, dates, currencies, and units 🔗
Using interpolation with raw numbers or dates can lead to confusion since it bypasses locale-aware formatting. Always format numbers, dates, and currencies using locale-specific utilities before insertion. You should also consider how to display units (kilometers vs. miles, Celsius vs. Fahrenheit, etc.).
HTML tags 🔗
Avoid using HTML and other formatting tags if possible. It can be confusing for translators. Prefer using non-translatable templates.
templates.json
{
"title": "<h1>{title}</h1>"
}
📚 ICU Message Format 🔗
In addition to the approaches discussed above, the ICU Message Format standard offers a powerful, cross-platform solution for common interpolation issues. It allows you to embed pluralization, gender variations, and argument reordering directly within a single message string, ensuring that translations are grammatically correct and contextually appropriate. It simplifies the localization process and can be integrated into various platforms and programming environments to handle complex language rules.
However, all the approaches discussed above apply, and it's important to remember them to use ICU Message Format correctly.
💙 How Localazy handles placeholders 🔗
Even when following best practices, rules can be bent, and subtle issues can still arise, like translating placeholder names literally by accident, which could break your code. Localazy's built-in validation warns about missing or misplaced placeholders and potential issues before they reach your users.

Localazy automatically identifies placeholders in various formats (whether they're ICU-style {variable}
, JavaScript template literals ${variable}
, or other common formats) and highlights them during translation. 🕵️♀️ This ensures that translators can see and maintain these elements while having the freedom to reorganize sentences as needed for their language.
Plus, our support for pluralization means you don't need to handle the complex plural rules discussed above manually. The correct forms are automatically applied based on the target language and your i18n library. This approach keeps your codebase clean while ensuring your app speaks naturally to users worldwide.
✔️ Conclusion 🔗
As you can see, avoiding string concatenation and naïve interpolation with complete sentence externalization and placeholders frees you from unexpected problems in your localization project in the future. Your messages will be grammatically correct and contextually appropriate in every language — and, most importantly, you will have an easier-to-maintain, cleaner codebase.
The end result will be a more natural user experience for your international audience. We hope these tips helped. Happy coding! 🧑💻