formatjs / intl-messageformat

[MIGRATED] Format a string with placeholders, including plural and select support to create localized messages.
http://formatjs.io/
Other
530 stars 77 forks source link

support for nested/hierarchal properties in replacement variables #113

Closed redbugz closed 9 years ago

redbugz commented 9 years ago

There are times would it be handy to resolve a nested/hierarchal property or key path.

new IntlMessageFormat('{a.key.path}').format({a:{key:{path:'value'}}});

But we get an error that periods are not allowed. However, dashes are allowed

> new IntlMessageFormat('{parent-child}').format({"parent-child":'child'});
'child'

I recognize this would lead to potential ambiguities, but they could be handled via policy or configuration if this is generally desirable feature. We are looking at replacing some our existing i18n functions that do support this, but this would require us to do some special pre-processing to flatten our data structures for replacement.

I'm just curious if this is feasible to allow this. I can probably do the PR to allow it, if it's decided it's a good idea and the rules for resolving the paths are clear. Obviously there are potential issues.

var data = {
  "parent": {
    "child": "I am a parent.child value"
  },
  "parent.child": "I am also a parent.child value, depending on implementation",
  "parent-child": "This is allowed today, as dashes are supported, but periods are not"
}

Just curious what the thoughts are about this.

caridy commented 9 years ago

unfortunately, we don't have a formal specification for ICU messages, only reference implementations in java and C++. I think we should NOT add capabilities for namespace lookup since that's a very javascript thing, but that's just me :). /cc @ericf @SlexAxton

SlexAxton commented 9 years ago

We have also punted this in messageformat.js for the same reasons. It'd break in any other implementation, and probably in any translator tooling.

redbugz commented 9 years ago

That's too bad. I guess that's what happens when there's no spec :frowning:

So for the Ember implementation, you force the data structures to be flattened for variable replacement? I'm not familiar with Ember, but I was thinking of this in context of Angular and Polymer, and at least in Angular it is highly common to use paths/hierarchy in variable replacements. I suppose it can be implemented at the wrapper level, it's just so tedious to arbitrarily flatten the data structures just for translations when the system already supports the existing hierarchy for data binding.

SlexAxton commented 9 years ago

Yea, fwiw, I use ember-intl and since you can pass everything in as a parameter, this pretty much never comes up for us.

<button>{{t "some.message" some=deeply.nested.thing can=always.be.accessed or=often.even.renamed}}</button>
SlexAxton commented 9 years ago

Alternatively, we also have a flatten helper:

<button>{{t "some.message" (flatten foo=a.b.foo bar=c.d.bar)}}</button>
SlexAxton commented 9 years ago

I actually like these better than having namespaces directly in message-format messages because it so closely ties the messages to the single implementation. I almost like to write my message with no knowledge of what variables I have, and then map the ones I end up having accordingly. That way my messages are maximally flexible.

redbugz commented 9 years ago

Ahh, OK. That makes more sense. I guess it isn't as tedious as I was thinking. For the express-intl I just created, I guess it would go like this:

var messages = {"en": { "some": { "message": "Hi {foo}, welcome to {bar}"}}} intl.get("some.message", {foo: a.b.foo, bar: c.d.bar})

In our existing strings we have it more like this: var messages = {"en": { "some": { "message": "Hi {a.b.foo}, welcome to {c.d.bar}"}}} var data = {a:{b:{foo: "foo"}}, c:{d:{bar: "bar"}}} intl.get("some.message", data) where data has a.b.foo and c.d.bar on it It's probably cleaner to have the abstraction like you said, frees up your strings from the data structure with just a thin mapping layer.

But there should still be some documentation on what characters are allowed just to make it clear. I have seen Java projects that use dots for their strings as separators, not for hierarchy, etc. So it depends on how much interoperability is desired.

redbugz commented 9 years ago

I can try and make a PR with some updates to the docs.

amccloud commented 8 years ago

@redbugz

This works:

let data = {
  world: {city: 'Las Vegas'},
  // ... and much more data
};

let formatter = new IntlMessageFormat('Hello {world/city}', 'en');
formatter.format(flatten(data, {delimiter: '/'})); // => "Hello Las Vegas"

This doesn't work:

let data = {
  world: {city: 'Las Vegas'},
  // ... and much more data
};

let formatter = new IntlMessageFormat('Hello {world.city}', 'en');
formatter.format(flatten(data, {delimiter: '.'}));

// => SyntaxError {message: "Expected ",", "}" or [^ \t\n\r,.+={}#] but "." found.", expected: Array[3], found: ".", offset: 12, line: 1…}

@caridy @SlexAxton Is there a reason why . is not treated just like / or -?

stramel commented 7 years ago

@amccloud Where is your flatten function coming from?

amccloud commented 7 years ago

@stramel https://www.npmjs.com/package/flat

stramel commented 7 years ago

@amccloud Thanks!