-
Notifications
You must be signed in to change notification settings - Fork 182
Update helper to allow binding hash to be passed #423
Conversation
A second parameter (non hash) should be able to be set to cover cases when a hash has been built and needs to be passed to the `{{t}}` helper.
|
Can you show some example usage? Also: is there a way you can make this backwards compatible? |
|
To answer your second question first, I believe it should be backwards compatible. The default for the second param is As for your first question, here is a simple example based off the wiki. someDynamicKey: "user.followers.title",
someBindingHash: { count: "followersCount" },These two properties could be set up (before translation) and sent as key and a hash of bindings to a template, passed down to a component, or passed to a service to get rendered elsewhere. It basically behaves like the The handlebars just looks like this: {{t someDynamicKey someBindingHash}}There was some talk around making a splat helper for handlebars (handlebars-lang/handlebars.js#1149), but in the mean time it would be nice to have support to pass a hash to this helper. Thoughts? Or would you like a more thorough example? |
|
What's wrong with {{t user.followers.title (hash count=followersCount)}}? |
|
Maybe I'm missing something? 😄 I'm pretty sure |
|
Sorry, I meant {{t user.followers.title count=followersCount}} |
|
Ah, makes sense. Nothing is wrong with that if you have a static translation key, and you know the bindings you'll want to apply for that key, like this example. If you want to only display one translated line of text, for example, a custom error message, but you have multiple translations for different error cases and different bindings for those cases, then what you are currently stuck with, in my opinion, are less desirable options. You can either make static key translations using the helper for each case with conditionals to switch between them, multiple templates and switch between those, or you have to use the service to translate in javascript, which you still have to make into a computed property, considering you want it to watch These are more complicated use cases, for sure, but it's nice to have the ability to dynamically pass keys and therefore also bindings for larger applications to cut down on the amount of code that needs to be written. |
It seems you're trying to add a layer of indirection, but I don't understand what it's doing or why. I think ember, Handlerbars, and ember-i18n already give you all the power you need.
Here's how that might look. // translations:
errors: {
foo: {
invalid: "Foo {{foo.name}} is invalid}}"
},
bar: {
invalid: "Bar {{bar.title}} is invalid}}"
}
}// component or controller:
errors: Ember.computed('foo', 'bar', function() {
return [
{ key: 'errors.foo.invalid', foo: this.get('foo') },
{ key: 'errors.bar.invalid', bar: this.get('bar') },
];
})and {{!-- template --}}
{{#each errors as |error|}}
{{t error.key error}}
{{/each}}I think ember, Handlebars, and ember-i18n already give you all the indirection you need. If not, please write out a more full code use-case, because I still don't understand the indirection you're going for. |
|
I'd like indirection for the bindings. The key was my mishap because like you mentioned you can already pass a dynamic key (I thought you meant a string in your post). Is this |
|
In my above example, {{#each errors as |error|}}
{{t error.key error}}
{{/each}}is equivalent to {{t 'errors.foo.invalid' key='errors.foo.invalid' foo=foo}}
{{t 'errors.bar.invalid' key='errors.bar.invalid' bar=bar}}The translation doesn't have interpolations for This functionality already exists in v4.0 due to the way Handlebars binding works. |
|
Aha! Now I understand :) This might be a change with the move to Glimmer, or it might be the way Handlebars has always worked and I was expecting the Helper (In the following examples, In handlebars-lang/handlebars.js#1050, @mixonic proposed the Handlebars splat operator, which would turn an object into hash params. {{t 'foo' *foo}}would be equivalent to {{ 'foo' bar='baz' }}@machty and @wycats are working on implementing something like that in handlebars-lang/handlebars.js#1149. Their preferred syntax (to better match ES6's splat operator) is {{t 'foo' =...foo}}Since that isn't merged, it obviously won't solve your problems now. Option 1We change the {{t 'foo' foo}}would be equivalent to {{t 'foo' bar='baz'}}The following would emit a warning and ignore the hash options: {{t 'foo' foo wibble=wobble}}Option 2Option 1, but combing context object and hash options is allowed. {{t 'foo' foo wibble=wobble}}would be equivalent to {{t 'foo' bar='baz' wibble=wobble}}The problem with Option 2 is that we can't merge the context hash into the context object (since we don't want to mutate it) and we can't merge the object into the hash (since it might be an Ember.Object or an instance of a class with prototype properties). The only way to make that work would be to move support down two layers to the translation compiler: export default function compileTemplate(template, rtl = false) {
return function renderTemplate(contextHash, contextObject) {
const result = template
// interpolate, preferring contextHash properties to those from contextObject:
.replace(tripleStache, (i, match) => getFromAorB(contextObject, contextObject, match))
.replace(doubleStache, (i, match) => escapeExpression(getFromAorB(contextObject, contextObject, match)));
const wrapped = rtl ? `\u202B${result}\u202C` : result;
return htmlSafe(wrapped);
};
}
function getFromAorB(a, b, key) {
const fromA = get(a, key);
return fromA === undefined ? get(b, key) : fromA;
}But people do override the template compiler. We would need to warn them if they compile templates to a function that only takes one argument (hash) instead of two (hash, object), at least if they use the extra argument. It would also mean adding support to the SummaryIf the Handlebars team are close to shipping their own splat operator, I'd prefer to just use that. If they're not, people will probably expect Option 2, so we should put in the effort to make that work. |
😄
Exactly, it would be great if the behaviour matched. With my code in this RFC,
|
What if you did the following? {{t 'foo' myObject foos=1}}
{{t 'foo' myObject foos=2}}If we copy If we copy from |
|
We could, however, change the implementation to make function mergedContext(contextObject, contextHash) {
return Ember.Object.extend(contextHash, {
unknownProperty(key) {
return get(contextObject, key);
}
}).create();
} |
|
I was just going to mention Could we also perhaps just do Edit: It gives preference to the hash while not setting on the object. |
|
I can write some tests to check all the cases we've been mentioning? |
It still does a |
|
Ah, that's a good point. It is better to be lazy 😆 . I'll change the code around. |
Newer versions of ember pash helper hash as EmptyObject which can't be extended.
|
Apparently in glimmer 2 you can't extend helper hashes anymore as they are |
|
How about Ember.Object.extend({
unknownProperty(key) {
const fromHash = get(hashContext, key);
fromHash === undefined ? get(objectContext, key) : fromHash;
}
}).create() |
|
Thoughts on the tests and the code so far? |
| const i18n = this.get('i18n'); | ||
| return i18n.t(key, interpolations); | ||
| function mergedContext(objectContext, hashContext) { | ||
| return EmberObject.extend({ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we're going to run into problems when a property of objectContext changes. We'll want a test for
{{t 'some.key' obj hashKey=hashValue}}where we change obj.someProperty and hashValue and make sure both are reflected in the translated text.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's a fair point. I'll add a test.
…or interpolations
|
This PR now makes it obvious how cumbersome the existing test suite is here. I'd like to rework as much as possible into component (integration) tests that
That doesn't have to happen before we merge this, but I'd like to have it happen soon. |
|
I was thinking that integration tests would be better when I was writing this. 😄 |
|
Integration tests for the helper: #425 |
|
Do you want me to wait until the tests are in to fix these ones or merge this first and come back and fix these tests? |
Instead of setting up complex interactions with a bunch of translations in the dummy app, write a component integration test that sets up exactly the template and binding needed for each scenario.
|
I think it might be easier to review the new tests here if we switch to integration tests first. I'm sorry to make you do more work. I'm happy to help migrate the tests! |
|
Sure, it's no problem. |
| 'with': { | ||
| interpolations: 'Clicks: {{clicks}}' | ||
| interpolations: 'Clicks: {{clicks}}', | ||
| interpolationsWithContextObject: 'Clicks: {{clicks}}, Clicks from hash: {{otherClicks}}' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No longer used
|
Released in v4.5.0 |
|
Thanks @jamesarosen 🎉 |

When translations need to occur across components or via services (for example, a flash message), it's easier to build a hash to send to the service to be rendered.
A second parameter (non hash) should be able to be set to cover cases when a hash has been built and needs to be passed to the
{{t}}helper.I'm not sure if this is in line with the vision for this addon, so this is just an RFC for now, I'm happy for comments or suggestions. Tests to come if this is all 👍