/* Copyright (c) 2014, Yahoo! Inc. All rights reserved. Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms. */ /* jshint esnext: true */ "use strict"; var intl$messageformat$$ = require("intl-messageformat"), intl$relativeformat$$ = require("intl-relativeformat"), intl$format$cache$$ = require("intl-format-cache"), src$utils$$ = require("./utils.js"); // ----------------------------------------------------------------------------- var getNumberFormat = intl$format$cache$$["default"](Intl.NumberFormat); var getDateTimeFormat = intl$format$cache$$["default"](Intl.DateTimeFormat); var getMessageFormat = intl$format$cache$$["default"](intl$messageformat$$["default"]); var getRelativeFormat = intl$format$cache$$["default"](intl$relativeformat$$["default"]); function registerWith(Handlebars) { var SafeString = Handlebars.SafeString, createFrame = Handlebars.createFrame, escape = Handlebars.Utils.escapeExpression; var helpers = { intl : intl, intlGet : intlGet, formatDate : formatDate, formatTime : formatTime, formatRelative : formatRelative, formatNumber : formatNumber, formatMessage : formatMessage, formatHTMLMessage: formatHTMLMessage, // Deprecated helpers (renamed): intlDate : deprecate('intlDate', formatDate), intlTime : deprecate('intlTime', formatTime), intlNumber : deprecate('intlNumber', formatNumber), intlMessage : deprecate('intlMessage', formatMessage), intlHTMLMessage: deprecate('intlHTMLMessage', formatHTMLMessage) }; for (var name in helpers) { if (helpers.hasOwnProperty(name)) { Handlebars.registerHelper(name, helpers[name]); } } function deprecate(name, suggestion) { return function () { if (typeof console !== 'undefined' && typeof console.warn === 'function') { console.warn( '{{' + name + '}} is deprecated, use: ' + '{{' + suggestion.name + '}}' ); } return suggestion.apply(this, arguments); }; } // -- Helpers -------------------------------------------------------------- function intl(options) { /* jshint validthis:true */ if (!options.fn) { throw new Error('{{#intl}} must be invoked as a block helper'); } // Create a new data frame linked the parent and create a new intl data // object and extend it with `options.data.intl` and `options.hash`. var data = createFrame(options.data), intlData = src$utils$$.extend({}, data.intl, options.hash); data.intl = intlData; return options.fn(this, {data: data}); } function intlGet(path, options) { var intlData = options.data && options.data.intl, pathParts = path.split('.'); var obj, len, i; // Use the path to walk the Intl data to find the object at the given // path, and throw a descriptive error if it's not found. try { for (i = 0, len = pathParts.length; i < len; i++) { obj = intlData = intlData[pathParts[i]]; } } finally { if (obj === undefined) { throw new ReferenceError('Could not find Intl object: ' + path); } } return obj; } function formatDate(date, format, options) { date = new Date(date); assertIsDate(date, 'A date or timestamp must be provided to {{formatDate}}'); if (!options) { options = format; format = null; } var locales = options.data.intl && options.data.intl.locales; var formatOptions = getFormatOptions('date', format, options); return getDateTimeFormat(locales, formatOptions).format(date); } function formatTime(date, format, options) { date = new Date(date); assertIsDate(date, 'A date or timestamp must be provided to {{formatTime}}'); if (!options) { options = format; format = null; } var locales = options.data.intl && options.data.intl.locales; var formatOptions = getFormatOptions('time', format, options); return getDateTimeFormat(locales, formatOptions).format(date); } function formatRelative(date, format, options) { date = new Date(date); assertIsDate(date, 'A date or timestamp must be provided to {{formatRelative}}'); if (!options) { options = format; format = null; } var locales = options.data.intl && options.data.intl.locales; var formatOptions = getFormatOptions('relative', format, options); var now = options.hash.now; // Remove `now` from the options passed to the `IntlRelativeFormat` // constructor, because it's only used when calling `format()`. delete formatOptions.now; return getRelativeFormat(locales, formatOptions).format(date, { now: now }); } function formatNumber(num, format, options) { assertIsNumber(num, 'A number must be provided to {{formatNumber}}'); if (!options) { options = format; format = null; } var locales = options.data.intl && options.data.intl.locales; var formatOptions = getFormatOptions('number', format, options); return getNumberFormat(locales, formatOptions).format(num); } function formatMessage(message, options) { if (!options) { options = message; message = null; } var hash = options.hash; // TODO: remove support form `hash.intlName` once Handlebars bugs with // subexpressions are fixed. if (!(message || typeof message === 'string' || hash.intlName)) { throw new ReferenceError( '{{formatMessage}} must be provided a message or intlName' ); } var intlData = options.data.intl || {}, locales = intlData.locales, formats = intlData.formats; // Lookup message by path name. User must supply the full path to the // message on `options.data.intl`. if (!message && hash.intlName) { message = intlGet(hash.intlName, options); } // When `message` is a function, assume it's an IntlMessageFormat // instance's `format()` method passed by reference, and call it. This // is possible because its `this` will be pre-bound to the instance. if (typeof message === 'function') { return message(hash); } if (typeof message === 'string') { message = getMessageFormat(message, locales, formats); } return message.format(hash); } function formatHTMLMessage() { /* jshint validthis:true */ var options = [].slice.call(arguments).pop(), hash = options.hash; var key, value; // Replace string properties in `options.hash` with HTML-escaped // strings. for (key in hash) { if (hash.hasOwnProperty(key)) { value = hash[key]; // Escape string value. if (typeof value === 'string') { hash[key] = escape(value); } } } // Return a Handlebars `SafeString`. This first unwraps the result to // make sure it's not returning a double-wrapped `SafeString`. return new SafeString(String(formatMessage.apply(this, arguments))); } // -- Utilities ------------------------------------------------------------ function assertIsDate(date, errMsg) { // Determine if the `date` is valid by checking if it is finite, which // is the same way that `Intl.DateTimeFormat#format()` checks. if (!isFinite(date)) { throw new TypeError(errMsg); } } function assertIsNumber(num, errMsg) { if (typeof num !== 'number') { throw new TypeError(errMsg); } } function getFormatOptions(type, format, options) { var hash = options.hash; var formatOptions; if (format) { if (typeof format === 'string') { formatOptions = intlGet('formats.' + type + '.' + format, options); } formatOptions = src$utils$$.extend({}, formatOptions, hash); } else { formatOptions = hash; } return formatOptions; } } exports.registerWith = registerWith; //# sourceMappingURL=helpers.js.map