Permalink
Browse files

feat($interpolate): extend interpolation with MessageFormat like syntax

For more detailed information refer to this document:
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.js`
or `angular-messageformat.min.js`.

In addition, your application module should depend on the "ngMessageFormat"
(e.g. angular.module('myApp', ['ngMessageFormat']);)

When you use the `ngMessageFormat`, the $interpolate gets overridden with
a new service that adds the new MessageFormat behavior.

**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
  the features availability for that syntax.)
- The first part of such a 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 yet supported.

Closes #11152
  • Loading branch information...
1 parent 170ff9a commit 1e58488ad65abf7031bab5813523bb9d86dbd28c @chirayuk chirayuk committed with petebacondarwin Feb 12, 2015
View
@@ -126,6 +126,9 @@ module.exports = function(grunt) {
ngLocale: {
files: { src: 'src/ngLocale/**/*.js' },
},
+ ngMessageFormat: {
+ files: { src: 'src/ngMessageFormat/**/*.js' },
+ },
ngMessages: {
files: { src: 'src/ngMessages/**/*.js' },
},
@@ -200,6 +203,10 @@ module.exports = function(grunt) {
dest: 'build/angular-resource.js',
src: util.wrap(files['angularModules']['ngResource'], 'module')
},
+ messageformat: {
+ dest: 'build/angular-messageFormat.js',
+ src: util.wrap(files['angularModules']['ngMessageFormat'], 'module')
+ },
messages: {
dest: 'build/angular-messages.js',
src: util.wrap(files['angularModules']['ngMessages'], 'module')
@@ -232,6 +239,7 @@ module.exports = function(grunt) {
animate: 'build/angular-animate.js',
cookies: 'build/angular-cookies.js',
loader: 'build/angular-loader.js',
+ messageformat: 'build/angular-messageFormat.js',
messages: 'build/angular-messages.js',
touch: 'build/angular-touch.js',
resource: 'build/angular-resource.js',
View
@@ -94,6 +94,13 @@ var angularFiles = {
'src/ngCookies/cookieStore.js',
'src/ngCookies/cookieWriter.js'
],
+ 'ngMessageFormat': [
+ 'src/ngMessageFormat/messageFormatCommon.js',
+ 'src/ngMessageFormat/messageFormatSelector.js',
+ 'src/ngMessageFormat/messageFormatInterpolationParts.js',
+ 'src/ngMessageFormat/messageFormatParser.js',
+ 'src/ngMessageFormat/messageFormatService.js'
+ ],
'ngMessages': [
'src/ngMessages/messages.js'
],
@@ -184,6 +191,7 @@ var angularFiles = {
'@angularSrcModules',
'src/ngScenario/browserTrigger.js',
'test/helpers/*.js',
+ 'test/ngMessageFormat/*.js',
'test/ngMock/*.js',
'test/ngCookies/*.js',
'test/ngRoute/**/*.js',
@@ -212,6 +220,7 @@ var angularFiles = {
angularFiles['angularSrcModules'] = [].concat(
angularFiles['angularModules']['ngAnimate'],
+ angularFiles['angularModules']['ngMessageFormat'],
angularFiles['angularModules']['ngMessages'],
angularFiles['angularModules']['ngCookies'],
angularFiles['angularModules']['ngResource'],
@@ -0,0 +1,6 @@
+@ngdoc error
+@name $interpolate:badexpr
+@fullName Expecting end operator
+@description
+
+The Angular expression is missing the corresponding closing operator.
@@ -0,0 +1,11 @@
+@ngdoc error
+@name $interpolate:dupvalue
+@fullName Duplicate choice in plural/select
+@description
+
+You have repeated a match selection for your plural or select MessageFormat
+extension in your interpolation expression. The different choices have to be unique.
+
+For more information about the MessageFormat syntax in interpolation
+expressions, please refer to MessageFormat extensions section at
+{@link guide/i18n#MessageFormat Angular i18n MessageFormat}
@@ -0,0 +1,12 @@
+@ngdoc error
+@name $interpolate:logicbug
+@fullName Bug in ngMessageFormat module
+@description
+
+You've just hit a bug in the ngMessageFormat module provided by 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!
+
+For more information about the MessageFormat syntax in interpolation
+expressions, please refer to MessageFormat extensions section at
+{@link guide/i18n#MessageFormat Angular i18n MessageFormat}
@@ -0,0 +1,17 @@
+@ngdoc error
+@name $interpolate:nochgmustache
+@fullName Redefinition of start/endSymbol incompatible with MessageFormat extensions
+@description
+
+You have redefined `$interpolate.startSymbol`/`$interpolate.endSymbol` and also
+loaded the `ngMessageFormat` module (provided by angular-messageFormat.min.js)
+while creating your injector.
+
+`ngMessageFormat` currently does not support redefinition of the
+startSymbol/endSymbol used by `$interpolate`. If this is affecting you, please
+file an issue and mention @chirayuk on it. This is intended to be fixed in a
+future commit and the github issue will help gauge urgency.
+
+For more information about the MessageFormat syntax in interpolation
+expressions, please refer to MessageFormat extensions section at
+{@link guide/i18n#MessageFormat Angular i18n MessageFormat}
@@ -0,0 +1,12 @@
+@ngdoc error
+@name $interpolate:reqarg
+@fullName Missing required argument for MessageFormat
+@description
+
+You must specify the MessageFormat function that you're using right after the
+comma following the Angular expression. Currently, the supported functions are
+"plural" and "select" (for gender selections.)
+
+For more information about the MessageFormat syntax in interpolation
+expressions, please refer to MessageFormat extensions section at
+{@link guide/i18n#MessageFormat Angular i18n MessageFormat}
@@ -0,0 +1,11 @@
+@ngdoc error
+@name $interpolate:reqcomma
+@fullName Missing comma following MessageFormat plural/select keyword
+@description
+
+The MessageFormat syntax requires a comma following the "plural" or "select"
+extension keyword in the extended interpolation syntax.
+
+For more information about the MessageFormat syntax in interpolation
+expressions, please refer to MessageFormat extensions section at
+{@link guide/i18n#MessageFormat Angular i18n MessageFormat}
@@ -0,0 +1,11 @@
+@ngdoc error
+@name $interpolate:reqendbrace
+@fullName Unterminated message for plural/select value
+@description
+
+The plural or select message for a value or keyword choice has no matching end
+brace to mark the end of the message.
+
+For more information about the MessageFormat syntax in interpolation
+expressions, please refer to MessageFormat extensions section at
+{@link guide/i18n#MessageFormat Angular i18n MessageFormat}
@@ -0,0 +1,6 @@
+@ngdoc error
+@name $interpolate:reqendinterp
+@fullName Unterminated interpolation
+@description
+
+The interpolation text does not have an ending `endSymbol` ("}}" by default) and is unterminated.
@@ -0,0 +1,12 @@
+@ngdoc error
+@name $interpolate:reqopenbrace
+@fullName An opening brace was expected but not found
+@description
+
+The plural or select extension keyword or values (such as "other", "male",
+"female", "=0", "one", "many", etc.) MUST be followed by a message enclosed in
+braces.
+
+For more information about the MessageFormat syntax in interpolation
+expressions, please refer to MessageFormat extensions section at
+{@link guide/i18n#MessageFormat Angular i18n MessageFormat}
@@ -0,0 +1,13 @@
+@ngdoc error
+@name $interpolate:reqother
+@fullName Required choice "other" for select/plural in MessageFormat
+@description
+
+Your interpolation expression with a MessageFormat extension for either
+"plural" or "select" (typically used for gender selection) does not contain a
+message for the choice "other". Using either select or plural MessageFormat
+extensions require that you provide a message for the selection "other".
+
+For more information about the MessageFormat syntax in interpolation
+expressions, please refer to MessageFormat extensions section at
+{@link guide/i18n#MessageFormat Angular i18n MessageFormat}
@@ -0,0 +1,12 @@
+@ngdoc error
+@name $interpolate:unknarg
+@fullName Unrecognized MessageFormat extension
+@description
+
+The MessageFormat extensions provided by `ngMessageFormat` are currently
+limited to "plural" and "select". The extension that you have used is either
+unsupported or invalid.
+
+For more information about the MessageFormat syntax in interpolation
+expressions, please refer to MessageFormat extensions section at
+{@link guide/i18n#MessageFormat Angular i18n MessageFormat}
@@ -0,0 +1,10 @@
+@ngdoc error
+@name $interpolate:unsafe
+@fullName MessageFormat extensions not allowed in secure context
+@description
+
+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.
+
+Read more about secure contexts at {@link ng.$sce Strict Contextual Escaping
+(SCE)} and about the MessageFormat extensions at {@link
+guide/i18n#MessageFormat Angular i18n MessageFormat}.
@@ -0,0 +1,6 @@
+@ngdoc error
+@name $interpolate:untermstr
+@fullName Unterminated string literal
+@description
+
+The string literal was not terminated in your Angular expression.
@@ -0,0 +1,8 @@
+@ngdoc error
+@name $interpolate:wantstring
+@fullName Expected the beginning of a string
+@description
+
+We expected to see the beginning of a string (either a single quote or a double
+quote character) in the expression but it was not found. The expression is
+invalid. If this is incorrect, please file an issue on github.
@@ -137,3 +137,115 @@ The Angular datetime filter uses the time zone settings of the browser. The same
application will show different time information depending on the time zone settings of the
computer that the application is running on. Neither JavaScript nor Angular currently supports
displaying the date with a timezone specified by the developer.
+
+
+<a name="MessageFormat"></a>
+## MessageFormat extensions
+
+AngularJS interpolations via `$interpolate` and in templates
+support an extended syntax based on a subset of the ICU
+MessageFormat that covers plurals and gender selections.
+
+Please refer to our [design doc](https://docs.google.com/a/google.com/document/d/1pbtW2yvtmFBikfRrJd8VAsabiFkKezmYZ_PbgdjQOVU/edit)
+for a lot more details. You may find it helpful to play with our [Plnkr Example](http://plnkr.co/edit/QBVRQ70dvKZDWmHW9RyR?p=preview).
+
+You can read more about the ICU MessageFormat syntax at
+[Formatting Messages | ICU User Guide](http://userguide.icu-project.org/formatparse/messages#TOC-MessageFormat).
+
+This extended syntax is provided by way of the
+`ngMessageFormat` module that your application can depend
+upon (shipped separately as `angular-messageFormat.min.js` and
+`angular-messageFormat.js`.) A current limitation of the
+`ngMessageFormat` module, is that it does not support
+redefining the `$interpolate` start and end symbols. Only the
+default `{{` and `}}` are allowed.
+
+This syntax extension, while based on MessageFormat, has
+been designed to be backwards compatible with existing
+AngularJS interpolation expressions. The key rule is simply
+this: **All interpolations are done inside double curlies.**
+The top level comma operator after an expression inside the
+double curlies causes MessageFormat extensions to be
+recognized. Such a top level comma is otherwise illegal in
+an Angular expression and is used by MessageFormat to
+specify the function (such as plural/select) and it's
+related syntax.
+
+To understand the extension, take a look at the ICU
+MessageFormat syntax as specified by the ICU documentation.
+Anywhere in that MessageFormat that you have regular message
+text and you want to substitute an expression, just put it
+in double curlies instead of single curlies that
+MessageFormat dictates. This has a huge advantage. **You
+are no longer limited to simple identifiers for
+substitutions**. Because you are using double curlies, you
+can stick in any arbitrary interpolation syntax there,
+including nesting more MessageFormat expressions! Some
+examples will make this clear. In the following example, I
+will only be showing you the AngularJS syntax.
+
+
+### Simple plural example
+
+```
+{{numMessages, plural,
+ =0 { You have no new messages }
+ =1 { You have one new message }
+ other { You have # new messages }
+}}
+```
+
+While I won't be teaching you MessageFormat here, you will
+note that the `#` symbol works as expected. You could have
+also written it as:
+
+```
+{{numMessages, plural,
+ =0 { You have no new messages }
+ =1 { You have one new message }
+ other { You have {{numMessages}} new messages }
+}}
+```
+
+where you explicitly typed in `numMessages` for "other"
+instead of using `#`. They are nearly the same except if
+you're using "offset". Refer to the ICU MessageFormat
+documentation to learn about offset.
+
+Please note that **other** is a **required** category (for
+both the plural syntax and the select syntax that is shown
+later.)
+
+
+### Simple select (for gender) example
+
+```
+{{friendGender, select,
+ male { Invite him }
+ female { Invite her }
+ other { Invite them }
+}}
+```
+
+### More complex example that combines some of these
+
+This is taken from the [plunker example](http://plnkr.co/edit/QBVRQ70dvKZDWmHW9RyR?p=preview) linked to earlier.
+
+```
+{{recipients.length, plural, offset:1
+ =0 {You ({{sender.name}}) gave no gifts}
+ =1 { {{ recipients[0].gender, select,
+ male {You ({{sender.name}}) gave him ({{recipients[0].name}}) a gift.}
+ female {You ({{sender.name}}) gave her ({{recipients[0].name}}) a gift.}
+ other {You ({{sender.name}}) gave them ({{recipients[0].name}}) a gift.}
+ }}
+ }
+ one { {{ recipients[0].gender, select,
+ male {You ({{sender.name}}) gave him ({{recipients[0].name}}) and one other person a gift.}
+ female {You ({{sender.name}}) gave her ({{recipients[0].name}}) and one other person a gift.}
+ other {You ({{sender.name}}) gave them ({{recipients[0].name}}) and one other person a gift.}
+ }}
+ }
+ other {You ({{sender.name}}) gave {{recipients.length}} people gifts. }
+}}
+```
View
@@ -185,14 +185,16 @@ module.exports = {
var mapFileName = mapFile.match(/[^\/]+$/)[0];
var errorFileName = file.replace(/\.js$/, '-errors.json');
var versionNumber = grunt.config('NG_VERSION').full;
+ var compilationLevel = (file === 'build/angular-messageFormat.js') ?
+ 'ADVANCED_OPTIMIZATIONS' : 'SIMPLE_OPTIMIZATIONS';
shell.exec(
'java ' +
this.java32flags() + ' ' +
'-Xmx2g ' +
'-cp bower_components/closure-compiler/compiler.jar' + classPathSep +
'bower_components/ng-closure-runner/ngcompiler.jar ' +
'org.angularjs.closurerunner.NgClosureRunner ' +
- '--compilation_level SIMPLE_OPTIMIZATIONS ' +
+ '--compilation_level ' + compilationLevel + ' ' +
'--language_in ECMASCRIPT5_STRICT ' +
'--minerr_pass ' +
'--minerr_errors ' + errorFileName + ' ' +
Oops, something went wrong.

0 comments on commit 1e58488

Please sign in to comment.