From 67bf3a77c16be1e430da349c7c05d783a154b901 Mon Sep 17 00:00:00 2001 From: Rish Date: Tue, 23 Feb 2021 16:25:28 +0530 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20Updated=20price=20helper=20to=20out?= =?UTF-8?q?put=20well=20formatted=20prices?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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'}} → "€"` --- core/frontend/helpers/price.js | 68 ++++++++++++++++++++++++++++++++- test/unit/helpers/price_spec.js | 59 ++++++++++++++++++++++++++++ 2 files changed, 126 insertions(+), 1 deletion(-) diff --git a/core/frontend/helpers/price.js b/core/frontend/helpers/price.js index 335d2fd3e2..628234912e 100644 --- a/core/frontend/helpers/price.js +++ b/core/frontend/helpers/price.js @@ -1,13 +1,79 @@ // # {{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 + }); + } -module.exports = function price(amount) { // CASE: if no amount is passed, e.g. `{{price}}` we throw an error if (arguments.length < 2) { throw new errors.IncorrectUsageError({ diff --git a/test/unit/helpers/price_spec.js b/test/unit/helpers/price_spec.js index 2029b34b3e..e8607bb213 100644 --- a/test/unit/helpers/price_spec.js +++ b/test/unit/helpers/price_spec.js @@ -42,4 +42,63 @@ describe('{{price}} helper', function () { .with({}) .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'); + }); });