cujojs / curl

curl.js is small, fast, extensible module loader that handles AMD, CommonJS Modules/1.1, CSS, HTML/text, and legacy scripts.
https://github.com/cujojs/curl/wiki
Other
1.89k stars 216 forks source link

cram i18n plugin options #213

Closed unscriptable closed 10 years ago

unscriptable commented 10 years ago

Since my previous attempt at creating an i18n plugin that allows multiple locales to be baked into the bundle, I am throwing some ideas here so they can be discussed.

Here's an option that won't take much more coding over what we have now. This is what will be output to the bundle. Basically, the default bundle will hold all of the locale-specific variants:

define('curl/plugin/i18n!strings', ['curl/plugin/locale', 'module'], function (getLocale, module) {
    var config, locale, bundles;
    config = typeof module.config == 'function' ? module.config() : {};
    locale = getLocale(config, module.id);
    bundles = {
        "default": { /* merged default bundle is here */ },
        "en": { /* merged en bundle is here */ },
        "en-us": { /* merged en-us bundle is here */ },
        "en-gb": { /* merged en-gb bundle is here */ }
    };
    if (!locale && !(locale in bundles)) locale = "default";
    return bundles[locale];
});

With some more effort, we could separate the run-time plugin's merge behavior into the curl/plugin/locale module and reuse it in the bundle:

define('curl/plugin/i18n!strings', ['curl/plugin/locale', 'module'], function (getLocale, module) {
    var config, locale, bundles;
    config = typeof module.config == 'function' ? module.config() : {};
    locale = getLocale(config, module.id);
    bundles = {
        "default": { /* unmerged default bundle is here */ },
        "en": { /* unmerged en bundle is here */ },
        "en-us": { /* unmerged en-us bundle is here */ },
        "en-gb": { /* unmerged en-gb bundle is here */ }
    };
    if (!locale && !(locale in bundles)) locale = "default";
    return getLocale.merge(bundles, locale);
});

Here's what I like about these ideas:

  1. No run-time plugin. Devs can use a minimal AMD environment shim such as almond.js.
  2. The code is very clear, imho.

Here's what I don't like:

  1. It relies on module.config(). While this is buried in the AMD standard docs, it might not be widely supported. Therefore, I added the sniff for it. Devs will have to be aware that the locale might not be determined correctly in some AMD environments.
  2. It's a lot of code to generate compared to the current, busted implementation.

Feedback welcome! Any other ideas?

-- John

cc @gehan

unscriptable commented 10 years ago

For comparison, here's what the current impl outputs (does not work due to the way AMD loaders check the cache before attempting to use plugins):

;define('curl/plugin/locale!strings/en-us.js', function () {
return {"beverage":"beer"};
});
define('curl/plugin/locale!strings/en-gb.js', function () {
return {"bus":"lorry","beverage":"pint"};
});
define('curl/plugin/locale!strings.js', function () {
return {"bus":"bus","beverage":"beverage"};
});
define('curl/plugin/i18n!strings.js', ['curl/plugin/locale!strings.js'], function (bundle) {
return bundle;
});
unscriptable commented 10 years ago

Hm, this could be condensed a bit if we push the config and locale logic into the merge function:

define('curl/plugin/i18n!strings', ['curl/plugin/locale', 'module'], function (getLocale, module) {
    return getLocale.merge(
        {
            "default": { /* unmerged default bundle is here */ },
            "en": { /* unmerged en bundle is here */ },
            "en-us": { /* unmerged en-us bundle is here */ },
            "en-gb": { /* unmerged en-gb bundle is here */ }
        }, 
        typeof module.config == 'function' ? module.config() : {}
    );
});
unscriptable commented 10 years ago

After prototyping and testing this idea, I realized it has a major flaw: if the dev specifies locale: true at run time, the bundle will have no way to fetch additional locales. Also, I think that keeping the i18n bundles as separate modules is a better idea even if it is less efficient in terms of bytes.

