Localization: Respect locale number format for floats (#5863)

This commit is contained in:
zubiden 2025-04-23 18:59:49 +02:00 committed by Alexander Zinchuk
parent b3c6ae2089
commit 29ec767eb2
16 changed files with 85 additions and 59 deletions

View File

@ -6,6 +6,7 @@ import { getAllNotificationsCount } from '../../util/folderManager';
import { formatIntegerCompact } from '../../util/textFormat'; import { formatIntegerCompact } from '../../util/textFormat';
import { useFolderManagerForUnreadCounters } from '../../hooks/useFolderManager'; import { useFolderManagerForUnreadCounters } from '../../hooks/useFolderManager';
import useLang from '../../hooks/useLang';
interface OwnProps { interface OwnProps {
isForAppBadge?: boolean; isForAppBadge?: boolean;
@ -15,6 +16,8 @@ const UnreadCounter: FC<OwnProps> = ({ isForAppBadge }) => {
useFolderManagerForUnreadCounters(); useFolderManagerForUnreadCounters();
const unreadNotificationsCount = getAllNotificationsCount(); const unreadNotificationsCount = getAllNotificationsCount();
const lang = useLang();
useEffect(() => { useEffect(() => {
if (isForAppBadge) { if (isForAppBadge) {
updateAppBadge(unreadNotificationsCount); updateAppBadge(unreadNotificationsCount);
@ -26,7 +29,7 @@ const UnreadCounter: FC<OwnProps> = ({ isForAppBadge }) => {
} }
return ( return (
<div className="unread-count active">{formatIntegerCompact(unreadNotificationsCount)}</div> <div className="unread-count active">{formatIntegerCompact(lang, unreadNotificationsCount)}</div>
); );
}; };

View File

@ -69,7 +69,7 @@ const SavedGift = ({
const ribbonText = gift.isPinned && gift.gift.type === 'starGiftUnique' const ribbonText = gift.isPinned && gift.gift.type === 'starGiftUnique'
? lang('GiftSavedNumber', { number: gift.gift.number }) ? lang('GiftSavedNumber', { number: gift.gift.number })
: totalIssued : totalIssued
? lang('ActionStarGiftLimitedRibbon', { total: formatIntegerCompact(totalIssued) }) ? lang('ActionStarGiftLimitedRibbon', { total: formatIntegerCompact(lang, totalIssued) })
: undefined; : undefined;
const { const {

View File

@ -118,7 +118,7 @@ const Archive: FC<OwnProps> = ({
</div> </div>
<Badge <Badge
className={styles.unreadCount} className={styles.unreadCount}
text={archiveUnreadCount ? formatIntegerCompact(archiveUnreadCount) : undefined} text={archiveUnreadCount ? formatIntegerCompact(lang, archiveUnreadCount) : undefined}
/> />
</div> </div>
</div> </div>
@ -143,7 +143,7 @@ const Archive: FC<OwnProps> = ({
</div> </div>
<Badge <Badge
className={styles.unreadCount} className={styles.unreadCount}
text={archiveUnreadCount ? formatIntegerCompact(archiveUnreadCount) : undefined} text={archiveUnreadCount ? formatIntegerCompact(lang, archiveUnreadCount) : undefined}
/> />
</div> </div>
</div> </div>

View File

@ -12,6 +12,7 @@ import { formatIntegerCompact } from '../../../util/textFormat';
import { extractCurrentThemeParams } from '../../../util/themeStyle'; import { extractCurrentThemeParams } from '../../../util/themeStyle';
import useDerivedState from '../../../hooks/useDerivedState'; import useDerivedState from '../../../hooks/useDerivedState';
import useLang from '../../../hooks/useLang';
import useLastCallback from '../../../hooks/useLastCallback'; import useLastCallback from '../../../hooks/useLastCallback';
import useOldLang from '../../../hooks/useOldLang'; import useOldLang from '../../../hooks/useOldLang';
@ -52,6 +53,7 @@ const ChatBadge: FC<OwnProps> = ({
const { requestMainWebView } = getActions(); const { requestMainWebView } = getActions();
const oldLang = useOldLang(); const oldLang = useOldLang();
const lang = useLang();
const { const {
unreadMentionsCount = 0, unreadReactionsCount = 0, unreadMentionsCount = 0, unreadReactionsCount = 0,
@ -136,7 +138,7 @@ const ChatBadge: FC<OwnProps> = ({
const unreadCountElement = (hasUnreadMark || unreadCount) ? ( const unreadCountElement = (hasUnreadMark || unreadCount) ? (
<div className={className}> <div className={className}>
{!hasUnreadMark && <AnimatedCounter text={formatIntegerCompact(unreadCount!)} />} {!hasUnreadMark && <AnimatedCounter text={formatIntegerCompact(lang, unreadCount!)} />}
</div> </div>
) : undefined; ) : undefined;

View File

@ -20,6 +20,7 @@ import { formatIntegerCompact } from '../../util/textFormat';
import useFlag from '../../hooks/useFlag'; import useFlag from '../../hooks/useFlag';
import useInfiniteScroll from '../../hooks/useInfiniteScroll'; import useInfiniteScroll from '../../hooks/useInfiniteScroll';
import useLang from '../../hooks/useLang';
import useLastCallback from '../../hooks/useLastCallback'; import useLastCallback from '../../hooks/useLastCallback';
import useOldLang from '../../hooks/useOldLang'; import useOldLang from '../../hooks/useOldLang';
@ -67,7 +68,8 @@ const ReactorListModal: FC<OwnProps & StateProps> = ({
const chatsById = getGlobal().chats.byId; const chatsById = getGlobal().chats.byId;
const usersById = getGlobal().users.byId; const usersById = getGlobal().users.byId;
const lang = useOldLang(); const oldLang = useOldLang();
const lang = useLang();
const [isClosing, startClosing, stopClosing] = useFlag(false); const [isClosing, startClosing, stopClosing] = useFlag(false);
const [chosenTab, setChosenTab] = useState<ApiReaction | undefined>(undefined); const [chosenTab, setChosenTab] = useState<ApiReaction | undefined>(undefined);
const canShowFilters = reactors && reactions && reactors.count >= MIN_REACTIONS_COUNT_FOR_FILTERS const canShowFilters = reactors && reactions && reactors.count >= MIN_REACTIONS_COUNT_FOR_FILTERS
@ -143,11 +145,11 @@ const ReactorListModal: FC<OwnProps & StateProps> = ({
isOpen={isOpen && !isClosing} isOpen={isOpen && !isClosing}
onClose={handleClose} onClose={handleClose}
className="ReactorListModal narrow" className="ReactorListModal narrow"
title={lang('Reactions')} title={oldLang('Reactions')}
onCloseAnimationEnd={handleCloseAnimationEnd} onCloseAnimationEnd={handleCloseAnimationEnd}
> >
{canShowFilters && ( {canShowFilters && (
<div className="Reactions" dir={lang.isRtl ? 'rtl' : undefined}> <div className="Reactions" dir={oldLang.isRtl ? 'rtl' : undefined}>
<Button <Button
className={buildClassName(!chosenTab && 'chosen')} className={buildClassName(!chosenTab && 'chosen')}
size="tiny" size="tiny"
@ -156,7 +158,7 @@ const ReactorListModal: FC<OwnProps & StateProps> = ({
onClick={() => setChosenTab(undefined)} onClick={() => setChosenTab(undefined)}
> >
<Icon name="heart" /> <Icon name="heart" />
{Boolean(reactors?.count) && formatIntegerCompact(reactors.count)} {Boolean(reactors?.count) && formatIntegerCompact(lang, reactors.count)}
</Button> </Button>
{allReactions.map((reaction) => { {allReactions.map((reaction) => {
const count = reactions?.results const count = reactions?.results
@ -175,14 +177,14 @@ const ReactorListModal: FC<OwnProps & StateProps> = ({
className="reaction-filter-emoji" className="reaction-filter-emoji"
availableReactions={availableReactions} availableReactions={availableReactions}
/> />
{Boolean(count) && formatIntegerCompact(count)} {Boolean(count) && formatIntegerCompact(lang, count)}
</Button> </Button>
); );
})} })}
</div> </div>
)} )}
<div dir={lang.isRtl ? 'rtl' : undefined} className="reactor-list-wrapper"> <div dir={oldLang.isRtl ? 'rtl' : undefined} className="reactor-list-wrapper">
{viewportIds?.length ? ( {viewportIds?.length ? (
<InfiniteScroll <InfiniteScroll
className="reactor-list custom-scroll" className="reactor-list custom-scroll"
@ -212,7 +214,7 @@ const ReactorListModal: FC<OwnProps & StateProps> = ({
<FullNameTitle peer={peer} withEmojiStatus /> <FullNameTitle peer={peer} withEmojiStatus />
<span className="status" dir="auto"> <span className="status" dir="auto">
<Icon name="heart-outline" className="status-icon" /> <Icon name="heart-outline" className="status-icon" />
{formatDateAtTime(lang, r.addedDate * 1000)} {formatDateAtTime(oldLang, r.addedDate * 1000)}
</span> </span>
</div> </div>
{r.reaction && ( {r.reaction && (
@ -238,7 +240,7 @@ const ReactorListModal: FC<OwnProps & StateProps> = ({
userId={peerId} userId={peerId}
noStatusOrTyping noStatusOrTyping
avatarSize="medium" avatarSize="medium"
status={seenByUser ? formatDateAtTime(lang, seenByUser * 1000) : undefined} status={seenByUser ? formatDateAtTime(oldLang, seenByUser * 1000) : undefined}
statusIcon="message-read" statusIcon="message-read"
/> />
</ListItem>, </ListItem>,
@ -255,7 +257,7 @@ const ReactorListModal: FC<OwnProps & StateProps> = ({
isText isText
onClick={handleClose} onClick={handleClose}
> >
{lang('Close')} {oldLang('Close')}
</Button> </Button>
</Modal> </Modal>
); );

View File

@ -7,6 +7,7 @@ import buildClassName from '../../util/buildClassName';
import { formatIntegerCompact } from '../../util/textFormat'; import { formatIntegerCompact } from '../../util/textFormat';
import useContextMenuHandlers from '../../hooks/useContextMenuHandlers'; import useContextMenuHandlers from '../../hooks/useContextMenuHandlers';
import useLang from '../../hooks/useLang';
import useOldLang from '../../hooks/useOldLang'; import useOldLang from '../../hooks/useOldLang';
import Icon from '../common/icons/Icon'; import Icon from '../common/icons/Icon';
@ -33,7 +34,8 @@ const ScrollDownButton: FC<OwnProps> = ({
onReadAll, onReadAll,
className, className,
}) => { }) => {
const lang = useOldLang(); const oldLang = useOldLang();
const lang = useLang();
// eslint-disable-next-line no-null/no-null // eslint-disable-next-line no-null/no-null
const ref = useRef<HTMLDivElement>(null); const ref = useRef<HTMLDivElement>(null);
@ -52,11 +54,11 @@ const ScrollDownButton: FC<OwnProps> = ({
className={styles.button} className={styles.button}
onClick={onClick} onClick={onClick}
onContextMenu={handleContextMenu} onContextMenu={handleContextMenu}
ariaLabel={lang(ariaLabelLang)} ariaLabel={oldLang(ariaLabelLang)}
> >
<Icon name={icon} className={styles.icon} /> <Icon name={icon} className={styles.icon} />
</Button> </Button>
{Boolean(unreadCount) && <div className={styles.unreadCount}>{formatIntegerCompact(unreadCount)}</div>} {Boolean(unreadCount) && <div className={styles.unreadCount}>{formatIntegerCompact(lang, unreadCount)}</div>}
{onReadAll && ( {onReadAll && (
<Menu <Menu
isOpen={isContextMenuOpen} isOpen={isContextMenuOpen}
@ -66,7 +68,7 @@ const ScrollDownButton: FC<OwnProps> = ({
positionX="right" positionX="right"
positionY="bottom" positionY="bottom"
> >
<MenuItem icon="readchats" onClick={onReadAll}>{lang('MarkAllAsRead')}</MenuItem> <MenuItem icon="readchats" onClick={onReadAll}>{oldLang('MarkAllAsRead')}</MenuItem>
</Menu> </Menu>
)} )}
</div> </div>

View File

@ -8,6 +8,7 @@ import { selectIsCurrentUserFrozen, selectPeer } from '../../../global/selectors
import buildClassName from '../../../util/buildClassName'; import buildClassName from '../../../util/buildClassName';
import { formatIntegerCompact } from '../../../util/textFormat'; import { formatIntegerCompact } from '../../../util/textFormat';
import useLang from '../../../hooks/useLang';
import useLastCallback from '../../../hooks/useLastCallback'; import useLastCallback from '../../../hooks/useLastCallback';
import useOldLang from '../../../hooks/useOldLang'; import useOldLang from '../../../hooks/useOldLang';
import useAsyncRendering from '../../right/hooks/useAsyncRendering'; import useAsyncRendering from '../../right/hooks/useAsyncRendering';
@ -40,7 +41,8 @@ const CommentButton: FC<OwnProps> = ({
const shouldRenderLoading = useAsyncRendering([isLoading], SHOW_LOADER_DELAY); const shouldRenderLoading = useAsyncRendering([isLoading], SHOW_LOADER_DELAY);
const lang = useOldLang(); const oldLang = useOldLang();
const lang = useLang();
const { const {
originMessageId, chatId, messagesCount, lastMessageId, lastReadInboxMessageId, recentReplierIds, originChannelId, originMessageId, chatId, messagesCount, lastMessageId, lastReadInboxMessageId, recentReplierIds, originChannelId,
} = threadInfo; } = threadInfo;
@ -76,7 +78,7 @@ const CommentButton: FC<OwnProps> = ({
function renderRecentRepliers() { function renderRecentRepliers() {
return ( return (
Boolean(recentRepliers?.length) && ( Boolean(recentRepliers?.length) && (
<div className="recent-repliers" dir={lang.isRtl ? 'rtl' : 'ltr'}> <div className="recent-repliers" dir={oldLang.isRtl ? 'rtl' : 'ltr'}>
{recentRepliers!.map((peer) => ( {recentRepliers!.map((peer) => (
<Avatar <Avatar
key={peer.id} key={peer.id}
@ -91,16 +93,16 @@ const CommentButton: FC<OwnProps> = ({
const hasUnread = Boolean(lastReadInboxMessageId && lastMessageId && lastReadInboxMessageId < lastMessageId); const hasUnread = Boolean(lastReadInboxMessageId && lastMessageId && lastReadInboxMessageId < lastMessageId);
const commentsText = messagesCount ? (lang('CommentsCount', '%COMMENTS_COUNT%', undefined, messagesCount) as string) const commentsText = messagesCount ? (oldLang('CommentsCount', '%COMMENTS_COUNT%', undefined, messagesCount))
.split('%') .split('%')
.map((s) => { .map((s) => {
return (s === 'COMMENTS_COUNT' ? <AnimatedCounter text={formatIntegerCompact(messagesCount)} /> : s); return (s === 'COMMENTS_COUNT' ? <AnimatedCounter text={formatIntegerCompact(lang, messagesCount)} /> : s);
}) })
: undefined; : undefined;
return ( return (
<div <div
data-cnt={formatIntegerCompact(messagesCount)} data-cnt={formatIntegerCompact(lang, messagesCount)}
className={buildClassName( className={buildClassName(
'CommentButton', 'CommentButton',
hasUnread && 'has-unread', hasUnread && 'has-unread',
@ -109,7 +111,7 @@ const CommentButton: FC<OwnProps> = ({
isLoading && 'loading', isLoading && 'loading',
asActionButton && 'as-action-button', asActionButton && 'as-action-button',
)} )}
dir={lang.isRtl ? 'rtl' : 'ltr'} dir={oldLang.isRtl ? 'rtl' : 'ltr'}
onClick={handleClick} onClick={handleClick}
role="button" role="button"
tabIndex={0} tabIndex={0}
@ -124,7 +126,7 @@ const CommentButton: FC<OwnProps> = ({
{!recentRepliers?.length && <Icon name="comments" />} {!recentRepliers?.length && <Icon name="comments" />}
{renderRecentRepliers()} {renderRecentRepliers()}
<div className="label" dir="auto"> <div className="label" dir="auto">
{messagesCount ? commentsText : lang('LeaveAComment')} {messagesCount ? commentsText : oldLang('LeaveAComment')}
</div> </div>
<div className="CommentButton_right"> <div className="CommentButton_right">
{isLoading && ( {isLoading && (

View File

@ -112,10 +112,14 @@ const MessageMeta: FC<OwnProps> = ({
const viewsTitle = useMemo(() => { const viewsTitle = useMemo(() => {
if (!message.viewsCount) return undefined; if (!message.viewsCount) return undefined;
let text = lang('MessageTooltipViews', { count: message.viewsCount }, { pluralValue: message.viewsCount }); let text = lang('MessageTooltipViews', {
count: lang.number(message.viewsCount),
}, { pluralValue: message.viewsCount });
if (message.forwardsCount) { if (message.forwardsCount) {
text += '\n'; text += '\n';
text += lang('MessageTooltipForwards', { count: message.forwardsCount }, { pluralValue: message.forwardsCount }); text += lang('MessageTooltipForwards', {
count: lang.number(message.forwardsCount),
}, { pluralValue: message.forwardsCount });
} }
return text; return text;
@ -160,7 +164,7 @@ const MessageMeta: FC<OwnProps> = ({
{Boolean(message.viewsCount) && ( {Boolean(message.viewsCount) && (
<> <>
<span className="message-views" title={viewsTitle}> <span className="message-views" title={viewsTitle}>
{formatIntegerCompact(message.viewsCount!)} {formatIntegerCompact(lang, message.viewsCount!)}
</span> </span>
<Icon name="channelviews" /> <Icon name="channelviews" />
</> </>
@ -168,7 +172,7 @@ const MessageMeta: FC<OwnProps> = ({
{!noReplies && Boolean(repliesThreadInfo?.messagesCount) && ( {!noReplies && Boolean(repliesThreadInfo?.messagesCount) && (
<span onClick={handleOpenThread} className="message-replies-wrapper" title={repliesTitle}> <span onClick={handleOpenThread} className="message-replies-wrapper" title={repliesTitle}>
<span className="message-replies"> <span className="message-replies">
<AnimatedCounter text={formatIntegerCompact(repliesThreadInfo!.messagesCount!)} /> <AnimatedCounter text={formatIntegerCompact(lang, repliesThreadInfo!.messagesCount!)} />
</span> </span>
<Icon name="reply-filled" /> <Icon name="reply-filled" />
</span> </span>

View File

@ -19,6 +19,7 @@ import useTimeout from '../../../hooks/schedulers/useTimeout';
import useAverageColor from '../../../hooks/useAverageColor'; import useAverageColor from '../../../hooks/useAverageColor';
import useFlag from '../../../hooks/useFlag'; import useFlag from '../../../hooks/useFlag';
import useHorizontalScroll from '../../../hooks/useHorizontalScroll'; import useHorizontalScroll from '../../../hooks/useHorizontalScroll';
import useLang from '../../../hooks/useLang';
import useLastCallback from '../../../hooks/useLastCallback'; import useLastCallback from '../../../hooks/useLastCallback';
import useOldLang from '../../../hooks/useOldLang'; import useOldLang from '../../../hooks/useOldLang';
@ -203,13 +204,14 @@ const SimilarChannels = ({
function SimilarChannel({ channel }: { channel: ApiChat }) { function SimilarChannel({ channel }: { channel: ApiChat }) {
const { openChat } = getActions(); const { openChat } = getActions();
const color = useAverageColor(channel, DEFAULT_BADGE_COLOR); const color = useAverageColor(channel, DEFAULT_BADGE_COLOR);
const lang = useLang();
return ( return (
<div className={styles.item} onClick={() => openChat({ id: channel.id })}> <div className={styles.item} onClick={() => openChat({ id: channel.id })}>
<Avatar className={styles.avatar} key={channel.id} size="large" peer={channel} /> <Avatar className={styles.avatar} key={channel.id} size="large" peer={channel} />
<div style={`background: ${color}`} className={styles.badge}> <div style={`background: ${color}`} className={styles.badge}>
<Icon name="user-filled" className={styles.icon} /> <Icon name="user-filled" className={styles.icon} />
<span className={styles.membersCount}>{formatIntegerCompact(channel?.membersCount || 0)} <span className={styles.membersCount}>{formatIntegerCompact(lang, channel?.membersCount || 0)}
</span> </span>
</div> </div>
<span className={styles.channelTitle}>{channel.title}</span> <span className={styles.channelTitle}>{channel.title}</span>

View File

@ -145,7 +145,9 @@ const StarGiftAction = ({
{action.gift.availabilityTotal && ( {action.gift.availabilityTotal && (
<GiftRibbon <GiftRibbon
color={backgroundColor || 'blue'} color={backgroundColor || 'blue'}
text={lang('ActionStarGiftLimitedRibbon', { total: formatIntegerCompact(action.gift.availabilityTotal) })} text={lang('ActionStarGiftLimitedRibbon', {
total: formatIntegerCompact(lang, action.gift.availabilityTotal),
})}
/> />
)} )}
<div className={styles.info}> <div className={styles.info}>

View File

@ -15,6 +15,7 @@ import { REM } from '../../../common/helpers/mediaDimensions';
import useSelector from '../../../../hooks/data/useSelector'; import useSelector from '../../../../hooks/data/useSelector';
import useContextMenuHandlers from '../../../../hooks/useContextMenuHandlers'; import useContextMenuHandlers from '../../../../hooks/useContextMenuHandlers';
import useEffectWithPrevDeps from '../../../../hooks/useEffectWithPrevDeps'; import useEffectWithPrevDeps from '../../../../hooks/useEffectWithPrevDeps';
import useLang from '../../../../hooks/useLang';
import useLastCallback from '../../../../hooks/useLastCallback'; import useLastCallback from '../../../../hooks/useLastCallback';
import usePrevious from '../../../../hooks/usePrevious'; import usePrevious from '../../../../hooks/usePrevious';
import useShowTransition from '../../../../hooks/useShowTransition'; import useShowTransition from '../../../../hooks/useShowTransition';
@ -76,6 +77,8 @@ const ReactionButton = ({
const counterRef = useRef<HTMLSpanElement>(null); const counterRef = useRef<HTMLSpanElement>(null);
const animationRef = useRef<Animation>(); const animationRef = useRef<Animation>();
const lang = useLang();
const isPaid = reaction.reaction.type === 'paid'; const isPaid = reaction.reaction.type === 'paid';
const starsState = useSelector(selectStarsState); const starsState = useSelector(selectStarsState);
@ -198,7 +201,7 @@ const ReactionButton = ({
{shouldRenderPaidCounter && ( {shouldRenderPaidCounter && (
<AnimatedCounter <AnimatedCounter
ref={counterRef} ref={counterRef}
text={`+${formatIntegerCompact(reaction.localAmount || prevAmount!)}`} text={`+${formatIntegerCompact(lang, reaction.localAmount || prevAmount!)}`}
className={styles.paidCounter} className={styles.paidCounter}
/> />
)} )}
@ -216,7 +219,7 @@ const ReactionButton = ({
<AvatarList size="mini" peers={recentReactors} /> <AvatarList size="mini" peers={recentReactors} />
) : ( ) : (
<AnimatedCounter <AnimatedCounter
text={formatIntegerCompact(reaction.count + (reaction.localAmount || 0))} text={formatIntegerCompact(lang, reaction.count + (reaction.localAmount || 0))}
className={styles.counter} className={styles.counter}
/> />
)} )}

View File

@ -12,6 +12,7 @@ import buildClassName from '../../../util/buildClassName';
import { formatIntegerCompact } from '../../../util/textFormat'; import { formatIntegerCompact } from '../../../util/textFormat';
import { extractCurrentThemeParams } from '../../../util/themeStyle'; import { extractCurrentThemeParams } from '../../../util/themeStyle';
import useLang from '../../../hooks/useLang';
import useLastCallback from '../../../hooks/useLastCallback'; import useLastCallback from '../../../hooks/useLastCallback';
import PeerBadge from '../../common/PeerBadge'; import PeerBadge from '../../common/PeerBadge';
@ -33,6 +34,8 @@ function WebAppGridItem({ user, isPopularApp }: OwnProps & StateProps) {
requestMainWebView, requestMainWebView,
} = getActions(); } = getActions();
const lang = useLang();
const handleClick = useLastCallback(() => { const handleClick = useLastCallback(() => {
if (!user) { if (!user) {
return; return;
@ -55,7 +58,7 @@ function WebAppGridItem({ user, isPopularApp }: OwnProps & StateProps) {
const title = user?.firstName; const title = user?.firstName;
const activeUserCount = user?.botActiveUsers; const activeUserCount = user?.botActiveUsers;
const badgeText = activeUserCount && isPopularApp ? formatIntegerCompact(activeUserCount) : undefined; const badgeText = activeUserCount && isPopularApp ? formatIntegerCompact(lang, activeUserCount) : undefined;
return ( return (
<div <div

View File

@ -11,6 +11,7 @@ import buildClassName from '../../../util/buildClassName';
import { formatFullDate } from '../../../util/dates/dateFormat'; import { formatFullDate } from '../../../util/dates/dateFormat';
import { formatInteger, formatIntegerCompact } from '../../../util/textFormat'; import { formatInteger, formatIntegerCompact } from '../../../util/textFormat';
import useLang from '../../../hooks/useLang';
import useOldLang from '../../../hooks/useOldLang'; import useOldLang from '../../../hooks/useOldLang';
import Icon from '../../common/icons/Icon'; import Icon from '../../common/icons/Icon';
@ -121,7 +122,8 @@ const StatisticsOverview: FC<OwnProps> = ({
className, className,
subtitle, subtitle,
}) => { }) => {
const lang = useOldLang(); const oldLang = useOldLang();
const lang = useLang();
const renderOverviewItemValue = ({ change, percentage }: StatisticsOverviewItem) => { const renderOverviewItemValue = ({ change, percentage }: StatisticsOverviewItem) => {
if (!change) { if (!change) {
@ -132,7 +134,9 @@ const StatisticsOverview: FC<OwnProps> = ({
return ( return (
<span className={buildClassName(styles.value, isChangeNegative && styles.negative)}> <span className={buildClassName(styles.value, isChangeNegative && styles.negative)}>
{isChangeNegative ? `-${formatIntegerCompact(Math.abs(change))}` : `+${formatIntegerCompact(change)}`} {isChangeNegative
? `-${formatIntegerCompact(lang, Math.abs(change))}`
: `+${formatIntegerCompact(lang, change)}`}
{percentage && ( {percentage && (
<> <>
{' '} {' '}
@ -156,7 +160,7 @@ const StatisticsOverview: FC<OwnProps> = ({
<span className={styles.tableHeading}> <span className={styles.tableHeading}>
${integerUsdPart}<span className={styles.decimalUsdPart}>.{decimalUsdPart}</span> ${integerUsdPart}<span className={styles.decimalUsdPart}>.{decimalUsdPart}</span>
</span> </span>
<h3 className={styles.tableHeading}>{lang(text)}</h3> <h3 className={styles.tableHeading}>{oldLang(text)}</h3>
</div> </div>
); );
}; };
@ -177,7 +181,7 @@ const StatisticsOverview: FC<OwnProps> = ({
{period && ( {period && (
<div className={styles.caption}> <div className={styles.caption}>
{formatFullDate(lang, period.minDate * 1000)} {formatFullDate(lang, period.maxDate * 1000)} {formatFullDate(oldLang, period.minDate * 1000)} {formatFullDate(oldLang, period.maxDate * 1000)}
</div> </div>
)} )}
</div> </div>
@ -202,7 +206,7 @@ const StatisticsOverview: FC<OwnProps> = ({
<b className={styles.tableValue}> <b className={styles.tableValue}>
{`${cell.isApproximate ? '≈' : ''}${formatInteger(field)}`} {`${cell.isApproximate ? '≈' : ''}${formatInteger(field)}`}
</b> </b>
<h3 className={styles.tableHeading}>{lang(cell.title)}</h3> <h3 className={styles.tableHeading}>{oldLang(cell.title)}</h3>
</td> </td>
); );
} }
@ -218,7 +222,7 @@ const StatisticsOverview: FC<OwnProps> = ({
<span className={cell.withAbsoluteValue ? styles.tableSecondaryValue : styles.tableValue}> <span className={cell.withAbsoluteValue ? styles.tableSecondaryValue : styles.tableValue}>
{field.percentage}% {field.percentage}%
</span> </span>
<h3 className={styles.tableHeading}>{lang(cell.title)}</h3> <h3 className={styles.tableHeading}>{oldLang(cell.title)}</h3>
</td> </td>
); );
} }
@ -226,11 +230,11 @@ const StatisticsOverview: FC<OwnProps> = ({
return ( return (
<td className={styles.tableCell}> <td className={styles.tableCell}>
<b className={styles.tableValue}> <b className={styles.tableValue}>
{formatIntegerCompact(field.current)} {formatIntegerCompact(lang, field.current)}
</b> </b>
{' '} {' '}
{renderOverviewItemValue(field)} {renderOverviewItemValue(field)}
<h3 className={styles.tableHeading}>{lang(cell.title)}</h3> <h3 className={styles.tableHeading}>{oldLang(cell.title)}</h3>
</td> </td>
); );
})} })}

View File

@ -4,6 +4,7 @@ import type { StatisticsMessageInteractionCounter, StatisticsStoryInteractionCou
import { formatIntegerCompact } from '../../../util/textFormat'; import { formatIntegerCompact } from '../../../util/textFormat';
import useLang from '../../../hooks/useLang';
import useOldLang from '../../../hooks/useOldLang'; import useOldLang from '../../../hooks/useOldLang';
import Icon from '../../common/icons/Icon'; import Icon from '../../common/icons/Icon';
@ -15,25 +16,26 @@ interface OwnProps {
} }
function StatisticsRecentPostMeta({ postStatistic }: OwnProps) { function StatisticsRecentPostMeta({ postStatistic }: OwnProps) {
const lang = useOldLang(); const oldLang = useOldLang();
const lang = useLang();
return ( return (
<div className={styles.meta}> <div className={styles.meta}>
{postStatistic.reactionsCount > 0 && ( {postStatistic.reactionsCount > 0 && (
<span className={styles.metaWithIcon}> <span className={styles.metaWithIcon}>
<Icon name="heart-outline" className={styles.metaIcon} /> <Icon name="heart-outline" className={styles.metaIcon} />
{formatIntegerCompact(postStatistic.reactionsCount)} {formatIntegerCompact(lang, postStatistic.reactionsCount)}
</span> </span>
)} )}
{postStatistic.forwardsCount > 0 && ( {postStatistic.forwardsCount > 0 && (
<span className={styles.metaWithIcon}> <span className={styles.metaWithIcon}>
<Icon name="forward" className={styles.metaIcon} /> <Icon name="forward" className={styles.metaIcon} />
{formatIntegerCompact(postStatistic.forwardsCount)} {formatIntegerCompact(lang, postStatistic.forwardsCount)}
</span> </span>
)} )}
{!postStatistic.forwardsCount && !postStatistic.reactionsCount {!postStatistic.forwardsCount && !postStatistic.reactionsCount
&& lang('ChannelStats.SharesCount_ZeroValueHolder')} && oldLang('ChannelStats.SharesCount_ZeroValueHolder')}
</div> </div>
); );
} }

View File

@ -1,6 +1,8 @@
export const clamp = (num: number, min: number, max: number) => (Math.min(max, Math.max(min, num))); export const clamp = (num: number, min: number, max: number) => (Math.min(max, Math.max(min, num)));
export const isBetween = (num: number, min: number, max: number) => (num >= min && num <= max); export const isBetween = (num: number, min: number, max: number) => (num >= min && num <= max);
export const round = (num: number, decimals: number = 0) => Math.round(num * 10 ** decimals) / 10 ** decimals; export const round = (num: number, decimals: number = 0) => Math.round(num * 10 ** decimals) / 10 ** decimals;
export const ceil = (num: number, decimals: number = 0) => Math.ceil(num * 10 ** decimals) / 10 ** decimals;
export const floor = (num: number, decimals: number = 0) => Math.floor(num * 10 ** decimals) / 10 ** decimals;
export const lerp = (start: number, end: number, interpolationRatio: number) => { export const lerp = (start: number, end: number, interpolationRatio: number) => {
return (1 - interpolationRatio) * start + interpolationRatio * end; return (1 - interpolationRatio) * start + interpolationRatio * end;
}; };

View File

@ -1,32 +1,25 @@
import type { OldLangFn } from '../hooks/useOldLang'; import type { OldLangFn } from '../hooks/useOldLang';
import type { LangFn } from './localization';
import EMOJI_REGEX from '../lib/twemojiRegex'; import EMOJI_REGEX from '../lib/twemojiRegex';
import fixNonStandardEmoji from './emoji/fixNonStandardEmoji'; import fixNonStandardEmoji from './emoji/fixNonStandardEmoji';
import { floor } from './math';
import withCache from './withCache'; import withCache from './withCache';
export function formatInteger(value: number) { export function formatInteger(value: number) {
return String(value).replace(/\d(?=(\d{3})+$)/g, '$& '); return String(value).replace(/\d(?=(\d{3})+$)/g, '$& ');
} }
function formatFixedNumber(number: number) { export function formatIntegerCompact(lang: LangFn, views: number) {
const fixed = String(number.toFixed(1));
if (fixed.substr(-2) === '.0') {
return Math.floor(number);
}
return number.toFixed(1).replace('.', ',');
}
export function formatIntegerCompact(views: number) {
if (views < 1e3) { if (views < 1e3) {
return views.toString(); return lang.number(views);
} }
if (views < 1e6) { if (views < 1e6) {
return `${formatFixedNumber(views / 1e3)}K`; return `${lang.number(floor(views / 1e3, 1))}K`;
} }
return `${formatFixedNumber(views / 1e6)}M`; return `${lang.number(floor(views / 1e6, 1))}M`;
} }
export function formatPercent(value: number, fractionDigits = 1) { export function formatPercent(value: number, fractionDigits = 1) {