jamesarosen / ember-i18n

Other
765 stars 184 forks source link

Interpolating a component #365

Closed jeremytm closed 6 years ago

jeremytm commented 8 years ago

Is this possible? I've tried passing in a component helper:

{{t 'key' club=(component 'comp-name') bid=1000}}

with content like:

{{club}} made a bid of {{bid}}

But no luck.

I would use helpers but they don't cut it for what I need to do.

Any suggestions?

jeremytm commented 8 years ago

With some help on Stack Overflow, I've created a component which adds this functionality.

Usage: {{t-t 'key' club=(component 'comp-name') bid=1000}}

jamesarosen commented 8 years ago

The problem is that components are objects that are in charge of their DOM lifecycle, but t emits bare strings, not DOM hierarchies.

jeremytm commented 8 years ago

My t-t component solves this problem by intertwining t's string output with the DOM from components.

I'm sure it could be improved, but it works for me. What do you think?

jamesarosen commented 8 years ago

I'd love to see some use-cases. We can add it to the wiki or you could release it as an ember addon and link to it from here.

jeremytm commented 8 years ago

Well there's my case for one. A sports management game. The game is made up of sports "clubs", owned by managers.

I have made a component which displays a club, including a club image and name. The component contains semi-complex DOM for displaying a placeholder if the club hasn't uploaded an image, or if they have uploaded an image, preloads the image and fades the image once loaded.

Here is a screenshot of this club component in use with the t-t component:

t-t-img

I'd be happy to make an addon at some point, once I find time to learn how.

deepflame commented 8 years ago

for me it was quite similar.

I had text that included some icons. I wanted to have the icons displayed by a component that I would just change the icon component once I use svg spritesheets.

That did not work and I had to display the icons at the end of the text in the template (which is sad).

@jeremytm thanks for the pointer. I will look into it to solve my problem.

jamesarosen commented 8 years ago

A related question came up in the #topic-i18n channel recently: "is it possible to include actions in a translation?" It's not, but you can do it manually in a component:

i18n: Ember.inject.service(),

text: computed('i18n.locale', function() {
  const i18n = this.get('i18n');
  const part1 = Ember.String.htmlSafe(`<a href='#' data-action='part1'>${i18n.t('part1')}</a>`);
  const part2 = Ember.String.htmlSafe(`<a href='#' data-action='part2'>${i18n.t('part2')}</a>`);
  return i18n.t('parts', { part1, part2 });
}),

click(e) {
  if ($(e.target).is('[data-action=part1]')) {
    ...
  }
  if ($(e.target).is('[data-action=part2]')) {
    ...
  }
}
jamesarosen commented 8 years ago

While we're here, it's also impossible to interpolation an <a {{action}}>.

One solution would be to allow the translations to be full HTMLBars templates, but there are some problems with that:

  1. It's very hard to translate "Need help? <a {{action 'needHelp'}}>Click here</a>.". If a translator makes a typo, it could totally break the app.
  2. There's no way to evaluate them in JS-land (routes, models, services, controllers, or components)

I don't have any good ideas. I keep hoping the core team will step in and provide some direction.

jameshoward commented 8 years ago

I favour breaking the translations up when you want to do stuff like that. E.g. in the sports management example don't you only need 'by' translating. {{value}} {{t 'by'}} {{comp-name}}

Same for 'Need help?' and 'Click here'. Although arguably you shouldn't use 'click here' at all and instead just link the need help, but that's an aside.

jamesarosen commented 8 years ago

The problem with "breaking translations up," also known as "composition instead of interpolation" is that it doesn't work for all languages. See, for example, this post on German and English sentence structure. Or this article on the relative position of adjectives and the nouns they modify in different languages.

Thus, {{value}} {{t 'by'}} {{comp-name}} might need to be {{t 'by'}} {{value}} {{comp-name}} in another language.

jameshoward commented 8 years ago

Yeah, that's a good point. Hadn't thought about how values may fit into a sentence at different points.

drewchandler commented 7 years ago

On a project I work on, I have extended ember-i18n to support "decorators" which allow us to keep html out of our translations. Here is an example:

// template
{{t "greeting" bold=(tag-decorator "b")}}

// translation
greeting: "Hello, [bold world]!"

// output
Hello, <b>world</b>!

I've done this by modifying app/utils/i18n/compile-template.js to look like this:

import get from 'ember-metal/get';
import { htmlSafe } from 'ember-string';
import Ember from 'ember';

const { Handlebars: { Utils: { escapeExpression } } } = Ember;
const tripleStache = /\{\{\{\s*(.*?)\s*\}\}\}/g;
const doubleStache = /\{\{\s*(.*?)\s*\}\}/g;
const brackets = /\[(.*?) (.*?)\]/g;
const placeholder = '{}';

export default function compileTemplate(template, rtl = false) {
  return function renderTemplate(data) {
    let result = template
      .replace(tripleStache, (i, match) => get(data, match))
      .replace(doubleStache, (i, match) => escapeExpression(get(data, match)))
      .replace(brackets, (i, decorator, content) => get(data, decorator).replace(placeholder, content));

    let wrapped = rtl ? `\u202B${result}\u202C` : result;

    return htmlSafe(wrapped);
  };
}

and by adding decorator helpers like this:

import { helper } from 'ember-helper';

export function tagDecorator([tagName], options) {
  let attributes = Object.keys(options)
    .map((k) => ` ${k}=\"${options[k]}\"`)
    .join('');

  return `<${tagName}${attributes}>{}</${tagName}>`;
}

export default helper(tagDecorator);
jamesarosen commented 7 years ago

When you write

{{t 'foo.baz' baz=(component 'some-thing')}}

the {{t}} helper gets a baz that's a CurlyComponentDefinition. That's not part of Ember's public API and it's not clear how we would (a) get its HTML or (b) manage its life-cycle (e.g. call willInsertElement, didInsertElement).

jamesarosen commented 6 years ago

jamesarosen/ember-i18n has been deprecated in favor of ember-intl.