复数、性别与 ICU 消息格式化
使用 ICU 消息语法处理复数、选择分支和参数化字符串。
复数、性别与 ICU 消息格式化 是 CoddyKit 上的免费 Flutter Mobile Development 课时。 这是第 2 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 Flutter Mobile Development 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 Flutter Mobile Development 课程共包含 4 节课。
本课时的部分内容尚未翻译,以英文显示。
Why ICU Messages Matter
Translating an app is more than swapping words. Different languages pluralize, gender, and order words differently. A naive string like "$count items" reads wrong as "1 items", and many languages have several plural forms.
Flutter solves this with ICU message syntax (International Components for Unicode), the same standard used across the industry. You write one message with rules for plurals, select/gender, and placeholders, and the right form is chosen at runtime per locale.
plural— pick a form based on a numberselect— pick a branch based on a string (e.g. gender)- placeholders — inject typed values like names, dates, numbers
The ARB File: Where Messages Live
Flutter's flutter_localizations + intl toolchain reads ARB files (Application Resource Bundle). Each locale has its own file: app_en.arb, app_tr.arb, etc.
An ARB entry has a key, a value (the ICU message), and an optional @key metadata object describing placeholders.
- Keys become generated Dart getters/methods.
- The
@keyblock declares each placeholder'stypeand optionalformat.
{
"appTitle": "My Shop",
"@appTitle": {
"description": "The title shown in the app bar"
},
"welcome": "Welcome, {name}!",
"@welcome": {
"placeholders": {
"name": { "type": "String" }
}
}
}Simple Placeholders
A placeholder is written in curly braces inside the message: {name}. The generated Dart code becomes a method whose parameter matches the placeholder name.
After enabling generate: true in pubspec.yaml and running flutter gen-l10n, you call it through AppLocalizations.
// app_en.arb
// "welcome": "Welcome, {name}!"
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context)!;
return Text(l10n.welcome('Aylin')); // -> "Welcome, Aylin!"
}Pluralization with ICU
The plural form picks a branch based on a number argument. Syntax: {count, plural, ...categories...}.
ICU plural categories are =0, =1, zero, one, two, few, many, other. The other branch is mandatory — it is the fallback when no other category matches. Use # to print the number itself.
=0,=1match the exact value.one,few,manyare language-defined categories (English only usesoneandother).
{
"itemsInCart": "{count, plural, =0{Your cart is empty} =1{1 item in cart} other{# items in cart}}",
"@itemsInCart": {
"placeholders": {
"count": { "type": "int" }
}
}
}Calling a Plural Message
The generated method takes the count argument. The framework selects the correct branch for the active locale automatically.
Notice that English only ever needs one/other, but a locale like Polish or Arabic will use few/many in its own ARB file — your Dart call site does not change.
final l10n = AppLocalizations.of(context)!;
Text(l10n.itemsInCart(0)); // "Your cart is empty"
Text(l10n.itemsInCart(1)); // "1 item in cart"
Text(l10n.itemsInCart(7)); // "7 items in cart"Why '=1' and 'one' Are Different
A common confusion: =1 is an exact value match, while one is a language plural category. They are not the same.
=1matches only the literal number 1.onematches whatever the locale's CLDR rules call the "one" category. In English that is just 1, but in some locales theonecategory also covers values like 21, 31, etc.
Best practice: provide other always; add one for natural singular/plural; use =0/=1 only when you need special wording ("empty", "no messages") that differs from the grammatical plural.
Gender and the 'select' Form
The select form branches on a string value rather than a number. The classic use is grammatical gender, which changes pronouns and verb agreement in many languages.
Syntax: {gender, select, male{...} female{...} other{...}}. Like plural, other is required as the fallback (and handles unknown/non-binary values gracefully).
{
"sharedPhoto": "{gender, select, male{He shared a photo} female{She shared a photo} other{They shared a photo}}",
"@sharedPhoto": {
"placeholders": {
"gender": { "type": "String" }
}
}
}Demonstrating select() in Pure Dart
You don't need Flutter to understand the intl primitives. The Intl.select function mirrors what the generated code does: it maps a key to a branch with an other fallback.
This standalone program shows the selection logic, including the fallback when an unexpected value arrives.
import 'package:intl/intl.dart';
String sharedPhoto(String gender) {
return Intl.select(gender, {
'male': 'He shared a photo',
'female': 'She shared a photo',
'other': 'They shared a photo',
}, name: 'sharedPhoto', args: [gender]);
}
void main() {
print(sharedPhoto('male')); // He shared a photo
print(sharedPhoto('female')); // She shared a photo
print(sharedPhoto('unknown')); // They shared a photo (fallback)
}Combining Gender and Plurals (Nesting)
ICU lets you nest a plural inside a select (or vice versa) to handle messages that vary by both gender and count. Place one block inside a branch of the other.
Keep nesting shallow — deeply nested messages are painful for translators. If it gets complex, consider splitting into separate keys.
{
"likes": "{gender, select, female{She got {count, plural, =0{no likes} =1{1 like} other{# likes}}} male{He got {count, plural, =0{no likes} =1{1 like} other{# likes}}} other{They got {count, plural, =0{no likes} =1{1 like} other{# likes}}}}",
"@likes": {
"placeholders": {
"gender": { "type": "String" },
"count": { "type": "int" }
}
}
}Formatted Placeholders: Numbers and Dates
Placeholders can be formatted per locale using the format field in metadata. Numbers use formats like decimalPattern or currency; dates use DateFormat skeletons such as yMMMd.
This means 1234.5 renders as 1,234.5 in en-US but 1.234,5 in many European locales — automatically.
{
"price": "Total: {amount}",
"@price": {
"placeholders": {
"amount": {
"type": "double",
"format": "currency",
"optionalParameters": { "symbol": "\u20ac", "decimalDigits": 2 }
}
}
},
"lastSeen": "Last seen {when}",
"@lastSeen": {
"placeholders": {
"when": { "type": "DateTime", "format": "yMMMd" }
}
}
}Formatting Numbers and Dates in Plain Dart
The same intl formatters power those ARB format fields. Here is a runnable program using NumberFormat and DateFormat directly, the way the generated localization code does under the hood.
import 'package:intl/intl.dart';
void main() {
final amount = 1234.5;
final usd = NumberFormat.currency(locale: 'en_US', symbol: '\$');
final eur = NumberFormat.currency(locale: 'de_DE', symbol: '\u20ac');
print(usd.format(amount)); // \$1,234.50
print(eur.format(amount)); // 1.234,50 \u20ac
final date = DateTime(2026, 6, 10);
print(DateFormat.yMMMd('en_US').format(date)); // Jun 10, 2026
print(DateFormat.yMMMd('tr_TR').format(date)); // 10 Haz 2026
}Quick Check: Choosing the Right Form
You need a message that says "no messages", "1 message", or "N messages" depending on a count, and you want it to also work correctly in locales with few/many categories. Which approach is correct?
Recap
You now know how to localize dynamic text in Flutter with ICU message syntax:
- ARB files hold messages per locale;
@keymetadata declares placeholder types and formats. - Placeholders
{name}inject typed values into messages. - plural
{count, plural, =0{} =1{} other{}}picks a form by number;otheris mandatory and#prints the value. - =1 vs one: exact-value match versus language plural category — use exact matches only for special wording.
- select
{gender, select, male{} female{} other{}}branches on a string; great for gender. - Nesting combines gender and count when needed.
- format metadata +
NumberFormat/DateFormatrender numbers and dates per locale.
Write messages so translators control wording per language while your Dart call sites stay unchanged.
常见问题解答
「复数、性别与 ICU 消息格式化」课时是免费的吗?
是的 — 「复数、性别与 ICU 消息格式化」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 Flutter Mobile Development 课程的其余内容,请升级到 CoddyKit PRO。 Flutter Mobile Development 课程共包含 4 节课。
「复数、性别与 ICU 消息格式化」这节课中我会学到什么?
使用 ICU 消息语法处理复数、选择分支和参数化字符串。 你通过在浏览器中直接运行的动手代码来练习 Flutter Mobile Development,全天候 AI 导师会在你学习这节课的过程中回答你的问题。
学习 Flutter Mobile Development 需要有经验吗?
无需任何先前经验。CoddyKit 上的 Flutter Mobile Development 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 2 节课,共 4 节。
「复数、性别与 ICU 消息格式化」课时需要多长时间?
大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。
我能在这节 Flutter Mobile Development 课中编写并运行代码吗?
能。每节 Flutter Mobile Development 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。
此课程中的所有课时
- ARB 文件与 gen_l10n 本地化工作流
- 复数、性别与 ICU 消息格式化
- RTL 布局与方向处理
- 语义、屏幕阅读器与无障碍组件