Skip to content

Commit

Permalink
Plans: add support for client-side currency formatting (#6046)
Browse files Browse the repository at this point in the history
  • Loading branch information
gwwar authored Jun 17, 2016
1 parent 8d17398 commit 6f56c8f
Show file tree
Hide file tree
Showing 9 changed files with 538 additions and 137 deletions.
79 changes: 79 additions & 0 deletions client/lib/format-currency/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
Format Currency
==========
Given a currency code, this library will take in a number and display it as money correctly.

Usage
==========
```javascript
import formatCurrency from 'lib/format-currency';

const USD = formatCurrency( 9800900.32, 'USD' ); // '$9,800,900.32'
const EUR = formatCurrency( 9800900.32, 'EUR' ); // '€9.800.900,32'
const JPY = formatCurrency( 9800900.32, 'JPY' ); // '¥9,800,900'
```

Or

```javascript
import { getCurrencyObject } from 'lib/format-currency';
const USD = getCurrencyObject( 9800900.32, 'USD' ); // { symbol: '$', integer: '9,800,900', fraction: '.32', sign: '' }

```
## Parameters

### `number`

<table>
<tr><th>Type</th><td>Number</td></tr>
<tr><th>Required</th><td>Yes</td></tr>
</table>

The number to format.

### `code`

<table>
<tr><th>Type</th><td>String</td></tr>
<tr><th>Required</th><td>Yes</td></tr>
</table>

The currency code to format with.

## Option Properties

### `symbol`

<table>
<tr><th>Type</th><td>String</td></tr>
<tr><th>Required</th><td>No</td></tr>
</table>

Overrides the currency symbol. Eg replacing 'A$' instead of '$'

### `decimal`

<table>
<tr><th>Type</th><td>String</td></tr>
<tr><th>Required</th><td>No</td></tr>
</table>

Overrides the decimal mark. eg: 9.99 vs 9,99

### `grouping`

<table>
<tr><th>Type</th><td>String</td></tr>
<tr><th>Required</th><td>No</td></tr>
</table>


Overrides the thousands symbol grouping.

### `precision`

<table>
<tr><th>Type</th><td>Number</td></tr>
<tr><th>Required</th><td>No</td></tr>
</table>

The number of decimal places to display.
128 changes: 128 additions & 0 deletions client/lib/format-currency/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
/**
* External dependencies
*/
import { numberFormat } from 'i18n-calypso';

const CURRENCIES = {
USD: {
symbol: '$',
grouping: ',',
decimal: '.',
precision: 2
},
AUD: {
symbol: 'A$',
grouping: ',',
decimal: '.',
precision: 2
},
CAD: {
symbol: 'C$',
grouping: ',',
decimal: '.',
precision: 2
},
EUR: {
symbol: '€',
grouping: '.',
decimal: ',',
precision: 2
},
GBP: {
symbol: '£',
grouping: ',',
decimal: '.',
precision: 2
},
JPY: {
symbol: '¥',
grouping: ',',
decimal: '.',
precision: 0
}
};

/**
* Formats money with a given currency code
* @param {Number} number number to format
* @param {String} code currency code e.g. 'USD'
* @param {Object} options options object
* @param {String} options.decimal decimal symbol e.g. ','
* @param {String} options.grouping thousands separator
* @param {Number} options.precision decimal digits
* @param {String} options.symbol currency symbol e.g. 'A$'
* @returns {?String} A formatted string.
*/
export default function formatCurrency( number, code, options = {} ) {
const currencyDefaults = getCurrencyDefaults( code );
if ( ! currencyDefaults || isNaN( number ) ) {
return null;
}
const {
decimal,
grouping,
precision,
symbol
} = { ...currencyDefaults, ...options };
const sign = number < 0 ? '-' : '';
const value = numberFormat( Math.abs( number ), {
decimals: precision,
thousandsSep: grouping,
decPoint: decimal
} );
return `${ sign }${ symbol }${ value }`;
}

/**
* Returns a formatted price object.
* @param {Number} number number to format
* @param {String} code currency code e.g. 'USD'
* @param {Object} options options object
* @param {String} options.decimal decimal symbol e.g. ','
* @param {String} options.grouping thousands separator
* @param {Number} options.precision decimal digits
* @param {String} options.symbol currency symbol e.g. 'A$'
* @returns {?String} A formatted string e.g. { symbol:'$', integer: '$99', fraction: '.99', sign: '-' }
*/
export function getCurrencyObject( number, code, options = {} ) {
const currencyDefaults = getCurrencyDefaults( code );
if ( ! currencyDefaults || isNaN( number ) ) {
return null;
}
const {
decimal,
grouping,
precision,
symbol
} = { ...currencyDefaults, ...options };
const sign = number < 0 ? '-' : '';
const absNumber = Math.abs( number );
const rawInteger = Math.floor( absNumber );
const integer = numberFormat( rawInteger, {
decimals: 0,
thousandsSep: grouping,
decPoint: decimal
} );
const fraction = precision > 0
? numberFormat( absNumber - rawInteger, {
decimals: precision,
thousandsSep: grouping,
decPoint: decimal
} ).slice( 1 )
: '';
return {
sign,
symbol,
integer,
fraction
};
}

/**
* Returns currency defaults.
* @param {String} code currency code
* @returns {?Object} currency defaults
*/
export function getCurrencyDefaults( code ) {
return CURRENCIES[ code ] || null;
}
147 changes: 147 additions & 0 deletions client/lib/format-currency/test/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
/**
* External dependencies
*/
import { expect } from 'chai';

/**
* Internal dependencies
*/
import formatCurrency, { getCurrencyDefaults, getCurrencyObject } from 'lib/format-currency';

describe( 'formatCurrency', () => {
it( 'formats a number to localized currency', () => {
const money = formatCurrency( 99.32, 'USD' );
expect( money ).to.equal( '$99.32' );
} );
it( 'adds a localized thousands separator', () => {
const money = formatCurrency( 9800900.32, 'USD' );
expect( money ).to.equal( '$9,800,900.32' );
} );
it( 'handles zero', () => {
const money = formatCurrency( 0, 'USD' );
expect( money ).to.equal( '$0.00' );
} );
it( 'handles negative values', () => {
const money = formatCurrency( -1234.56789, 'USD' );
expect( money ).to.equal( '-$1,234.57' );
} );
it( 'unknown currency codes return null', () => {
const money = formatCurrency( 9800900.32, {} );
expect( money ).to.equal( null );
} );

describe( 'supported currencies', () => {
it( 'USD', () => {
const money = formatCurrency( 9800900.32, 'USD' );
expect( money ).to.equal( '$9,800,900.32' );
} );
it( 'AUD', () => {
const money = formatCurrency( 9800900.32, 'AUD' );
expect( money ).to.equal( 'A$9,800,900.32' );
} );
it( 'CAD', () => {
const money = formatCurrency( 9800900.32, 'CAD' );
expect( money ).to.equal( 'C$9,800,900.32' );
} );
it( 'EUR', () => {
const money = formatCurrency( 9800900.32, 'EUR' );
expect( money ).to.equal( '€9.800.900,32' );
} );
it( 'GBP', () => {
const money = formatCurrency( 9800900.32, 'GBP' );
expect( money ).to.equal( '£9,800,900.32' );
} );
it( 'JPY', () => {
const money = formatCurrency( 9800900.32, 'JPY' );
expect( money ).to.equal( '¥9,800,900' );
} );
} );

describe( 'getCurrencyDefaults()', () => {
it( 'returns currency defaults', () => {
const audDefaults = getCurrencyDefaults( 'AUD' );
expect( audDefaults ).to.eql( {
symbol: 'A$',
grouping: ',',
decimal: '.',
precision: 2
} );
} );
} );

describe( 'getCurrencyObject()', () => {
it( 'handles zero', () => {
const money = getCurrencyObject( 0, 'USD' );
expect( money ).to.eql( {
symbol: '$',
integer: '0',
fraction: '.00',
sign: ''
} );
} );
it( 'handles negative values', () => {
const money = getCurrencyObject( -1234.56789, 'USD' );
expect( money ).to.eql( {
symbol: '$',
integer: '1,234',
fraction: '.57',
sign: '-'
} );
} );
describe( 'supported currencies', () => {
it( 'USD', () => {
const money = getCurrencyObject( 9800900.32, 'USD' );
expect( money ).to.eql( {
symbol: '$',
integer: '9,800,900',
fraction: '.32',
sign: ''
} );
} );
it( 'AUD', () => {
const money = getCurrencyObject( 9800900.32, 'AUD' );
expect( money ).to.eql( {
symbol: 'A$',
integer: '9,800,900',
fraction: '.32',
sign: ''
} );
} );
it( 'CAD', () => {
const money = getCurrencyObject( 9800900.32, 'CAD' );
expect( money ).to.eql( {
symbol: 'C$',
integer: '9,800,900',
fraction: '.32',
sign: ''
} ); } );
it( 'EUR', () => {
const money = getCurrencyObject( 9800900.32, 'EUR' );
expect( money ).to.eql( {
symbol: '€',
integer: '9.800.900',
fraction: ',32',
sign: ''
} );
} );
it( 'GBP', () => {
const money = getCurrencyObject( 9800900.32, 'GBP' );
expect( money ).to.eql( {
symbol: '£',
integer: '9,800,900',
fraction: '.32',
sign: ''
} );
} );
it( 'JPY', () => {
const money = getCurrencyObject( 9800900.32, 'JPY' );
expect( money ).to.eql( {
symbol: '¥',
integer: '9,800,900',
fraction: '',
sign: ''
} );
} );
} );
} );
} );
Loading

0 comments on commit 6f56c8f

Please sign in to comment.