Skip to content
This repository was archived by the owner on Apr 12, 2024. It is now read-only.

Commit 383ffb7

Browse files
committed
docs(i18n): expand the MessageFormat syntax documentation
1 parent 2a156c2 commit 383ffb7

File tree

2 files changed

+194
-65
lines changed

2 files changed

+194
-65
lines changed

docs/content/guide/i18n.ngdoc

+193-64
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,10 @@ application means providing translations and localized formats for the abstracte
1818
Angular supports i18n/l10n for {@link ng.filter:date date}, {@link ng.filter:number number} and
1919
{@link ng.filter:currency currency} filters.
2020

21-
Additionally, Angular supports localizable pluralization support through the {@link
22-
ng.directive:ngPluralize `ngPluralize` directive}.
21+
Localizable pluralization is supported via the {@link ng.directive:ngPluralize `ngPluralize`
22+
directive}. Additionally, you can use <a href="#MessageFormat">MessageFormat extensions</a> to
23+
`$interpolate` for localizable pluralization and gender support in all interpolations via the
24+
`ngMessageFormat` module.
2325

2426
All localizable Angular components depend on locale-specific rule sets managed by the {@link
2527
ng.$locale `$locale` service}.
@@ -142,96 +144,200 @@ displaying the date with a timezone specified by the developer.
142144
<a name="MessageFormat"></a>
143145
## MessageFormat extensions
144146

