Format numbers internationally.
This commit is contained in:
parent
c5b7cc5ac4
commit
5319232918
12 changed files with 64 additions and 59 deletions
|
@ -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">
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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 && (
|
||||
<>
|
||||
|
|
|
@ -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={() => {
|
||||
|
|
|
@ -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) && (
|
||||
|
|
|
@ -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)}
|
||||
|
|
|
@ -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" />}
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
);
|
||||
|
|
|
@ -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 =
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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 });
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue