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'}} → "€"`
This commit is contained in:
Rish 2021-02-23 16:25:28 +05:30 committed by Rishabh Garg
parent 30371b0986
commit 67bf3a77c1
2 changed files with 126 additions and 1 deletions

View File

@ -1,13 +1,79 @@
// # {{price}} helper // # {{price}} helper
// //
// Usage: `{{price 2100}}` // 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. // Returns amount equal to the dominant denomintation of the currency.
// For example, if 2100 is passed, it will return 21. // For example, if 2100 is passed, it will return 21.
const isNumber = require('lodash/isNumber'); const isNumber = require('lodash/isNumber');
const {errors, i18n} = require('../services/proxy'); 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
});
}
module.exports = function price(amount) {
// CASE: if no amount is passed, e.g. `{{price}}` we throw an error // CASE: if no amount is passed, e.g. `{{price}}` we throw an error
if (arguments.length < 2) { if (arguments.length < 2) {
throw new errors.IncorrectUsageError({ throw new errors.IncorrectUsageError({

View File

@ -42,4 +42,63 @@ describe('{{price}} helper', function () {
.with({}) .with({})
.should.equal('20'); .should.equal('20');
}); });
it('will format with plan object', function () {
const plan = {
nickname: 'Monthly',
amount: 500,
interval: 'month',
currency: 'USD',
currency_symbol: '$'
};
const rendered = helpers.price.call({}, plan, {});
rendered.should.be.equal('$5');
});
it('will format with plan object with number format', function () {
const plan = {
nickname: 'Monthly',
amount: 500,
interval: 'month',
currency: 'USD',
currency_symbol: '$'
};
const rendered = helpers.price.call({}, plan, {hash: {numberFormat: 'long'}});
rendered.should.be.equal('$5.00');
});
it('will format symbol if only currency - USD', function () {
const rendered = helpers.price.call({}, {hash: {currency: 'USD'}});
rendered.should.be.equal('$');
});
it('will format symbol if only currency - EUR', function () {
const rendered = helpers.price.call({}, {hash: {currency: 'EUR'}});
rendered.should.be.equal('€');
});
it('will format with amount and currency', function () {
const rendered = helpers.price.call({}, 500, {hash: {currency: 'USD'}});
rendered.should.be.equal('$5');
});
it('will format with long number format', function () {
const rendered = helpers.price.call({}, 500, {hash: {currency: 'USD', numberFormat: 'long'}});
rendered.should.be.equal('$5.00');
});
it('will format with short number format with decimal value', function () {
const rendered = helpers.price.call({}, 505, {hash: {currency: 'EUR', numberFormat: 'short'}});
rendered.should.be.equal('€5.05');
});
it('will format with short number format without decimal value', function () {
const rendered = helpers.price.call({}, 500, {hash: {currency: 'EUR', numberFormat: 'short'}});
rendered.should.be.equal('€5');
});
it('will format with name currency format', function () {
const rendered = helpers.price.call({}, 500, {hash: {currency: 'USD', currencyFormat: 'name'}});
rendered.should.be.equal('5 US dollars');
});
}); });