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

feat($interpolate): MessageFormat extensions #11152

Closed
wants to merge 1 commit into from

Conversation

chirayuk
Copy link
Contributor

See plunker example

Extend interpolation with MessageFormat like syntax.

Ref: https://docs.google.com/a/google.com/document/d/1pbtW2yvtmFBikfRrJd8VAsabiFkKezmYZ_PbgdjQOVU/edit

Example:

{{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.

@0x-r4bbit
Copy link
Contributor

@chirayuk thank you for implementing this! Where can I start helping out for the docs?

@chirayuk chirayuk force-pushed the msgFmtService branch 2 times, most recently from 250781a to d91e802 Compare March 5, 2015 00:40
@chirayuk
Copy link
Contributor Author

chirayuk commented Mar 5, 2015

Changes since the last time:

  • Separate self contained module. Use angular-messageformat.js and depend on ngMessageFormat.
  • NO changes to existing $interpolate or $parse. I did a major refactor and the core no longer knows or cares about this. However, this is a private service and relies on core implementation.
  • Full $interpolate replacement
  • Supports mustHaveExpression, trustedContext and allOrNothing.
  • Escaping compatible with $interpolate.
  • Includes the entire contents of interpolateSpec.js to test the replacement behavior. See https://github.com/angular/angular.js/pull/11152/files#diff-3746da2fc13376182459459ead442464R229
  • Support for # symbol to mean (number-offset) inside plural messages. (For compatibility with messageformat.)
  • Escaping support inside MessageFormat expressions.
  • Accurate recognition of angular expressions—understands subexpressions, strings/string escapes, etc.
  • Compilation with Closure's ADVANCED_OPTIMIZATIONS mode.
  • And other stuff I can't remember. :)


var noop = angular['noop'],
isFunction = angular['isFunction'],
toJson = angular['toJson'];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why use lookup (angular['noop']) rather than property (angular.noop) syntax here. Is it because of the Google Closure compilation optimizations?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. I'm using the ADVANCED_OPTIMIZATIONS mode of the Closure compiler which will rewrite angular.noop into something like angular.x. This module has limited interaction with outside code (and injection has existing array syntax to guard against minification) so there aren't too many places that do this kind of explicit lookup.

@petebacondarwin
Copy link
Member

There are a bunch of jshint and jscs errors. Can you ensure that grunt ci-checks passes?

@petebacondarwin
Copy link
Member

I think that the filenames should be messageFormat.js and messageFormatSpec.js for consistency with other angular files.

return cachedFn;
}
function parsedFn(context) { return text; };
var unwatch;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this unwatch variable declared outside the watchDelegate function?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed. It was cruft from a refactor.

@petebacondarwin
Copy link
Member

I think it would make it easier to grok this module if it was broken up into a few smaller src files. For instance you have message selector stuff, parsing rules, decorating $interpolate.

I can see that the code is generally structured as classes. In the theme of writing stuff that will run the same code base in V2 as V1, perhaps this code could be written as ES6 classes and then transpiled down for this V1 ngMessageFormat module?

case "(": return ")";
default: return null;
}
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would this (and getBeginOperator) benefit from being written as:

var END_OPERATORS { '{': '}', '[': ']', '(': ')' };
function getEndOperator(opBegin) {
 return END_OPERATORS[opBegin];
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A jsperf I wrote a little while back strongly favors the switch statement. (https://jsperf.com/map-vs-switch-vs-if) I don't particularly think one reads any clearer than the other so I went with the switch statement.

@petebacondarwin
Copy link
Member

A few nitpicks and questions but generally looking good. It would be nice to see a state diagram of the parser somewhere for good measure.

@petebacondarwin petebacondarwin added this to the 1.4.0-beta.6 / 1.3.15 milestone Mar 11, 2015
@chirayuk chirayuk force-pushed the msgFmtService branch 5 times, most recently from 62c0849 to 2aaaaa6 Compare March 12, 2015 02:48
@chirayuk
Copy link
Contributor Author

  • ensure that grunt ci-checks passes
  • filenames should be messageFormat.js and messageFormatSpec.js
  • split into multiple files
  • add error docs
  • expand guide/i18n documentation
  • show state diagram
  • This is a good idea. I will do this in a follow up commit.
  • Use ES6 classes and transpile
  • This is out of scope for this commit. I intend to do so in a future commit via some mechanical refactoring changes.

@chirayuk
Copy link
Contributor Author

@petebacondarwin @IgorMinar ready for another review.

@chirayuk chirayuk force-pushed the msgFmtService branch 2 times, most recently from 9c2d3b3 to 3507e6f Compare March 12, 2015 06:37
@description

You have repeated a match selection for your plural or select MessageFormat
extension in your interpolation interpolation expression. The different
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

interpolation interpolation?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed. Thanks!

@petebacondarwin
Copy link
Member

Nice one @chirayuk - this looks even better!

The only serious concern I have is with the bindingInfo bits.

Other than that it looks good to merge, once the few docs fixes are in place.

@chirayuk
Copy link
Contributor Author

Other than the bindingInfo bits—that are easy to fix—the other comments have been addressed. I'll address the bindingInfo bit this after after I get back from the Japanese Consulate. Thanks for all the comments!

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.
@chirayuk chirayuk closed this in 1e58488 Mar 17, 2015
netman92 pushed a commit to netman92/angular.js that referenced this pull request Aug 8, 2015
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 angular#11152
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants