Skip to Content
Course content

132: Structuring Translatable Strings

Click on the "Edit" button in the top corner of the screen to edit your slide content.

I've seen this mistake in almost every mid-sized project I've joined. A developer builds a great feature, and then the product manager says, "Great, now we need to launch in Spanish and Japanese." The developer looks at their code and realizes they've littered the entire codebase with template literals that are impossible to translate.

Here is a typical snippet of code that looks fine today, but will be a nightmare tomorrow:

function welcomeUser(user) {
  const greeting = `Hello, ${user.name}! You have ${user.messages.length} unread messages.`;
  return greeting;
}

The Fragility of Interpolated Sentences

At first glance, this is clean JS. But from a translation perspective, it's broken. You're assuming that every language follows the English word order: [Greeting], [Name]! You have [Number] [Adjective] [Noun].

In other languages, the word order shifts. The adjective might come after the noun, or the verb might move to the end of the sentence. If you hand this to a translator, they can't move the ${user.name} or ${user.messages.length} around because those are hardcoded into the logic of your JavaScript function. You'd have to rewrite your JS logic for every single language you support, which is a fast track to a codebase that is impossible to maintain.

Decoupling Content from Logic

The fix is to treat your strings as data, not as code. Instead of writing the sentence in your function, you use a "key" to look up the sentence in a translation file. We use placeholders (like {name}) that the translation engine can swap out regardless of where they appear in the sentence.

Here is how I would restructure that welcome message:

const translations = {
  en: {
    welcome_msg: "Hello, {name}! You have {count} unread messages.",
  },
  es: {
    welcome_msg: "¡Hola, {name}! Tienes {count} mensajes sin leer.",
  },
  ja: {
    welcome_msg: "{name}さん、こんにちは!未読メッセージが{count}件あります。",
  }
};

function t(key, locale, variables = {}) {
  let text = translations[locale][key];
  
  // We replace the placeholders with the actual values
  Object.keys(variables).forEach(varName => {
    text = text.replace(`{${varName}}`, variables[varName]);
  });
  
  return text;
}

// Usage
const user = { name: "Alex", messages: [1, 2, 3] };
console.log(t('welcome_msg', 'en', { name: user.name, count: user.messages.length }));
console.log(t('welcome_msg', 'ja', { name: user.name, count: user.messages.length }));

Now, the JavaScript doesn't care about the sentence structure. The translator can move {name} to the end of the sentence in the ja (Japanese) object, and the code will still work perfectly. I've effectively separated the "what" (the data) from the "how" (the logic).

Handling the Pluralization Headache

If you stop there, you'll hit another wall: pluralization. In English, we have "1 message" and "2 messages." Some languages have three or more forms depending on the number. If you just append an "s" to the end of a word, your app will look amateurish in almost every language except English.

The professional way to handle this is to provide multiple keys for the same string based on a count. Instead of one welcome_msg, you might have welcome_msg_one and welcome_msg_other. Your translation function then checks the count and picks the correct key. It's slightly more work upfront, but it's the only way to ensure your UI feels native to the user.




📋 Practical Task

Build a Dynamic Shopping Cart Translator

You are building a shopping cart for an international store. You need to create a system that can handle different languages and correctly handle pluralization for the number of items in the cart.

Requirements:

  • Create a locales object containing translations for en (English) and fr (French).
  • Include keys for cart_empty, cart_single (for 1 item), and cart_multiple (for 2+ items).
  • Implement a function getCartMessage(count, locale) that:
    • Returns the cart_empty string if the count is 0.
    • Returns the cart_single string if the count is 1.
    • Returns the cart_multiple string if the count is 2 or more.
    • Correctly replaces a {count} placeholder in the string with the actual number.

Example Expected Output:

getCartMessage(0, 'en'); // "Your cart is empty"
getCartMessage(1, 'en'); // "You have 1 item in your cart"
getCartMessage(5, 'en'); // "You have 5 items in your cart"
getCartMessage(5, 'fr'); // "Vous avez 5 articles dans votre panier"
Rating
0 0

There are no comments for now.

to be the first to leave a comment.