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

Commit 3507e6f

Browse files
committed
feat($interpolate): MessageFormat extensions
Extend interpolation with MessageFormat like syntax. Ref: <https://docs.google.com/a/google.com/document/d/1pbtW2yvtmFBikfRrJd8VAsabiFkKezmYZ_PbgdjQOVU/edit> Example: ```html {{recipients.length, plural, offset:1 =0 {You gave no gifts} =1 { {{ recipients[0].gender, select, male {You gave him a gift.} female {You gave her a gift.} other {You gave them a gift.} }} } one { {{ recipients[0].gender, select, male {You gave him and one other person a gift.} female {You gave her and one other person a gift.} other {You gave them and one other person a gift.} }} } other {You gave {{recipients[0].gender}} and # other people gifts. } }} ``` This is a SEPARATE module so you MUST include angular-messageformat.min.js. In addition, your application module should depend on the "ngMessageFormat". (e.g. angular.module('myApp', ['ngMessageFormat']);) $interpolate automatically gets the new behavior. Quick note on syntax differences from MessageFormat: - MessageFormat directives are always inside {{ }} instead of single { }. This ensures a consistent interpolation syntax (else you could interpolate in more than one way and have to pick one based on feature availability for that syntax.) - The first word inside such syntax can be an arbitrary Angular expression instead of a single identifier. - You can nest them as deep as you want. As mentioned earlier, you would use {{ }} to start the nested interpolation that may optionally include select/plural extensions. - Only "select" and "plural" keywords are currently recognized. - Quoting support is coming in a future commit. - Positional arguments/placeholders are not supported. They don't make sense in Angular templates anyway (they are only helpful when using API calls from a programming language.) - Redefining of the startSymbol and endSymbol used for interpolation is not currently supported yet.
1 parent bfd7b22 commit 3507e6f

29 files changed

+1940
-12
lines changed

Gruntfile.js

