Merge pull request woocommerce/woocommerce-admin#1342 from woocommerce/fix/1090-currency-settings-formatting

Honor WooCommerce Settings When Formatting Currency/Price
This commit is contained in:
Jeff Stieler 2019-01-17 17:06:13 -07:00 committed by GitHub
commit 4407e05bc4
15 changed files with 146 additions and 66 deletions

View File

@ -105,7 +105,7 @@ class OrdersReportTable extends Component {
line_items,
items_sold,
coupon_lines,
currency,
currency_symbol,
net_revenue,
} = row;
@ -167,7 +167,7 @@ class OrdersReportTable extends Component {
value: coupons.map( item => item.code ).join( ' ' ),
},
{
display: formatCurrency( net_revenue, currency ),
display: formatCurrency( net_revenue, currency_symbol ),
value: net_revenue,
},
];

View File

@ -133,11 +133,11 @@ function OrdersPanel( { orders, isRequesting, isError } ) {
</span>
{ refundValue ? (
<span>
<s>{ formatCurrency( total, order.currency ) }</s>{' '}
{ formatCurrency( remainingTotal, order.currency ) }
<s>{ formatCurrency( total, order.currency_symbol ) }</s>{' '}
{ formatCurrency( remainingTotal, order.currency_symbol ) }
</span>
) : (
<span>{ formatCurrency( total, order.currency ) }</span>
<span>{ formatCurrency( total, order.currency_symbol ) }</span>
) }
</div>
}

View File