I'm going to think about the current implementation a bit more. There is probably an easy way to make it work.

Also, I'm going to think about a way to tell the cram plugin not to use a run-time plugin in the bundle. Something like { lockLocale: true, locale: false } or { forceLocale: false } to force the locale to false, regardless of run-time setting.

gehan commented 10 years ago

What was wrong with just including the string modules without the plugin prefix? The i18n plugin would run as usual but it will already have all the locale strings in the cache so it won't need to fetch anything. You'd be including the full i18n/locale plugins though.

;define('strings/en-us.js', function () {
return {"beverage":"beer"};
});
define('strings/en-gb.js', function () {
return {"bus":"lorry","beverage":"pint"};
});
define('strings.js', function () {
return {"bus":"bus","beverage":"beverage"};
});

// full locale plugin
define('curl/plugins/locale', function(i18n){
});
// full i18n plugin
define('curl/plugins/i18n', ['curl/plugins/i18n'], function(i18n){
});
unscriptable commented 10 years ago

You are right. I realized this last night while trying to sleep (not trying all that hard, obviously). I didn't understand what you were talking about the first time you mentioned it. :/

Any ideas how we could let the dev decide to omit the plugins from the bundle? Some way to specify that there will not be a need to dynamically load any further locales.

gehan commented 10 years ago

Why would you want to do that? Would only save a few kb. Or is that really to exclude runtime plugins for use in almond say?

unscriptable commented 10 years ago

Yah. Minimal loaders, such as almond.js, might not know how to invoke plugins.

gehan commented 10 years ago

I guess something like this then?


// Define all the string packages
;define('strings/en-us.js', function () {
return {"beverage":"beer"};
});
define('strings/en-gb.js', function () {
return {"bus":"lorry","beverage":"pint"};
});
define('strings.js', function () {
return {"bus":"bus","beverage":"beverage"};
});

// You'll have to run some code at runtime otherwise you can't sniff for locale,
// but can include the locale module and run it, but not as a plugin
define('curl/plugin/i18n!strings.js', ['curl/plugin/locale'], function (locale) {
    // Sniff the browsers locale
    var thisLocale = locale.getLocale();
    // Fetch the relevant module for the current locale. If it is included in the
    // bundle then it'll be no requests. If it is not included then it'll result in
    // requests. If using almond or a minimal loader then I guess they don't
    // know how to fetch modules so you'd have to set fetching modules
    // as an option?
    return locale.fetchAndMergeBundle(thisLocale);
});
unscriptable commented 10 years ago

I just pushed the latest i18n fixes to curl/dev.

unscriptable commented 10 years ago

Your code snippet is about right. The one exception is that the fetchAndMergeBundle function can't actually fetch. It's too late to do async operations once we're inside a module. But that's the point: if the dev specifies forceLocale:false or forceLocale:en-us when running cram, then the code in the bundle should never attempt to fetch.

gehan commented 10 years ago

Works great now! Nice work :)

One thing, curl seems to not bundle the domReady plugin for some reason, I have to include it manually. Not sure if it's my build or something. Not a big thing.

Well that means our build is now 2 files! (the other one is lodash) - awesome!

unscriptable commented 10 years ago

Interesting about the domReady! plugin. Closing this issue. Next is lodash. :)

unscriptable commented 10 years ago

curl 0.7.6 published. You should be able to bower update to get the latest version in cram.

gehan commented 10 years ago

Awesome! I'll try it out tomorrow. Assume there's also going to be a cram release? Can still point to the dev branch though

On 21 August 2013 21:47, John Hann notifications@github.com wrote:

curl 0.7.6 published. You should be able to bower update to get the latest version in cram.

— Reply to this email directly or view it on GitHubhttps://github.com/cujojs/curl/issues/213#issuecomment-23048935 .

unscriptable commented 10 years ago

gonna try to fix the lodash issues before releasing cram.