Hello World

Language 101

All chapters

Part 2 · Words

Language 101

Chapter 5 14 min read 14 sections

Here's one of the most common strings in software:

{ "home.welcome": "Welcome back, {name}!" }

It looks like the easiest thing in the world to translate. Then the translations come back with questions.

The Spanish translator needs to know whether the user is a man or a woman, because "welcome" is "bienvenido" for one and "bienvenida" for the other. The French translator wants to know whether your product calls people "tu" or "vous". The Czech translator points out that the name itself has to change: Petr becomes "Petře" when you greet him, and leaving it as "Petr" reads like it was written by someone who doesn't speak Czech. The Japanese translator asks whether you really want to greet customers by their first name at all.

That's four questions about one short English string, and English never made you answer any of them.

<title id="g1-title">One welcome message across languages</title> Welcome back, Petr in English. A Czech version built from one template leaves the name as Petr. The correct Czech uses the vocative, Vítejte zpět, Petře. The Spanish greeting for Ana uses the feminine Bienvenida to agree with her. ENGLISHPWelcome back, Petr!CZECH, ONE TEMPLATEPVítejte zpět, Petr!Name not in the vocativeCZECHPVítejte zpět, Petře!Vocative: Petr becomes PetřeSPANISH, FOR ANAA¡Bienvenida de nuevo, Ana!Agrees with Ana

This chapter is a tour of the ways languages differ that catch software out. It won't make you a linguist, and you don't need to remember the details. The point is to know what can change, so you stop writing code that assumes it can't. Each section ends with what it means for your software and which chapter deals with it.

Counting things

English has two forms of a noun when you count: "1 dog", "2 dogs". Plenty of languages need more.

Russian picks the form from the last digit of the number. 1 dog is "1 собака", 2 dogs is "2 собаки", 5 dogs is "5 собак", and 21 dogs goes back to "21 собака", because 21 ends in 1. Polish has three forms too, but it disagrees with Russian about 21.

Arabic adds a form English lost centuries ago, the dual, for exactly two of something. One book is "كتاب" (kitāb), two books is "كتابان" (kitābān), and three or more is "كتب" (kutub). Arabic and Welsh each use six plural categories in the Unicode CLDR data that most software relies on:

new Intl.PluralRules('ar').resolvedOptions().pluralCategories
// ['zero', 'one', 'two', 'few', 'many', 'other']

new Intl.PluralRules('ja').resolvedOptions().pluralCategories
// ['other']

Japanese and Chinese sit at the other end. The noun doesn't change at all. "Book" is 书 in Chinese whether there's one or a hundred. Instead, both languages put a counter between the number and the noun, and the counter depends on what you're counting:

  • In Chinese, a book takes 本 (一本书, one book), a flat sheet takes 张 (一张纸, one sheet of paper), and many animals take 只 (一只猫, one cat).
  • In Japanese, long thin things take 本 (鉛筆三本, three pencils), small animals take 匹 (猫三匹, three cats), and flat things take 枚 (紙三枚, three sheets of paper).

So "3 items" has no single translation in Japanese. The translator needs to know what the items are.

For your software: never pick a plural form with count === 1. Use the plural rules built into your platform, and tell translators what's being counted.

Who is "you"?

English uses "you" for everyone, from your best friend to the King. Most European languages make you choose:

Language Informal Formal
French tu vous
German du Sie
Spanish tú usted
Dutch je u

The choice changes more than the pronoun. The verb usually changes with it, so "Can you help me?" is a different sentence in each register.

<title id="g2-title">Informal and formal ways to ask "Can you help me?"</title> The question Can you help me? in French, German, Spanish and Japanese, once in an informal form and once in a formal form, with the words that change highlighted: tu and vous, du and Sie, puedes and puede, and two different Japanese verb endings. INFORMAL Friends, family, children FORMAL Strangers, customers, elders FRENCH Tu peux m'aider ? Vous pouvez m'aider ? GERMAN Kannst du mir helfen? Können Sie mir helfen? SPANISH ¿Puedes ayudarme? ¿Puede ayudarme? JAPANESE 手伝ってくれる? 手伝っていただけますか? "Can you help me?" English has one way to say it. These languages make you pick a relationship first.

Japanese and Korean go further. Instead of a different pronoun, they have levels of politeness built into the verb, and Japanese often avoids saying "you" at all. A casual Japanese "Can you help me?" is 手伝ってくれる?, and a polite one is 手伝っていただけますか?. Both languages also add honorifics to names, like the Japanese "-san".

