diff options
author | Eric Bailey <git@esb.lol> | 2025-09-04 19:36:23 -0500 |
---|---|---|
committer | GitHub <noreply@github.com> | 2025-09-04 19:36:23 -0500 |
commit | c129108b786a3389181c401b0bdfe1a3de528ebb (patch) | |
tree | e075d0c41c8829d9c0b66464ba7cb86dd105be4e | |
parent | 0b480bdaf862b0f93ed480589f81433bd6c93126 (diff) | |
download | voidsky-c129108b786a3389181c401b0bdfe1a3de528ebb.tar.zst |
108 fixes (#8977)
* Translation comment * Fix error handling in starter pack generation * Allow access to DM settings for age restricted users * Leave post stat unit formatting up to translators
-rw-r--r-- | src/components/PostControls/RepostButton.tsx | 9 | ||||
-rw-r--r-- | src/components/PostControls/index.tsx | 11 | ||||
-rw-r--r-- | src/components/PostControls/util.ts | 58 | ||||
-rw-r--r-- | src/components/StarterPack/ProfileStarterPacks.tsx | 2 | ||||
-rw-r--r-- | src/components/ageAssurance/AgeRestrictedScreen.tsx | 6 | ||||
-rw-r--r-- | src/components/dialogs/nuxs/BookmarksAnnouncement.tsx | 6 | ||||
-rw-r--r-- | src/lib/custom-animations/CountWheel.tsx | 14 | ||||
-rw-r--r-- | src/lib/custom-animations/CountWheel.web.tsx | 8 | ||||
-rw-r--r-- | src/lib/notifications/notifications.ts | 3 | ||||
-rw-r--r-- | src/screens/Messages/ChatList.tsx | 13 | ||||
-rw-r--r-- | src/screens/Messages/Settings.tsx | 13 | ||||
-rw-r--r-- | src/screens/PostThread/components/ThreadItemAnchor.tsx | 13 |
12 files changed, 89 insertions, 67 deletions
diff --git a/src/components/PostControls/RepostButton.tsx b/src/components/PostControls/RepostButton.tsx index 522e80dd3..d4a3960a7 100644 --- a/src/components/PostControls/RepostButton.tsx +++ b/src/components/PostControls/RepostButton.tsx @@ -10,7 +10,7 @@ import {Button, ButtonText} from '#/components/Button' import * as Dialog from '#/components/Dialog' import {CloseQuote_Stroke2_Corner1_Rounded as Quote} from '#/components/icons/Quote' import {Repost_Stroke2_Corner3_Rounded as Repost} from '#/components/icons/Repost' -import {formatPostStatCount} from '#/components/PostControls/util' +import {useFormatPostStatCount} from '#/components/PostControls/util' import {Text} from '#/components/Typography' import { PostControlButton, @@ -25,7 +25,6 @@ interface Props { onQuote: () => void big?: boolean embeddingDisabled: boolean - compactCount?: boolean } let RepostButton = ({ @@ -35,12 +34,12 @@ let RepostButton = ({ onQuote, big, embeddingDisabled, - compactCount, }: Props): React.ReactNode => { const t = useTheme() - const {_, i18n} = useLingui() + const {_} = useLingui() const requireAuth = useRequireAuth() const dialogControl = Dialog.useDialogControl() + const formatPostStatCount = useFormatPostStatCount() const onPress = () => requireAuth(() => dialogControl.open()) @@ -88,7 +87,7 @@ let RepostButton = ({ <PostControlButtonIcon icon={Repost} /> {typeof repostCount !== 'undefined' && repostCount > 0 && ( <PostControlButtonText testID="repostCount"> - {formatPostStatCount(i18n, repostCount, {compact: compactCount})} + {formatPostStatCount(repostCount)} </PostControlButtonText> )} </PostControlButton> diff --git a/src/components/PostControls/index.tsx b/src/components/PostControls/index.tsx index 834ad8e7d..f91bcd8a5 100644 --- a/src/components/PostControls/index.tsx +++ b/src/components/PostControls/index.tsx @@ -27,7 +27,7 @@ import { import * as Toast from '#/view/com/util/Toast' import {atoms as a, flatten, useBreakpoints} from '#/alf' import {Reply as Bubble} from '#/components/icons/Reply' -import {formatPostStatCount} from '#/components/PostControls/util' +import {useFormatPostStatCount} from '#/components/PostControls/util' import {BookmarkButton} from './BookmarkButton' import { PostControlButton, @@ -69,7 +69,7 @@ let PostControls = ({ viaRepost?: {uri: string; cid: string} variant?: 'compact' | 'normal' | 'large' }): React.ReactNode => { - const {_, i18n} = useLingui() + const {_} = useLingui() const {openComposer} = useOpenComposer() const {feedDescriptor} = useFeedFeedbackContext() const [queueLike, queueUnlike] = usePostLikeMutationQueue( @@ -95,6 +95,7 @@ let PostControls = ({ ) const replyDisabled = post.viewer?.replyDisabled const {gtPhone} = useBreakpoints() + const formatPostStatCount = useFormatPostStatCount() const [hasLikeIconBeenToggled, setHasLikeIconBeenToggled] = useState(false) @@ -232,9 +233,7 @@ let PostControls = ({ <PostControlButtonIcon icon={Bubble} /> {typeof post.replyCount !== 'undefined' && post.replyCount > 0 && ( <PostControlButtonText> - {formatPostStatCount(i18n, post.replyCount, { - compact: variant === 'compact', - })} + {formatPostStatCount(post.replyCount)} </PostControlButtonText> )} </PostControlButton> @@ -247,7 +246,6 @@ let PostControls = ({ onQuote={onQuote} big={big} embeddingDisabled={Boolean(post.viewer?.embeddingDisabled)} - compactCount={variant === 'compact'} /> </View> <View style={[a.flex_1, a.align_start]}> @@ -288,7 +286,6 @@ let PostControls = ({ big={big} isLiked={Boolean(post.viewer?.like)} hasBeenToggled={hasLikeIconBeenToggled} - compactCount={variant === 'compact'} /> </PostControlButton> </View> diff --git a/src/components/PostControls/util.ts b/src/components/PostControls/util.ts index 5d3ea74e4..b8050a85a 100644 --- a/src/components/PostControls/util.ts +++ b/src/components/PostControls/util.ts @@ -1,24 +1,48 @@ -import {type I18n} from '@lingui/core' +import {useCallback} from 'react' +import {msg} from '@lingui/macro' +import {useLingui} from '@lingui/react' /** * This matches `formatCount` from `view/com/util/numeric/format.ts`, but has * additional truncation logic for large numbers. `roundingMode` should always * match the original impl, regardless of if we add more formatting here. */ -export function formatPostStatCount( - i18n: I18n, - count: number, - { - compact = false, - }: { - compact?: boolean - } = {}, -): string { - const isOver10k = count >= 10_000 - return i18n.number(count, { - notation: 'compact', - maximumFractionDigits: isOver10k || compact ? 0 : 1, - // @ts-expect-error - roundingMode not in the types - roundingMode: 'trunc', - }) +export function useFormatPostStatCount() { + const {i18n} = useLingui() + + return useCallback( + (postStatCount: number) => { + const isOver1k = postStatCount >= 1_000 + const isOver10k = postStatCount >= 10_000 + const isOver1M = postStatCount >= 1_000_000 + const formatted = i18n.number(postStatCount, { + notation: 'compact', + maximumFractionDigits: isOver10k ? 0 : 1, + // @ts-expect-error - roundingMode not in the types + roundingMode: 'trunc', + }) + const count = formatted.replace(/\D+$/g, '') + + if (isOver1M) { + return i18n._( + msg({ + message: `${count}M`, + comment: + 'For post statistics. Indicates a number in the millions. Please use the shortest format appropriate for your language.', + }), + ) + } else if (isOver1k) { + return i18n._( + msg({ + message: `${count}K`, + comment: + 'For post statistics. Indicates a number in the thousands. Please use the shortest format appropriate for your language.', + }), + ) + } else { + return count + } + }, + [i18n], + ) } diff --git a/src/components/StarterPack/ProfileStarterPacks.tsx b/src/components/StarterPack/ProfileStarterPacks.tsx index 73aee28f4..bbe0bc52b 100644 --- a/src/components/StarterPack/ProfileStarterPacks.tsx +++ b/src/components/StarterPack/ProfileStarterPacks.tsx @@ -214,7 +214,7 @@ function Empty() { onError: e => { logger.error('Failed to generate starter pack', {safeMessage: e}) setIsGenerating(false) - if (e.name === 'NOT_ENOUGH_FOLLOWERS') { + if (e.message.includes('NOT_ENOUGH_FOLLOWERS')) { followersDialogControl.open() } else { errorDialogControl.open() diff --git a/src/components/ageAssurance/AgeRestrictedScreen.tsx b/src/components/ageAssurance/AgeRestrictedScreen.tsx index b47cc5b0c..1430aaaff 100644 --- a/src/components/ageAssurance/AgeRestrictedScreen.tsx +++ b/src/components/ageAssurance/AgeRestrictedScreen.tsx @@ -18,10 +18,12 @@ export function AgeRestrictedScreen({ children, screenTitle, infoText, + rightHeaderSlot, }: { children: React.ReactNode screenTitle?: string infoText?: string + rightHeaderSlot?: React.ReactNode }) { const {_} = useLingui() const copy = useAgeAssuranceCopy() @@ -46,12 +48,12 @@ export function AgeRestrictedScreen({ <Layout.Screen> <Layout.Header.Outer> <Layout.Header.BackButton /> - <Layout.Header.Content> + <Layout.Header.Content align="left"> <Layout.Header.TitleText> {screenTitle ?? <Trans>Unavailable</Trans>} </Layout.Header.TitleText> </Layout.Header.Content> - <Layout.Header.Slot /> + {rightHeaderSlot ?? <Layout.Header.Slot />} </Layout.Header.Outer> <Layout.Content> <View style={[a.p_lg]}> diff --git a/src/components/dialogs/nuxs/BookmarksAnnouncement.tsx b/src/components/dialogs/nuxs/BookmarksAnnouncement.tsx index c63558334..266e226a6 100644 --- a/src/components/dialogs/nuxs/BookmarksAnnouncement.tsx +++ b/src/components/dialogs/nuxs/BookmarksAnnouncement.tsx @@ -117,7 +117,11 @@ export function BookmarksAnnouncement() { }, ]} alt={_( - msg`A screenshot of a post with a new button next to the share button that allows you to save the post to your bookmarks. The post is from @jcsalterego.bsky.social and reads "inventing a saturday that immediately follows monday".`, + msg({ + message: `A screenshot of a post with a new button next to the share button that allows you to save the post to your bookmarks. The post is from @jcsalterego.bsky.social and reads "inventing a saturday that immediately follows monday".`, + comment: + 'Contains a post that originally appeared in English. Consider translating the post text if it makes sense in your language, and noting that the post was translated from English.', + }), )} /> </View> diff --git a/src/lib/custom-animations/CountWheel.tsx b/src/lib/custom-animations/CountWheel.tsx index 6db22554e..b8763827a 100644 --- a/src/lib/custom-animations/CountWheel.tsx +++ b/src/lib/custom-animations/CountWheel.tsx @@ -6,13 +6,12 @@ import Animated, { useReducedMotion, withTiming, } from 'react-native-reanimated' -import {i18n} from '@lingui/core' import {decideShouldRoll} from '#/lib/custom-animations/util' import {s} from '#/lib/styles' import {Text} from '#/view/com/util/text/Text' import {atoms as a, useTheme} from '#/alf' -import {formatPostStatCount} from '#/components/PostControls/util' +import {useFormatPostStatCount} from '#/components/PostControls/util' const animationConfig = { duration: 400, @@ -92,13 +91,11 @@ export function CountWheel({ big, isLiked, hasBeenToggled, - compactCount, }: { likeCount: number big?: boolean isLiked: boolean hasBeenToggled: boolean - compactCount?: boolean }) { const t = useTheme() const shouldAnimate = !useReducedMotion() && hasBeenToggled @@ -111,12 +108,9 @@ export function CountWheel({ const [key, setKey] = React.useState(0) const [prevCount, setPrevCount] = React.useState(likeCount) const prevIsLiked = React.useRef(isLiked) - const formattedCount = formatPostStatCount(i18n, likeCount, { - compact: compactCount, - }) - const formattedPrevCount = formatPostStatCount(i18n, prevCount, { - compact: compactCount, - }) + const formatPostStatCount = useFormatPostStatCount() + const formattedCount = formatPostStatCount(likeCount) + const formattedPrevCount = formatPostStatCount(prevCount) React.useEffect(() => { if (isLiked === prevIsLiked.current) { diff --git a/src/lib/custom-animations/CountWheel.web.tsx b/src/lib/custom-animations/CountWheel.web.tsx index fc2505ead..be48e19d7 100644 --- a/src/lib/custom-animations/CountWheel.web.tsx +++ b/src/lib/custom-animations/CountWheel.web.tsx @@ -1,13 +1,12 @@ import React from 'react' import {View} from 'react-native' import {useReducedMotion} from 'react-native-reanimated' -import {i18n} from '@lingui/core' import {decideShouldRoll} from '#/lib/custom-animations/util' import {s} from '#/lib/styles' -import {formatCount} from '#/view/com/util/numeric/format' import {Text} from '#/view/com/util/text/Text' import {atoms as a, useTheme} from '#/alf' +import {useFormatPostStatCount} from '#/components/PostControls/util' const animationConfig = { duration: 400, @@ -55,8 +54,9 @@ export function CountWheel({ const [prevCount, setPrevCount] = React.useState(likeCount) const prevIsLiked = React.useRef(isLiked) - const formattedCount = formatCount(i18n, likeCount) - const formattedPrevCount = formatCount(i18n, prevCount) + const formatPostStatCount = useFormatPostStatCount() + const formattedCount = formatPostStatCount(likeCount) + const formattedPrevCount = formatPostStatCount(prevCount) React.useEffect(() => { if (isLiked === prevIsLiked.current) { diff --git a/src/lib/notifications/notifications.ts b/src/lib/notifications/notifications.ts index 67a38a52c..03dc4726f 100644 --- a/src/lib/notifications/notifications.ts +++ b/src/lib/notifications/notifications.ts @@ -11,6 +11,7 @@ import {isNative} from '#/platform/detection' import {useAgeAssuranceContext} from '#/state/ageAssurance' import {type SessionAccount, useAgent, useSession} from '#/state/session' import BackgroundNotificationHandler from '#/../modules/expo-background-notification-handler' +import {IS_DEV} from '#/env' /** * @private @@ -129,7 +130,7 @@ export function useGetAndRegisterPushToken() { }: { isAgeRestricted?: boolean } = {}) => { - if (!isNative) return + if (!isNative || IS_DEV) return /** * This will also fire the listener added via `addPushTokenListener`. That diff --git a/src/screens/Messages/ChatList.tsx b/src/screens/Messages/ChatList.tsx index 345446464..cb8598e79 100644 --- a/src/screens/Messages/ChatList.tsx +++ b/src/screens/Messages/ChatList.tsx @@ -74,7 +74,18 @@ export function MessagesScreen(props: Props) { return ( <AgeRestrictedScreen screenTitle={_(msg`Chats`)} - infoText={aaCopy.chatsInfoText}> + infoText={aaCopy.chatsInfoText} + rightHeaderSlot={ + <Link + to="/messages/settings" + label={_(msg`Chat settings`)} + size="small" + color="secondary"> + <ButtonText> + <Trans>Chat settings</Trans> + </ButtonText> + </Link> + }> <MessagesScreenInner {...props} /> </AgeRestrictedScreen> ) diff --git a/src/screens/Messages/Settings.tsx b/src/screens/Messages/Settings.tsx index 6015c07cd..b4b84aafe 100644 --- a/src/screens/Messages/Settings.tsx +++ b/src/screens/Messages/Settings.tsx @@ -12,8 +12,6 @@ import {useSession} from '#/state/session' import * as Toast from '#/view/com/util/Toast' import {atoms as a} from '#/alf' import {Admonition} from '#/components/Admonition' -import {AgeRestrictedScreen} from '#/components/ageAssurance/AgeRestrictedScreen' -import {useAgeAssuranceCopy} from '#/components/ageAssurance/useAgeAssuranceCopy' import {Divider} from '#/components/Divider' import * as Toggle from '#/components/forms/Toggle' import * as Layout from '#/components/Layout' @@ -25,16 +23,7 @@ type AllowIncoming = 'all' | 'none' | 'following' type Props = NativeStackScreenProps<CommonNavigatorParams, 'MessagesSettings'> export function MessagesSettingsScreen(props: Props) { - const {_} = useLingui() - const aaCopy = useAgeAssuranceCopy() - - return ( - <AgeRestrictedScreen - screenTitle={_(msg`Chat Settings`)} - infoText={aaCopy.chatsInfoText}> - <MessagesSettingsScreenInner {...props} /> - </AgeRestrictedScreen> - ) + return <MessagesSettingsScreenInner {...props} /> } export function MessagesSettingsScreenInner({}: Props) { diff --git a/src/screens/PostThread/components/ThreadItemAnchor.tsx b/src/screens/PostThread/components/ThreadItemAnchor.tsx index 08dd272f7..66ef96200 100644 --- a/src/screens/PostThread/components/ThreadItemAnchor.tsx +++ b/src/screens/PostThread/components/ThreadItemAnchor.tsx @@ -52,7 +52,7 @@ import {PostAlerts} from '#/components/moderation/PostAlerts' import {type AppModerationCause} from '#/components/Pills' import {Embed, PostEmbedViewContext} from '#/components/Post/Embed' import {PostControls} from '#/components/PostControls' -import {formatPostStatCount} from '#/components/PostControls/util' +import {useFormatPostStatCount} from '#/components/PostControls/util' import {ProfileHoverCard} from '#/components/ProfileHoverCard' import * as Prompt from '#/components/Prompt' import {RichText} from '#/components/RichText' @@ -176,11 +176,12 @@ const ThreadItemAnchorInner = memo(function ThreadItemAnchorInner({ postSource?: PostSource }) { const t = useTheme() - const {_, i18n} = useLingui() + const {_} = useLingui() const {openComposer} = useOpenComposer() const {currentAccount, hasSession} = useSession() const {gtTablet} = useBreakpoints() const feedFeedback = useFeedFeedback(postSource?.feedSourceInfo, hasSession) + const formatPostStatCount = useFormatPostStatCount() const post = postShadow const record = item.value.post.record @@ -439,7 +440,7 @@ const ThreadItemAnchorInner = memo(function ThreadItemAnchorInner({ testID="repostCount-expanded" style={[a.text_md, t.atoms.text_contrast_medium]}> <Text style={[a.text_md, a.font_bold, t.atoms.text]}> - {formatPostStatCount(i18n, post.repostCount)} + {formatPostStatCount(post.repostCount)} </Text>{' '} <Plural value={post.repostCount} @@ -457,7 +458,7 @@ const ThreadItemAnchorInner = memo(function ThreadItemAnchorInner({ testID="quoteCount-expanded" style={[a.text_md, t.atoms.text_contrast_medium]}> <Text style={[a.text_md, a.font_bold, t.atoms.text]}> - {formatPostStatCount(i18n, post.quoteCount)} + {formatPostStatCount(post.quoteCount)} </Text>{' '} <Plural value={post.quoteCount} @@ -473,7 +474,7 @@ const ThreadItemAnchorInner = memo(function ThreadItemAnchorInner({ testID="likeCount-expanded" style={[a.text_md, t.atoms.text_contrast_medium]}> <Text style={[a.text_md, a.font_bold, t.atoms.text]}> - {formatPostStatCount(i18n, post.likeCount)} + {formatPostStatCount(post.likeCount)} </Text>{' '} <Plural value={post.likeCount} one="like" other="likes" /> </Text> @@ -485,7 +486,7 @@ const ThreadItemAnchorInner = memo(function ThreadItemAnchorInner({ testID="bookmarkCount-expanded" style={[a.text_md, t.atoms.text_contrast_medium]}> <Text style={[a.text_md, a.font_bold, t.atoms.text]}> - {formatPostStatCount(i18n, post.bookmarkCount)} + {formatPostStatCount(post.bookmarkCount)} </Text>{' '} <Plural value={post.bookmarkCount} |