VIEW THIS AS

Auto mode follows the Route Engine until you choose a viewpoint.

YOU ARE HERE

ROUTE CHECK

CONNECTED TO

WHAT NEXT

Use the canonical route for this room, or HELP if you are unsure.

How People Translate Quickly | ICU MessageFormat Plurals and Selects: Keep Dynamic Messages Grammatically Complete Across Languages

People searching ICU MessageFormat, plural localization, select localization, ICU plural rules, MessageFormat translation, localize dynamic messages, or how to translate strings with variables and plural forms are usually dealing with a structural language problem rather than a vocabulary problem. A sentence such as “You have 1 message” looks simple in English, but software often has to produce different wording for zero, one, two, few, many, and other quantities, while names, dates, prices, gender, roles, and status values also change at runtime. If developers split these elements into fragments or ask translators to translate only the words around placeholders, the resulting message can become impossible to reorder naturally.

Current ICU guidance treats a user-visible message as one localizable unit with arguments inside it. ICU MessageFormat supports plural, select, selectordinal, number/date formatting, and nested choices so translators can see and reorder the whole sentence. ICU also recommends putting complex arguments around complete submessages rather than exposing tiny grammatical fragments. This current search-result language matters because it describes the real optimization target: not “make placeholders clever,” but give the translator a complete grammatical message while the software supplies changing data safely.

This article has one dominant job: design, translate, test, and review ICU-style dynamic messages so plural and select logic stays grammatically correct across locales without multiplying translation work or forcing source-language word order onto the target. It does not replace the existing placeholders-and-nontranslatables article, which protects runtime tokens generally, and it does not replace the auto-translation-rules article, which transforms structured dates, units, and numbers. This page owns the message-logic layer: plural branches, select branches, argument names, offsets, full-sentence variants, nested conditions, translator context, and runtime testing.

Quick answer

A reliable ICU MessageFormat workflow is:

  1. keep one complete user-visible message together rather than concatenating sentence fragments;
  2. give every argument a descriptive name such as {count}, {host}, or {dueDate};
  3. use plural for quantity-driven grammatical choices;
  4. use select for a fixed set of categories such as role or status;
  5. use selectordinal only when the message really depends on ordinal forms such as 1st, 2nd, or 3rd;
  6. include an other branch because plural systems require a safe general case;
  7. let target locales add or remove plural categories according to their own language rules;
  8. write full sentences inside complex branches rather than asking translators to assemble grammar from fragments;
  9. protect the MessageFormat syntax as structure while letting translators move arguments;
  10. test every branch with real runtime values before release.

The central rule is:

localize the whole message; let runtime data fill the variables.

Why dynamic messages are harder than ordinary strings

A static string has one visible surface:

Your report is ready.

A dynamic message can produce many surfaces:

You have no reports.

You have one report.

You have 2 reports.

Ana has one report.

Ana has 12 reports.

Your report is due tomorrow.

The translator is not translating one sentence.

They are translating a small grammar system.

If the system is designed badly, the linguist spends time working around the code rather than translating meaning.

The fragment trap

A common source implementation looks like:

"You have " + count + " message" + pluralSuffix

This assumes:

  • number appears in the same position;
  • noun follows number;
  • plural can be created by a suffix;
  • word order stays English;
  • zero can use the same structure;
  • every language has the same plural distinction.

Those assumptions fail quickly.

The implementation feels efficient for one source language and creates repeated difficulty for every target.

MessageFormat solves a different problem

ICU MessageFormat is designed to make the message pattern localizable.

Instead of code assembling fragments, the pattern can contain the logic:

