Format numbers internationally.

This commit is contained in:
Franco Montenegro 2022-04-26 12:28:23 -03:00 committed by jessopb
parent c5b7cc5ac4
commit 5319232918
12 changed files with 64 additions and 59 deletions

View file

@ -6,6 +6,7 @@ import CreditAmount from 'component/common/credit-amount';
import DateTime from 'component/dateTime';
import YoutubeBadge from 'component/youtubeBadge';
import SUPPORTED_LANGUAGES from 'constants/supported_languages';
import { formatNumber } from 'util/number';
type Props = {
claim: ChannelClaim,
@ -74,7 +75,7 @@ function ChannelAbout(props: Props) {
</div>
<label>{__('Total Uploads')}</label>
<div className="media__info-text">{claim.meta.claims_in_channel}</div>
<div className="media__info-text">{formatNumber(claim.meta.claims_in_channel || 0, 2, true)}</div>
<label>{__('Last Updated')}</label>
<div className="media__info-text">

View file

@ -9,7 +9,7 @@ import * as COLLECTIONS_CONSTS from 'constants/collections';
import { isChannelClaim } from 'util/claim';
import { formatLbryUrlForWeb } from 'util/url';
import { formatClaimPreviewTitle } from 'util/formatAriaLabel';
import { toCompactNotation } from 'util/string';
import { formatNumber } from 'util/number';
import Tooltip from 'component/common/tooltip';
import FileThumbnail from 'component/fileThumbnail';
import UriIndicator from 'component/uriIndicator';
@ -138,7 +138,6 @@ const ClaimPreview = forwardRef<any, {}>((props: Props, ref: any) => {
indexInContainer,
channelSubCount,
swipeLayout = false,
lang,
showEdit,
dragHandleProps,
unavailableUris,
@ -162,8 +161,8 @@ const ClaimPreview = forwardRef<any, {}>((props: Props, ref: any) => {
if (channelSubCount === undefined) {
return <span />;
}
const formattedSubCount = toCompactNotation(channelSubCount, lang, 10000);
const formattedSubCountLocale = Number(channelSubCount).toLocaleString();
const formattedSubCount = formatNumber(channelSubCount, 2, true);
const formattedSubCountLocale = formatNumber(channelSubCount, 2, false);
return (
<div className="media__subtitle">
<Tooltip title={formattedSubCountLocale} followCursor placement="top">

View file

@ -5,6 +5,7 @@ import DateTime from 'component/dateTime';
import Button from 'component/button';
import FileViewCountInline from 'component/fileViewCountInline';
import { parseURI } from 'util/lbryURI';
import { formatNumber } from 'util/number';
type Props = {
uri: string,
@ -34,7 +35,7 @@ function ClaimPreviewSubtitle(props: Props) {
<>
{isChannel &&
type !== 'inline' &&
`${claimsInChannel} ${claimsInChannel === 1 ? __('upload') : __('uploads')}`}
`${formatNumber(claimsInChannel, 2, true)} ${claimsInChannel === 1 ? __('upload') : __('uploads')}`}
{!isChannel && (
<>

View file

@ -8,6 +8,7 @@ import { FF_MAX_CHARS_IN_COMMENT } from 'constants/form-field';
import { SITE_NAME, ENABLE_COMMENT_REACTIONS } from 'config';
import React, { useEffect, useState } from 'react';
import { parseURI } from 'util/lbryURI';
import { formatNumber } from 'util/number';
import DateTime from 'component/dateTime';
import Button from 'component/button';
import Expandable from 'component/expandable';
@ -384,7 +385,7 @@ function CommentView(props: Props) {
label={
numDirectReplies < 2
? __('Show reply')
: __('Show %count% replies', { count: numDirectReplies })
: __('Show %count% replies', { count: formatNumber(numDirectReplies, 2, true) })
}
button="link"
onClick={() => {

View file

@ -9,6 +9,7 @@ import Button from 'component/button';
import ChannelThumbnail from 'component/channelThumbnail';
import { useHistory } from 'react-router';
import { useIsMobile } from 'effects/use-screensize';
import { formatNumber } from 'util/number';
type Props = {
myReacts: Array<string>,
@ -109,7 +110,11 @@ export default function CommentReactions(props: Props) {
'comment__action--active': myReacts && myReacts.includes(REACTION_TYPES.LIKE),
})}
onClick={handleCommentLike}
label={<span className="comment__reaction-count">{getCountForReact(REACTION_TYPES.LIKE)}</span>}
label={
<span className="comment__reaction-count">
{formatNumber(getCountForReact(REACTION_TYPES.LIKE), 2, true)}
</span>
}
/>
<Button
title={__('Downvote')}
@ -119,7 +124,11 @@ export default function CommentReactions(props: Props) {
'comment__action--active': myReacts && myReacts.includes(REACTION_TYPES.DISLIKE),
})}
onClick={handleCommentDislike}
label={<span className="comment__reaction-count">{getCountForReact(REACTION_TYPES.DISLIKE)}</span>}
label={
<span className="comment__reaction-count">
{formatNumber(getCountForReact(REACTION_TYPES.DISLIKE), 2, true)}
</span>
}
/>
{!shouldHide && ENABLE_CREATOR_REACTIONS && (canCreatorReact || creatorLiked) && (

View file

@ -4,7 +4,7 @@ import * as ICONS from 'constants/icons';
import React from 'react';
import classnames from 'classnames';
import Button from 'component/button';
import { formatNumberWithCommas } from 'util/number';
import { formatNumber } from 'util/number';
import NudgeFloating from 'component/nudgeFloating';
type Props = {
claim: StreamClaim,
@ -18,16 +18,8 @@ type Props = {
};
function FileReactions(props: Props) {
const {
claim,
uri,
doFetchReactions,
doReactionLike,
doReactionDislike,
myReaction,
likeCount,
dislikeCount,
} = props;
const { claim, uri, doFetchReactions, doReactionLike, doReactionDislike, myReaction, likeCount, dislikeCount } =
props;
const claimId = claim && claim.claim_id;
const channel = claim && claim.signing_channel && claim.signing_channel.name;
@ -59,7 +51,7 @@ function FileReactions(props: Props) {
className={classnames('button--file-action button-like', {
'button--file-action-active': myReaction === REACTION_TYPES.LIKE,
})}
label={<>{formatNumberWithCommas(likeCount, 0)}</>}
label={<>{formatNumber(likeCount, 2, true)}</>}
iconSize={18}
icon={likeIcon}
onClick={() => doReactionLike(uri)}
@ -70,7 +62,7 @@ function FileReactions(props: Props) {
className={classnames('button--file-action button-dislike', {
'button--file-action-active': myReaction === REACTION_TYPES.DISLIKE,
})}
label={<>{formatNumberWithCommas(dislikeCount, 0)}</>}
label={<>{formatNumber(dislikeCount, 2, true)}</>}
iconSize={18}
icon={dislikeIcon}
onClick={() => doReactionDislike(uri)}

View file

@ -2,7 +2,7 @@
import React from 'react';
import HelpLink from 'component/common/help-link';
import Tooltip from 'component/common/tooltip';
import { toCompactNotation } from 'util/string';
import { formatNumber } from 'util/number';
type Props = {
claimId: ?string, // this
@ -14,8 +14,8 @@ type Props = {
function FileViewCount(props: Props) {
const { claimId, uri, fetchViewCount, viewCount } = props; // claimId
const countCompact = toCompactNotation(viewCount);
const countFullResolution = Number(viewCount).toLocaleString();
const countCompact = formatNumber(Number(viewCount), 2, true);
const countFullResolution = formatNumber(Number(viewCount), 2, false);
React.useEffect(() => {
if (claimId) {
@ -24,7 +24,7 @@ function FileViewCount(props: Props) {
}, [fetchViewCount, uri, claimId]);
return (
<Tooltip label={countFullResolution}>
<Tooltip title={`${countFullResolution}`}>
<span className="media__subtitle--centered">
{viewCount !== 1 ? __('%view_count% views', { view_count: countCompact }) : __('1 view')}
{<HelpLink href="https://lbry.com/faq/views" />}

View file

@ -1,4 +1,6 @@
// @flow
import { formatCredits } from 'util/format-credits';
type Props = {
uri: ?string,
isResolvingUri: boolean,
@ -23,7 +25,7 @@ function BidHelpText(props: Props) {
bidHelpText = __(
'If you bid more than %amount% LBRY Credits, when someone navigates to %uri%, it will load your published content. However, you can get a longer version of this URL for any bid.',
{
amount: amountNeededForTakeover,
amount: formatCredits(amountNeededForTakeover, 2, true),
uri: uri,
}
);

View file

@ -3,6 +3,7 @@ import * as ICONS from 'constants/icons';
import * as PAGES from 'constants/pages';
import React from 'react';
import { parseURI } from 'util/lbryURI';
import { formatNumber } from 'util/number';
import { YOUTUBE_STATUSES } from 'lbryinc';
import Page from 'component/page';
import SubscribeButton from 'component/subscribeButton';
@ -93,7 +94,7 @@ function ChannelPage(props: Props) {
const { channelName } = parseURI(uri);
const { permanent_url: permanentUrl } = claim;
const claimId = claim.claim_id;
const formattedSubCount = Number(subCount).toLocaleString();
const formattedSubCount = formatNumber(subCount, 2, true);
const isBlocked = claim && blockedChannels.includes(claim.permanent_url);
const isMuted = claim && mutedChannels.includes(claim.permanent_url);
const isMyYouTubeChannel =

View file

@ -2,6 +2,7 @@
import { SORT_COMMENTS_NEW, SORT_COMMENTS_BEST, SORT_COMMENTS_CONTROVERSIAL } from 'constants/comment';
import { FREE_GLOBAL_STICKERS, PAID_GLOBAL_STICKERS } from 'constants/stickers';
import * as REACTION_TYPES from 'constants/reactions';
import { formatNumber } from 'util/number';
const ALL_VALID_STICKERS = [...FREE_GLOBAL_STICKERS, ...PAID_GLOBAL_STICKERS];
const stickerRegex = /(<stkr>:[A-Z0-9_]+:<stkr>)/;
@ -118,7 +119,7 @@ export function getCommentsListTitle(totalComments: number) {
const title =
(totalComments === 0 && __('Leave a comment')) ||
(totalComments === 1 && __('1 comment')) ||
__('%total_comments% comments', { total_comments: totalComments });
__('%total_comments% comments', { total_comments: formatNumber(totalComments, 2, true) });
return title;
}

View file

@ -1,39 +1,27 @@
function numberWithCommas(x) {
var parts = x.toString().split('.');
parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ',');
return parts.join('.');
}
export function formatCredits(amount, precision, shortFormat = false) {
let actualAmount = parseFloat(amount);
let actualPrecision = parseFloat(precision);
let suffix = '';
const actualAmount = Number(amount);
const safePrecision = Math.min(20, Math.max(1, precision));
if (Number.isNaN(actualAmount) || actualAmount === 0) return '0';
if (actualAmount >= 1000000) {
if (precision <= 7) {
if (shortFormat) {
actualAmount = actualAmount / 1000000;
suffix = 'M';
} else {
actualPrecision -= 7;
}
}
} else if (actualAmount >= 1000) {
if (precision <= 4) {
if (shortFormat) {
actualAmount = actualAmount / 1000;
suffix = 'K';
} else {
actualPrecision -= 4;
}
}
if (shortFormat) {
const formatter = new Intl.NumberFormat(undefined, {
minimumFractionDigits: safePrecision,
maximumFractionDigits: safePrecision,
roundingIncrement: 5,
// Display suffix (M, K, etc.)
notation: 'compact',
compactDisplay: 'short',
});
return formatter.format(actualAmount);
}
return (
numberWithCommas(actualAmount.toFixed(actualPrecision >= 0 ? actualPrecision : 1).replace(/\.*0+$/, '')) + suffix
);
const formatter = new Intl.NumberFormat(undefined, {
minimumFractionDigits: safePrecision,
maximumFractionDigits: safePrecision,
roundingIncrement: 5,
});
return formatter.format(actualAmount);
}
export function formatFullPrice(amount, precision = 1) {

View file

@ -1,5 +1,15 @@
// @flow
export function formatNumber(num: number, numberOfDigits?: number, short: boolean = false): string {
const safePrecision = Math.min(20, numberOfDigits || 0);
const formatter = new Intl.NumberFormat(undefined, {
maximumFractionDigits: safePrecision,
notation: short ? 'compact' : 'standard',
compactDisplay: 'short',
});
return formatter.format(num);
}
export function formatNumberWithCommas(num: number, numberOfDigits?: number): string {
return num.toLocaleString('en', { minimumFractionDigits: numberOfDigits !== undefined ? numberOfDigits : 8 });
}