269 lines
8.7 KiB
JavaScript
269 lines
8.7 KiB
JavaScript
|
|
/*
|
||
|
|
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
|