Improve notification localization (#3911)

* Update FeedItem.tsx

* Update FeedItem.tsx

* Update FeedItem.tsx

* Update FeedItem.tsx

* Update FeedItem.tsx

* Update FeedItem.tsx

* Update FeedItem.tsx

* Update FeedItem.tsx

* Update FeedItem.tsx

* Update PostMeta.tsx

* Update RightNav.tsx

* Update FeedItem.tsx

* Update FeedItem.tsx

* a11y

* Update FeedItem.tsx

* Update PostThreadItem.tsx

* Update PostThreadItem.tsx

* revert

* Update FeedItem.tsx

* Update FeedItem.tsx

* Update FeedItem.tsx

* Revert "Merge remote-tracking branch 'upstream/main' into Improve-notification-localization"

This reverts commit f435d1e7ed, reversing
changes made to dae2aee676.

* Reapply "Merge remote-tracking branch 'upstream/main' into Improve-notification-localization"

This reverts commit c93ac19048.

* Update ThreadgateBtn.tsx
This commit is contained in:
Minseo Lee
2024-10-01 23:59:51 +09:00
committed by GitHub
parent b59d6dfbb5
commit 85491e2ae1
15 changed files with 260 additions and 125 deletions
+2 -2
View File
@@ -1,6 +1,6 @@
import 'react-native-url-polyfill/auto'
import 'lib/sentry' // must be near top
import 'view/icons'
import '#/lib/sentry' // must be near top
import '#/view/icons'
import React, {useEffect, useState} from 'react'
import {GestureHandlerRootView} from 'react-native-gesture-handler'
@@ -5,15 +5,15 @@ import {flip, offset, shift, size, useFloating} from '@floating-ui/react-dom'
import {msg, plural} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {isTouchDevice} from '#/lib/browser'
import {getModerationCauseKey} from '#/lib/moderation'
import {makeProfileLink} from '#/lib/routes/links'
import {sanitizeDisplayName} from '#/lib/strings/display-names'
import {sanitizeHandle} from '#/lib/strings/handles'
import {useProfileShadow} from '#/state/cache/profile-shadow'
import {useModerationOpts} from '#/state/preferences/moderation-opts'
import {usePrefetchProfileQuery, useProfileQuery} from '#/state/queries/profile'
import {useSession} from '#/state/session'
import {isTouchDevice} from 'lib/browser'
import {useProfileShadow} from 'state/cache/profile-shadow'
import {formatCount} from '#/view/com/util/numeric/format'
import {UserAvatar} from '#/view/com/util/UserAvatar'
import {ProfileHeaderHandle} from '#/screens/Profile/Header/Handle'
@@ -7,9 +7,9 @@ import {BottomSheetFlatListMethods} from '@discord/bottom-sheet'
import {msg, Trans} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {useInitialNumToRender} from 'lib/hooks/useInitialNumToRender'
import {isWeb} from 'platform/detection'
import {useSession} from 'state/session'
import {useInitialNumToRender} from '#/lib/hooks/useInitialNumToRender'
import {isWeb} from '#/platform/detection'
import {useSession} from '#/state/session'
import {WizardAction, WizardState} from '#/screens/StarterPack/Wizard/State'
import {atoms as a, native, useTheme, web} from '#/alf'
import {Button, ButtonText} from '#/components/Button'
+1 -1
View File
@@ -2,8 +2,8 @@ import React from 'react'
import {Pressable, View, ViewStyle} from 'react-native'
import Animated, {LinearTransition} from 'react-native-reanimated'
import {HITSLOP_10} from '#/lib/constants'
import {isNative} from '#/platform/detection'
import {HITSLOP_10} from 'lib/constants'
import {
atoms as a,
flatten,
@@ -3,7 +3,7 @@ import {View} from 'react-native'
import {msg, Trans} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {useAgent, useSession} from 'state/session'
import {useAgent, useSession} from '#/state/session'
import {atoms as a} from '#/alf'
import {Button, ButtonText} from '#/components/Button'
import * as Dialog from '#/components/Dialog'
+2 -2
View File
@@ -13,6 +13,8 @@ import {
RichText,
} from '@atproto/api'
import {isNetworkError} from '#/lib/strings/errors'
import {shortenLinks, stripInvalidMentions} from '#/lib/strings/rich-text-manip'
import {logger} from '#/logger'
import {ComposerImage, compressImage} from '#/state/gallery'
import {writePostgateRecord} from '#/state/queries/postgate'
@@ -22,8 +24,6 @@ import {
threadgateAllowUISettingToAllowRecordValue,
writeThreadgateRecord,
} from '#/state/queries/threadgate'
import {isNetworkError} from 'lib/strings/errors'
import {shortenLinks, stripInvalidMentions} from 'lib/strings/rich-text-manip'
import {LinkMeta} from '../link-meta/link-meta'
import {uploadBlob} from './upload-blob'
+1 -1
View File
@@ -4,7 +4,7 @@ import {
MediaTypeOptions,
} from 'expo-image-picker'
import * as Toast from 'view/com/util/Toast'
import * as Toast from '#/view/com/util/Toast'
import {getDataUriSize} from './util'
export async function openPicker(opts?: ImagePickerOptions) {
+1 -1
View File
@@ -2,7 +2,7 @@ import {AppBskyFeedDefs, AppBskyFeedPost} from '@atproto/api'
import * as bcp47Match from 'bcp-47-match'
import lande from 'lande'
import {hasProp} from 'lib/type-guards'
import {hasProp} from '#/lib/type-guards'
import {
AppLanguage,
LANGUAGES_MAP_CODE2,
@@ -1,7 +1,7 @@
import React from 'react'
import {View} from 'react-native'
import {ScrollView} from 'view/com/util/Views'
import {ScrollView} from '#/view/com/util/Views'
import {atoms as a} from '#/alf'
import {Button, ButtonText} from '#/components/Button'
import {Text} from '#/components/Typography'
+7 -7
View File
@@ -5,18 +5,18 @@ import {msg, Trans} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {useQueryClient} from '@tanstack/react-query'
import {useGoBack} from '#/lib/hooks/useGoBack'
import {sanitizeHandle} from '#/lib/strings/handles'
import {logger} from '#/logger'
import {RQKEY_ROOT as listQueryRoot} from '#/state/queries/list'
import {useGoBack} from 'lib/hooks/useGoBack'
import {sanitizeHandle} from 'lib/strings/handles'
import {useListBlockMutation, useListMuteMutation} from 'state/queries/list'
import {useListBlockMutation, useListMuteMutation} from '#/state/queries/list'
import {
UsePreferencesQueryResponse,
useRemoveFeedMutation,
} from 'state/queries/preferences'
import {useSession} from 'state/session'
import * as Toast from 'view/com/util/Toast'
import {CenteredView} from 'view/com/util/Views'
} from '#/state/queries/preferences'
import {useSession} from '#/state/session'
import * as Toast from '#/view/com/util/Toast'
import {CenteredView} from '#/view/com/util/Views'
import {atoms as a, useBreakpoints, useTheme} from '#/alf'
import {Button, ButtonIcon, ButtonText} from '#/components/Button'
import {EyeSlash_Stroke2_Corner0_Rounded as EyeSlash} from '#/components/icons/EyeSlash'
+21 -21
View File
@@ -15,35 +15,35 @@ import {useNavigation} from '@react-navigation/native'
import {NativeStackScreenProps} from '@react-navigation/native-stack'
import {useQueryClient} from '@tanstack/react-query'
import {batchedUpdates} from '#/lib/batchedUpdates'
import {HITSLOP_20} from '#/lib/constants'
import {isBlockedOrBlocking, isMuted} from '#/lib/moderation/blocked-and-muted'
import {makeProfileLink, makeStarterPackLink} from '#/lib/routes/links'
import {CommonNavigatorParams, NavigationProp} from '#/lib/routes/types'
import {logEvent} from '#/lib/statsig/statsig'
import {cleanError} from '#/lib/strings/errors'
import {getStarterPackOgCard} from '#/lib/strings/starter-pack'
import {logger} from '#/logger'
import {isWeb} from '#/platform/detection'
import {updateProfileShadow} from '#/state/cache/profile-shadow'
import {useModerationOpts} from '#/state/preferences/moderation-opts'
import {getAllListMembers} from '#/state/queries/list-members'
import {useResolvedStarterPackShortLink} from '#/state/queries/resolve-short-link'
import {useResolveDidQuery} from '#/state/queries/resolve-uri'
import {useShortenLink} from '#/state/queries/shorten-link'
import {useDeleteStarterPackMutation} from '#/state/queries/starter-packs'
import {useStarterPackQuery} from '#/state/queries/starter-packs'
import {useAgent, useSession} from '#/state/session'
import {useLoggedOutViewControls} from '#/state/shell/logged-out'
import {
ProgressGuideAction,
useProgressGuideControls,
} from '#/state/shell/progress-guide'
import {batchedUpdates} from 'lib/batchedUpdates'
import {HITSLOP_20} from 'lib/constants'
import {isBlockedOrBlocking, isMuted} from 'lib/moderation/blocked-and-muted'
import {makeProfileLink, makeStarterPackLink} from 'lib/routes/links'
import {CommonNavigatorParams, NavigationProp} from 'lib/routes/types'
import {logEvent} from 'lib/statsig/statsig'
import {getStarterPackOgCard} from 'lib/strings/starter-pack'
import {isWeb} from 'platform/detection'
import {updateProfileShadow} from 'state/cache/profile-shadow'
import {useModerationOpts} from 'state/preferences/moderation-opts'
import {getAllListMembers} from 'state/queries/list-members'
import {useResolvedStarterPackShortLink} from 'state/queries/resolve-short-link'
import {useResolveDidQuery} from 'state/queries/resolve-uri'
import {useShortenLink} from 'state/queries/shorten-link'
import {useStarterPackQuery} from 'state/queries/starter-packs'
import {useAgent, useSession} from 'state/session'
import {useLoggedOutViewControls} from 'state/shell/logged-out'
import {useSetActiveStarterPack} from 'state/shell/starter-pack'
import {useSetActiveStarterPack} from '#/state/shell/starter-pack'
import {PagerWithHeader} from '#/view/com/pager/PagerWithHeader'
import {ProfileSubpageHeader} from '#/view/com/profile/ProfileSubpageHeader'
import * as Toast from '#/view/com/util/Toast'
import {PagerWithHeader} from 'view/com/pager/PagerWithHeader'
import {ProfileSubpageHeader} from 'view/com/profile/ProfileSubpageHeader'
import {CenteredView} from 'view/com/util/Views'
import {CenteredView} from '#/view/com/util/Views'
import {bulkWriteFollows} from '#/screens/Onboarding/util'
import {atoms as a, useBreakpoints, useTheme} from '#/alf'
import {Button, ButtonIcon, ButtonText} from '#/components/Button'
+18 -18
View File
@@ -19,32 +19,32 @@ import {useLingui} from '@lingui/react'
import {useFocusEffect, useNavigation} from '@react-navigation/native'
import {NativeStackScreenProps} from '@react-navigation/native-stack'
import {logger} from '#/logger'
import {HITSLOP_10, STARTER_PACK_MAX_SIZE} from 'lib/constants'
import {createSanitizedDisplayName} from 'lib/moderation/create-sanitized-display-name'
import {CommonNavigatorParams, NavigationProp} from 'lib/routes/types'
import {logEvent} from 'lib/statsig/statsig'
import {sanitizeDisplayName} from 'lib/strings/display-names'
import {sanitizeHandle} from 'lib/strings/handles'
import {enforceLen} from 'lib/strings/helpers'
import {HITSLOP_10, STARTER_PACK_MAX_SIZE} from '#/lib/constants'
import {createSanitizedDisplayName} from '#/lib/moderation/create-sanitized-display-name'
import {CommonNavigatorParams, NavigationProp} from '#/lib/routes/types'
import {logEvent} from '#/lib/statsig/statsig'
import {sanitizeDisplayName} from '#/lib/strings/display-names'
import {sanitizeHandle} from '#/lib/strings/handles'
import {enforceLen} from '#/lib/strings/helpers'
import {
getStarterPackOgCard,
parseStarterPackUri,
} from 'lib/strings/starter-pack'
import {isAndroid, isNative, isWeb} from 'platform/detection'
import {useModerationOpts} from 'state/preferences/moderation-opts'
import {useAllListMembersQuery} from 'state/queries/list-members'
import {useProfileQuery} from 'state/queries/profile'
} from '#/lib/strings/starter-pack'
import {logger} from '#/logger'
import {isAndroid, isNative, isWeb} from '#/platform/detection'
import {useModerationOpts} from '#/state/preferences/moderation-opts'
import {useAllListMembersQuery} from '#/state/queries/list-members'
import {useProfileQuery} from '#/state/queries/profile'
import {
useCreateStarterPackMutation,
useEditStarterPackMutation,
useStarterPackQuery,
} from 'state/queries/starter-packs'
import {useSession} from 'state/session'
import {useSetMinimalShellMode} from 'state/shell'
} from '#/state/queries/starter-packs'
import {useSession} from '#/state/session'
import {useSetMinimalShellMode} from '#/state/shell'
import * as Toast from '#/view/com/util/Toast'
import {UserAvatar} from 'view/com/util/UserAvatar'
import {CenteredView} from 'view/com/util/Views'
import {UserAvatar} from '#/view/com/util/UserAvatar'
import {CenteredView} from '#/view/com/util/Views'
import {useWizardState, WizardStep} from '#/screens/StarterPack/Wizard/State'
import {StepDetails} from '#/screens/StarterPack/Wizard/StepDetails'
import {StepFeeds} from '#/screens/StarterPack/Wizard/StepFeeds'
+4 -4
View File
@@ -1,10 +1,10 @@
import React from 'react'
import {StyleProp, View, ViewStyle} from 'react-native'
import {ExternalEmbedDraft} from 'lib/api/index'
import {Gif} from 'state/queries/tenor'
import {ExternalEmbedRemoveBtn} from 'view/com/composer/ExternalEmbedRemoveBtn'
import {ExternalLinkEmbed} from 'view/com/util/post-embeds/ExternalLinkEmbed'
import {ExternalEmbedDraft} from '#/lib/api/index'
import {Gif} from '#/state/queries/tenor'
import {ExternalEmbedRemoveBtn} from '#/view/com/composer/ExternalEmbedRemoveBtn'
import {ExternalLinkEmbed} from '#/view/com/util/post-embeds/ExternalLinkEmbed'
import {atoms as a, useTheme} from '#/alf'
import {Loader} from '#/components/Loader'
import {Text} from '#/components/Typography'
@@ -13,15 +13,15 @@ import {Text as TiptapText} from '@tiptap/extension-text'
import {generateJSON} from '@tiptap/html'
import {EditorContent, JSONContent, useEditor} from '@tiptap/react'
import {useColorSchemeStyle} from '#/lib/hooks/useColorSchemeStyle'
import {usePalette} from '#/lib/hooks/usePalette'
import {blobToDataUri, isUriImage} from '#/lib/media/util'
import {useActorAutocompleteFn} from '#/state/queries/actor-autocomplete'
import {useColorSchemeStyle} from 'lib/hooks/useColorSchemeStyle'
import {blobToDataUri, isUriImage} from 'lib/media/util'
import {textInputWebEmitter} from '#/view/com/composer/text-input/textInputWebEmitter'
import {
LinkFacetMatch,
suggestLinkCardUri,
} from 'view/com/composer/text-input/text-input-util'
} from '#/view/com/composer/text-input/text-input-util'
import {textInputWebEmitter} from '#/view/com/composer/text-input/textInputWebEmitter'
import {atoms as a, useAlf} from '#/alf'
import {Portal} from '#/components/Portal'
import {normalizeTextStyles} from '#/components/Typography'
+192 -57
View File
@@ -1,4 +1,10 @@
import React, {memo, useEffect, useMemo, useState} from 'react'
import React, {
memo,
type ReactElement,
useEffect,
useMemo,
useState,
} from 'react'
import {
Animated,
Pressable,
@@ -17,7 +23,7 @@ import {
} from '@atproto/api'
import {AtUri} from '@atproto/api'
import {TID} from '@atproto/common-web'
import {msg, plural, Trans} from '@lingui/macro'
import {msg, Plural, plural, Trans} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {useNavigation} from '@react-navigation/native'
import {useQueryClient} from '@tanstack/react-query'
@@ -164,7 +170,30 @@ let FeedItem = ({
)
}
let action = ''
const formattedCount =
authors.length > 1 ? formatCount(i18n, authors.length - 1) : ''
const firstAuthorName = sanitizeDisplayName(
authors[0].profile.displayName || authors[0].profile.handle,
)
const niceTimestamp = niceDate(i18n, item.notification.indexedAt)
let a11yAuthor = firstAuthorName
let author = (
<TextLink
key={authors[0].href}
style={[pal.text, s.bold]}
href={authors[0].href}
text={
<Text emoji style={[pal.text, s.bold]}>
{forceLTR(firstAuthorName)}
</Text>
}
disableMismatchWarning
/>
)
let a11yLabel = ''
let action: ReactElement
let icon = (
<HeartIconFilled
size="xl"
@@ -174,10 +203,59 @@ let FeedItem = ({
]}
/>
)
if (item.type === 'post-like') {
action = _(msg`liked your post`)
a11yLabel =
authors.length > 1
? _(
msg`${a11yAuthor} and ${plural(authors.length - 1, {
one: `${formattedCount} other`,
other: `${formattedCount} others`,
})} liked your post`,
)
: _(msg`${a11yAuthor} liked your post`)
action =
authors.length > 1 ? (
<Trans>
{author} and{' '}
<Text style={[pal.text, s.bold]}>
<Plural
value={authors.length - 1}
one={`${formattedCount} other`}
other={`${formattedCount} others`}
/>
</Text>{' '}
liked your post
</Trans>
) : (
<Trans>{author} liked your post</Trans>
)
} else if (item.type === 'repost') {
action = _(msg`reposted your post`)
a11yLabel =
authors.length > 1
? _(
msg`${a11yAuthor} and ${plural(authors.length - 1, {
one: `${formattedCount} other`,
other: `${formattedCount} others`,
})} reposted your post`,
)
: _(msg`${a11yAuthor} reposted your post`)
action =
authors.length > 1 ? (
<Trans>
{author} and{' '}
<Text style={[pal.text, s.bold]}>
<Plural
value={authors.length - 1}
one={`${formattedCount} other`}
other={`${formattedCount} others`}
/>
</Text>{' '}
reposted your post
</Trans>
) : (
<Trans>{author} reposted your post</Trans>
)
icon = <RepostIcon size="xl" style={{color: t.palette.positive_600}} />
} else if (item.type === 'follow') {
let isFollowBack = false
@@ -202,39 +280,120 @@ let FeedItem = ({
}
if (isFollowBack) {
action = _(msg`followed you back`)
a11yLabel =
authors.length > 1
? _(
msg`${a11yAuthor} and ${plural(authors.length - 1, {
one: `${formattedCount} other`,
other: `${formattedCount} others`,
})} followed you back`,
)
: _(msg`${a11yAuthor} followed you back`)
action =
authors.length > 1 ? (
<Trans>
{author} and{' '}
<Text style={[pal.text, s.bold]}>
<Plural
value={authors.length - 1}
one={`${formattedCount} other`}
other={`${formattedCount} others`}
/>
</Text>{' '}
followed you back
</Trans>
) : (
<Trans>{author} followed you back</Trans>
)
} else {
action = _(msg`followed you`)
a11yLabel =
authors.length > 1
? _(
msg`${a11yAuthor} and ${plural(authors.length - 1, {
one: `${formattedCount} other`,
other: `${formattedCount} others`,
})} followed you`,
)
: _(msg`${a11yAuthor} followed you`)
action =
authors.length > 1 ? (
<Trans>
{author} and{' '}
<Text style={[pal.text, s.bold]}>
<Plural
value={authors.length - 1}
one={`${formattedCount} other`}
other={`${formattedCount} others`}
/>
</Text>{' '}
followed you
</Trans>
) : (
<Trans>{author} followed you</Trans>
)
}
icon = <PersonPlusIcon size="xl" style={{color: t.palette.primary_500}} />
} else if (item.type === 'feedgen-like') {
action = _(msg`liked your custom feed`)
a11yLabel =
authors.length > 1
? _(
msg`${a11yAuthor} and ${plural(authors.length - 1, {
one: `${formattedCount} other`,
other: `${formattedCount} others`,
})} liked your custom feed`,
)
: _(msg`${a11yAuthor} liked your custom feed`)
action =
authors.length > 1 ? (
<Trans>
{author} and{' '}
<Text style={[pal.text, s.bold]}>
<Plural
value={authors.length - 1}
one={`${formattedCount} other`}
other={`${formattedCount} others`}
/>
</Text>{' '}
liked your custom feed
</Trans>
) : (
<Trans>{author} liked your custom feed</Trans>
)
} else if (item.type === 'starterpack-joined') {
a11yLabel =
authors.length > 1
? _(
msg`${a11yAuthor} and ${plural(authors.length - 1, {
one: `${formattedCount} other`,
other: `${formattedCount} others`,
})} signed up with your starter pack`,
)
: _(msg`${a11yAuthor} signed up with your starter pack`)
action =
authors.length > 1 ? (
<Trans>
{author} and{' '}
<Text style={[pal.text, s.bold]}>
<Plural
value={authors.length - 1}
one={`${formattedCount} other`}
other={`${formattedCount} others`}
/>
</Text>{' '}
signed up with your starter pack
</Trans>
) : (
<Trans>{author} signed up with your starter pack</Trans>
)
icon = (
<View style={{height: 30, width: 30}}>
<StarterPack width={30} gradient="sky" />
</View>
)
action = _(msg`signed up with your starter pack`)
} else {
return null
}
const formattedCount =
authors.length > 1 ? formatCount(i18n, authors.length - 1) : ''
const firstAuthorName = sanitizeDisplayName(
authors[0].profile.displayName || authors[0].profile.handle,
)
const niceTimestamp = niceDate(i18n, item.notification.indexedAt)
const a11yLabelUsers =
authors.length > 1
? _(msg` and `) +
plural(authors.length - 1, {
one: `${formattedCount} other`,
other: `${formattedCount} others`,
})
: ''
const a11yLabel = `${firstAuthorName}${a11yLabelUsers} ${action} ${niceTimestamp}`
a11yLabel += ` · ${niceTimestamp}`
return (
<Link
@@ -303,42 +462,18 @@ let FeedItem = ({
/>
<ExpandedAuthorsList visible={isAuthorsExpanded} authors={authors} />
<Text
style={[styles.meta, a.self_start]}
style={[styles.meta, a.self_start, pal.text]}
accessibilityHint=""
accessibilityLabel={a11yLabel}>
<TextLink
key={authors[0].href}
style={[pal.text, s.bold]}
href={authors[0].href}
text={
<Text emoji style={[pal.text, s.bold]}>
{forceLTR(firstAuthorName)}
</Text>
}
disableMismatchWarning
/>
{authors.length > 1 ? (
<>
<Text style={[pal.text]}>
{' '}
<Trans>and</Trans>{' '}
</Text>
<Text style={[pal.text, s.bold]}>
{plural(authors.length - 1, {
one: `${formattedCount} other`,
other: `${formattedCount} others`,
})}
</Text>
</>
) : undefined}
<Text style={[pal.text]}> {action}</Text>
{action}
<TimeElapsed timestamp={item.notification.indexedAt}>
{({timeElapsed}) => (
<Text
style={[pal.textLight, styles.pointer]}
title={niceTimestamp}>
{' ' + timeElapsed}
</Text>
<>
<Text style={[a.ml_xs, pal.textLight]}>&middot;</Text>
<Text style={[a.ml_xs, pal.textLight]} title={niceTimestamp}>
{timeElapsed}
</Text>
</>
)}
</TimeElapsed>
</Text>