145-
AngularJS interpolations via `$interpolate` and in templates
146-
support an extended syntax based on a subset of the ICU
147-
MessageFormat that covers plurals and gender selections.
147+
You can write localizable plural and gender based messages in Angular interpolation expressions and
148+
`$interpolate` calls.
149+
150+
This syntax extension is provided by way of the `ngMessageFormat` module that your application can
151+
depend upon (shipped separately as `angular-messageFormat.min.js` and `angular-messageFormat.js`.)
152+
A current limitation of the `ngMessageFormat` module, is that it does not support redefining the
153+
`$interpolate` start and end symbols. Only the default `{{` and `}}` are allowed.
154+
155+
The syntax extension is based on a subset of the ICU MessageFormat syntax that covers plurals and
156+
gender selections. Please refer to the links in the “Further Reading” section at the bottom of this
157+
section.
158+
159+
You may find it helpful to play with our [Plnkr Example](http://plnkr.co/edit/QBVRQ70dvKZDWmHW9RyR?p=preview)
160+
as you read the examples below.
161+
162+
### Plural Syntax
163+
164+
The syntax for plural based message selection looks like the following:
165+
166+
```text
167+
{{NUMERIC_EXPRESSION, plural,
168+
=0 {MESSAGE_WHEN_VALUE_IS_0}
169+
=1 {MESSAGE_WHEN_VALUE_IS_1}
170+
=2 {MESSAGE_WHEN_VALUE_IS_2}
171+
=3 {MESSAGE_WHEN_VALUE_IS_3}
172+
...
173+
zero {MESSAGE_WHEN_PLURAL_CATEGORY_IS_ZERO}
174+
one {MESSAGE_WHEN_PLURAL_CATEGORY_IS_ONE}
175+
two {MESSAGE_WHEN_PLURAL_CATEGORY_IS_TWO}
176+
few {MESSAGE_WHEN_PLURAL_CATEGORY_IS_FEW}
177+
many {MESSAGE_WHEN_PLURAL_CATEGORY_IS_MANY}
178+
other {MESSAGE_WHEN_THERE_IS_NO_MATCH}
179+
}}
180+
```
148181

149-
Please refer to our [design doc](https://docs.google.com/a/google.com/document/d/1pbtW2yvtmFBikfRrJd8VAsabiFkKezmYZ_PbgdjQOVU/edit)
150-
for a lot more details. You may find it helpful to play with our [Plnkr Example](http://plnkr.co/edit/QBVRQ70dvKZDWmHW9RyR?p=preview).
182+
Please note that whitespace (including newline) is generally insignificant except as part of the
183+
actual message text that occurs in curly braces. Whitespace is generally used to aid readability.
151184

152-
You can read more about the ICU MessageFormat syntax at
153-
[Formatting Messages | ICU User Guide](http://userguide.icu-project.org/formatparse/messages#TOC-MessageFormat).
185+
Here, `NUMERIC_EXPRESSION` is an expression that evaluates to a numeric value based on which the
186+
displayed message should change based on pluralization rules.
187+
188+
Following the Angular expression, you would denote the plural extension syntax by the `, plural,`
189+
syntax element. The spaces there are optional.
190+
191+
This is followed by a list of selection keyword and corresponding message pairs. The "other"
192+
keyword and corresponding message are **required** but you may have as few or as many of the other
193+
categories as you need.
194+
195+
#### Selection Keywords
196+
197+
The selection keywords can be either exact matches or language dependent [plural
198+
categories](http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html).
199+
200+
Exact matches are written as the equal sign followed by the exact value. `=0`, `=1`, `=2` and
201+
`=123` are all examples of exact matches. Note that there should be no space between the equal sign
202+
and the numeric value.
203+
204+
Plural category matches are single words corresponding to the [plural
205+
categories](http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html) of
206+
the CLDR plural category spec. These categories vary by locale. The "en" (English) locale, for
207+
example, defines just "one" and "other" while the "ga" (Irish) locale defines "one", "two", "few",
208+
"many" and "other". Typically, you would just write the categories for your language. During
209+
translation, the translators will add or remove more categories depending on the target locale.
210+
211+
Exact matches always win over keyword matches. Therefore, if you define both `=0` and `zero`, when
212+
the value of the expression is zero, the `=0` message is the one that will be selected. (The
213+
duplicate keyword categories are helpful when used with the optional `offset` syntax described
214+
later.)
154215

155-
This extended syntax is provided by way of the
156-
`ngMessageFormat` module that your application can depend
157-
upon (shipped separately as `angular-messageFormat.min.js` and
158-
`angular-messageFormat.js`.) A current limitation of the
159-
`ngMessageFormat` module, is that it does not support
160-
redefining the `$interpolate` start and end symbols. Only the
161-
default `{{` and `}}` are allowed.
162-
163-
This syntax extension, while based on MessageFormat, has
164-
been designed to be backwards compatible with existing
165-
AngularJS interpolation expressions. The key rule is simply
166-
this: **All interpolations are done inside double curlies.**
167-
The top level comma operator after an expression inside the
168-
double curlies causes MessageFormat extensions to be
169-
recognized. Such a top level comma is otherwise illegal in
170-
an Angular expression and is used by MessageFormat to
171-
specify the function (such as plural/select) and it's
172-
related syntax.
173-
174-
To understand the extension, take a look at the ICU
175-
MessageFormat syntax as specified by the ICU documentation.
176-
Anywhere in that MessageFormat that you have regular message
177-
text and you want to substitute an expression, just put it
178-
in double curlies instead of single curlies that
179-
MessageFormat dictates. This has a huge advantage. **You
180-
are no longer limited to simple identifiers for
181-
substitutions**. Because you are using double curlies, you
182-
can stick in any arbitrary interpolation syntax there,
183-
including nesting more MessageFormat expressions! Some
184-
examples will make this clear. In the following example, I
185-
will only be showing you the AngularJS syntax.
186216

217+
#### Messages
218+
219+
Messages immediately follow a selection keyword and are optionally preceded by whitespace. They are
220+
written in single curly braces (`{}`). They may contain Angular interpolation syntax inside them.
221+
In addition, the `#` symbol is a placeholder for the actual numeric value of the expression.
187222

188223
### Simple plural example
189224

190-
```
225+
```text
191226
{{numMessages, plural,
192-
=0 { You have no new messages }
193-
=1 { You have one new message }
194-
other { You have # new messages }
227+
=0 {You have no new messages}
228+
=1 {You have one new message}
229+
other {You have # new messages}
195230
}}
196231
```
197232

198-
While I won't be teaching you MessageFormat here, you will
199-
note that the `#` symbol works as expected. You could have
200-
also written it as:
233+
Because these messages can themselves contain Angular expressions, you could also write this as
234+
follows:
201235

202-
```
236+
```text
203237
{{numMessages, plural,
204-
=0 { You have no new messages }
205-
=1 { You have one new message }
206-
other { You have {{numMessages}} new messages }
238+
=0 {You have no new messages}
239+
=1 {You have one new message}
240+
other {You have {{numMessages}} new messages}
207241
}}
208242
```
209243

210-
where you explicitly typed in `numMessages` for "other"
211-
instead of using `#`. They are nearly the same except if
212-
you're using "offset". Refer to the ICU MessageFormat
213-
documentation to learn about offset.
214244

215-
Please note that **other** is a **required** category (for
216-
both the plural syntax and the select syntax that is shown
217-
later.)
245+
### Plural syntax with optional `offset`
246+
247+
The plural syntax supports an optional `offset` syntax that is used in matching. It's simpler to
248+
explain this with an example.
249+
250+
```text
251+
{{recipients.length, plural, offset:1
252+
=0 {You gave no gifts}
253+
=1 {You gave {{recipients[0].name}} a gift}
254+
one {You gave {{recipients[0].name}} and one other person a gift}
255+
other {You gave {{recipients[0].name}} and # other people a gift}
256+
}}
257+
```
258+
259+
When an `offset` is specified, the matching works as follows. First, the exact value of the Angular
260+
expression is matched against the exact matches (i.e. `=N` selectors) to find a match. If there is
261+
one, that message is used. If there was no match, then the offset value is subtracted from the
262+
value of the expression and locale specific pluralization rules are applied to this new value to
263+
obtain its plural category (such as “one”, “few”, “many”, etc.) and a match is attempted against the
264+
keyword selectors and the matching message is used. If there was no match, then the “other”
265+
category (required) is used. The value of the `#` character inside a message is the value of
266+
original expression reduced by the offset value that was specified.
267+
268+
### Escaping / Quoting
269+
270+
You will need to escape curly braces or the `#` character inside message texts if you want them to
271+
be treated literally with no special meaning. You may quote/escape any character in your message
272+
text by preceding it with a `\` (backslash) character. The backslash character removes any special
273+
meaning to the character that immediately follows it. Therefore, you can escape or quote the
274+
backslash itself by preceding it with another backslash character.
218275

219276

220-
### Simple select (for gender) example
277+
### Gender (aka select) Syntax
221278

279+
The gender support is provided by the more generic "select" syntax that is more akin to a switch
280+
statement. It is general enough to support use for gender based messages.
281+
282+
The syntax for gender based message selection looks like the following:
283+
284+
```text
285+
{{EXPRESSION, select,
286+
male {MESSAGE_WHEN_EXPRESSION_IS_MALE}
287+
female {MESSAGE_WHEN_EXPRESSION_IS_FEMALE}
288+
...
289+
other {MESSAGE_WHEN_THERE_IS_NO_GENDER_MATCH}
290+
}}
222291
```
292+
293+
Please note that whitespace (including newline) is generally insignificant except as part of the
294+
actual message text that occurs in curly braces. Whitespace is generally used to aid readability.
295+
296+
Here, `EXPRESSION` is an Angular expression that evaluates to the gender of the person that
297+
is used to select the message that should be displayed.
298+
299+
The Angular expression is followed by `, select,` where the spaces are optional.
300+
301+
This is followed by a list of selection keyword and corresponding message pairs. The "other"
302+
keyword and corresponding message are **required** but you may have as few or as many of the other
303+
gender values as you need (i.e. it isn't restricted to male/female.) Note however, that the
304+
matching is **case-sensitive**.
305+
306+
#### Selection Keywords
307+
308+
Selection keywords are simple words like "male" and "female". The keyword, "other", and it's
309+
corresponding message are required while others are optional. It is used when the Angular
310+
expression does not match (case-insensitively) any of the other keywords specified.
311+
312+
#### Messages
313+
314+
Messages immediately follow a selection keyword and are optionally preceded by whitespace. They are
315+
written in single curly braces (`{}`). They may contain Angular interpolation syntax inside them.
316+
317+
### Simple gender example
318+
319+
```text
223320
{{friendGender, select,
224-
male { Invite him }
225-
female { Invite her }
226-
other { Invite them }
321+
male {Invite him}
322+
female {Invite her}
323+
other {Invite them}
227324
}}
228325
```
229326

230-
### More complex example that combines some of these
327+
### Nesting
328+
329+
As mentioned in the syntax for plural and select, the embedded messages can contain Angular
330+
interpolation syntax. Since you can use MessageFormat extensions in Angular interpolation, this
331+
allows you to nest plural and gender expressions in any order.
332+
333+
Please note that if these are intended to reach a translator and be translated, it is recommended
334+
that the messages appear as a whole and not be split up.
335+
336+
### More complex example that demonstrates nesting
231337

232338
This is taken from the [plunker example](http://plnkr.co/edit/QBVRQ70dvKZDWmHW9RyR?p=preview) linked to earlier.
233339

234-
```
340+
```text
235341
{{recipients.length, plural, offset:1
236342
=0 {You ({{sender.name}}) gave no gifts}
237343
=1 { {{ recipients[0].gender, select,
@@ -249,3 +355,26 @@ This is taken from the [plunker example](http://plnkr.co/edit/QBVRQ70dvKZDWmHW9R
249355
other {You ({{sender.name}}) gave {{recipients.length}} people gifts. }
250356
}}
251357
```
358+
359+
### Differences from the ICU MessageFormat syntax
360+
361+
This section is useful to you if you're already familiar with the ICU MessageFormat syntax.
362+
363+
This syntax extension, while based on MessageFormat, has been designed to be backwards compatible
364+
with existing AngularJS interpolation expressions. The key rule is simply this: **All
365+
interpolations are done inside double curlies.** The top level comma operator after an expression
366+
inside the double curlies causes MessageFormat extensions to be recognized. Such a top level comma
367+
is otherwise illegal in an Angular expression and is used by MessageFormat to specify the function
368+
(such as plural/select) and it's related syntax.
369+
370+
To understand the extension, take a look at the ICU MessageFormat syntax as specified by the ICU
371+
documentation. Anywhere in that MessageFormat that you have regular message text and you want to
372+
substitute an expression, just put it in double curlies instead of single curlies that MessageFormat
373+
dictates. This has a huge advantage. **You are no longer limited to simple identifiers for
374+
substitutions**. Because you are using double curlies, you can stick in any arbitrary interpolation
375+
syntax there, including nesting more MessageFormat expressions!
376+
377+
### Further Reading
378+
For more details, please refer to our [design doc](https://docs.google.com/a/google.com/document/d/1pbtW2yvtmFBikfRrJd8VAsabiFkKezmYZ_PbgdjQOVU/edit).
379+
You can read more about the ICU MessageFormat syntax at
380+
[Formatting Messages | ICU User Guide](http://userguide.icu-project.org/formatparse/messages#TOC-MessageFormat).

src/ngMessageFormat/messageFormatService.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ var $$MessageFormatFactory = ['$parse', '$locale', '$sce', '$exceptionHandler',
4747

4848
var $$interpolateDecorator = ['$$messageFormat', '$delegate', function $$interpolateDecorator($$messageFormat, $interpolate) {
4949
if ($interpolate['startSymbol']() != "{{" || $interpolate['endSymbol']() != "}}") {
50-
throw $interpolateMinErr('nochgmustache', 'angular-messageformat.js currently does not allow you to use custom start and end symbols for interpolation.');
50+
throw $interpolateMinErr('nochgmustache', 'angular-messageFormat.js currently does not allow you to use custom start and end symbols for interpolation.');
5151
}
5252
var interpolate = $$messageFormat['interpolate'];
5353
interpolate['startSymbol'] = $interpolate['startSymbol'];

0 commit comments

Comments
 (0)