Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Break out helpers into individual exports #55

Closed
clarle opened this issue Dec 1, 2014 · 11 comments
Closed

Break out helpers into individual exports #55

clarle opened this issue Dec 1, 2014 · 11 comments

Comments

@clarle
Copy link
Contributor

clarle commented Dec 1, 2014

I was trying to use this library earlier with Ember CLI, but ran into some issues. One of the biggest limitations of this library right now is how all of the helper functions are stuck within registerWith.

For Ember, I need to be able to modify out registerWith to call Ember.Handlebars.registerBoundHelper rather than just registerHelper, or otherwise the data-binding won't cause a re-render when a model changes.

The best way for me to implement this would probably be for HandlebarsIntl to break out the helpers away from the registerWith function, and have them each be exported. Then, it'd be a lot easier for me to import them and create a separate dist package just for Ember in a build step.

@ericf
Copy link
Collaborator

ericf commented Dec 1, 2014

One of the biggest limitations of this library right now is how all of the helper functions are stuck within registerWith.

This is mainly because of {{formatHTMLMessage}} needing to access the correct Handlebars.SafeString constructor. We can look into changing this where each helper that needs access to Handlebars will either reference it as a global in the browser, or call require('handlebars') in Node. While this will make the helper functions more portable, there will be still be the problem of needing to access Ember.Handlebars (or whatever the namespace is) for Ember.


Also, @jasonmit has already begun working on an Ember CLI Add-on for FormatJS.

@jasonmit
Copy link
Contributor

jasonmit commented Dec 1, 2014

@clarle one other caveat, block helpers do not use registerBoundHelper (intl, intlGet). So registerWith needs to account for both helper types.

So far, I've opted for forking handlebars-intl since there are a few other edge cases like this. But I have an issue opened to come back to see what we can do to make handlebars-intl more reusable with Ember.Handlebars.

If you would like to collaborate on this, I'll invite you to my private repo on git.corp and we can tackle these issues together.

@bakura10
Copy link

bakura10 commented Dec 2, 2014

Hi,

This library is currently not easy at all to work with Ember. After a lot of tries, I've found a way to make it work. First, create an initializer:

import Ember from 'ember';
import locales from '../locales/locales';

/**
 * Configure the Intl
 */
export function initialize() {
  window.HandlebarsIntl.registerWith(window.Handlebars);

  var lookUp = ['formatDate', 'formatTime', 'formatRelative', 'formatNumber', 'formatMessage', 'formatHTMLMessage'],
      helpers = {};

  for (var key in window.Handlebars.helpers) {
    if (lookUp.contains(key)) {
      helpers[key] = window.Handlebars.helpers[key];
    }
  }

  Ember.Handlebars.registerBoundHelper('formatDate', function(value, hash) {
    if (typeof hash === 'object') {
      hash = hash.hash;
    }

    return helpers['formatDate'](value, hash, {data: {intl: locales}});
  });

  Ember.Handlebars.registerBoundHelper('formatTime', function(value, hash) {
    if (typeof hash === 'object') {
      hash = hash.hash;
    }

    return helpers['formatTime'](value, hash, {data: {intl: locales}});
  });

  Ember.Handlebars.registerBoundHelper('formatNumber', function(value, hash) {
    if (typeof hash === 'object') {
      hash = hash.hash;
    }

    return helpers['formatNumber'](value, hash, {data: {intl: locales}});
  });
}

export default {
  name: 'setup-intl',
  initialize: initialize
};

What this code does is registering the helper using the "registerWith" method, then manually registering all of them again but as bound helper (in this code I've only registered the helper I'm using, but you could add formatMessage too). This code automatically transfers a "intl" data.

So in your "app" folder, create a "locales" folder, and a "locales.js" file, and fill it. For instnace:

export default {
  locales: 'en',
  formats: {
    date: {
      medium: {
        day: 'numeric',
        month: 'short',
        year: 'numeric'
      }
    }
  }
};

You should load the right fill according to the locale.

This is really not convenient to work with, and I hope the formatJS can come with a cleaner solution and more Emberish solution qucikly :).

EDIT : in an ideal world, an Ember-CLI addon should expose a "localesPath" in the Brocfile (and would default to "locales" folder), and a "defaultLocale" option (would default to "en"). The locale folder would contain files named after the language ("en", "fr"...). When loading, the addon would first fetch the browser language. If there is such a locale in the locales folder, it would use this locale, otherwise would fallback to "defautltLocale". It should also offer a way to dynamically force a locale.

