UNPKG

curl-amd

Version:

curl.js is small, fast, extensible module loader that handles AMD, CommonJS Modules/1.1, CSS, HTML/text, and legacy scripts.

225 lines (204 loc) 8.76 kB
/** MIT License (c) copyright 2010-2013 B Cavalier & J Hann */ /** * curl i18n plugin * * Fetch the user's locale-specific i18n bundle (e.g. strings/en-us.js"), * any less-specific versions (e.g. "strings/en.js"), and a default i18n bundle * (e.g. "strings.js"). All of these files are optional, but at least one * is required or an error is propagated. * * If no locale-specific versions are found, use a default i18n bundle, if any. * * If multiple locale-specific versions are found, merge them such that * the more specific versions override properties in the less specific. * * Browsers follow the language specifiers in the Language-Tags feature of * RFC 5646: http://tools.ietf.org/html/rfc5646 * * Example locales: "en", "en-US", "en-GB", "fr-FR", "kr", "cn", "cn-" * * These are lower-cased before being transformed into module ids. This * plugin uses a simple algorithm to formulate file names from locales. * It's probably easier to show an example than to describe it. Take a * look at the examples section for more information. The algorithm may * also be overridden via the localToModuleId configuration option. * * Nomenclature (to clarify usages of "bundle" herein): * * i18n bundle: A collection of javascript variables in the form of a JSON or * javascript object and exported via an AMD define (or CommonJS exports * if you are using the cjsm11 shim). These are typically strings, but * can be just about anything language-specific or location-specific. * * AMD bundle: A concatenated set of AMD modules (or AMD-wrapped CommonJS * modules) that can be loaded more efficiently than individual modules. * * Configuration options: * * locale {Boolean|String|Function} (default === true) * tl;dr: `false` means only do the minimum work to get the correct locale * bundle or use the default. `true` means do whatever possible to try * to get the correct locale bundle before using the default. A string * means do the minimum work to get the locale specified in the string * or use the default. The default value of this param is `true` for * backwards compat, but you probably want `false` to prevent extra * fetches to the server when a locale isn't already loaded. * * If an explicit `true` value is provided, the plugin will sniff the * browser's clientInformation.language property (or fallback equivalent) * to determine the locale and seek a locale-specific i18n bundle. If the * bundle is not already loaded, it will be fetched, potentially in many * parts -- one part for each dash-delimited term in the locale. * If the no bundles are found, an error is returned. * * If an explicit `false` value is provided, the plugin will sniff the * browser's locale and use it if it is already loaded. If it is not loaded * it will attempt to use (and potentially fetch) the default bundle. * (Note: within a bundle, the default bundle will not be fetched from the * server.) If the default bundle is not found, an error is returned. * This is a great option when you don't want this plugin to attempt to fetch * (possibly unsupported) locales automatically, like `true` does. * * If this is a string, it is assumed to be an RFC 5646-compatible language * specifier(s). The plugin will seek the i18n bundle for this locale * and use the default bundle if it doesn't exist. (Note: within a bundle, * the default bundle will not be fetched from the server.) * This is an excellent option to test specific locales or to override * the browser's locale at run-time. * * This option may also be a function that returns a language specifier * string or boolean, which will be processed as per the details above. * The absolute module id and a language specifier are passed as the * parameters to this function. * * Note: contrary to our advice for most other plugin options, locale * should be specified at the package level or at the top level of the * configuration. Specifying it at the plugin level won't work when * loading code in a bundle since the i18n plugin is not used in a bundle. * The locale! plugin may be used, instead. For instance, the following * configuration for the i18n plugin will not be visible to anything in a * bundle: * * curl.config({ plugins: { i18n: { locale: false } } }); * * Use one of these configuration strategies, instead: * * // locale is configured for the "myApp" package * curl.config({ * packages: { * myApp: { location: 'myapp', config: { locale: false } } * } * }); * * // locale is configured for all packages * curl.config({ locale: false }); * * localeToModuleId {Function} a function that translates a locale string to a * module id where an AMD-formatted string bundle may be found. The default * format is a module whose name is the locale located under the default * (non-locale-specific) bundle. For example, if the default bundle is * "myview/strings.js", the en-us version will be "myview/strings/en-us.js". * Parameters: moduleId {String}, locale {String}, returns {String} * * locales {Array} a build-time-only option that specifies an array of * language specifier strings. These i18n bundles will be built into the * AMD bundle as locale bundles. * * During a build, locale-specific i18n bundles are merged into a single * bundle. This allows the lightweight locale! plugin to be used in the * build, rather than the larger i18n! plugin. The locale! plugin takes the * same run-time configuration options as the i18n! plugin and will exhibit * nearly* identical behavior. For instance, if you specify the locales for * "en" and "en-us" for a module, "foo", two separate i18n bundles, * "foo/en" and "foo/en-us" will be included in the AMD bundle. * * *The locale! plugin only fetches additional locale bundles when the * locale config option is true. * * @example * * `var strings = require("i18n!myapp/myview/strings");` * * If the current user's locale is "en-US", this plugin will simultaneously * seek the following modules unless "i18n!myapp/myview/strings" is already * loaded: * * "myapp/myview/strings.js" * * "myapp/myview/strings/en.js" * * "myapp/myview/strings/en-us.js" * * If none are found, an error is propagated. If neither "en" or "en-us" * is found, "strings" (the default bundle) is used. If only "en" or "en-us" * is found, it is used. If both are found, "en-us" is used to override "en" * and the merged result is used. * */ define(/*=='curl/plugin/i18n',==*/ ['./locale'], function (getLocale) { return { load: function (resId, require, loaded, config) { var eb, toId, locale, bundles, fetched, id, ids, specifiers, i; eb = loaded.error; if (!resId) { eb(new Error('blank i18n bundle id.')); } // resolve config options toId = config['localeToModuleId'] || getLocale.toModuleId; locale = getLocale(config, resId); // keep track of what bundles we've found ids = [resId]; bundles = []; fetched = 0; // only look for locales if we sniffed one and the dev // hasn't said "don't fetch" via `locale: false`. if (locale && config.locale !== false) { // get variations / specificities // determine all the variations / specificities we might find ids = ids.concat(locale.split('-')); specifiers = []; // correct. start loop at 1! default bundle was already fetched for (i = 1; i < ids.length; i++) { // add next part to specifiers specifiers[i - 1] = ids[i]; // create bundle id id = toId(resId, specifiers.join('-')); // fetch and save found bundles, while silently skipping // missing ones fetch(require, id, i, got, countdown); } } // get the default bundle, if any. this goes after we get // variations to ensure that ids.length is set correctly. fetch(require, resId, 0, got, countdown); function got (bundle, i) { bundles[i] = bundle; countdown(); } function countdown () { var base; if (++fetched == ids.length) { if (bundles.length == 0) { eb(new Error('No i18n bundles found: "' + resId + '", locale "' + locale + '"')); } else { base = bundles[0] || {}; for (i = 1; i < bundles.length; i++) { base = mixin(base, bundles[i]); } loaded(base); } } } }, 'cramPlugin': '../cram/i18n' }; function fetch (require, id, i, cb, eb) { require([id], function (bundle) { cb(bundle, i); }, eb); } function mixin (base, props) { var obj = {}, p; for (p in base) obj[p] = base[p]; if (props) { for (p in props) obj[p] = props[p]; } return obj; } });