None of this is a translation detail. It's a decision about your brand's voice, and you have to make it once per language. IKEA famously uses the informal "du" in Germany, and its German slogan "Wohnst du noch oder lebst du schon?" is built around it. A bank would usually pick "Sie". Whichever you pick, use it everywhere. A product that switches between "du" and "Sie" from one screen to the next reads like it was translated by several people who never spoke to each other, because it probably was.

For your software: decide the register for each language before translation starts, and write it in your translators' style guide.

Gender gets everywhere

English nouns don't have gender. "The file" and "the photo" behave the same way. In French, "fichier" (file) is masculine and "photo" is feminine, and the words around them have to agree:

  • "Le fichier a été supprimé." (The file has been deleted.)
  • "La photo a été supprimée." (The photo has been deleted.)

So a message like "{item} has been deleted" can't be translated into French as one string. The article and the ending of "deleted" both depend on which item it is.

Even "it" can need a gender. "Are you sure you want to delete it?" is "Voulez-vous vraiment le supprimer ?" for a file and "Voulez-vous vraiment la supprimer ?" for a photo, and the word deciding it doesn't appear in the sentence at all. Ben Hamill's falsehoods programmers believe about language makes the same point: a translator can't pick the right word without knowing what "it" refers to.

The person reading matters too. A Spanish welcome message is "Bienvenido" for a man and "Bienvenida" for a woman. In Russian, the past tense agrees with the subject's gender, so "you added" is "ты добавил" to a man and "ты добавила" to a woman.

In Hebrew and Arabic, even "you" changes. Hebrew has "אתה" (ata) for a man and "את" (at) for a woman, and the words around it follow, so "Are you sure?" is "אתה בטוח?" to a man and "את בטוחה?" to a woman. Arabic does the same with "أنتَ" (anta) and "أنتِ" (anti).

For your software: don't put nouns into sentences with placeholders when the sentence has to agree with them. Write a separate message for each case. Don't ask users for their gender just to fix grammar, either. Translators can often write a neutral version, and when they can't, ICU's select lets them write one sentence per case with a sensible default.

Words change shape: cases

In English, a name stays the same wherever it goes in a sentence. In languages with grammatical cases, nouns change their endings depending on what they're doing in the sentence, and names are nouns too.

Czech uses a special form, the vocative, for addressing someone directly. Petr becomes "Petře", Jana becomes "Jano", and a friendly email starts "Ahoj Petře," or "Milá Jano,". A greeting that drops the plain name into "Ahoj {name}," gets this wrong for nearly everyone. Polish works the same way: a formal email to Anna starts "Pani Anno".

Russian changes names after prepositions. "Anna" is "Анна", but "a letter from Anna" is "письмо от Анны". Russian dates do it too. The month on its own is "июль", but in a date it changes to "июля", because the date means "the 6th of July":

new Intl.DateTimeFormat('ru', { dateStyle: 'long' }).format(new Date(2026, 6, 6))
// '6 июля 2026 г.'
(new IntlDateFormatter('ru', IntlDateFormatter::LONG, IntlDateFormatter::NONE))
    ->format(new DateTimeImmutable('2026-07-06'))
// '6 июля 2026 г.'

Finnish has fifteen cases, and they do the job English does with prepositions. "Helsinki" becomes "Helsingissä" for "in Helsinki" and "Helsingistä" for "from Helsinki". A heading like "Events in {city}" can't be translated into Finnish without changing the city's name.

For your software: avoid dropping names and places into the middle of sentences. Labels like "City: Helsinki" survive any language. Let the platform format dates rather than assembling them from a day number and a month name.

Word order

English puts the verb in the middle: "I eat an apple". Japanese, Korean and Turkish put it at the end:

  • Japanese: 私はりんごを食べます (I, apple, eat)
  • Korean: 나는 사과를 먹는다 (I, apple, eat)
  • Turkish: Ben elma yerim (I, apple, eat)

German keeps the verb second in a main clause, but sends it to the end of a subordinate one. "I know that you're coming today" is "Ich weiß, dass du heute kommst", literally "I know that you today come".

The verb also has to stay in second place when something else comes first. "When it rains, I dance" is "Wenn es regnet, tanze ich", literally "When it rains, dance I". The whole "Wenn es regnet" clause counts as the first item, so the verb jumps ahead of "ich". Copy the English order, "Wenn es regnet, ich tanze", and it's wrong. That's an easy mistake for an English speaker, and code that glues a condition onto a sentence makes it automatically.

