Ghost/core/frontend/helpers/price.js
Rish 67bf3a77c1 Updated price helper to output well formatted prices
refs https://github.com/TryGhost/Team/issues/472

The current `{{price}}` helper only works with `amount` to convert it into right value but doesn't allow any formatting with currency etc, leaving most of the work to theme.  We want to be able to output well formatted prices. E.g. the API returns 5000 for 5 EUR but we want to output €5.

The updated {{price}} helper can take a plan object or plan amount currency and use them to output a well formatted price. It works with JS's built in Intl.NumberFormat behaviour to return output in different formats, also allowing theme devs to override formatting with options.

Examples:

With Plan object => `{{price plan}} → "€5"`
With Plan object and custom number format =>  `{{price plan numberFormat="long"}} → "€5.00"`
Output only currency symbol =>  `{{price currency='EUR'}} → "€"`
2021-02-25 13:01:24 +05:30

99 lines
2.9 KiB
JavaScript

// # {{price}} helper
//
// Usage: `{{price 2100}}`
// Usage: `{{price plan}}`
// Usage: `{{price plan numberFormat="long"}}`
// Usage: `{{price plan currencyFormat="code"}}`
// Usage: `{{price plan currencyFormat="name"}}`
// Usage: `{{price 500 currency="USD"}}`
// Usage: `{{price currency="USD"}}`
//
// Returns amount equal to the dominant denomintation of the currency.
// For example, if 2100 is passed, it will return 21.
const isNumber = require('lodash/isNumber');
const {errors, i18n} = require('../services/proxy');
const _ = require('lodash');
function formatter({amount, currency, numberFormat = 'short', currencyFormat = 'symbol', locale}) {
const formatterOptions = {
style: 'currency',
currency: currency,
currencyDisplay: currencyFormat
};
if (numberFormat === 'short') {
formatterOptions.minimumFractionDigits = 0;
}
if (amount) {
return new Intl.NumberFormat(locale, formatterOptions).format(amount);
} else {
const val = new Intl.NumberFormat('en', {
style: 'currency',
currency,
currencyDisplay: 'symbol',
minimumFractionDigits: 0,
maximumFractionDigits: 0
}).format(0);
return val.replace(/[\d\s.,]/g, '');
}
}
module.exports = function price(planOrAmount, options) {
let plan;
let amount;
if (arguments.length === 1) {
options = planOrAmount;
}
if (arguments.length === 2) {
if (_.isNumber(planOrAmount)) {
amount = planOrAmount;
} else if (_.isObject(planOrAmount)) {
plan = planOrAmount;
}
}
options = options || {};
options.hash = options.hash || {};
const {currency, numberFormat = 'short', currencyFormat = 'symbol', locale = _.get(options, 'data.site.lang', 'en')} = options.hash;
if (plan) {
return formatter({
amount: plan.amount && (plan.amount / 100),
currency: currency || plan.currency,
currencyFormat,
numberFormat,
locale
});
}
if (currency) {
return formatter({
amount: amount && (amount / 100),
currency: currency,
currencyFormat,
numberFormat,
locale
});
}
// CASE: if no amount is passed, e.g. `{{price}}` we throw an error
if (arguments.length < 2) {
throw new errors.IncorrectUsageError({
message: i18n.t('warnings.helpers.price.attrIsRequired')
});
}
// CASE: if amount is passed, but it is undefined we throw an error
if (amount === undefined) {
throw new errors.IncorrectUsageError({
message: i18n.t('warnings.helpers.price.attrIsRequired')
});
}
if (!isNumber(amount)) {
throw new errors.IncorrectUsageError({
message: i18n.t('warnings.helpers.price.attrMustBeNumeric')
});
}
return amount / 100;
};