diff --git a/client/lib/format-number-compact/README.md b/client/lib/format-number-compact/README.md deleted file mode 100644 index 4ac0c7276c3eba..00000000000000 --- a/client/lib/format-number-compact/README.md +++ /dev/null @@ -1,33 +0,0 @@ -# Format Number Compact - -Given a language code, this library will take in a number and display it in a compact format. - -## Usage - -```javascript -import formatNumberCompact from 'calypso/lib/format-number-compact'; - -const noChange = formatNumberCompact( 999, 'en' ); // '999' -const shortEn = formatNumberCompact( 1234, 'en' ); // '1.2K' -const shortEs = formatNumberCompact( 12567, 'es' ); // '12,6 mil' -``` - -## Parameters - -### `number` - - - - -
TypeNumber
RequiredYes
- -The number to format. - -### `code` - - - - -
TypeString
RequiredYes
- -The language code to format with. diff --git a/client/lib/format-number-compact/index.js b/client/lib/format-number-compact/index.js deleted file mode 100644 index f77ba261a35b22..00000000000000 --- a/client/lib/format-number-compact/index.js +++ /dev/null @@ -1,61 +0,0 @@ -import i18n, { numberFormat } from 'i18n-calypso'; -import { THOUSANDS } from './thousands'; - -/** - * Formats a number to a short format given a language code - * @param {number} number number to format - * @param {string} code language code e.g. 'es' - * @returns {?string} A formatted string. - */ -export default function formatNumberCompact( number, code = i18n.getLocaleSlug() ) { - //use numberFormat directly from i18n in this case! - if ( isNaN( number ) || ! THOUSANDS[ code ] ) { - return null; - } - - const { symbol, unitValue = 1000 } = THOUSANDS[ code ]; - - const sign = number < 0 ? '-' : ''; - const absNumber = Math.abs( number ); - - // no-op if we have a small number - if ( absNumber < unitValue ) { - return `${ sign }${ absNumber }`; - } - - //show 2 sig figs, otherwise take leading sig figs. - const decimals = absNumber < unitValue * 10 ? 1 : 0; - - // TODO clk numberFormat - // can deprecate complately in favour of `{ notation: 'compact' }` in Intl.NumberFormat - const value = numberFormat( absNumber / unitValue, { - decimals, - } ); - - return `${ sign }${ value }${ symbol }`; -} - -const ONE_K = 1000; -const ONE_M = ONE_K * 1000; -const ONE_G = ONE_M * 1000; - -/* - * Format a number larger than 1000 by appending a metric unit (K, M, G) and rounding to - * the received decimal point, defaults to 0. - * TODO: merge with formatNumberCompact by adding support for metric units other than 'K' - */ -export function formatNumberMetric( number, decimalPoints = 1 ) { - if ( number < ONE_K ) { - return String( number ); - } - - if ( number < ONE_M ) { - return ( number / ONE_K ).toFixed( decimalPoints ) + 'K'; - } - - if ( number < ONE_G ) { - return ( number / ONE_M ).toFixed( decimalPoints ) + 'M'; - } - - return ( number / ONE_G ).toFixed( decimalPoints ) + 'G'; -} diff --git a/client/lib/format-number-compact/test/index.js b/client/lib/format-number-compact/test/index.js deleted file mode 100644 index 772de50eac87d6..00000000000000 --- a/client/lib/format-number-compact/test/index.js +++ /dev/null @@ -1,341 +0,0 @@ -import i18n from 'i18n-calypso'; -import formatNumberCompact, { formatNumberMetric } from 'calypso/lib/format-number-compact'; - -describe( 'formatNumberCompact', () => { - beforeEach( function () { - i18n.configure(); // ensure everything is reset - i18n.setLocale( { - '': { - localeSlug: 'en', - }, - } ); - } ); - - test( 'does nothing if number is < 1000', () => { - const counts = formatNumberCompact( 999, 'en' ); - expect( counts ).toEqual( '999' ); - } ); - test( 'shows 2 sig figs for counts < 10000', () => { - const counts = formatNumberCompact( 1234, 'en' ); - expect( counts ).toEqual( '1.2K' ); - } ); - test( 'shows leading sig figs for counts > 10000', () => { - const counts = formatNumberCompact( 123456, 'en' ); - expect( counts ).toEqual( '123K' ); - } ); - test( 'rounds abbreviated counts', () => { - const counts = formatNumberCompact( 1897, 'en' ); - expect( counts ).toEqual( '1.9K' ); - } ); - test( 'shows groupings for huge numbers', () => { - const counts = formatNumberCompact( 123456789, 'en' ); - expect( counts ).toEqual( '123,457K' ); - } ); - test( 'handles negative numbers', () => { - const counts = formatNumberCompact( -123456789, 'en' ); - expect( counts ).toEqual( '-123,457K' ); - } ); - describe( 'es', () => { - beforeEach( function () { - i18n.setLocale( { - '': { - localeSlug: 'es', - }, - } ); - } ); - test( 'shows 2 sig figs for counts < 10000', () => { - const counts = formatNumberCompact( 1234, 'es' ); - expect( counts ).toEqual( '1,2 mil' ); - } ); - test( 'shows leading sig figs for counts > 10000', () => { - const counts = formatNumberCompact( 123456, 'es' ); - expect( counts ).toEqual( '123 mil' ); - } ); - } ); - describe( 'pt-br', () => { - beforeEach( function () { - i18n.setLocale( { - '': { - localeVariant: 'pt-br', - localeSlug: 'pt', - }, - } ); - } ); - test( 'shows 2 sig figs for counts < 10000', () => { - const counts = formatNumberCompact( 1234, 'pt-br' ); - expect( counts ).toEqual( '1,2 mil' ); - } ); - test( 'shows leading sig figs for counts > 10000', () => { - const counts = formatNumberCompact( 123456, 'pt-br' ); - expect( counts ).toEqual( '123 mil' ); - } ); - } ); - describe( 'de', () => { - beforeEach( function () { - i18n.setLocale( { - '': { - localeSlug: 'de', - }, - } ); - } ); - test( 'shows 2 sig figs for counts < 10000', () => { - const counts = formatNumberCompact( 1234, 'de' ); - expect( counts ).toEqual( '1,2 Tsd.' ); - } ); - test( 'shows leading sig figs for counts > 10000', () => { - const counts = formatNumberCompact( 123456, 'de' ); - expect( counts ).toEqual( '123 Tsd.' ); - } ); - } ); - describe( 'fr', () => { - beforeEach( function () { - i18n.setLocale( { - '': { - localeSlug: 'fr', - }, - } ); - } ); - test( 'shows 2 sig figs for counts < 10000', () => { - const counts = formatNumberCompact( 1234, 'fr' ); - expect( counts ).toEqual( '1,2 k' ); - } ); - test( 'shows leading sig figs for counts > 10000', () => { - const counts = formatNumberCompact( 123456, 'fr' ); - expect( counts ).toEqual( '123 k' ); - } ); - } ); - describe( 'he', () => { - beforeEach( function () { - i18n.setLocale( { - '': { - localeSlug: 'he', - }, - } ); - } ); - test( 'shows 2 sig figs for counts < 10000', () => { - const counts = formatNumberCompact( 1234, 'he' ); - expect( counts ).toEqual( '1.2K' ); - } ); - test( 'shows leading sig figs for counts > 10000', () => { - const counts = formatNumberCompact( 123456, 'he' ); - expect( counts ).toEqual( '123K' ); - } ); - } ); - describe( 'ja', () => { - beforeEach( function () { - i18n.setLocale( { - '': { - localeSlug: 'ja', - }, - } ); - } ); - test( 'does not modify counts < 10000', () => { - const counts = formatNumberCompact( 1234, 'ja' ); - expect( counts ).toEqual( '1234' ); - } ); - test( 'shows 2 sig figs for counts just over 10000', () => { - const counts = formatNumberCompact( 12345, 'ja' ); - expect( counts ).toEqual( '1.2万' ); - } ); - test( 'shows leading sig figs for counts > 100000', () => { - const counts = formatNumberCompact( 1234567, 'ja' ); - expect( counts ).toEqual( '123万' ); - } ); - } ); - describe( 'it', () => { - beforeEach( function () { - i18n.setLocale( { - '': { - localeSlug: 'it', - }, - } ); - } ); - test( 'does not support a compact format, use numberFormat directly from i18n', () => { - const counts = formatNumberCompact( 1234, 'it' ); - expect( counts ).toEqual( null ); - } ); - } ); - describe( 'nl', () => { - beforeEach( function () { - i18n.setLocale( { - '': { - localeSlug: 'nl', - }, - } ); - } ); - test( 'shows 2 sig figs for counts < 10000', () => { - const counts = formatNumberCompact( 1234, 'nl' ); - expect( counts ).toEqual( '1,2K' ); - } ); - test( 'shows leading sig figs for counts > 10000', () => { - const counts = formatNumberCompact( 123456, 'nl' ); - expect( counts ).toEqual( '123K' ); - } ); - } ); - describe( 'ru', () => { - beforeEach( function () { - i18n.setLocale( { - '': { - localeSlug: 'ru', - }, - } ); - } ); - test( 'the short form is too long to be useful, use numberFormat directly from i18n', () => { - const counts = formatNumberCompact( 1234, 'ru' ); - expect( counts ).toEqual( null ); - } ); - } ); - describe( 'tr', () => { - beforeEach( function () { - i18n.setLocale( { - '': { - localeSlug: 'tr', - }, - } ); - } ); - test( 'shows 2 sig figs for counts < 10000', () => { - const counts = formatNumberCompact( 1234, 'tr' ); - expect( counts ).toEqual( '1,2 B' ); - } ); - test( 'shows leading sig figs for counts > 10000', () => { - const counts = formatNumberCompact( 123456, 'tr' ); - expect( counts ).toEqual( '123 B' ); - } ); - } ); - describe( 'id', () => { - beforeEach( function () { - i18n.setLocale( { - '': { - localeSlug: 'id', - }, - } ); - } ); - test( 'shows 2 sig figs for counts < 10000', () => { - const counts = formatNumberCompact( 1234, 'id' ); - expect( counts ).toEqual( '1,2 rb' ); - } ); - test( 'shows leading sig figs for counts > 10000', () => { - const counts = formatNumberCompact( 123456, 'id' ); - expect( counts ).toEqual( '123 rb' ); - } ); - } ); - describe( 'zh-cn', () => { - beforeEach( function () { - i18n.setLocale( { - '': { - localeVariant: 'zh-cn', - localeSlug: 'zh', - }, - } ); - } ); - test( 'does not modify counts < 10000', () => { - const counts = formatNumberCompact( 1234, 'zh-cn' ); - expect( counts ).toEqual( '1234' ); - } ); - test( 'shows 2 sig figs for counts just over 10000', () => { - const counts = formatNumberCompact( 12345, 'zh-cn' ); - expect( counts ).toEqual( '1.2万' ); - } ); - test( 'shows leading sig figs for counts > 100000', () => { - const counts = formatNumberCompact( 1234567, 'zh-cn' ); - expect( counts ).toEqual( '123万' ); - } ); - } ); - describe( 'zh-tw', () => { - beforeEach( function () { - i18n.setLocale( { - '': { - localeVariant: 'zh-tw', - localeSlug: 'zh', - }, - } ); - } ); - test( 'does not modify counts < 10000', () => { - const counts = formatNumberCompact( 1234, 'zh-tw' ); - expect( counts ).toEqual( '1234' ); - } ); - test( 'shows 2 sig figs for counts just over 10000', () => { - const counts = formatNumberCompact( 12345, 'zh-tw' ); - expect( counts ).toEqual( '1.2萬' ); - } ); - test( 'shows leading sig figs for counts > 100000', () => { - const counts = formatNumberCompact( 1234567, 'zh-tw' ); - expect( counts ).toEqual( '123萬' ); - } ); - } ); - describe( 'ko', () => { - beforeEach( function () { - i18n.setLocale( { - '': { - localeSlug: 'ko', - }, - } ); - } ); - test( 'shows 2 sig figs for counts < 10000', () => { - const counts = formatNumberCompact( 1234, 'ko' ); - expect( counts ).toEqual( '1.2천' ); - } ); - test( 'shows leading sig figs for counts > 10000', () => { - const counts = formatNumberCompact( 123456, 'ko' ); - expect( counts ).toEqual( '123천' ); - } ); - } ); - describe( 'ar', () => { - beforeEach( function () { - i18n.setLocale( { - '': { - localeSlug: 'ar', - }, - } ); - } ); - test( 'shows 2 sig figs for counts < 10000', () => { - const counts = formatNumberCompact( 1234, 'ar' ); - expect( counts ).toEqual( '1.2 ألف' ); - } ); - test( 'shows leading sig figs for counts > 10000', () => { - const counts = formatNumberCompact( 123456, 'ar' ); - expect( counts ).toEqual( '123 ألف' ); - } ); - } ); - describe( 'sv', () => { - beforeEach( function () { - i18n.setLocale( { - '': { - localeSlug: 'sv', - }, - } ); - } ); - test( 'shows 2 sig figs for counts < 10000', () => { - const counts = formatNumberCompact( 1234, 'sv' ); - expect( counts ).toEqual( '1,2 tn' ); - } ); - test( 'shows leading sig figs for counts > 10000', () => { - const counts = formatNumberCompact( 123456, 'sv' ); - expect( counts ).toEqual( '123 tn' ); - } ); - } ); -} ); - -describe( 'formatNumberMetric', () => { - test( 'does not abbreviate numbers smaller than 1000', () => { - expect( formatNumberMetric( 123 ) ).toEqual( '123' ); - } ); - - test( 'appends the K unit', () => { - expect( formatNumberMetric( 123456 ) ).toEqual( '123.5K' ); - } ); - - test( 'appends the M unit', () => { - expect( formatNumberMetric( 123456789 ) ).toEqual( '123.5M' ); - } ); - - test( 'appends the G unit', () => { - expect( formatNumberMetric( 123456789012 ) ).toEqual( '123.5G' ); - } ); - - test( 'rounds numbers with metric units to 1 decimal point', () => { - expect( formatNumberMetric( 4500 ) ).toEqual( '4.5K' ); - expect( formatNumberMetric( 12490 ) ).toEqual( '12.5K' ); - expect( formatNumberMetric( 123546789 ) ).toEqual( '123.5M' ); - } ); -} ); diff --git a/client/lib/format-number-compact/thousands.js b/client/lib/format-number-compact/thousands.js deleted file mode 100644 index 2de703c8a26e24..00000000000000 --- a/client/lib/format-number-compact/thousands.js +++ /dev/null @@ -1,50 +0,0 @@ -export const THOUSANDS = { - en: { - symbol: 'K', - }, - es: { - symbol: ' mil', - }, - 'pt-br': { - symbol: ' mil', - }, - de: { - symbol: ' Tsd.', - }, - fr: { - symbol: ' k', - }, - he: { - symbol: 'K', - }, - ja: { - symbol: '万', - unitValue: 10000, - }, - nl: { - symbol: 'K', - }, - tr: { - symbol: ' B', - }, - id: { - symbol: ' rb', - }, - 'zh-cn': { - symbol: '万', - unitValue: 10000, - }, - 'zh-tw': { - symbol: '萬', - unitValue: 10000, - }, - ko: { - symbol: '천', - }, - ar: { - symbol: ' ألف', - }, - sv: { - symbol: ' tn', - }, -}; diff --git a/client/my-sites/marketing/do-it-for-me/difm-landing.tsx b/client/my-sites/marketing/do-it-for-me/difm-landing.tsx index d124c0cfb38eb8..16f537f4028197 100644 --- a/client/my-sites/marketing/do-it-for-me/difm-landing.tsx +++ b/client/my-sites/marketing/do-it-for-me/difm-landing.tsx @@ -699,7 +699,7 @@ export default function DIFMLanding( { components: { strong: , }, - args: [ numberFormat( CHARACTER_LIMIT, {} ) ], + args: [ numberFormat( CHARACTER_LIMIT ) ], } ) }

diff --git a/client/my-sites/plugins/plugin-details-header/index.jsx b/client/my-sites/plugins/plugin-details-header/index.jsx index 05edf530fa9cef..8260a200abd9cd 100644 --- a/client/my-sites/plugins/plugin-details-header/index.jsx +++ b/client/my-sites/plugins/plugin-details-header/index.jsx @@ -1,12 +1,11 @@ import { Badge, Button } from '@automattic/components'; -import { useTranslate } from 'i18n-calypso'; +import { useTranslate, numberFormat } from 'i18n-calypso'; import { useSelector } from 'react-redux'; import { useLocalizedMoment } from 'calypso/components/localized-moment'; import { useMarketplaceReviewsQuery, useMarketplaceReviewsStatsQuery, } from 'calypso/data/marketplace/use-marketplace-reviews'; -import { formatNumberMetric } from 'calypso/lib/format-number-compact'; import { preventWidows } from 'calypso/lib/formatting'; import PluginIcon from 'calypso/my-sites/plugins/plugin-icon/plugin-icon'; import PluginRatings from 'calypso/my-sites/plugins/plugin-ratings/'; @@ -133,7 +132,7 @@ const PluginDetailsHeader = ( { { translate( 'Active installations' ) }
- { formatNumberMetric( plugin.active_installs, 0 ) } + { numberFormat( plugin.active_installs, { notation: 'compact' } ) }
) } diff --git a/client/my-sites/plugins/plugin-details-sidebar/index.jsx b/client/my-sites/plugins/plugin-details-sidebar/index.jsx index 70e5762d68ba3a..46f0743e82c034 100644 --- a/client/my-sites/plugins/plugin-details-sidebar/index.jsx +++ b/client/my-sites/plugins/plugin-details-sidebar/index.jsx @@ -4,12 +4,11 @@ import { WPCOM_FEATURES_INSTALL_PURCHASED_PLUGINS, } from '@automattic/calypso-products'; import { localizeUrl } from '@automattic/i18n-utils'; -import { useTranslate } from 'i18n-calypso'; +import { useTranslate, numberFormat } from 'i18n-calypso'; import { useCallback } from 'react'; import { useSelector } from 'react-redux'; import './style.scss'; import { recordTracksEvent } from 'calypso/lib/analytics/tracks'; -import { formatNumberMetric } from 'calypso/lib/format-number-compact'; import { PlanUSPS, USPS } from 'calypso/my-sites/plugins/plugin-details-CTA/usps'; import PluginDetailsSidebarUSP from 'calypso/my-sites/plugins/plugin-details-sidebar-usp'; import usePluginsSupportText from 'calypso/my-sites/plugins/use-plugins-support-text/'; @@ -161,7 +160,7 @@ const PluginDetailsSidebar = ( { { translate( 'Active installations' ) }
- { formatNumberMetric( active_installs, 0 ) } + { numberFormat( active_installs, { notation: 'compact' } ) }
) } diff --git a/client/my-sites/stats/stats-views/months.jsx b/client/my-sites/stats/stats-views/months.jsx index 4eeeb3cb6cfe16..26457c5ab559e0 100644 --- a/client/my-sites/stats/stats-views/months.jsx +++ b/client/my-sites/stats/stats-views/months.jsx @@ -1,10 +1,9 @@ import page from '@automattic/calypso-router'; import { Popover } from '@automattic/components'; -import { localize } from 'i18n-calypso'; +import { localize, numberFormat } from 'i18n-calypso'; import PropTypes from 'prop-types'; import { createRef, createElement, PureComponent } from 'react'; import { withLocalizedMoment } from 'calypso/components/localized-moment'; -import { formatNumberMetric } from 'calypso/lib/format-number-compact'; class Month extends PureComponent { static propTypes = { @@ -64,7 +63,7 @@ class Month extends PureComponent { } const StatsViewsMonths = ( props ) => { - const { translate, dataKey, data, numberFormat, moment, siteSlug, showYearTotal = false } = props; + const { translate, dataKey, data, moment, siteSlug, showYearTotal = false } = props; const dataEntries = data ? Object.entries( data ) : []; const isAverageChart = dataKey === 'average'; let earliestDate = moment(); @@ -132,7 +131,7 @@ const StatsViewsMonths = ( props ) => { totals.months[ month ] += value; totals.yearsCount[ year ] += 1; totals.monthsCount[ month ] += 1; - displayValue = formatNumberMetric( value ); + displayValue = numberFormat( value, { decimals: 1, notation: 'compact' } ); } totalValue += value; diff --git a/client/reader/stream/reader-tag-sidebar/index.jsx b/client/reader/stream/reader-tag-sidebar/index.jsx index 9724cb208d663c..2720ce81f9efbb 100644 --- a/client/reader/stream/reader-tag-sidebar/index.jsx +++ b/client/reader/stream/reader-tag-sidebar/index.jsx @@ -1,13 +1,12 @@ import { Button } from '@automattic/components'; import { addLocaleToPathLocaleInFront } from '@automattic/i18n-utils'; -import { useTranslate } from 'i18n-calypso'; +import { useTranslate, numberFormat } from 'i18n-calypso'; import moment from 'moment'; import { connect, useDispatch, useSelector } from 'react-redux'; import TagLink from 'calypso/blocks/reader-post-card/tag-link'; import { useBloggingPrompts } from 'calypso/data/blogging-prompt/use-blogging-prompts'; import { useRelatedMetaByTag } from 'calypso/data/reader/use-related-meta-by-tag'; import { useTagStats } from 'calypso/data/reader/use-tag-stats'; -import formatNumberCompact from 'calypso/lib/format-number-compact'; import { recordAction, recordGaEvent } from 'calypso/reader/stats'; import ReaderListFollowingItem from 'calypso/reader/stream/reader-list-followed-sites/item'; import { isUserLoggedIn } from 'calypso/state/current-user/selectors'; @@ -86,13 +85,13 @@ const ReaderTagSidebar = ( {
- { formatNumberCompact( tagStats?.data?.total_posts ) } + { numberFormat( tagStats?.data?.total_posts, { notation: 'compact' } ) } { translate( 'Posts' ) }
- { formatNumberCompact( tagStats?.data?.total_sites ) } + { numberFormat( tagStats?.data?.total_sites, { notation: 'compact' } ) } { translate( 'Sites' ) }
diff --git a/client/reader/stream/site-feed-sidebar/index.jsx b/client/reader/stream/site-feed-sidebar/index.jsx index a6ef4b094ccb81..fe31508fbb19be 100644 --- a/client/reader/stream/site-feed-sidebar/index.jsx +++ b/client/reader/stream/site-feed-sidebar/index.jsx @@ -1,9 +1,8 @@ import formatNumber from '@automattic/components/src/number-formatters/lib/format-number'; -import { useTranslate, getLocaleSlug } from 'i18n-calypso'; +import { useTranslate, getLocaleSlug, numberFormat } from 'i18n-calypso'; import { useDispatch } from 'react-redux'; import ReaderFeedHeaderFollow from 'calypso/blocks/reader-feed-header/follow'; import TagLink from 'calypso/blocks/reader-post-card/tag-link'; -import formatNumberCompact from 'calypso/lib/format-number-compact'; import { recordAction, recordGaEvent } from 'calypso/reader/stats'; import { recordReaderTracksEvent } from 'calypso/state/reader/analytics/actions'; import '../style.scss'; @@ -47,7 +46,7 @@ const FeedStreamSidebar = ( { { postCount && (
- { formatNumberCompact( postCount ) } + { numberFormat( postCount, { notation: 'compact' } ) } { translate( 'Post', 'Posts', { count: postCount } ) } diff --git a/client/reader/tags/trending-tags.tsx b/client/reader/tags/trending-tags.tsx index d8138b61fdf8fa..d45236616a3c51 100644 --- a/client/reader/tags/trending-tags.tsx +++ b/client/reader/tags/trending-tags.tsx @@ -1,7 +1,7 @@ import { addLocaleToPathLocaleInFront } from '@automattic/i18n-utils'; +import { numberFormat } from 'i18n-calypso'; import titlecase from 'to-title-case'; import { recordTracksEvent } from 'calypso/lib/analytics/tracks'; -import formatNumberCompact from 'calypso/lib/format-number-compact'; import { TagResult } from './controller'; interface TrendingTagsProps { @@ -27,7 +27,9 @@ const TagRow = ( props: TagRowProps ) => {
{ titlecase( props.title ) } - { formatNumberCompact( props.count ) } + + { numberFormat( props.count, { notation: 'compact' } ) } +
); diff --git a/client/signup/steps/website-content/dialogs.tsx b/client/signup/steps/website-content/dialogs.tsx index 28e3835823ac90..37d5d3f61ac9eb 100644 --- a/client/signup/steps/website-content/dialogs.tsx +++ b/client/signup/steps/website-content/dialogs.tsx @@ -124,7 +124,7 @@ export function ContentGuidelinesDialog( { components: { strong: , }, - args: [ numberFormat( CHARACTER_LIMIT, {} ) ], + args: [ numberFormat( CHARACTER_LIMIT ) ], } ) } diff --git a/client/signup/steps/website-content/section-types/contact-page-details.tsx b/client/signup/steps/website-content/section-types/contact-page-details.tsx index 5c48784dd60959..9e8f594bd81637 100644 --- a/client/signup/steps/website-content/section-types/contact-page-details.tsx +++ b/client/signup/steps/website-content/section-types/contact-page-details.tsx @@ -36,7 +36,7 @@ export function ContactPageDetails( { "Please shorten your text to under %(characterLimit)s characters for optimal formatting. If it remains over this limit, we'll optimize it with AI when building your site.", { args: { - characterLimit: numberFormat( CHARACTER_LIMIT, {} ), + characterLimit: numberFormat( CHARACTER_LIMIT ), }, comment: '%(characterLimit)s is a formatted number, eg: 5,000.', } diff --git a/client/signup/steps/website-content/section-types/custom-page-details.tsx b/client/signup/steps/website-content/section-types/custom-page-details.tsx index 41ea5918025c06..6b277fc3145aad 100644 --- a/client/signup/steps/website-content/section-types/custom-page-details.tsx +++ b/client/signup/steps/website-content/section-types/custom-page-details.tsx @@ -59,7 +59,7 @@ export function CustomPageDetails( { "Please shorten your text to under %(characterLimit)s characters for optimal formatting. If it remains over this limit, we'll optimize it with AI when building your site.", { args: { - characterLimit: numberFormat( CHARACTER_LIMIT, {} ), + characterLimit: numberFormat( CHARACTER_LIMIT ), }, comment: '%(characterLimit)s is a formatted number, eg: 5,000.', } diff --git a/client/signup/steps/website-content/section-types/default-page-details.tsx b/client/signup/steps/website-content/section-types/default-page-details.tsx index f1cae37324d6a1..4b5ab596cbc5d9 100644 --- a/client/signup/steps/website-content/section-types/default-page-details.tsx +++ b/client/signup/steps/website-content/section-types/default-page-details.tsx @@ -47,7 +47,7 @@ export function DefaultPageDetails( { "Please shorten your text to under %(characterLimit)s characters for optimal formatting. If it remains over this limit, we'll optimize it with AI when building your site.", { args: { - characterLimit: numberFormat( CHARACTER_LIMIT, {} ), + characterLimit: numberFormat( CHARACTER_LIMIT ), }, comment: '%(characterLimit)s is a formatted number, eg: 5,000.', } diff --git a/packages/components/src/count/format-number-compact.js b/packages/components/src/count/format-number-compact.js deleted file mode 100644 index f77ba261a35b22..00000000000000 --- a/packages/components/src/count/format-number-compact.js +++ /dev/null @@ -1,61 +0,0 @@ -import i18n, { numberFormat } from 'i18n-calypso'; -import { THOUSANDS } from './thousands'; - -/** - * Formats a number to a short format given a language code - * @param {number} number number to format - * @param {string} code language code e.g. 'es' - * @returns {?string} A formatted string. - */ -export default function formatNumberCompact( number, code = i18n.getLocaleSlug() ) { - //use numberFormat directly from i18n in this case! - if ( isNaN( number ) || ! THOUSANDS[ code ] ) { - return null; - } - - const { symbol, unitValue = 1000 } = THOUSANDS[ code ]; - - const sign = number < 0 ? '-' : ''; - const absNumber = Math.abs( number ); - - // no-op if we have a small number - if ( absNumber < unitValue ) { - return `${ sign }${ absNumber }`; - } - - //show 2 sig figs, otherwise take leading sig figs. - const decimals = absNumber < unitValue * 10 ? 1 : 0; - - // TODO clk numberFormat - // can deprecate complately in favour of `{ notation: 'compact' }` in Intl.NumberFormat - const value = numberFormat( absNumber / unitValue, { - decimals, - } ); - - return `${ sign }${ value }${ symbol }`; -} - -const ONE_K = 1000; -const ONE_M = ONE_K * 1000; -const ONE_G = ONE_M * 1000; - -/* - * Format a number larger than 1000 by appending a metric unit (K, M, G) and rounding to - * the received decimal point, defaults to 0. - * TODO: merge with formatNumberCompact by adding support for metric units other than 'K' - */ -export function formatNumberMetric( number, decimalPoints = 1 ) { - if ( number < ONE_K ) { - return String( number ); - } - - if ( number < ONE_M ) { - return ( number / ONE_K ).toFixed( decimalPoints ) + 'K'; - } - - if ( number < ONE_G ) { - return ( number / ONE_M ).toFixed( decimalPoints ) + 'M'; - } - - return ( number / ONE_G ).toFixed( decimalPoints ) + 'G'; -} diff --git a/packages/components/src/count/index.jsx b/packages/components/src/count/index.jsx index 0d7abd91057ca6..72ef374d3487a1 100644 --- a/packages/components/src/count/index.jsx +++ b/packages/components/src/count/index.jsx @@ -1,7 +1,6 @@ import clsx from 'clsx'; -import { localize } from 'i18n-calypso'; +import { localize, numberFormat } from 'i18n-calypso'; import PropTypes from 'prop-types'; -import formatNumberCompact from './format-number-compact'; import './style.scss'; @@ -10,14 +9,16 @@ export const Count = ( { primary = false, compact = false, forwardRef, - numberFormat, + numberFormat: numberFormatFromProps, translate, locale, ...props } ) => { + const effectiveNumberFormat = numberFormatFromProps ?? numberFormat; + return ( - { compact ? formatNumberCompact( count ) || numberFormat( count ) : numberFormat( count ) } + { compact ? numberFormat( count, { notation: 'compact' } ) : effectiveNumberFormat( count ) } ); }; diff --git a/packages/components/src/count/test/index.jsx b/packages/components/src/count/test/index.jsx index b63492f66cee25..5bdc13406acfb7 100644 --- a/packages/components/src/count/test/index.jsx +++ b/packages/components/src/count/test/index.jsx @@ -11,9 +11,9 @@ describe( 'Count', () => { expect( numberFormatSpy ).toHaveBeenCalledWith( 23 ); } ); - test( 'should call `formatNumberCompact` if `compact` prop is `true`', () => { + test( 'should call `i18n.numberFormat` with `compact` if `true`', () => { const { container } = render( ); - expect( container.firstChild ).toHaveTextContent( '1.0K' ); + expect( container.firstChild ).toHaveTextContent( '1K' ); } ); test( 'should render with primary class', () => { diff --git a/packages/components/src/count/thousands.js b/packages/components/src/count/thousands.js deleted file mode 100644 index 2de703c8a26e24..00000000000000 --- a/packages/components/src/count/thousands.js +++ /dev/null @@ -1,50 +0,0 @@ -export const THOUSANDS = { - en: { - symbol: 'K', - }, - es: { - symbol: ' mil', - }, - 'pt-br': { - symbol: ' mil', - }, - de: { - symbol: ' Tsd.', - }, - fr: { - symbol: ' k', - }, - he: { - symbol: 'K', - }, - ja: { - symbol: '万', - unitValue: 10000, - }, - nl: { - symbol: 'K', - }, - tr: { - symbol: ' B', - }, - id: { - symbol: ' rb', - }, - 'zh-cn': { - symbol: '万', - unitValue: 10000, - }, - 'zh-tw': { - symbol: '萬', - unitValue: 10000, - }, - ko: { - symbol: '천', - }, - ar: { - symbol: ' ألف', - }, - sv: { - symbol: ' tn', - }, -}; diff --git a/packages/i18n-calypso/CHANGELOG.md b/packages/i18n-calypso/CHANGELOG.md index 99f795ac761b71..eff465b1153275 100644 --- a/packages/i18n-calypso/CHANGELOG.md +++ b/packages/i18n-calypso/CHANGELOG.md @@ -1,3 +1,7 @@ +## 7.2.1 + +- `compact` option for `numberFormat` + ## 7.2.0 - Replace `number_format` with `Intl.NumberFormat` diff --git a/packages/i18n-calypso/package.json b/packages/i18n-calypso/package.json index 593474b538868e..3bc262cbe096c9 100644 --- a/packages/i18n-calypso/package.json +++ b/packages/i18n-calypso/package.json @@ -1,6 +1,6 @@ { "name": "i18n-calypso", - "version": "7.2.0", + "version": "7.2.1", "description": "I18n JavaScript library on top of Tannin originally used in Calypso.", "main": "dist/cjs/index.js", "module": "dist/esm/index.js", diff --git a/packages/i18n-calypso/src/i18n.js b/packages/i18n-calypso/src/i18n.js index edc0e17381c5e2..ded0c78ffb5532 100644 --- a/packages/i18n-calypso/src/i18n.js +++ b/packages/i18n-calypso/src/i18n.js @@ -171,20 +171,30 @@ I18N.prototype.emit = function ( ...args ) { * @param {string | number} number to format (required) * @param {number | Object} options Number of decimal places or options object (optional) * @param {boolean} forceLatin Whether to use latin numbers by default (optional. default = true) - * @returns {string | number} Formatted number as string, or original number if formatting fails + * @returns {string | number} Formatted number as string, or original number if formatting fails. Null otherwise. */ I18N.prototype.numberFormat = function ( number, options = {}, forceLatin = true ) { const decimals = typeof options === 'number' ? options : options.decimals || 0; const browserSafeLocale = this.getBrowserSafeLocale(); + /** + * TS will flag this as an error, but best to check for undefined here for older usages + * `Intl.NumberFormat` will return NaN for undefined values, which is not helpful. Null becomes 0, also potentially risky. + */ + if ( typeof number === 'undefined' || number === null ) { + warn( 'numberFormat() requires a defined and non-null value as the first argument' ); + return number; + } + try { return Intl.NumberFormat( `${ browserSafeLocale }${ forceLatin ? '-u-nu-latn' : '' }`, { minimumFractionDigits: decimals, // default is 0 maximumFractionDigits: decimals, // default is the greater between minimumFractionDigits and 3 // TODO clk numberFormat this may be the only difference, where some cases use 2 (they can just pass the option to Intl.NumberFormat) + ...( options?.notation && { notation: options.notation } ), } ).format( number ); } catch ( error ) { - warn( 'Error formatting number with Intl.NumberFormat: ', number, error ); + warn( 'numberFormat(): Error formatting number with Intl.NumberFormat: ', number, error ); } return number; diff --git a/packages/i18n-calypso/test/index.js b/packages/i18n-calypso/test/index.js index 443e1d24af806c..ab33fc4945ad1c 100644 --- a/packages/i18n-calypso/test/index.js +++ b/packages/i18n-calypso/test/index.js @@ -202,7 +202,7 @@ describe( 'I18n', function () { } ); describe( 'getBrowserSafeLocale()', function () { - it( 'should return locale without variant when localeVariant is set', function () { + it( 'should return locale without variant when localeVariant is set with underscore _', function () { i18n.setLocale( { '': { localeVariant: 'de_AT', @@ -212,7 +212,7 @@ describe( 'I18n', function () { expect( i18n.getBrowserSafeLocale() ).toBe( 'de' ); } ); - it( 'should return locale with region code when localeVariant is set', function () { + it( 'should return locale with region code when localeVariant is set with dash -', function () { i18n.setLocale( { '': { localeVariant: 'en-US', @@ -281,6 +281,27 @@ describe( 'I18n', function () { ).toBe( '2.500,330' ); } ); } ); + + describe( 'compact notation', function () { + describe( 'ar', () => { + beforeEach( function () { + i18n.setLocale( { + '': { + localeVariant: undefined, + localeSlug: 'ar', + }, + } ); + } ); + test( 'defaults to latin notation and localised unit', () => { + expect( numberFormat( 1234, { notation: 'compact', decimals: 1 } ) ).toEqual( '1.2 ألف' ); + } ); + test( 'non-latin/original notation and localised unit', () => { + expect( numberFormat( 1234, { notation: 'compact', decimals: 1 }, false ) ).toEqual( + '١٫٢ ألف' + ); + } ); + } ); + } ); } ); describe( 'hashed locale data', function () { diff --git a/packages/i18n-calypso/types/index.d.ts b/packages/i18n-calypso/types/index.d.ts index 4bab489aca8427..c438c745ab3746 100644 --- a/packages/i18n-calypso/types/index.d.ts +++ b/packages/i18n-calypso/types/index.d.ts @@ -64,6 +64,7 @@ export type TranslateResult = ExistingReactNode; export interface NumberFormatOptions { decimals?: number; + notation: Intl.NumberFormatOptions[ 'notation' ]; } export type TranslateHook = ( @@ -92,8 +93,8 @@ export interface I18N { translate( original: string, plural: string, options: TranslateOptionsPlural ): ExistingReactNode; translate( original: string, plural: string, options: TranslateOptionsPluralText ): string; - numberFormat( number: number, numberOfDecimalPlaces: number ): string; - numberFormat( number: number, options: NumberFormatOptions ): string; + numberFormat( number: number, numberOfDecimalPlaces?: number ): string | number; + numberFormat( number: number, options?: NumberFormatOptions ): string | number; setLocale( localeData: LocaleData ): void; addTranslations( localeData: LocaleData ): void;