{count, plural,
  one {You have one message.}
  other {You have # messages.}
}

The translator receives the whole relation.

They can change:

  • word order;
  • noun form;
  • verb agreement;
  • punctuation;
  • number placement.

The code supplies count.

The message owns grammar.

Step 1: use meaningful argument names

Weak:

{0}
{1}
{2}

Stronger:

{count}
{userName}
{dueDate}

Descriptive names reduce mistakes.

A translator can understand:

{count} is quantity.

They do not have to remember:

{2} was the invoice number, unless this is another string.

Good argument names are miniature context notes.

Argument names should describe data, not English grammar

Good:

{count}

Weak:

{pluralNoun}

The target language may not need a plural noun in the same way.

Good:

{host}

Weak:

{subjectBeforeVerb}

Do not encode source syntax into the argument name.

The variable should identify the runtime value.

Step 2: use plural for grammatical quantity choices

Plural categories are not just:

  • singular;
  • plural.

Different languages can have categories such as:

  • zero;
  • one;
  • two;
  • few;
  • many;
  • other.

The available categories depend on locale.

A translator working into Arabic, Polish, Russian, Welsh, Slovenian, or other languages may need different branches from English.

The software should let the target language express its own plural system.

English does not define the world’s plural logic

English commonly distinguishes:

  • one;
  • other.

That simplicity can encourage developers to hard-code:

count == 1 ? singular : plural

This is not a multilingual plural model.

A localization library should use locale plural rules.

The number 2 may select a special category in one language and other in another.

The translator should not be asked to reproduce English branch logic.

The other branch is essential

In ICU-style plural selection, other is the safe general category.

A message without a valid other branch is structurally fragile.

Even when the source language appears to need only one or two forms, the pattern should provide the general path the runtime expects.

Think of other as the grammatical default branch.

Exact-number branches are different from plural categories

ICU syntax can distinguish:

=0
=1
=2

from:

zero
one
two
few
many
other

Exact-number selection asks:

Is the numeric value exactly 0?

Plural-category selection asks:

Which plural category does this locale assign to the value?

These are different mechanisms.

Use exact numbers only when product meaning truly depends on that exact value.

Worked example 1: inbox count

Source intent:

  • zero messages: “Your inbox is empty.”
  • one message: “You have one new message.”
  • everything else: “You have # new messages.”

Pattern conceptually:

{count, plural,
  =0 {Your inbox is empty.}
  one {You have one new message.}
  other {You have # new messages.}
}

This is better than forcing the zero case into:

You have 0 new messages.

The product can choose a natural experience per exact value.

Translators should be able to rewrite each branch fully

Do not tell the translator:

Keep “You have” outside the plural so we only translate it once.

That optimization saves a few characters in source.

It removes target-language freedom.

ICU guidance recommends making complex branches large enough that translators can form complete sentences.

Duplication inside a resource file can be cheaper than grammatical constraints across every locale.

Step 3: use select for fixed categories

select works well when runtime supplies a keyword such as:

  • admin;
  • member;
  • guest.

Conceptual example:

{role, select,
  admin {Administrator access}
  member {Member access}
  other {Standard access}
}

The keywords are application data.

The branch text is localizable.

Do not translate select keys unless the runtime changes too

In:

admin {...}
member {...}
other {...}

admin and member may be code values.

The target translator normally translates the branch content, not the selector tokens.

Protect the syntax.

If a translator changes admin to the target-language word, the runtime may stop matching it.

Step 4: use selectordinal for real ordinal grammar

Ordinal messages include:

  • 1st;
  • 2nd;
  • 3rd;
  • 4th.

Some languages express ordinals differently.

If the message says:

You finished 2nd.

use a locale-aware ordinal mechanism rather than concatenating:

2 + "nd".

Do not assume English suffixes.

Step 5: keep full sentences inside nested logic

A dynamic message may depend on both:

  • gender/role;
  • count.

A source developer might be tempted to build many fragments.

A safer pattern can nest:

select → plural → complete sentence.

For example, a host role is selected first, then quantity.

The translator sees each final sentence shape.

This increases the resource pattern length.

It reduces hidden grammatical coupling.

Nest only when the user-visible message truly depends on both dimensions

Every extra dimension multiplies branches.

Three roles × six plural categories can become large.

Before nesting, ask:

  • Does the wording actually differ?
  • Can neutral wording remove the gender branch?
  • Can one branch use the same target for several categories?

The goal is expressive completeness, not maximal combinatorics.

Branch explosion is a design smell

A message with:

  • gender;
  • plan;
  • count;
  • status;
  • device type;

can become impossible to maintain.

Consider:

  • splitting distinct product messages;
  • using neutral language;
  • moving nonlinguistic decisions into code;
  • simplifying UI.

MessageFormat is powerful.

It should not become a miniature programming language for every screen.

Step 6: preserve arguments but allow movement

Source:

{userName} invited {count} guests.

A target language may prefer:

{count} guests were invited by {userName}.

The placeholders must move.

A CAT tool should protect the token identity while letting the translator place it naturally.

The syntax is structural.

The location is linguistic.

Do not freeze placeholder order

A development comment such as:

{userName} must come before {count}

should be viewed suspiciously unless the runtime contract truly requires it.

In normal localization, the message pattern should control display order.

The translator should be allowed to reorder variables.

Step 7: give examples for ambiguous values

Argument:

{value}

What is it?

  • price;
  • count;
  • username;
  • version?

Use descriptions.

Better argument:

{price}

Developer note:

Localized currency amount, e.g. $29.00.

Context reduces query time.

Step 8: keep formatting locale-aware

A message can contain arguments representing:

  • numbers;
  • dates;
  • times.

Do not preformat everything as English strings before passing it into the message.

Prefer locale-aware number and date formatting where the framework supports it.

The runtime value should be formatted for the target locale.

The translator should place it in the sentence.

Worked example 2: due date

Weak implementation:

"Due on " + englishFormattedDate

Better dynamic message:

Due on {dueDate}

where dueDate is formatted under the target locale.

Now date order, month names, and punctuation can match the user’s locale.

MessageFormat and plural rules

ICU plural selection is normally driven by locale plural rules.

The translator should not manually guess which numeric values belong to:

  • few;
  • many;
  • other.

The framework does the selection.

The translator supplies grammatically correct branches.

This division of labor is efficient.

Step 9: separate syntax QA from language QA

A MessageFormat target can fail in two independent ways.

Syntax failure

  • missing brace;
  • renamed select key;
  • missing other;
  • broken nesting.

Linguistic failure

  • wrong plural noun;
  • unnatural branch;
  • agreement error.

Run both checks.

A fluent branch with broken syntax will not render.

Valid syntax with bad grammar still harms users.

Step 10: validate braces

Patterns use braces heavily.

One missing } can break the whole message.

Use a parser or MessageFormat-aware validator.

Do not rely on visual brace counting.

This is exactly the kind of deterministic error software should catch.

Step 11: validate argument names

Source uses:

{count}

Target accidentally uses:

{counts}

The syntax may still parse.

At runtime, the value may be missing.

Compare source and target argument sets.

The target can reorder arguments.

It should not silently rename them.

Step 12: validate branch keywords

For select, source keys may be:

  • admin;
  • member;
  • other.

Target must retain the runtime keywords.

It may add target-language wording inside each branch.

If the target removes other, fallback behavior may fail.

Step 13: validate required plural branches

Target languages may require categories absent in source.

A localization platform should support locale-specific plural forms.

Do not force every locale to use the English branch set.

Equally, do not require every target to translate categories that its locale can never select unless the system uses them for exact-number branches.

Step 14: do not equate one with the number 1 universally

Plural category one is a grammatical category.

Its selected numbers are locale-specific.

Exact branch =1 means numeric equality.

This distinction is important in plural logic.

Developers should not use one as a synonym for =1.

Step 15: test zero explicitly

Many products want special zero wording.

Examples:

  • No files yet.
  • Your cart is empty.
  • Nothing to review.

If the product wants that experience, use an exact zero branch.

If not, let zero follow the locale’s normal plural category.

Design choice should be explicit.

Step 16: test decimals

Plural rules can behave differently for:

  • 1;
  • 1.0;
  • 1.5.

If runtime passes decimal quantities, test them.

A message designed only with integers may fail in measurements or finance.

Step 17: test negative numbers if possible

Some metrics can be negative:

  • balance;
  • temperature;
  • score difference.

If the message can receive negative values, test them.

Do not assume the count domain begins at zero unless product logic guarantees it.

Step 18: test large numbers

Large values can trigger:

  • formatting;
  • grouping separators;
  • layout expansion.

Test:

  • 1;
  • 2;
  • 11;
  • 101;
  • 1,000;
  • 1,000,000

where relevant.

The plural branch may be grammatically fine while UI width fails.

Step 19: test select unknown values

What happens if runtime supplies:

role = contractor

but pattern contains only:

  • admin;
  • member;
  • other?

other should provide a safe path.

Do not rely on every backend value being forever known.

Step 20: test nested combinations

A pattern with:

  • role;
  • count

needs representative combinations.

You do not always need every mathematical combination manually if automated tests exist.

At minimum test:

  • each fixed select branch;
  • each plural category;
  • key edge values;
  • other.

Coverage should reflect the logic.

A branch test matrix

RoleCountExpected branch
admin0admin + exact zero
admin1admin + one
admin5admin + other/few by locale
member1member + one
unknown2other role + target plural

Build automated tests from the matrix.

Step 21: use preview with real runtime examples

A CAT editor may display the raw pattern.

That is necessary but cognitively heavy.

If the platform can preview:

Ana has 5 messages.

use it.

A rendered example helps translators catch agreement, punctuation, and variable placement.

Do not replace the pattern view entirely; translators still need to see branch structure.

Step 22: provide pseudo values

For arguments:

{userName}

preview with:

Alexandra Fernández

not:

X

Long realistic values expose layout.

For:

{count}

preview several counts.

Context should represent real runtime pressure.

Step 23: do not concatenate around MessageFormat

Bad hybrid:

prefix + messageFormatPattern + suffix

If prefix and suffix form the same grammatical sentence, you have recreated the fragment problem.

The entire user-facing sentence should belong to the message pattern.

Use code composition only for units that are genuinely independent.

Step 24: do not split punctuation outside the message

Bad:

translate("message") + "."

Some languages or UI surfaces may not use the same punctuation.

Put punctuation inside the localizable message when it belongs to the message.

This also improves target-only review.

Step 25: do not create one key per English plural form manually

Weak resource model:

  • files_one;
  • files_many.

This can be workable in simple frameworks but often encodes English logic.

A plural-aware message resource is more scalable.

Use the platform’s plural model.

Step 26: understand framework-specific MessageFormat support

Not every framework implements ICU syntax identically.

Examples include ICU4J, ICU4C, JavaScript messageformat libraries, Flutter ARB/ICU style, and TMS-specific ICU support.

Test in the actual runtime.

Do not assume one online validator perfectly matches production.

Step 27: ICU MessageFormat 1 versus MessageFormat 2

Current ICU documentation describes the long-standing MessageFormat API and also a newer MessageFormat 2 direction or technical preview.

Do not mix syntax from different generations casually.

Record which format the project uses.

A translator should not be asked to infer parser version.

Step 28: quote and escaping rules matter

MessageFormat patterns can have quoting rules for apostrophes and braces.

These rules can be surprising.

Use a MessageFormat-aware editor.

Do not ask translators to manually escape syntax from memory.

If apostrophe handling is a known risk in the framework, test target languages that use apostrophes frequently.

Step 29: translators need syntax highlighting

A good localization interface visually separates:

  • literal language;
  • placeholders;
  • selectors;
  • braces.

This reduces accidental syntax edits.

A raw plain-text field makes complex patterns unnecessarily risky.

Tooling affects translation speed.

Step 30: protect selector skeleton, not whole branch text

The translator should be able to edit:

one {You have one message.}

inside the braces.

They should not be able to accidentally change one or destroy the brace structure without warning.

A good editor distinguishes structural tokens from linguistic content.

Failure mode 1: concatenated fragments

Result:

target cannot reorder grammar.

Repair:

one complete message.

Failure mode 2: English singular/plural binary hard-coded

Result:

languages with other plural categories break.

Repair:

locale plural rules.

Failure mode 3: translator changes select key

Result:

runtime branch never matches.

Repair:

protect selector tokens.

Failure mode 4: missing other

Result:

unhandled runtime value.

Repair:

required default branch.

Failure mode 5: argument names are numeric and undocumented

Result:

translator swaps values.

Repair:

descriptive arguments.

Failure mode 6: number/date formatted in source locale before message

Result:

target sentence contains wrong locale format.

Repair:

locale-aware formatting.

Failure mode 7: only count=1 tested

Result:

other branches fail in production.

Repair:

branch matrix.

Failure mode 8: message syntax valid but branch grammar wrong

Result:

runtime works, language fails.

Repair:

linguistic review of rendered examples.

Failure mode 9: branch explosion

Result:

translation and maintenance cost explodes.

Repair:

simplify product logic or neutral wording.

Failure mode 10: source punctuation outside message

Result:

target punctuation constrained.

Repair:

keep message complete.

A MessageFormat design checklist

Before development:

  • complete sentence?
  • descriptive arguments?
  • plural/select logic necessary?
  • other branch present?
  • exact-value branches justified?
  • formatting locale-aware?
  • concatenation avoided?
  • translator notes available?

Before translation:

  • syntax protected?
  • plural categories supported per locale?
  • rendered examples available?
  • argument meanings clear?

Before release:

  • every branch parsed?
  • every target branch reviewed?
  • edge values tested?
  • layout checked with long values?

A safe translator workflow

When you receive a complex pattern:

  1. identify the outer selector;
  2. identify every argument;
  3. read each branch as a complete message;
  4. translate branch meaning naturally;
  5. move placeholders where grammar needs them;
  6. preserve argument names and selector keys;
  7. run syntax QA;
  8. preview representative values;
  9. review branches target-only.

Do not translate the pattern left to right like normal prose.

Map the logic first.

A safe developer workflow

Before sending strings to localization:

  1. remove sentence concatenation;
  2. name arguments clearly;
  3. keep complex arguments around full submessages;
  4. attach comments and examples;
  5. test source pattern;
  6. export through the real TMS.

Discover more from eduKate Singapore

Subscribe now to keep reading and get access to the full archive.

Continue reading