ICU MessageFormat is a Unicode standard for writing one translatable string that adapts to count, gender, and other variables, and it is still hard to translate because the syntax was designed for code, not for the people who localize it. The spec has existed for well over a decade, and the people who break on it most are non-technical translators staring at nested curly braces. globalize.now is AI-powered localization infrastructure that sits a layer below this problem: it extracts strings and generates the locale files where these ICU patterns live, then keeps them in sync on every Git push.
What is ICU MessageFormat?
ICU MessageFormat is a syntax for expressing a message whose final wording depends on runtime data. Rather than concatenating fragments in code, you write one pattern and let the formatter choose the correct branch per locale.
The most common use is pluralization:
{count, plural,
one {You have # message}
other {You have # messages}
}The # is replaced by the number, and the one / otherbranches map to a language's plural categories. English has two; Polish, Russian, and Arabic have more. ICU also handles gender and other choices through select:
{gender, select,
male {He liked your post}
female {She liked your post}
other {They liked your post}
}This is why ICU became the de facto pluralization standard: it pushes grammatical complexity into data that can vary per language, instead of into branching logic that only works for the language it was written in. Runtime libraries like FormatJS, i18next, and next-intl all understand it.
Why is ICU MessageFormat so hard to translate?
It is hard to translate because the syntax was built for developers, and the people who actually translate strings usually are not developers.
A translator opening a locale file does not see “You have 3 messages.” They see curly braces, the keywords plural and select, plural categories they may not recognize, and a # placeholder that looks like a typo. One deleted bracket or one renamed category and the string throws at runtime instead of rendering. The structure that makes ICU powerful for engineers is the same structure that makes it fragile in the hands of a linguist.
This is the gap the localization industry has quietly lived with for years. Most tools handle it by showing the raw pattern and hoping the translator does not touch the wrong character, or by hiding plural forms behind a separate editor that still requires the translator to understand plural categories. No vendor has fully closed the distance between the spec's complexity and a non-technical translator's mental model.
What changed with MessageFormat 2.0?
MessageFormat 2.0 is a ground-up redesign of the syntax, and in 2025 it became a stable standard rather than a draft.
MF2 reached Final Candidate status in March 2025 and is now a stable part of CLDR, published as part of the Unicode LDML technical standard and refined through the LDML 47 and 48 releases. The reference implementation tracks the spec as of the LDML 48.2 version released in early 2026. The new syntax separates inputs from match logic and supports named formatting functions, which makes complex messages more readable:
.input {$count :number}
.match $count
one {{You have {$count} message}}
* {{You have {$count} messages}}The intent is a pattern that is easier to extend and slightly less hostile to read than the original nested braces.
Does MessageFormat 2.0 fix the translator problem?
Not yet, for two reasons.
First, MF2 improves the ergonomics of the syntax, but it is still syntax. A translator still encounters .match, plural keys, and braces. The cognitive load is lower than MF1, but it has not disappeared, and a non-technical translator can still break a message by editing the wrong token.
Second, almost nobody is running it in production. Adoption remains limited well into 2026: the TC39 process for the JavaScript side wants roughly a dozen organizations using MF2 in production before it advances further, and that bar has not been met. In practice, most apps you ship today still run on the original ICU MessageFormat through libraries like FormatJS and i18next. So the translator-facing problem is not solved by a spec; it is solved by where the syntax is produced and maintained.
Where does ICU MessageFormat break in AI-generated apps?
It breaks early, because AI coding tools rarely emit correct ICU in the first place.
When you build with Cursor, Claude Code, or Lovable, the generated code tends to handle counts the naive way:
// What an AI coding tool often generates
const label = count + " " + (count === 1 ? "message" : "messages");That logic is correct for English and wrong for most other languages, because it assumes exactly two plural forms. It also bakes the sentence into application code, so there is no ICU pattern in a locale file for a translator to localize at all. The string is invisible to the localization layer until someone notices the broken grammar in production. AI tools also duplicate keys and scatter hardcoded text across components, so even teams that want ICU end up with strings that never made it into a translatable file. The globalize.now developer overview is built around catching exactly this class of string before it ships.
How should developers handle ICU MessageFormat in 2026?
Treat ICU as generated infrastructure, not hand-authored content.
The practical rule: developers and AI tools should not be writing plural concatenation in code, and translators should not be hand-editing raw ICU braces. The ICU pattern belongs in a locale file that is produced and kept current automatically, then rendered at runtime by a library like FormatJS, i18next, or next-intl. That keeps the syntax correct at the point it is created and consistent across every locale as the app changes. If you are weighing where ICU fits against your runtime layer, the globalize.now vs i18next comparison covers how infrastructure and runtime split the work.
This is the layer globalize.now operates on. It extracts hardcoded strings from your codebase, generates the keys and locale files where ICU patterns live, and re-syncs them on every Git push, so the structure stays correct without a manual export step. It does not try to be the translator's editor or replace your runtime library; it makes sure the ICU-shaped files those tools depend on actually exist and stay in sync. The vibe-coders guide walks through the same setup for apps built with Lovable, Bolt, and Replit, and the broader workflow lives on the globalize.now homepage.