For your software: any sentence your code assembles from pieces has its word order fixed by your code. Give translators whole sentences with named placeholders, so they can move things around. That's where messages that change shape start.

The little words next to a placeholder

The words right next to a placeholder often depend on what goes in it.

English already does this with "a" and "an". A template like "Add a {item}" works for "a photo" and breaks for "a apple".

French drops the vowel of "le" and "la" before another vowel, so "le fichier" but "l'image". Spanish changes "y" (and) to "e" before a word starting with an "i" sound, and "o" (or) to "u" before an "o" sound. Intl.ListFormat already knows about the first one:

new Intl.ListFormat('es').format(['Ana', 'Inés'])          // 'Ana e Inés'
new Intl.ListFormat('es').format(['Ana', 'Pedro', 'Hugo']) // 'Ana, Pedro y Hugo'
(new IntlListFormatter('es'))->format(['Ana', 'Inés'])          // 'Ana e Inés'
(new IntlListFormatter('es'))->format(['Ana', 'Pedro', 'Hugo']) // 'Ana, Pedro y Hugo'

Korean particles change depending on whether the word before them ends in a vowel or a consonant. The object marker is "를" after a vowel, as in "사과를" (apple), and "을" after a consonant, as in "책을" (book). A template with a fixed particle after {item} is wrong for about half of all words.

For your software: treat a placeholder and the grammar around it as one unit the translator controls. If a value changes the words around it, it needs its own message, or a message format that can handle it.

Very long words, and no spaces at all

German builds new words by joining old ones together. "Datenschutzerklärung" is a privacy policy, built from data, protection and declaration. The record is longer. A law in Mecklenburg-Vorpommern was called the "Rindfleischetikettierungsüberwachungsaufgabenübertragungsgesetz", 63 letters long, until it was repealed in 2013. Words like that don't wrap, and they'll break any layout that assumed a button label is one or two short words.

Thai, Chinese and Japanese go the other way. They don't put spaces between words at all. "ภาษาไทยไม่มีช่องว่าง" means "Thai has no spaces", and nothing in it tells you where one word ends. The browser has to work that out with dictionaries, and so does your code if it counts, truncates or searches by word:

const words = new Intl.Segmenter('th', { granularity: 'word' })
[...words.segment('ภาษาไทยไม่มีช่องว่าง')].map(s => s.segment)
// ['ภาษา', 'ไทย', 'ไม่มี', 'ช่อง', 'ว่าง']
$words = IntlBreakIterator::createWordInstance('th');
$words->setText('ภาษาไทยไม่มีช่องว่าง');
iterator_to_array($words->getPartsIterator())
// ['ภาษา', 'ไทย', 'ไม่มี', 'ช่อง', 'ว่าง']

For your software: don't split text on spaces to count words, truncate a preview or build a search index. Use Intl.Segmenter or your platform's equivalent.

Capital letters

English capitalises names and the start of sentences, and loves Title Case in headings. Other languages that use the Latin alphabet have their own rules:

  • German capitalises every noun. "The cat" is "die Katze". The formal "you", "Sie", is capitalised too, which is how readers tell it apart from "sie" (she or they). "Können Sie mir helfen?" means "Can you help me?". Lowercase it and "Können sie mir helfen?" means "Can they help me?".
  • French and Spanish write headings in sentence case, and write days and months in lower case. The same date comes out as "Monday, 6 July 2026" in British English and "lundi 6 juillet 2026" in French.
  • Turkish has two letter i's, and case conversion has to know which language it's in.

Many scripts have no capital letters at all, including Arabic, Hebrew, Chinese, Japanese, Thai and Devanagari. text-transform: uppercase does nothing to them, so any design that uses capitals for emphasis needs another way to show it.

For your software: don't change the case of translated strings in code. If a heading needs to be in Title Case in English, write it that way in the English string, and let each translator follow their own language's rules.

Punctuation and quotation marks

Punctuation is part of the language, not decoration around it.