@ -1,20 +1,36 @@
/** @format */
/**
* External dependencies
*/
import { get } from 'lodash';
const number_format = require( 'locutus/php/strings/number_format' );
/**
* Formats a number using site's current locale
*
* @format
* @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/NumberFormat
* @param {Number|String} number number to format
* @returns {?String} A formatted string.
* @see http://locutus.io/php/strings/number_format/
* @param {Number|String} number number to format
* @param {int|null} [precision=null] optional decimal precision
* @returns {?String} A formatted string.
*/
export function numberFormat( number ) {
const locale = wcSettings.siteLocale || 'en-US'; // Default so we don't break.
export function numberFormat( number, precision = null ) {
if ( 'number' !== typeof number ) {
number = parseFloat( number );
}
if ( isNaN( number ) ) {
return '';
}
return new Intl.NumberFormat( locale ).format( number );
const decimalSeparator = get( wcSettings, [ 'currency', 'decimal_separator' ], '.' );
const thousandSeparator = get( wcSettings, [ 'currency', 'thousand_separator' ], ',' );
precision = parseInt( precision );
if ( isNaN( precision ) ) {
const [ , decimals ] = number.toString().split( '.' );
precision = decimals ? decimals.length : 0;
}
return number_format( number, precision, decimalSeparator, thousandSeparator );
}

View File

@ -5,7 +5,7 @@
import { numberFormat } from '../index';
describe( 'numberFormat', () => {
it( 'should default to en-US formatting', () => {
it( 'should default to precision=null decimal=. thousands=,', () => {
expect( numberFormat( 1000 ) ).toBe( '1,000' );
} );
@ -16,4 +16,20 @@ describe( 'numberFormat', () => {
it( 'should accept a string', () => {
expect( numberFormat( '10000' ) ).toBe( '10,000' );
} );
it( 'maintains all decimals if no precision specified', () => {
expect( numberFormat( '10000.123456' ) ).toBe( '10,000.123456' );
} );
it( 'maintains all decimals if invalid precision specified', () => {
expect( numberFormat( '10000.123456', 'not a number' ) ).toBe( '10,000.123456' );
} );
it( 'uses store currency settings, not locale', () => {
global.wcSettings.siteLocale = 'en-US';
global.wcSettings.currency.decimal_separator = ',';
global.wcSettings.currency.thousand_separator = '.';
expect( numberFormat( '12345.6789', 3 ) ).toBe( '12.345,679' );
} );
} );

View File

@ -72,6 +72,9 @@ class WC_Admin_Api_Init {
add_action( self::CUSTOMERS_BATCH_ACTION, array( __CLASS__, 'customer_lookup_process_batch' ) );
add_action( self::ORDERS_BATCH_ACTION, array( __CLASS__, 'orders_lookup_process_batch' ) );
add_action( self::ORDERS_LOOKUP_BATCH_INIT, array( __CLASS__, 'orders_lookup_batch_init' ) );
// Add currency symbol to orders endpoint response.
add_filter( 'woocommerce_rest_prepare_shop_order_object', array( __CLASS__, 'add_currency_symbol_to_order_response' ) );
}
/**
@ -757,6 +760,22 @@ class WC_Admin_Api_Init {
add_action( 'woocommerce_after_register_post_type', array( __CLASS__, 'regenerate_report_data' ), 20 );
}
/**
* Add the currency symbol (in addition to currency code) to each Order
* object in REST API responses. For use in formatCurrency().
*
* @param {WP_REST_Response} $response REST response object.
* @returns {WP_REST_Response}
*/
public static function add_currency_symbol_to_order_response( $response ) {
$response_data = $response->get_data();
$currency_code = $response_data['currency'];
$currency_symbol = get_woocommerce_currency_symbol( $currency_code );
$response_data['currency_symbol'] = html_entity_decode( $currency_symbol );
$response->set_data( $response_data );
return $response;
}
}
new WC_Admin_Api_Init();

View File

@ -269,10 +269,13 @@ function wc_admin_currency_settings() {
return apply_filters(
'wc_currency_settings',
array(
'code' => $code,
'precision' => wc_get_price_decimals(),
'symbol' => get_woocommerce_currency_symbol( $code ),
'position' => get_option( 'woocommerce_currency_pos' ),
'code' => $code,
'precision' => wc_get_price_decimals(),
'symbol' => html_entity_decode( get_woocommerce_currency_symbol( $code ) ),
'position' => get_option( 'woocommerce_currency_pos' ),
'decimal_separator' => wc_get_price_decimal_separator(),
'thousand_separator' => wc_get_price_thousand_separator(),
'price_format' => html_entity_decode( get_woocommerce_price_format() ),
)
);
}

View File

@ -12910,6 +12910,11 @@
"semver": "^5.4.1"
}
},
"locutus": {
"version": "2.0.10",
"resolved": "https://registry.npmjs.org/locutus/-/locutus-2.0.10.tgz",
"integrity": "sha512-AZg2kCqrquMJ5FehDsBidV0qHl98NrsYtseUClzjAQ3HFnsDBJTCwGVplSQ82t9/QfgahqvTjaKcZqZkHmS0wQ=="
},
"lodash": {
"version": "4.17.11",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz",

View File

@ -136,6 +136,7 @@
"history": "4.7.2",
"html-to-react": "1.3.4",
"interpolate-components": "1.1.1",
"locutus": "^2.0.10",
"lodash": "^4.17.11",
"marked": "0.6.0",
"prismjs": "^1.15.0",

View File

@ -1,3 +1,6 @@
# 1.4.1 (unreleased)
- Chart component: format numbers and prices using store currency settings.
# 1.4.0
- Add download log ip address autocompleter to search component
- Add order number autocompleter to search component

View File

@ -5,7 +5,6 @@
import { __, sprintf } from '@wordpress/i18n';
import classNames from 'classnames';
import { Component, createRef, Fragment } from '@wordpress/element';
import { decodeEntities } from '@wordpress/html-entities';
import { formatDefaultLocale as d3FormatDefaultLocale } from 'd3-format';
import { get, isEqual, partial } from 'lodash';
import Gridicon from 'gridicons';
@ -26,11 +25,28 @@ import ChartPlaceholder from './placeholder';
import { H, Section } from '../section';
import { D3Chart, D3Legend } from './d3chart';
function getD3CurrencyFormat( symbol, position ) {
switch ( position ) {
case 'left_space':
return [ symbol + ' ', '' ];
case 'right':
return [ '', symbol ];
case 'right_space':
return [ '', ' ' + symbol ];
case 'left':
default:
return [ symbol, '' ];
}
}
const currencySymbol = get( wcSettings, [ 'currency', 'symbol' ], '' );
const symbolPosition = get( wcSettings, [ 'currency', 'position' ], 'left' );
d3FormatDefaultLocale( {
decimal: '.',
thousands: ',',
decimal: get( wcSettings, [ 'currency', 'decimal_separator' ], '.' ),
thousands: get( wcSettings, [ 'currency', 'thousand_separator' ], ',' ),
grouping: [ 3 ],
currency: [ decodeEntities( get( wcSettings, 'currency.symbol', '' ) ), '' ],
currency: getD3CurrencyFormat( currencySymbol, symbolPosition ),
} );
function getOrderedKeys( props, previousOrderedKeys = [] ) {

View File

@ -1,3 +1,8 @@
# 1.1.0 (unreleased)
- Format using store currency settings (instead of locale)
- Add optional currency symbol parameter
# 1.0.0
- Released package

View File

@ -17,10 +17,9 @@ _This package assumes that your code will run in an **ES2015+** environment. If
```JS
import { formatCurrency, getCurrencyFormatDecimal, getCurrencyFormatString } from '@woocommerce/currency';
// Formats money with a given currency code. Uses site's current locale for symbol formatting,
// from the wcSettings global. Defaults to `en-US`. If no currency provided, this is also
// pulled from wcSettings, and defaults to USD.
const total = formatCurrency( 20.923, 'USD' ); // '$20.92'
// Formats money with a given currency symbol. Uses site's currency settings for formatting,
// from the wcSettings global. Defaults to symbol=`$`, precision=2, decimalSeparator=`.`, thousandSeparator=`,`
const total = formatCurrency( 20.923, '$' ); // '$20.92'
// Get the rounded decimal value of a number at the precision used for the current currency,
// from the wcSettings global. Defaults to 2.

View File

@ -3,28 +3,35 @@
* External dependencies
*/
import { get, isNaN } from 'lodash';
import { sprintf } from '@wordpress/i18n';
/**
* Formats money with a given currency code. Uses site's current locale for symbol formatting
*
* @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/NumberFormat
* @param {Number|String} number number to format
* @param {String} currency currency code e.g. 'USD'
* @returns {?String} A formatted string.
* Internal dependencies
*/
export function formatCurrency( number, currency ) {
const locale = wcSettings.siteLocale || 'en-US'; // Default so we don't break.
// default to wcSettings if currency is not passed in
if ( ! currency ) {
currency = get( wcSettings, 'currency.code', 'USD' );
import { numberFormat } from 'lib/number';
/**
* Formats money with a given currency code. Uses site's currency settings for formatting.
*
* @param {Number|String} number number to format
* @param {String} currencySymbol currency code e.g. '$'
* @returns {?String} A formatted string.
*/
export function formatCurrency( number, currencySymbol ) {
// default to wcSettings (and then to $) if currency symbol is not passed in
if ( ! currencySymbol ) {
currencySymbol = get( wcSettings, [ 'currency', 'symbol' ], '$' );
}
if ( 'number' !== typeof number ) {
number = parseFloat( number );
const precision = get( wcSettings, [ 'currency', 'precision' ], 2 );
const formattedNumber = numberFormat( number, precision );
const priceFormat = get( wcSettings, [ 'currency', 'price_format' ], '%1$s%2$s' );
if ( '' === formattedNumber ) {
return formattedNumber;
}
if ( isNaN( number ) ) {
return '';
}
return new Intl.NumberFormat( locale, { style: 'currency', currency } ).format( number );
return sprintf( priceFormat, currencySymbol, formattedNumber );
}
/**

View File

@ -10,32 +10,22 @@ describe( 'formatCurrency', () => {
expect( formatCurrency( 30 ) ).toBe( '$30.00' );
} );
it( 'should round a number to 2 decimal places in USD', () => {
expect( formatCurrency( 9.49258, 'USD' ) ).toBe( '$9.49' );
expect( formatCurrency( 30, 'USD' ) ).toBe( '$30.00' );
expect( formatCurrency( 3.0002, 'USD' ) ).toBe( '$3.00' );
} );
it( 'should uses store currency settings, not locale-based', () => {
global.wcSettings.currency.code = 'JPY';
global.wcSettings.currency.precision = 3;
global.wcSettings.currency.decimal_separator = ',';
global.wcSettings.currency.thousand_separator = '.';
global.wcSettings.currency.price_format = '%2$s%1$s';
it( 'should round a number to 2 decimal places in GBP', () => {
expect( formatCurrency( 8.9272, 'GBP' ) ).toBe( '£8.93' );
expect( formatCurrency( 11, 'GBP' ) ).toBe( '£11.00' );
expect( formatCurrency( 7.0002, 'GBP' ) ).toBe( '£7.00' );
} );
it( 'should round a number to 0 decimal places in JPY', () => {
expect( formatCurrency( 1239.88, 'JPY' ) ).toBe( '¥1,240' );
expect( formatCurrency( 1500, 'JPY' ) ).toBe( '¥1,500' );
expect( formatCurrency( 33715.02, 'JPY' ) ).toBe( '¥33,715' );
} );
it( 'should correctly convert and round a string', () => {
expect( formatCurrency( '19.80', 'USD' ) ).toBe( '$19.80' );
expect( formatCurrency( 9.49258, '¥' ) ).toBe( '9,493¥' );
expect( formatCurrency( 3000, '¥' ) ).toBe( '3.000,000¥' );
expect( formatCurrency( 3.0002, '¥' ) ).toBe( '3,000¥' );
} );
it( "should return empty string when given an input that isn't a number", () => {
expect( formatCurrency( 'abc', 'USD' ) ).toBe( '' );
expect( formatCurrency( false, 'USD' ) ).toBe( '' );
expect( formatCurrency( null, 'USD' ) ).toBe( '' );
expect( formatCurrency( 'abc' ) ).toBe( '' );
expect( formatCurrency( false ) ).toBe( '' );
expect( formatCurrency( null ) ).toBe( '' );
} );
} );

View File

@ -44,7 +44,7 @@ wooCommercePackages.forEach( lib => {
global.wcSettings = {
adminUrl: 'https://vagrant.local/wp/wp-admin/',
locale: 'en-US',
currency: { code: 'USD', precision: 2, symbol: '&#36;' },
currency: { code: 'USD', precision: 2, symbol: '$' },
date: {
dow: 0,
},