@ericf
Copy link
Collaborator

ericf commented Dec 2, 2014

@bakura10 We will be making a first-class Ember integration for FormatJS so that you don't have to hack around things like this. I've started to prototype what it would look like to add an explicit layer between the Intl* APIs and the high-level integrations since the integrations essentially do very similar things.

This "helpers" layer would provide wrapper functions over top of the Intl* APIs, normalize arguments, cache the underlying Intl* instances, and possibly provide the locale data. Having this layer would mean the integrations libs themselves would become even smaller and be much easier to write since they can all depend on this core "helpers" layer.

@jasonmit
Copy link
Contributor

jasonmit commented Dec 2, 2014

in an ideal world, an Ember-CLI addon should expose a "localesPath" in the Brocfile (and would default to "locales" folder), and a "defaultLocale" option (would default to "en"). The locale folder would contain files named after the language ("en", "fr"...). When loading, the addon would first fetch the browser language. If there is such a locale in the locales folder, it would use this locale, otherwise would fallback to "defautltLocale". It should also offer a way to dynamically force a locale.

This is something I'm experimenting with, but my approach is slightly different. It will attempt to extract all the CLDR data via configuration within your Brocfile. You're able to tell the broccoli-addon, responsible for the extracting, to extract to either the assets/locale folder or into the vendor file directly (in the case where you always have a default). These modules will all be in require syntax so that it plays nice with the ember-cli resolver. You'll also be able to add locale modules, similar to what you have above with app/locales/*, and it will skip that locale during the extracting process.

When the application is initializing, I'll check the browser language and load the locale module (if available) or defer the application from loading further while I fetch the locale module and, if needed, the Intl shim.

Something I'm still thinking about is how do we change the application-wide locale at runtime? If we take the weaving the data property through the helpers then we're adding a limitation. In order for all the helpers to rerender we would explicitly need to trigger the view's rerender instead of leveraging data binding.

@bakura10
Copy link

bakura10 commented Dec 2, 2014

This sounds exciting. Is there some work already to play with? :)

@froskos
Copy link

froskos commented Dec 4, 2014

Something I'm still thinking about is how do we change the application-wide locale at runtime? If we take the weaving the data property through the helpers then we're adding a limitation. In order for all the helpers to rerender we would explicitly need to trigger the view's rerender instead of leveraging data binding.

That is quite related to the main issue I found on my way to integrate Handlebars-intl with Ember. While on the side of Format JS injecting the data looks as a good strategy, Ember is thought in a different way when it comes to rendering. Basically one could never expect to get things working just by injecting whatever into the handlebars data channel, because Ember is just agnostic to its existence.

Extending the Ember.View object will work at view rendering time. The problem comes when the Format JS helper is used inside another helper which leads it not to be calculated at Ember.View rendering time. A good example is a Format JS helper wrapped in a Ember's bound if helper. Later on, when due to a change of state, the bound helper should re-render to add the new elements, the data object will not be there. Because it was not injected there. When Handlebars calls the helper... it will not know that this data should be injected. Hence FormatJS helpers declared inside the Ember helpers will fail to find it.

@ericf
Copy link
Collaborator

ericf commented Mar 17, 2015

For Ember there's now Ember Intl:
http://formatjs.io/ember/

We could could move the helpers out as exports if we wanted to only support Handlebars 3.0, which removes the need the instanceof check on SafeString instances.

@gabel
Copy link

gabel commented Apr 30, 2015

With webpack + handlebars-loader it won't work out either. The helper should be split into seperate modules.export to be included only if required.

@ericf
Copy link
Collaborator

ericf commented May 2, 2015

The helper should be split into seperate modules.export to be included only if required.

This is only possible now with Handlebars v3 and its improved SafeString support which now only needs to implement a toHTML() method.

But that said, it's not likely to be a high priority to refactor this package in that way. I also have no idea what Webpack + Handlebars-Loader even tries to do… but seems crazy that you wouldn't be able to call HandlebarsIntl.register() during some lifecycle event of the handlebars-loader.

@gabel
Copy link

gabel commented May 4, 2015

Thanks for the feedback - i'll go with i18next and SafeString.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

7 participants