<title id="g3-title">The same quotation punctuated in six languages</title> He said Hello, written in English, German, French, Spanish, Japanese and Chinese. Each language uses different quotation marks and punctuation: curly quotes, German low-high quotes, French guillemets with spaces, Spanish guillemets with an opening exclamation mark, Japanese corner brackets and Chinese full-width punctuation. ENGLISH He said, “Hello!” GERMAN Er sagte: „Hallo!“ FRENCH Il a dit : « Bonjour ! » SPANISH Dijo: «¡Hola!» JAPANESE 彼は「こんにちは!」と言った。 CHINESE 他说:“你好!” French also puts a narrow no-break space before ! ? : ; and inside « ».
  • Spanish opens questions and exclamations with an upside-down mark: "¿Cómo estás?", "¡Hola!".
  • French puts a narrow no-break space before "!", "?", ":" and ";", and inside its « guillemets »: "Vous pouvez m'aider ?".
  • German opens quotes low and closes them high: „Hallo“.
  • Japanese quotes with corner brackets, 「こんにちは」, and ends sentences with 。. Chinese uses full-width punctuation like "," and "。".
  • Arabic has its own comma "،" and question mark "؟".
  • Greek uses ";" as its question mark.

For your software: keep punctuation inside the translated string. Code that appends "?" or ":" to a translation is choosing punctuation for every language.

Lists and ordinals

Joining a list of names sounds trivial. It isn't, even in English:

const names = ['Anna', 'Ben', 'Chloé']

new Intl.ListFormat('en').format(names)    // 'Anna, Ben, and Chloé'
new Intl.ListFormat('en-GB').format(names) // 'Anna, Ben and Chloé'
new Intl.ListFormat('de').format(names)    // 'Anna, Ben und Chloé'
new Intl.ListFormat('ja').format(names)    // 'Anna、Ben、Chloé'

new Intl.ListFormat('es').format(['Ana', 'Luis']) // 'Ana y Luis'
new Intl.ListFormat('es').format(['Ana', 'Inés']) // 'Ana e Inés'
$names = ['Anna', 'Ben', 'Chloé'];

(new IntlListFormatter('en'))->format($names)    // 'Anna, Ben, and Chloé'
(new IntlListFormatter('en-GB'))->format($names) // 'Anna, Ben and Chloé'
(new IntlListFormatter('de'))->format($names)    // 'Anna, Ben und Chloé'
(new IntlListFormatter('ja'))->format($names)    // 'Anna、Ben、Chloé'

(new IntlListFormatter('es'))->format(['Ana', 'Luis']) // 'Ana y Luis'
(new IntlListFormatter('es'))->format(['Ana', 'Inés']) // 'Ana e Inés'

American English uses the Oxford comma by default and British English doesn't. Japanese uses its own comma, "、", and no word for "and" at all. Spanish changes its "and" depending on the next word: "y" becomes "e" before a word that starts with an "i" sound, so it's "Ana y Luis" but "Ana e Inés".

Ordinals vary just as much. English has "1st", "2nd", "3rd" and "4th". French writes "1er" and then "2e", "3e". German writes "1." with a full stop. Spanish writes "1.º" or "1.ª" depending on the gender of the thing being counted. English ordinal suffixes can be chosen with plural rules. For other languages, put ordinals in your translations rather than building them in code.

Yes, no and negative questions

In English, "yes" and "no" describe the facts. Ask "Aren't you coming?" and a person who isn't coming says "No".

In Japanese, はい (yes) and いいえ (no) agree or disagree with the question. Someone who isn't coming answers はい, meaning "That's right, I'm not." Japanese learners are taught to always repeat the verb after answering a negative question, because the bare word is so easy to misread. French and German have a separate word, "si" and "doch", for contradicting a negative question.

This matters for dialogs. "Don't you want to save your changes?" with buttons labelled "Yes" and "No" is ambiguous in English and worse in translation.

For your software: ask positive questions, and label buttons with what they do, like "Save" and "Don't save", rather than "Yes" and "No". The buttons then translate cleanly whatever the language does with negatives.

Scripts and direction

Languages also differ in how they're written down. Arabic and Hebrew run right to left, which flips your whole layout. Serbian is written in both Cyrillic and Latin script. Some scripts need fonts your design system has never heard of. Supporting them is a whole topic of its own, from right-to-left design to fonts that can display every script you support.

Checklist

  • No plural is chosen with count === 1; plural rules come from the platform
  • Translators know what's being counted, so they can choose counters and forms
  • Each language has a chosen register, formal or informal, written in the style guide
  • Messages that depend on a noun's or a person's gender are separate messages, with a neutral default
  • Names and places aren't dropped into the middle of sentences where they'd need to change form
  • Every sentence is one string with named placeholders, never assembled from fragments
  • Words next to placeholders, like articles and particles, belong to the translated string
  • Word counts, truncation and search use word segmentation, not spaces
  • Code never changes the case of translated text
  • Punctuation lives inside translations, not appended in code
  • Lists use Intl.ListFormat or your platform's equivalent
  • Dialogs ask positive questions, and buttons say what they do

Further reading