diff --git a/src/components/ProfileHoverCard/index.web.tsx b/src/components/ProfileHoverCard/index.web.tsx index 024867b1a3..74a4956de6 100644 --- a/src/components/ProfileHoverCard/index.web.tsx +++ b/src/components/ProfileHoverCard/index.web.tsx @@ -358,7 +358,7 @@ function Inner({ hide: () => void }) { const t = useTheme() - const {_} = useLingui() + const {_, i18n} = useLingui() const {currentAccount} = useSession() const moderation = React.useMemo( () => moderateProfile(profile, moderationOpts), @@ -371,8 +371,8 @@ function Inner({ logContext: 'ProfileHoverCard', }) const blockHide = profile.viewer?.blocking || profile.viewer?.blockedBy - const following = formatCount(profile.followsCount || 0) - const followers = formatCount(profile.followersCount || 0) + const following = formatCount(i18n, profile.followsCount || 0) + const followers = formatCount(i18n, profile.followersCount || 0) const pluralizedFollowers = plural(profile.followersCount || 0, { one: 'follower', other: 'followers', diff --git a/src/components/dms/MessageItem.tsx b/src/components/dms/MessageItem.tsx index c5ff810915..1c425580e7 100644 --- a/src/components/dms/MessageItem.tsx +++ b/src/components/dms/MessageItem.tsx @@ -130,7 +130,7 @@ let MessageItemMetadata = ({ style: StyleProp }): React.ReactNode => { const t = useTheme() - const {_} = useLingui() + const {_, i18n} = useLingui() const {message} = item const handleRetry = useCallback( @@ -149,10 +149,10 @@ let MessageItemMetadata = ({ const date = new Date(timestamp) const now = new Date() - const time = new Intl.DateTimeFormat(undefined, { + const time = i18n.date(date, { hour: 'numeric', minute: 'numeric', - }).format(date) + }) const diff = now.getTime() - date.getTime() @@ -174,15 +174,15 @@ let MessageItemMetadata = ({ return _(msg`Yesterday, ${time}`) } - return new Intl.DateTimeFormat(undefined, { + return i18n.date(date, { hour: 'numeric', minute: 'numeric', day: 'numeric', month: 'numeric', year: 'numeric', - }).format(date) + }) }, - [_], + [_, i18n], ) return ( diff --git a/src/components/forms/DateField/index.shared.tsx b/src/components/forms/DateField/index.shared.tsx index 1f54bdc8be..814bbed7cc 100644 --- a/src/components/forms/DateField/index.shared.tsx +++ b/src/components/forms/DateField/index.shared.tsx @@ -1,12 +1,12 @@ import React from 'react' import {Pressable, View} from 'react-native' +import {useLingui} from '@lingui/react' import {android, atoms as a, useTheme, web} from '#/alf' import * as TextField from '#/components/forms/TextField' import {useInteractionState} from '#/components/hooks/useInteractionState' import {CalendarDays_Stroke2_Corner0_Rounded as CalendarDays} from '#/components/icons/CalendarDays' import {Text} from '#/components/Typography' -import {localizeDate} from './utils' // looks like a TextField.Input, but is just a button. It'll do something different on each platform on press // iOS: open a dialog with an inline date picker @@ -25,6 +25,7 @@ export function DateFieldButton({ isInvalid?: boolean accessibilityHint?: string }) { + const {i18n} = useLingui() const t = useTheme() const { @@ -91,7 +92,7 @@ export function DateFieldButton({ t.atoms.text, {lineHeight: a.text_md.fontSize * 1.1875}, ]}> - {localizeDate(value)} + {i18n.date(value, {timeZone: 'UTC'})} diff --git a/src/components/forms/DateField/utils.ts b/src/components/forms/DateField/utils.ts index c787272fe8..04bb482ce1 100644 --- a/src/components/forms/DateField/utils.ts +++ b/src/components/forms/DateField/utils.ts @@ -1,16 +1,5 @@ -import {getLocales} from 'expo-localization' - -const LOCALE = getLocales()[0] - // we need the date in the form yyyy-MM-dd to pass to the input export function toSimpleDateString(date: Date | string): string { const _date = typeof date === 'string' ? new Date(date) : date return _date.toISOString().split('T')[0] } - -export function localizeDate(date: Date | string): string { - const _date = typeof date === 'string' ? new Date(date) : date - return new Intl.DateTimeFormat(LOCALE.languageTag, { - timeZone: 'UTC', - }).format(_date) -} diff --git a/src/screens/Profile/Header/Metrics.tsx b/src/screens/Profile/Header/Metrics.tsx index e3537f44be..756eb1f89e 100644 --- a/src/screens/Profile/Header/Metrics.tsx +++ b/src/screens/Profile/Header/Metrics.tsx @@ -17,9 +17,9 @@ export function ProfileHeaderMetrics({ profile: Shadow }) { const t = useTheme() - const {_} = useLingui() - const following = formatCount(profile.followsCount || 0) - const followers = formatCount(profile.followersCount || 0) + const {_, i18n} = useLingui() + const following = formatCount(i18n, profile.followsCount || 0) + const followers = formatCount(i18n, profile.followersCount || 0) const pluralizedFollowers = plural(profile.followersCount || 0, { one: 'follower', other: 'followers', @@ -54,7 +54,7 @@ export function ProfileHeaderMetrics({ - {formatCount(profile.postsCount || 0)}{' '} + {formatCount(i18n, profile.postsCount || 0)}{' '} {plural(profile.postsCount || 0, {one: 'post', other: 'posts'})} diff --git a/src/view/com/notifications/FeedItem.tsx b/src/view/com/notifications/FeedItem.tsx index c20a8e9ee3..7fe85cfe53 100644 --- a/src/view/com/notifications/FeedItem.tsx +++ b/src/view/com/notifications/FeedItem.tsx @@ -67,7 +67,7 @@ let FeedItem = ({ }): React.ReactNode => { const queryClient = useQueryClient() const pal = usePalette('default') - const {_} = useLingui() + const {_, i18n} = useLingui() const [isAuthorsExpanded, setAuthorsExpanded] = useState(false) const itemHref = useMemo(() => { if (item.type === 'post-like' || item.type === 'repost') { @@ -175,7 +175,8 @@ let FeedItem = ({ return null } - let formattedCount = authors.length > 1 ? formatCount(authors.length - 1) : '' + let formattedCount = + authors.length > 1 ? formatCount(i18n, authors.length - 1) : '' return ( void }): React.ReactNode => { const pal = usePalette('default') - const {_} = useLingui() + const {_, i18n} = useLingui() const langPrefs = useLanguagePrefs() const {openComposer} = useComposerControls() const [limitLines, setLimitLines] = React.useState( @@ -339,7 +339,7 @@ let PostThreadItemLoaded = ({ type="lg" style={pal.textLight}> - {formatCount(post.repostCount)} + {formatCount(i18n, post.repostCount)} {' '} - {formatCount(post.likeCount)} + {formatCount(i18n, post.likeCount)} {' '} diff --git a/src/view/com/util/forms/DateInput.tsx b/src/view/com/util/forms/DateInput.tsx index 0104562aa5..bfbb2ff55e 100644 --- a/src/view/com/util/forms/DateInput.tsx +++ b/src/view/com/util/forms/DateInput.tsx @@ -1,19 +1,18 @@ -import React, {useState, useCallback} from 'react' +import React, {useCallback, useState} from 'react' import {StyleProp, StyleSheet, TextStyle, View, ViewStyle} from 'react-native' +import DatePicker from 'react-native-date-picker' import { FontAwesomeIcon, FontAwesomeIconStyle, } from '@fortawesome/react-native-fontawesome' -import {isIOS, isAndroid} from 'platform/detection' -import {Button, ButtonType} from './Button' -import {Text} from '../text/Text' +import {useLingui} from '@lingui/react' + +import {usePalette} from 'lib/hooks/usePalette' import {TypographyVariant} from 'lib/ThemeContext' import {useTheme} from 'lib/ThemeContext' -import {usePalette} from 'lib/hooks/usePalette' -import {getLocales} from 'expo-localization' -import DatePicker from 'react-native-date-picker' - -const LOCALE = getLocales()[0] +import {isAndroid, isIOS} from 'platform/detection' +import {Text} from '../text/Text' +import {Button, ButtonType} from './Button' interface Props { testID?: string @@ -30,16 +29,11 @@ interface Props { } export function DateInput(props: Props) { + const {i18n} = useLingui() const [show, setShow] = useState(false) const theme = useTheme() const pal = usePalette('default') - const formatter = React.useMemo(() => { - return new Intl.DateTimeFormat(LOCALE.languageTag, { - timeZone: props.handleAsUTC ? 'UTC' : undefined, - }) - }, [props.handleAsUTC]) - const onChangeInternal = useCallback( (date: Date) => { setShow(false) @@ -74,7 +68,9 @@ export function DateInput(props: Props) { - {formatter.format(props.value)} + {i18n.date(props.value, { + timeZone: props.handleAsUTC ? 'UTC' : undefined, + })} diff --git a/src/view/com/util/numeric/format.ts b/src/view/com/util/numeric/format.ts index 71d8d73e04..cca9fc7e73 100644 --- a/src/view/com/util/numeric/format.ts +++ b/src/view/com/util/numeric/format.ts @@ -1,19 +1,12 @@ -export const formatCount = (num: number) => - Intl.NumberFormat('en-US', { +import type {I18n} from '@lingui/core' + +export const formatCount = (i18n: I18n, num: number) => { + return i18n.number(num, { notation: 'compact', maximumFractionDigits: 1, // `1,953` shouldn't be rounded up to 2k, it should be truncated. // @ts-expect-error: `roundingMode` doesn't seem to be in the typings yet // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat/NumberFormat#roundingmode roundingMode: 'trunc', - }).format(num) - -export function formatCountShortOnly(num: number): string { - if (num >= 1000000) { - return (num / 1000000).toFixed(1) + 'M' - } - if (num >= 1000) { - return (num / 1000).toFixed(1) + 'K' - } - return String(num) + }) } diff --git a/src/view/screens/AppPasswords.tsx b/src/view/screens/AppPasswords.tsx index 65cbb7374e..2b94184e62 100644 --- a/src/view/screens/AppPasswords.tsx +++ b/src/view/screens/AppPasswords.tsx @@ -18,7 +18,6 @@ import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries' import {CommonNavigatorParams} from '#/lib/routes/types' import {cleanError} from '#/lib/strings/errors' import {useModalControls} from '#/state/modals' -import {useLanguagePrefs} from '#/state/preferences' import { useAppPasswordDeleteMutation, useAppPasswordsQuery, @@ -218,9 +217,8 @@ function AppPassword({ privileged?: boolean }) { const pal = usePalette('default') - const {_} = useLingui() + const {_, i18n} = useLingui() const control = useDialogControl() - const {contentLanguages} = useLanguagePrefs() const deleteMutation = useAppPasswordDeleteMutation() const onDelete = React.useCallback(async () => { @@ -232,9 +230,6 @@ function AppPassword({ control.open() }, [control]) - const primaryLocale = - contentLanguages.length > 0 ? contentLanguages[0] : 'en-US' - return ( Created{' '} - {Intl.DateTimeFormat(primaryLocale, { + {i18n.date(createdAt, { year: 'numeric', month: 'numeric', day: 'numeric', hour: '2-digit', minute: '2-digit', second: '2-digit', - }).format(new Date(createdAt))} + })} {privileged && ( diff --git a/src/view/shell/Drawer.tsx b/src/view/shell/Drawer.tsx index be35b314b4..c6a3c4e8b0 100644 --- a/src/view/shell/Drawer.tsx +++ b/src/view/shell/Drawer.tsx @@ -33,7 +33,7 @@ import {colors, s} from 'lib/styles' import {useTheme} from 'lib/ThemeContext' import {isWeb} from 'platform/detection' import {NavSignupCard} from '#/view/shell/NavSignupCard' -import {formatCountShortOnly} from 'view/com/util/numeric/format' +import {formatCount} from 'view/com/util/numeric/format' import {Text} from 'view/com/util/text/Text' import {UserAvatar} from 'view/com/util/UserAvatar' import {useTheme as useAlfTheme} from '#/alf' @@ -68,7 +68,7 @@ let DrawerProfileCard = ({ account: SessionAccount onPressProfile: () => void }): React.ReactNode => { - const {_} = useLingui() + const {_, i18n} = useLingui() const pal = usePalette('default') const {data: profile} = useProfileQuery({did: account.did}) @@ -100,7 +100,7 @@ let DrawerProfileCard = ({ - {formatCountShortOnly(profile?.followersCount ?? 0)} + {formatCount(i18n, profile?.followersCount ?? 0)} {' '} - {formatCountShortOnly(profile?.followsCount ?? 0)} + {formatCount(i18n, profile?.followsCount ?? 0)} {' '}