+8
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,9 @@ module.exports = function(grunt) {
126126
ngLocale: {
127127
files: { src: 'src/ngLocale/**/*.js' },
128128
},
129+
ngMessageFormat: {
130+
files: { src: 'src/ngMessageFormat/**/*.js' },
131+
},
129132
ngMessages: {
130133
files: { src: 'src/ngMessages/**/*.js' },
131134
},
@@ -200,6 +203,10 @@ module.exports = function(grunt) {
200203
dest: 'build/angular-resource.js',
201204
src: util.wrap(files['angularModules']['ngResource'], 'module')
202205
},
206+
messageformat: {
207+
dest: 'build/angular-messageFormat.js',
208+
src: util.wrap(files['angularModules']['ngMessageFormat'], 'module_closure')
209+
},
203210
messages: {
204211
dest: 'build/angular-messages.js',
205212
src: util.wrap(files['angularModules']['ngMessages'], 'module')
@@ -232,6 +239,7 @@ module.exports = function(grunt) {
232239
animate: 'build/angular-animate.js',
233240
cookies: 'build/angular-cookies.js',
234241
loader: 'build/angular-loader.js',
242+
messageformat: 'build/angular-messageFormat.js',
235243
messages: 'build/angular-messages.js',
236244
touch: 'build/angular-touch.js',
237245
resource: 'build/angular-resource.js',

angularFiles.js

+9
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,13 @@ var angularFiles = {
9191
'ngCookies': [
9292
'src/ngCookies/cookies.js'
9393
],
94+
'ngMessageFormat': [
95+
'src/ngMessageFormat/messageFormatCommon.js',
96+
'src/ngMessageFormat/messageFormatSelector.js',
97+
'src/ngMessageFormat/messageFormatInterpolationParts.js',
98+
'src/ngMessageFormat/messageFormatParser.js',
99+
'src/ngMessageFormat/messageFormatService.js'
100+
],
94101
'ngMessages': [
95102
'src/ngMessages/messages.js'
96103
],
@@ -181,6 +188,7 @@ var angularFiles = {
181188
'@angularSrcModules',
182189
'src/ngScenario/browserTrigger.js',
183190
'test/helpers/*.js',
191+
'test/ngMessageFormat/*.js',
184192
'test/ngMock/*.js',
185193
'test/ngCookies/*.js',
186194
'test/ngRoute/**/*.js',
@@ -209,6 +217,7 @@ var angularFiles = {
209217

210218
angularFiles['angularSrcModules'] = [].concat(
211219
angularFiles['angularModules']['ngAnimate'],
220+
angularFiles['angularModules']['ngMessageFormat'],
212221
angularFiles['angularModules']['ngMessages'],
213222
angularFiles['angularModules']['ngCookies'],
214223
angularFiles['angularModules']['ngResource'],
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
@ngdoc error
2+
@name $interpolate:badexpr
3+
@fullName Expecting end operator
4+
@description
5+
6+
The Angular expression is missing the corresponding closing operator.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
@ngdoc error
2+
@name $interpolate:dupvalue
3+
@fullName Duplicate choice in plural/select
4+
@description
5+
6+
You have repeated a match selection for your plural or select MessageFormat
7+
extension in your interpolation interpolation expression. The different
8+
choices have to be unique
9+
10+
For more information about the MessageFormat syntax in interpolation
11+
expressions, please refer to MessageFormat extensions section at
12+
{@link guide/i18n#MessageFormat Angular i18n MessageFormat}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
@ngdoc error
2+
@name $interpolate:logicbug
3+
@fullName Bug in ngMessageFormat module
4+
@description
5+
6+
You've just hit a bug in the ngMessageFormat module provided by provided by
7+
angular-messageFormat.min.js. Please file a github issue for this and provide the interpolation text that caused you to hit this bug mentioning the exact version of AngularJS used and we will fix it!
8+
9+
For more information about the MessageFormat syntax in interpolation
10+
expressions, please refer to MessageFormat extensions section at
11+
{@link guide/i18n#MessageFormat Angular i18n MessageFormat}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
@ngdoc error
2+
@name $interpolate:nochgmustache
3+
@fullName Redefinition of start/endSymbol incompatible with MessageFormat extensions
4+
@description
5+
6+
You have redefined `$interpolate.startSymbol`/`$interpolate.endSymbol` and also
7+
loaded the `ngMessageFormat` module (provided by angular-messageFormat.min.js)
8+
while creating your injector.
9+
10+
`ngMessageFormat` currently does not support redefinition of the
11+
startSymbol/endSymbol used by `$interpolate`. If this is affecting you, please
12+
file an issue and mention @chirayuk on it. This is intended to be fixed in a
13+
future commit and the github issue will help gauage urgency.
14+
15+
For more information about the MessageFormat syntax in interpolation
16+
expressions, please refer to MessageFormat extensions section at
17+
{@link guide/i18n#MessageFormat Angular i18n MessageFormat}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
@ngdoc error
2+
@name $interpolate:reqarg
3+
@fullName Missing required argument for MessageFormat
4+
@description
5+
6+
You must specify the MessageFormat function that you're using right after the
7+
comma following the Angular expression. Currently, the supported functions are
8+
"plural" and "select" (for gender selections.)
9+
10+
For more information about the MessageFormat syntax in interpolation
11+
expressions, please refer to MessageFormat extensions section at
12+
{@link guide/i18n#MessageFormat Angular i18n MessageFormat}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
@ngdoc error
2+
@name $interpolate:reqcomma
3+
@fullName Missing comma following MessageFormat plural/select keyword
4+
@description
5+
6+
The MessageFormat syntax requires a comma following the "plural" or "select"
7+
extension keyword in the extended interpolation syntax.
8+
9+
For more information about the MessageFormat syntax in interpolation
10+
expressions, please refer to MessageFormat extensions section at
11+
{@link guide/i18n#MessageFormat Angular i18n MessageFormat}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
@ngdoc error
2+
@name $interpolate:reqendbrace
3+
@fullName Unterminated message for plural/select value
4+
@description
5+
6+
The plural or select message for a value or keyword choice has no matching end
7+
brace to mark the end of the message.
8+
9+
For more information about the MessageFormat syntax in interpolation
10+
expressions, please refer to MessageFormat extensions section at
11+
{@link guide/i18n#MessageFormat Angular i18n MessageFormat}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
@ngdoc error
2+
@name $interpolate:reqendinterp
3+
@fullName Unterminated interpolation
4+
@description
5+
6+
The interpolation text does not have an ending `endSymbol` ("}}" by default) and is unterminated.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
@ngdoc error
2+
@name $interpolate:reqopenbrace
3+
@fullName An opening brace was expected but not found
4+
@description
5+
6+
The plural or select extension keyword or values (such as "other", "male",
7+
"female", "=0", "one", "many", etc.) MUST be followed by a message enclosed in
8+
braces.
9+
10+
For more information about the MessageFormat syntax in interpolation
11+
expressions, please refer to MessageFormat extensions section at
12+
{@link guide/i18n#MessageFormat Angular i18n MessageFormat}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
@ngdoc error
2+
@name $interpolate:reqother
3+
@fullName Required choice "other" for select/plural in MessageFormat
4+
@description
5+
6+
Your interpolation expression with a MessageFormat extension for either
7+
"plural" or "select" (typically used for gender selection) does not contain a
8+
message for the choice "other". Using either select or plural MessageFormat
9+
extensions require that you provide a message for the selection "other".
10+
11+
For more information about the MessageFormat syntax in interpolation
12+
expressions, please refer to MessageFormat extensions section at
13+
{@link guide/i18n#MessageFormat Angular i18n MessageFormat}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
@ngdoc error
2+
@name $interpolate:unknarg
3+
@fullName Unrecognized MessageFormat extension
4+
@description
5+
6+
The MessageFormat extensions provided by `ngMessageFormat` are currently
7+
limited to "plural" and "select". The extension that you have used is either
8+
unsupported or invalid.
9+
10+
For more information about the MessageFormat syntax in interpolation
11+
expressions, please refer to MessageFormat extensions section at
12+
{@link guide/i18n#MessageFormat Angular i18n MessageFormat}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
@ngdoc error
2+
@name $interpolate:unsafe
3+
@fullName MessageFormat extensions not allowed in secure context
4+
@description
5+
6+
You have attempted to use a MessageFormat extension in your interpolation expression that is marked as a secure context. For security purposes, this is not supported.
7+
8+
Read more about secure contexts at {@link ng.$sce Strict Contextual Escaping
9+
(SCE)} and about the MessageFormat extensions at {@link
10+
guide/i18n#MessageFormat Angular i18n MessageFormat}.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
@ngdoc error
2+
@name $interpolate:untermstr
3+
@fullName Unterminated string literal
4+
@description
5+
6+
The string literal was not terminated in your Angular expression.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
@ngdoc error
2+
@name $interpolate:wantstring
3+
@fullName Expected the beginning of a string
4+
@description
5+
6+
We expected to see the beginning of a string (either a single quote or a double
7+
quote character) in the expression but it was not found. The expression is
8+
invalid. If this is incorrect, please file an issue on github.

docs/content/guide/i18n.ngdoc

+103
Original file line numberDiff line numberDiff line change
@@ -137,3 +137,106 @@ The Angular datetime filter uses the time zone settings of the browser. The same
137137
application will show different time information depending on the time zone settings of the
138138
computer that the application is running on. Neither JavaScript nor Angular currently supports
139139
displaying the date with a timezone specified by the developer.
140+
141+
142+
<a name="MessageFormat"></a>
143+
## MessageFormat extensions
144+
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.
148+
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).
151+
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).
154+
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 this
159+
module is that it is not compatible with `$interpolate` used
160+
with redefined start and end symbols.
161+
162+
This syntax extension, while based on MessageFormat, has
163+
been designed to be backwards compatible with existing
164+
AngularJS interpolation expressions. The key rule is simply
165+
this: **All interpolations are done inside double curlies.**
166+
Consider valid MessageFormat syntax. Anywhere in that
167+
MessageFormat that you have regular message text and you
168+
want to substitute an expression, just put it in double
169+
curlies instead of single curlies that MessageFormat
170+
dictates. This has a huge advantage. **You are no longer
171+
limited to simple identifiers for substitutions**. Because
172+
you are using double curlies, you can stick in any arbitrary
173+
interpolation syntax there, including nesting more
174+
MessageFormat! Some examples will make this clear. In the
175+
following example, I will only be showing you the AngularJS
176+
syntax.
177+
178+
179+
### Simple plural example
180+
181+
```
182+
{{numMessages, plural,
183+
=0 { You have no new messages }
184+
=1 { You have one new message }
185+
other { You have # new messages }
186+
}}
187+
```
188+
189+
While I won't be teaching you MessageFormat here, you will
190+
not that the `#` symbol works as expected. You could have
191+
also written it as:
192+
193+
```
194+
{{numMessages, plural,
195+
=0 { You have no new messages }
196+
=1 { You have one new message }
197+
other { You have {{numMessages}} new messages }
198+
}}
199+
```
200+
201+
where you explicitly typed in `numMessages` for "other"
202+
instead of using `#`. They are nearly the same except if
203+
you're using "offset". Refer to the ICU MessageFormat
204+
documentation to learn about offset.
205+
206+
Please note that **other** is a **required** category (for
207+
both the plural syntax and the select syntax that is shown
208+
later.)
209+
210+
211+
### Simple select (for gender) example
212+
213+
```
214+
{{friendGender, select,
215+
male { Invite him }
216+
female { Invite her }
217+
other { Invite them }
218+
}}
219+
```
220+
221+
### More complex example that combines some of these
222+
223+
This is taken from the [plunker example](http://plnkr.co/edit/QBVRQ70dvKZDWmHW9RyR?p=preview) linked to earlier.
224+
225+
```
226+
{{recipients.length, plural, offset:1
227+
=0 {You ({{sender.name}}) gave no gifts}
228+
=1 { {{ recipients[0].gender, select,
229+
male {You ({{sender.name}}) gave him ({{recipients[0].name}}) a gift.}
230+
female {You ({{sender.name}}) gave her ({{recipients[0].name}}) a gift.}
231+
other {You ({{sender.name}}) gave them ({{recipients[0].name}}) a gift.}
232+
}}
233+
}
234+
one { {{ recipients[0].gender, select,
235+
male {You ({{sender.name}}) gave him ({{recipients[0].name}}) and one other person a gift.}
236+
female {You ({{sender.name}}) gave her ({{recipients[0].name}}) and one other person a gift.}
237+
other {You ({{sender.name}}) gave them ({{recipients[0].name}}) and one other person a gift.}
238+
}}
239+
}
240+
other {You ({{sender.name}}) gave {{recipients.length}} people gifts. }
241+
}}
242+
```

lib/grunt/utils.js

+8-1
Original file line numberDiff line numberDiff line change
@@ -185,15 +185,22 @@ module.exports = {
185185
var mapFileName = mapFile.match(/[^\/]+$/)[0];
186186
var errorFileName = file.replace(/\.js$/, '-errors.json');
187187
var versionNumber = grunt.config('NG_VERSION').full;
188+
var compilationLevel = 'SIMPLE_OPTIMIZATIONS';
189+
var googMINIFIED = '';
190+
if (file === 'build/angular-messageFormat.js') {
191+
compilationLevel = 'ADVANCED_OPTIMIZATIONS';
192+
googMINIFIED = '--define goog.MINIFIED=true ';
193+
}
188194
shell.exec(
189195
'java ' +
190196
this.java32flags() + ' ' +
191197
'-Xmx2g ' +
192198
'-cp bower_components/closure-compiler/compiler.jar' + classPathSep +
193199
'bower_components/ng-closure-runner/ngcompiler.jar ' +
194200
'org.angularjs.closurerunner.NgClosureRunner ' +
195-
'--compilation_level SIMPLE_OPTIMIZATIONS ' +
201+
'--compilation_level ' + compilationLevel + ' ' +
196202
'--language_in ECMASCRIPT5_STRICT ' +
203+
googMINIFIED +
197204
'--minerr_pass ' +
198205
'--minerr_errors ' + errorFileName + ' ' +
199206
'--minerr_url http://errors.angularjs.org/' + versionNumber + '/ ' +

src/module_closure.prefix

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
/**
2+
* @license AngularJS v"NG_VERSION_FULL"
3+
* (c) 2010-2015 Google, Inc. http://angularjs.org
4+
* License: MIT
5+
*/
6+
/** @const */
7+
var goog = goog || {};
8+
/** @define {boolean} */
9+
goog.MINIFIED = false;
10+
(function(window, angular, undefined) {

src/module_closure.suffix

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
2+
})(window, window.angular);

0 commit comments

Comments
 (0)