Merge remote-tracking branch 'origin/main' into app-1934

* origin/main: (31 commits)
  Nightly source-language update
  Fix inverted condition in moveIfNecessary (#10238)
  Disable viewport zoom in prod (#10233)
  Fix embed options border radius (#10237)
  Fix bskyembed skeleton padding (#10236)
  Fix server state query returning undefined (#10232)
  handle single-line text truncation (#10227)
  use dvh for `util_screen_outer` (#10230)
  Nightly source-language update
  bskyweb: update to golang v1.26 (#10208)
  Refactor chat list implementation (#10059)
  Rename moderation link in settings to "Moderation and content filters" (#10192)
  Disable keyboard preload on iOS to prevent keyboard flash (#10214)
  PostControls: restore likeCount testID (#10221)
  Remove direct dependency on `@expo/config-plugins` (#10152)
  Tweak embed styles (#10226)
  [APP-2065] yassify app embed (#10210)
  Check for undefined moderationOpts (#10224)
  Remove flex to fix view collapse (#10219)
  Nightly source-language update
  ...
This commit is contained in:
Eric Bailey
2026-04-14 17:37:24 -05:00
129 changed files with 1981 additions and 1469 deletions
+3 -3
View File
@@ -215,11 +215,11 @@ function InnerApp() {
}
function App() {
const [isReady, setReady] = useState(false)
const [isReady, setIsReady] = useState(false)
useEffect(() => {
void Promise.all([initPersistedState(), Geo.resolve(), setupDeviceId]).then(
() => setReady(true),
() => setIsReady(true),
)
}, [])
@@ -235,7 +235,7 @@ function App() {
<Geo.Provider>
<AppConfigProvider>
<A11yProvider>
<KeyboardControllerProvider>
<KeyboardControllerProvider preload={false}>
<OnboardingProvider>
<AnalyticsContext>
<SessionProvider>
+2 -2
View File
@@ -194,11 +194,11 @@ function InnerApp() {
}
function App() {
const [isReady, setReady] = useState(false)
const [isReady, setIsReady] = useState(false)
useEffect(() => {
void Promise.all([initPersistedState(), Geo.resolve(), setupDeviceId]).then(
() => setReady(true),
() => setIsReady(true),
)
}, [])
+22 -18
View File
@@ -105,19 +105,18 @@ export function getConfigFromCache():
)
}
let configPrefetchPromise: Promise<void> | undefined
export async function prefetchConfig() {
export function prefetchConfig() {
if (configPrefetchPromise) {
logger.debug(`prefetchAgeAssuranceConfig: already in progress`)
return
}
configPrefetchPromise = new Promise(async resolve => {
configPrefetchPromise = (async () => {
await cacheHydrationPromise
const cached = getConfigFromCache()
if (cached) {
logger.debug(`prefetchAgeAssuranceConfig: using cache`)
resolve()
} else {
try {
logger.debug(`prefetchAgeAssuranceConfig: resolving...`)
@@ -126,15 +125,14 @@ export async function prefetchConfig() {
configQueryKey,
res,
)
} catch (e: any) {
} catch (err) {
const e = err as Error
logger.warn(`prefetchAgeAssuranceConfig: failed`, {
safeMessage: e.message,
})
} finally {
resolve()
}
}
})
})()
}
export async function refetchConfig() {
logger.debug(`refetchConfig: fetching...`)
@@ -185,7 +183,7 @@ export async function getServerState({agent}: {agent: AtpAgent}) {
const geolocation = device.get(['mergedGeolocation'])
if (!geolocation || !geolocation.countryCode) {
logger.error(`getServerState: missing geolocation countryCode`)
return
return null
}
const {data} = await agent.app.bsky.ageassurance.getState({
countryCode: geolocation.countryCode,
@@ -227,8 +225,11 @@ export async function prefetchServerState({agent}: {agent: AtpAgent}) {
try {
logger.debug(`prefetchServerState: resolving...`)
const res = await networkRetry(3, () => getServerState({agent}))
qc.setQueryData<AppBskyAgeassuranceGetState.OutputSchema>(qk, res)
} catch (e: any) {
if (res) {
qc.setQueryData<AppBskyAgeassuranceGetState.OutputSchema>(qk, res)
}
} catch (err) {
const e = err as Error
logger.warn(`prefetchServerState: failed`, {
safeMessage: e.message,
})
@@ -239,16 +240,18 @@ export async function refetchServerState({agent}: {agent: AtpAgent}) {
if (!did) return
logger.debug(`refetchServerState: fetching...`)
const res = await networkRetry(3, () => getServerState({agent}))
qc.setQueryData<AppBskyAgeassuranceGetState.OutputSchema>(
createServerStateQueryKey({did}),
res,
)
if (res) {
qc.setQueryData<AppBskyAgeassuranceGetState.OutputSchema>(
createServerStateQueryKey({did}),
res,
)
}
return res
}
export function usePatchServerState() {
const {currentAccount} = useSession()
return useCallback(
async (next: AppBskyAgeassuranceDefs.State) => {
(next: AppBskyAgeassuranceDefs.State) => {
if (!currentAccount) return
const did = currentAccount.did
const prev = getServerStateFromCache({did})
@@ -313,7 +316,7 @@ export function useServerStateQuery() {
// only refetch when needed
if (isAssured || !isAArequired) return
refetch()
void refetch()
})
}, [did, refetch, isAssured])
@@ -409,7 +412,8 @@ export async function prefetchOtherRequiredData({agent}: {agent: AtpAgent}) {
logger.debug(`prefetchOtherRequiredData: resolving...`)
const res = await networkRetry(3, () => getOtherRequiredData({agent}))
qc.setQueryData<OtherRequiredData>(qk, res)
} catch (e: any) {
} catch (err) {
const e = err as Error
logger.warn(`prefetchOtherRequiredData: failed`, {
safeMessage: e.message,
})
@@ -418,7 +422,7 @@ export async function prefetchOtherRequiredData({agent}: {agent: AtpAgent}) {
export function usePatchOtherRequiredData() {
const {currentAccount} = useSession()
return useCallback(
async (next: OtherRequiredData) => {
(next: OtherRequiredData) => {
if (!currentAccount) return
const did = currentAccount.did
const prev = getOtherRequiredDataFromCache({did})
+1 -1
View File
@@ -85,7 +85,7 @@ function InnerProvider({children}: {children: React.ReactNode}) {
const handleAccessUpdate = useCallback(
(s: AgeAssuranceState) => {
getAndRegisterPushToken({
void getAndRegisterPushToken({
isAgeRestricted: s.access !== AgeAssuranceAccess.Full,
})
},
+1
View File
@@ -99,6 +99,7 @@ export function useOnAgeAssuranceAccessUpdate(
useEffect(() => {
if (prevAccess !== state.access) {
// eslint-disable-next-line react-hooks/set-state-in-effect
setPrevAccess(state.access)
cb(state)
logger.debug(`useOnAgeAssuranceAccessUpdate`, {state})
+1 -1
View File
@@ -20,7 +20,7 @@ export const atoms = {
*/
util_screen_outer: [
web({
minHeight: '100vh',
minHeight: '100dvh',
}),
native({
height: '100%',
+1
View File
@@ -10,6 +10,7 @@ export enum Features {
ImportContactsSettingsDisable = 'import_contacts:settings:disable',
LiveNowBetaDisable = 'live_now_beta:disable',
ImageUploadsHighResolution = 'image_uploads:high_resolution',
ImageUploadsBlobSize2mbEnabled = 'image_uploads:blob_size_2mb:enabled',
GroupChatsEnable = 'group_chats:enable',
DmsNewMessageComposerEnable = 'dms:new_message_composer:enable',
+13 -5
View File
@@ -8,7 +8,6 @@ import Animated, {
useAnimatedStyle,
useSharedValue,
} from 'react-native-reanimated'
import {useSafeAreaInsets} from 'react-native-safe-area-context'
import {useSift, type UseSiftReturn} from '@bsky.app/sift'
import {
facets,
@@ -121,7 +120,6 @@ export function Composer({
...rest
}: ComposerProps) {
const {theme: t, fonts} = useAlf()
const insets = useSafeAreaInsets()
/*
* Meat and potatoes
@@ -140,7 +138,6 @@ export function Composer({
offset: a.p_sm.padding,
placement: autocompletePlacement,
dynamicWidth: IS_WEB,
insets,
})
/*
@@ -306,7 +303,13 @@ export function Composer({
{IS_WEB && (
<View
pointerEvents="none"
style={[a.absolute, a.inset_0, a.z_10, {overflow: 'hidden'}]}>
style={[a.absolute, a.inset_0, a.z_10, {overflow: 'hidden'}]}
ref={node => {
if (IS_WEB && node) {
// @ts-ignore web only a11y
node.setAttribute('inert', '')
}
}}>
<Animated.View
style={[
contentPaddingStyle,
@@ -335,6 +338,8 @@ export function Composer({
web({
caretColor: textStyle.color ?? 'black',
overscrollBehavior: 'none',
scrollbarWidth: 'thin',
scrollbarColor: `${t.palette.contrast_200} transparent`,
}),
]}
{...rest}
@@ -368,6 +373,7 @@ export function Composer({
{activeFacet && activeFacet.type !== 'url' && (
<AutocompleteInner
inverted={autocompletePlacement?.startsWith('top')}
sift={sift}
activeFacet={activeFacet}
onDismiss={() => setActiveFacet(null)}
@@ -382,10 +388,12 @@ export function Composer({
*/
function AutocompleteInner({
inverted,
sift,
activeFacet,
onDismiss,
}: {
inverted?: boolean
sift: UseSiftReturn
activeFacet: TapperActiveFacet
onDismiss: () => void
@@ -410,7 +418,7 @@ function AutocompleteInner({
return items && items.length ? (
<AutocompleteBase
inverted={!IS_WEB}
inverted={inverted}
sift={sift}
data={items}
render={props => {
+35
View File
@@ -0,0 +1,35 @@
import {type StyleProp, View, type ViewStyle} from 'react-native'
import {
GlassView as ExpoGlassView,
type GlassViewProps as ExpoGlassViewProps,
isGlassEffectAPIAvailable,
isLiquidGlassAvailable,
} from 'expo-glass-effect'
import {useTheme} from '#/alf'
export const IS_GLASS_AVAILABLE =
isLiquidGlassAvailable() && isGlassEffectAPIAvailable()
/**
* Liquid Glass View that uses `expo-glass-effect`
*
* If unavailable, falls back to a regular `View`. Use `fallbackStyle` to customize the fallback appearance.
*/
export const GlassView = IS_GLASS_AVAILABLE ? InnerGlassView : FallbackView
export type GlassViewProps = ExpoGlassViewProps & {
fallbackStyle?: StyleProp<ViewStyle>
}
function InnerGlassView({
fallbackStyle: _fallbackStyle,
...props
}: GlassViewProps) {
const t = useTheme()
return <ExpoGlassView colorScheme={t.scheme} {...props} />
}
function FallbackView({fallbackStyle, style, ...props}: GlassViewProps) {
return <View style={[fallbackStyle, style]} {...props} />
}
@@ -22,7 +22,13 @@ import {Text} from '#/components/Typography'
import {useAnalytics} from '#/analytics'
import type * as bsky from '#/types/bsky'
export function RecentChats({postUri}: {postUri: string}) {
export function RecentChats({
postUri,
onBeforePress,
}: {
postUri: string
onBeforePress?: () => void
}) {
const ax = useAnalytics()
const control = useDialogContext()
const {currentAccount} = useSession()
@@ -32,6 +38,7 @@ export function RecentChats({postUri}: {postUri: string}) {
const navigation = useNavigation<NavigationProp>()
const onSelectChat = (convoId: string) => {
onBeforePress?.()
control.close(() => {
ax.metric('share:press:recentDm', {})
navigation.navigate('MessagesConversation', {
@@ -5,12 +5,14 @@ import {msg} from '@lingui/core/macro'
import {useLingui} from '@lingui/react'
import {Trans} from '@lingui/react/macro'
import {useNavigation} from '@react-navigation/native'
import {useQueryClient} from '@tanstack/react-query'
import {makeProfileLink} from '#/lib/routes/links'
import {type NavigationProp} from '#/lib/routes/types'
import {shareText, shareUrl} from '#/lib/sharing'
import {toShareUrl} from '#/lib/strings/url-helpers'
import {useProfileShadow} from '#/state/cache/profile-shadow'
import {precachePost} from '#/state/queries/post'
import {useSession} from '#/state/session'
import {atoms as a} from '#/alf'
import {Admonition} from '#/components/Admonition'
@@ -40,6 +42,7 @@ let ShareMenuItems = ({
const sendViaChatControl = useDialogControl()
const [devModeEnabled] = useDevMode()
const aa = useAgeAssurance()
const queryClient = useQueryClient()
const postUri = post.uri
const postAuthor = useProfileShadow(post.author)
@@ -77,7 +80,12 @@ let ShareMenuItems = ({
onShareProp()
}
const onBeforeShareViaChat = () => {
precachePost(queryClient, postUri, post)
}
const onSelectChatToShareTo = (conversation: string) => {
onBeforeShareViaChat()
navigation.navigate('MessagesConversation', {
conversation,
embed: postUri,
@@ -98,7 +106,10 @@ let ShareMenuItems = ({
{hasSession && aa.state.access === aa.Access.Full && (
<Menu.Group>
<Menu.ContainerItem>
<RecentChats postUri={postUri} />
<RecentChats
postUri={postUri}
onBeforePress={onBeforeShareViaChat}
/>
</Menu.ContainerItem>
<Menu.Item
testID="postDropdownSendViaDMBtn"
+1 -1
View File
@@ -303,7 +303,7 @@ let PostControls = ({
isToggled={Boolean(post.viewer?.like)}
hasBeenToggled={hasLikeIconBeenToggled}
renderCount={({count}) => (
<PostControlButtonText>
<PostControlButtonText testID="likeCount">
{formatPostStatCount(count)}
</PostControlButtonText>
)}
+3 -9
View File
@@ -109,7 +109,7 @@ function EmbedDialogInner({
<Trans>Embed post</Trans>
</Text>
<Text
style={[a.text_md, t.atoms.text_contrast_medium, a.leading_normal]}>
style={[a.text_md, t.atoms.text_contrast_medium, a.leading_snug]}>
<Trans>
Embed this post in your website. Simply copy the following snippet
and paste it into the HTML code of your website.
@@ -117,12 +117,7 @@ function EmbedDialogInner({
</Text>
</View>
<View
style={[
a.border,
t.atoms.border_contrast_low,
a.rounded_sm,
a.overflow_hidden,
]}>
style={[a.border, t.atoms.border_contrast_low, {borderRadius: 18}]}>
<Button
label={
showCustomisation
@@ -190,10 +185,9 @@ function EmbedDialogInner({
<Button
label={_(msg`Copy code`)}
color="primary"
variant="solid"
size="large"
onPress={() => {
navigator.clipboard.writeText(snippet)
void navigator.clipboard.writeText(snippet)
setCopied(true)
}}>
{copied ? (
+2 -1
View File
@@ -91,7 +91,8 @@ export function ChatEmptyPill() {
onPressOut={onPressOut}>
<Text
style={[a.font_semi_bold, a.pointer_events_none]}
selectable={false}>
selectable={false}
emoji>
{prompts[promptIndex]}
</Text>
</AnimatedPressable>
+5
View File
@@ -3,3 +3,8 @@ import {createSinglePathSVG} from './TEMPLATE'
export const PaperPlane_Stroke2_Corner0_Rounded = createSinglePathSVG({
path: 'M3.374 3.22a1 1 0 0 1 1.073-.114l16 8a1 1 0 0 1 0 1.788l-16 8a1 1 0 0 1-1.417-1.136L4.97 12 3.03 4.243a1 1 0 0 1 .344-1.023ZM6.781 13l-1.284 5.133L17.764 12 5.497 5.867 6.781 11H9a1 1 0 1 1 0 2H6.78Z',
})
export const PaperPlaneVertical_Filled_Stroke2_Corner1_Rounded =
createSinglePathSVG({
path: 'M10.655 3.718c.55-1.116 2.14-1.116 2.69 0l7.548 15.317c.578 1.172-.515 2.471-1.768 2.103L13 19.336V15a1 1 0 0 0-2 0v4.336l-6.124 1.802c-1.254.369-2.346-.93-1.769-2.103l7.548-15.317Z',
})
@@ -1,10 +1,12 @@
import {useCallback} from 'react'
import {useCallback, useMemo} from 'react'
import {View} from 'react-native'
import {Image} from 'expo-image'
import {type AppBskyActorDefs, type AppBskyEmbedExternal} from '@atproto/api'
import {msg} from '@lingui/core/macro'
import {useLingui} from '@lingui/react'
import {Trans} from '@lingui/react/macro'
import {
type AppBskyActorDefs,
type AppBskyEmbedExternal,
moderateStatus,
} from '@atproto/api'
import {Trans, useLingui} from '@lingui/react/macro'
import {useNavigation} from '@react-navigation/native'
import {useQueryClient} from '@tanstack/react-query'
@@ -19,8 +21,10 @@ import {Button, ButtonIcon, ButtonText} from '#/components/Button'
import * as Dialog from '#/components/Dialog'
import {CircleInfo_Stroke2_Corner0_Rounded as CircleInfoIcon} from '#/components/icons/CircleInfo'
import {Globe_Stroke2_Corner0_Rounded} from '#/components/icons/Globe'
import {Image_Stroke2_Corner0_Rounded as ImageIcon} from '#/components/icons/Image'
import {SquareArrowTopRight_Stroke2_Corner0_Rounded as SquareArrowTopRightIcon} from '#/components/icons/SquareArrowTopRight'
import {createStaticClick, SimpleInlineLinkText} from '#/components/Link'
import * as Hider from '#/components/moderation/Hider'
import {useGlobalReportDialogControl} from '#/components/moderation/ReportDialog'
import * as ProfileCard from '#/components/ProfileCard'
import {Text} from '#/components/Typography'
@@ -64,7 +68,7 @@ function DialogInner({
navigation: NavigationProp
status: AppBskyActorDefs.StatusView
}) {
const {_} = useLingui()
const {t: l} = useLingui()
const control = Dialog.useDialogContext()
const onPressOpenProfile = useCallback(() => {
@@ -77,7 +81,7 @@ function DialogInner({
return (
<Dialog.ScrollableInner
label={_(msg`${sanitizeHandle(profile.handle)} is live`)}
label={l`${sanitizeHandle(profile.handle)} is live`}
contentContainerStyle={[a.pt_0, a.px_0]}
style={[web({maxWidth: 420}), a.overflow_hidden]}>
<LiveStatus
@@ -105,45 +109,56 @@ export function LiveStatus({
onPressOpenProfile: () => void
}) {
const ax = useAnalytics()
const {_} = useLingui()
const {t: l} = useLingui()
const t = useTheme()
const queryClient = useQueryClient()
const openLink = useOpenLink()
const moderationOpts = useModerationOpts()
const reportDialogControl = useGlobalReportDialogControl()
const dialogContext = Dialog.useDialogContext()
const moderation = useMemo(() => {
if (!moderationOpts) return undefined
return moderateStatus(profile, moderationOpts)
}, [profile, moderationOpts])
return (
<>
{embed.external.thumb && (
<View
style={[
t.atoms.bg_contrast_25,
a.w_full,
a.aspect_card,
android([
a.overflow_hidden,
{
borderTopLeftRadius: a.rounded_md.borderRadius,
borderTopRightRadius: a.rounded_md.borderRadius,
},
]),
]}>
<Image
source={embed.external.thumb}
contentFit="cover"
style={[a.absolute, a.inset_0]}
accessibilityIgnoresInvertColors
/>
<LiveIndicator
size="large"
style={[
a.absolute,
{top: tokens.space.lg, left: tokens.space.lg},
a.align_start,
]}
/>
</View>
<Hider.Outer modui={moderation?.ui('contentMedia')}>
<Hider.Mask>
<ModeratedImage />
</Hider.Mask>
<Hider.Content>
<View
style={[
t.atoms.bg_contrast_25,
a.w_full,
a.aspect_card,
android([
a.overflow_hidden,
{
borderTopLeftRadius: a.rounded_md.borderRadius,
borderTopRightRadius: a.rounded_md.borderRadius,
},
]),
]}>
<Image
source={embed.external.thumb}
contentFit="cover"
style={[a.absolute, a.inset_0]}
accessibilityIgnoresInvertColors
/>
<LiveIndicator
size="large"
style={[
a.absolute,
{top: tokens.space.lg, left: tokens.space.lg},
a.align_start,
]}
/>
</View>
</Hider.Content>
</Hider.Outer>
)}
<View
style={[
@@ -171,7 +186,7 @@ export function LiveStatus({
</View>
</View>
<Button
label={_(msg`Watch now`)}
label={l`Watch now`}
size={platform({native: 'large', web: 'small'})}
color="primary"
variant="solid"
@@ -200,7 +215,7 @@ export function LiveStatus({
/>
</View>
<Button
label={_(msg`Open profile`)}
label={l`Open profile`}
size="small"
color="secondary"
variant="solid"
@@ -231,7 +246,7 @@ export function LiveStatus({
</View>
{status && (
<SimpleInlineLinkText
label={_(msg`Report this livestream`)}
label={l`Report this livestream`}
{...createStaticClick(() => {
function open() {
reportDialogControl.open({
@@ -256,3 +271,57 @@ export function LiveStatus({
</>
)
}
function ModeratedImage() {
const t = useTheme()
const {t: l} = useLingui()
const hider = Hider.useHider()
return (
<View
style={[
a.p_lg,
a.py_xl,
a.align_center,
a.justify_center,
t.atoms.bg_contrast_25,
]}>
<View style={[a.align_center, a.gap_sm, {maxWidth: 200}]}>
<ImageIcon size="lg" fill={t.atoms.text_contrast_medium.color} />
<Text
style={[
a.italic,
a.leading_snug,
a.text_center,
t.atoms.text_contrast_medium,
]}>
{hider.meta.allowOverride ? (
<Trans comment="Image has been moderated and user has the option of showing it temporarily">
Image is hidden due to your moderation settings.
</Trans>
) : (
/*
* In practice, if `allowOverride` is false, we won't even allow this
* dialog to open. That is handled in
* `#/features/liveNow/index.tsx`. But for clarity, I've included
* this here.
*/
<Trans comment="Image has been moderated and is not visible to the user">
Image is unavailable.
</Trans>
)}
</Text>
{hider.meta.allowOverride && (
<SimpleInlineLinkText
label={l`Show anyway`}
{...createStaticClick(() => {
hider.setIsContentVisible(true)
})}>
<Trans>Show anyway</Trans>
</SimpleInlineLinkText>
)}
</View>
</View>
)
}
+25 -7
View File
@@ -6,6 +6,7 @@ import {
AppBskyEmbedExternal,
AtUri,
ComAtprotoRepoPutRecord,
moderateStatus,
} from '@atproto/api'
import {retry} from '@atproto/common-web'
import {msg} from '@lingui/core/macro'
@@ -21,6 +22,7 @@ import {
updateProfileShadow,
useMaybeProfileShadow,
} from '#/state/cache/profile-shadow'
import {useModerationOpts} from '#/state/preferences/moderation-opts'
import {useAgent, useSession} from '#/state/session'
import {useTickEveryMinute} from '#/state/shell'
import {useDialogContext} from '#/components/Dialog'
@@ -37,6 +39,13 @@ export const DEFAULT_ALLOWED_DOMAINS = [
'bluecast.app',
]
const DEFAULT_STATE = {
status: '',
isDisabled: false,
isActive: false,
record: {},
} satisfies AppBskyActorDefs.StatusView
export type LiveNowConfig = {
canGoLive: boolean
currentAccountAllowedHosts: Set<string>
@@ -87,10 +96,24 @@ export function useActorStatus(actor?: bsky.profile.AnyProfileView) {
const shadowed = useMaybeProfileShadow(actor)
const tick = useTickEveryMinute()
const config = useLiveNowConfig()
const moderationOpts = useModerationOpts()
const moderation = useMemo(() => {
if (!actor || !('status' in actor && actor.status)) return undefined
if (!moderationOpts) return undefined
return moderateStatus(actor, moderationOpts)
}, [actor, moderationOpts])
return useMemo(() => {
void tick // revalidate every minute
/*
* Do not even allow Live Now to show if filtered for `contentList`.
*/
if (moderation && moderation.ui('contentList').filter) {
return DEFAULT_STATE
}
if (shadowed && 'status' in shadowed && shadowed.status) {
const isValid = isStatusValidForViewers(shadowed.status, config)
const isDisabled = shadowed.status.isDisabled || false
@@ -118,14 +141,9 @@ export function useActorStatus(actor?: bsky.profile.AnyProfileView) {
record: shadowed.status.record,
} satisfies AppBskyActorDefs.StatusView
} else {
return {
status: '',
isDisabled: false,
isActive: false,
record: {},
} satisfies AppBskyActorDefs.StatusView
return DEFAULT_STATE
}
}, [shadowed, config, tick])
}, [shadowed, config, tick, moderation])
}
export function isStatusStillActive(timeStr: string | undefined) {
+2
View File
@@ -53,6 +53,7 @@ interface PostOpts {
type FeatureFlags = {
highResolutionImages?: boolean
increasedBlobSizeLimit?: boolean
}
export async function post(
@@ -320,6 +321,7 @@ async function resolveMedia(
logger.debug(`Compressing image #${i}`)
const {path, width, height, mime} = await compressImage(image, {
highResolution: featureFlags?.highResolutionImages,
increasedBlobSizeLimit: featureFlags?.increasedBlobSizeLimit,
})
logger.debug(`Uploading image #${i}`)
const res = await uploadBlob(agent, path, mime)
+1
View File
@@ -71,5 +71,6 @@ function useKeyboardShortcuts() {
scopes: ['global'],
preventDefault: true,
description: l`Focus the search field`,
useKey: true, // Support international and alternate keyboard layouts
})
}
+1 -1
View File
@@ -8,7 +8,7 @@ msgstr ""
"Language: an\n"
"Project-Id-Version: 49a8cb746fbc2ae5707392ee41ddec4c\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2026-04-07 21:54\n"
"PO-Revision-Date: 2026-04-08 18:50\n"
"Last-Translator: \n"
"Language-Team: Aragonese\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+60 -59
View File
@@ -8,7 +8,7 @@ msgstr ""
"Language: ar_SA\n"
"Project-Id-Version: 49a8cb746fbc2ae5707392ee41ddec4c\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2026-04-07 21:54\n"
"PO-Revision-Date: 2026-04-08 18:50\n"
"Last-Translator: \n"
"Language-Team: Arabic, Saudi Arabia\n"
"Plural-Forms: nplurals=6; plural=(n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 && n%100<=10 ? 3 : n%100>=11 && n%100<=99 ? 4 : 5);\n"
@@ -971,7 +971,7 @@ msgstr ""
msgid "Add to lists"
msgstr ""
#: src/components/PostControls/BookmarkButton.tsx:142
#: src/components/PostControls/BookmarkButton.tsx:144
msgid "Add to saved posts"
msgstr ""
@@ -1837,20 +1837,20 @@ msgstr ""
msgid "Browse custom feeds"
msgstr ""
#: src/components/FeedInterstitials.tsx:627
#: src/components/FeedInterstitials.tsx:534
msgid "Browse more accounts"
msgstr ""
#: src/components/FeedInterstitials.tsx:757
#: src/components/FeedInterstitials.tsx:664
msgid "Browse more feeds on the Explore page"
msgstr ""
#: src/components/FeedInterstitials.tsx:738
#: src/components/FeedInterstitials.tsx:741
#: src/components/FeedInterstitials.tsx:645
#: src/components/FeedInterstitials.tsx:648
msgid "Browse more suggestions"
msgstr ""
#: src/components/FeedInterstitials.tsx:766
#: src/components/FeedInterstitials.tsx:673
msgid "Browse more suggestions on the Explore page"
msgstr ""
@@ -1894,7 +1894,7 @@ msgstr ""
#: src/components/LabelingServiceCard/index.tsx:62
#: src/components/moderation/ReportDialog/index.tsx:853
#: src/screens/Search/components/StarterPackCard.tsx:107
#: src/screens/Search/Explore.tsx:969
#: src/screens/Search/Explore.tsx:970
msgid "By {0}"
msgstr ""
@@ -1988,9 +1988,9 @@ msgstr ""
msgid "Cancel search"
msgstr ""
#: src/components/PostControls/index.tsx:109
#: src/components/PostControls/index.tsx:140
#: src/components/PostControls/index.tsx:168
#: src/components/PostControls/index.tsx:110
#: src/components/PostControls/index.tsx:141
#: src/components/PostControls/index.tsx:169
#: src/state/shell/composer/index.tsx:107
msgid "Cannot interact with a blocked user"
msgstr ""
@@ -3262,7 +3262,7 @@ msgstr ""
msgid "Discover new custom feeds"
msgstr ""
#: src/screens/Search/Explore.tsx:451
#: src/screens/Search/Explore.tsx:452
msgid "Discover new feeds"
msgstr ""
@@ -3299,7 +3299,7 @@ msgstr ""
msgid "Dismiss this section"
msgstr ""
#: src/components/FeedInterstitials.tsx:441
#: src/components/FeedInterstitials.tsx:348
msgid "Dismiss this suggestion"
msgstr ""
@@ -4024,16 +4024,16 @@ msgstr ""
msgid "Failed to load conversations"
msgstr ""
#: src/screens/Search/Explore.tsx:528
#: src/screens/Search/Explore.tsx:573
#: src/screens/Search/Explore.tsx:619
#: src/screens/Search/Explore.tsx:529
#: src/screens/Search/Explore.tsx:574
#: src/screens/Search/Explore.tsx:620
msgid "Failed to load feeds"
msgstr ""
#: src/screens/Search/Explore.tsx:487
#: src/screens/Search/Explore.tsx:542
#: src/screens/Search/Explore.tsx:587
#: src/screens/Search/Explore.tsx:633
#: src/screens/Search/Explore.tsx:488
#: src/screens/Search/Explore.tsx:543
#: src/screens/Search/Explore.tsx:588
#: src/screens/Search/Explore.tsx:634
msgid "Failed to load feeds preferences"
msgstr ""
@@ -4063,14 +4063,14 @@ msgstr ""
msgid "Failed to load preference."
msgstr ""
#: src/screens/Search/Explore.tsx:480
#: src/screens/Search/Explore.tsx:535
#: src/screens/Search/Explore.tsx:580
#: src/screens/Search/Explore.tsx:626
#: src/screens/Search/Explore.tsx:481
#: src/screens/Search/Explore.tsx:536
#: src/screens/Search/Explore.tsx:581
#: src/screens/Search/Explore.tsx:627
msgid "Failed to load suggested feeds"
msgstr ""
#: src/screens/Search/Explore.tsx:390
#: src/screens/Search/Explore.tsx:391
msgid "Failed to load suggested follows"
msgstr ""
@@ -4132,7 +4132,7 @@ msgstr ""
msgid "Failed to save settings. Please try again."
msgstr ""
#: src/screens/Settings/InterestsSettings.tsx:137
#: src/screens/Settings/InterestsSettings.tsx:147
msgctxt "toast"
msgid "Failed to save your interests."
msgstr ""
@@ -5716,7 +5716,7 @@ msgstr ""
#. Accessibility label for the like button when the post has not been liked, verb form followed by number of likes and noun form
#. placeholder {0}: post.likeCount || 0
#: src/components/PostControls/index.tsx:284
#: src/components/PostControls/index.tsx:287
msgid "Like ({0, plural, one {# like} other {# likes}})"
msgstr ""
@@ -5935,12 +5935,12 @@ msgstr ""
msgid "Live link"
msgstr ""
#: src/screens/Search/Explore.tsx:88
#: src/screens/Search/Explore.tsx:90
msgid "Load more"
msgstr ""
#: src/screens/Search/Explore.tsx:517
#: src/screens/Search/Explore.tsx:608
#: src/screens/Search/Explore.tsx:518
#: src/screens/Search/Explore.tsx:609
msgid "Load more suggested feeds"
msgstr ""
@@ -6117,10 +6117,12 @@ msgstr ""
msgid "Message from server: {0}"
msgstr ""
#: src/screens/Messages/components/MessageComposer.tsx:166
#: src/screens/Messages/components/MessageInput.tsx:154
msgid "Message input field"
msgstr ""
#: src/screens/Messages/components/MessageComposer.tsx:69
#: src/screens/Messages/components/MessageInput.tsx:79
#: src/screens/Messages/components/MessageInput.web.tsx:60
msgid "Message is too long"
@@ -6689,7 +6691,7 @@ msgid "No results"
msgstr ""
#. placeholder {0}: interestsDisplayNames[selectedInterest]
#: src/screens/Search/Explore.tsx:826
#: src/screens/Search/Explore.tsx:827
msgid "No results for \"{0}\"."
msgstr ""
@@ -6706,7 +6708,7 @@ msgstr ""
msgid "No results found for “<0>{query}</0>”."
msgstr ""
#: src/screens/Search/Explore.tsx:830
#: src/screens/Search/Explore.tsx:831
msgid "No results."
msgstr ""
@@ -6985,6 +6987,7 @@ msgstr ""
msgid "Open drawer menu"
msgstr ""
#: src/screens/Messages/components/MessageComposer.tsx:143
#: src/screens/Messages/components/MessageInput.web.tsx:180
#: src/view/com/composer/Composer.tsx:1979
msgid "Open emoji picker"
@@ -7600,7 +7603,7 @@ msgctxt "action"
msgid "Post"
msgstr ""
#: src/screens/PostThread/index.tsx:552
#: src/screens/PostThread/index.tsx:553
msgctxt "description"
msgid "Post"
msgstr ""
@@ -8127,7 +8130,7 @@ msgstr ""
msgid "Remove from saved feeds"
msgstr ""
#: src/components/PostControls/BookmarkButton.tsx:141
#: src/components/PostControls/BookmarkButton.tsx:143
#: src/screens/Bookmarks/index.tsx:262
msgid "Remove from saved posts"
msgstr ""
@@ -8288,7 +8291,7 @@ msgstr ""
#. Accessibility label for the reply button, verb form followed by number of replies and noun form
#. placeholder {0}: post.replyCount || 0
#: src/components/PostControls/index.tsx:242
#: src/components/PostControls/index.tsx:243
msgid "Reply ({0, plural, one {# reply} other {# replies}})"
msgstr ""
@@ -8774,23 +8777,19 @@ msgstr ""
msgid "Search for \"{interestsDisplayName}\" (active)"
msgstr ""
#: src/screens/Search/components/AutocompleteResults.tsx:47
#: src/screens/Search/components/AutocompleteResults.tsx:54
msgid "Search for \"{searchText}\""
msgstr ""
#: src/view/shell/desktop/Search.tsx:129
msgid "Search for “{tQuery}”"
msgstr ""
#: src/screens/StarterPack/Wizard/index.tsx:552
msgid "Search for feeds that you want to suggest to others."
msgstr ""
#: src/screens/Search/Explore.tsx:377
#: src/screens/Search/Explore.tsx:378
msgid "Search for more accounts"
msgstr ""
#: src/screens/Search/Explore.tsx:454
#: src/screens/Search/Explore.tsx:455
msgid "Search for more feeds"
msgstr ""
@@ -8879,12 +8878,12 @@ msgstr ""
msgid "See jobs at Bluesky"
msgstr ""
#: src/components/FeedInterstitials.tsx:583
#: src/components/FeedInterstitials.tsx:641
#: src/components/FeedInterstitials.tsx:490
#: src/components/FeedInterstitials.tsx:548
msgid "See more"
msgstr ""
#: src/components/FeedInterstitials.tsx:564
#: src/components/FeedInterstitials.tsx:471
msgid "See more suggested profiles"
msgstr ""
@@ -9052,7 +9051,7 @@ msgid "Select your date of birth"
msgstr ""
#: src/screens/Onboarding/StepInterests/index.tsx:69
#: src/screens/Settings/InterestsSettings.tsx:168
#: src/screens/Settings/InterestsSettings.tsx:178
msgid "Select your interests from the options below"
msgstr ""
@@ -9092,6 +9091,7 @@ msgstr ""
msgid "Send feedback"
msgstr ""
#: src/screens/Messages/components/MessageComposer.tsx:216
#: src/screens/Messages/components/MessageInput.tsx:194
#: src/screens/Messages/components/MessageInput.web.tsx:234
msgid "Send message"
@@ -9580,7 +9580,7 @@ msgstr ""
msgid "Some of your verifications are invalid."
msgstr ""
#: src/components/FeedInterstitials.tsx:720
#: src/components/FeedInterstitials.tsx:627
msgid "Some other feeds you might like"
msgstr ""
@@ -9640,7 +9640,7 @@ msgstr ""
msgid "Sorry, we're unable to load account suggestions at this time."
msgstr ""
#: src/App.native.tsx:141
#: src/App.native.tsx:140
#: src/App.web.tsx:119
msgid "Sorry! Your session expired. Please sign in again."
msgstr ""
@@ -9726,7 +9726,7 @@ msgstr ""
msgid "Starter pack is invalid"
msgstr ""
#: src/screens/Search/Explore.tsx:663
#: src/screens/Search/Explore.tsx:664
#: src/view/screens/Profile.tsx:241
msgid "Starter Packs"
msgstr ""
@@ -9842,12 +9842,12 @@ msgstr ""
msgid "Suggested"
msgstr ""
#: src/screens/Search/Explore.tsx:374
#: src/screens/Search/Explore.tsx:375
msgid "Suggested accounts"
msgstr ""
#. Accounts suggested to the user for them to follow
#: src/components/FeedInterstitials.tsx:561
#: src/components/FeedInterstitials.tsx:468
#: src/screens/Onboarding/StepSuggestedAccounts/index.tsx:180
msgid "Suggested for you"
msgstr ""
@@ -10189,7 +10189,7 @@ msgstr ""
msgid "There was an issue fetching notifications. Tap here to try again."
msgstr ""
#: src/screens/Search/Explore.tsx:1025
#: src/screens/Search/Explore.tsx:1026
#: src/view/com/posts/PostFeed.tsx:773
msgid "There was an issue fetching posts. Tap here to try again."
msgstr ""
@@ -10892,7 +10892,7 @@ msgstr ""
#. Accessibility label for the like button when the post has been liked, verb followed by number of likes and noun
#. placeholder {0}: post.likeCount || 0
#: src/components/PostControls/index.tsx:276
#: src/components/PostControls/index.tsx:279
msgid "Unlike ({0, plural, one {# like} other {# likes}})"
msgstr ""
@@ -11491,7 +11491,7 @@ msgid "View your default post interaction settings"
msgstr ""
#: src/view/com/home/HomeHeaderLayout.web.tsx:57
#: src/view/com/home/HomeHeaderLayoutMobile.tsx:75
#: src/view/com/home/HomeHeaderLayoutMobile.tsx:82
msgid "View your feeds and explore more"
msgstr ""
@@ -11561,7 +11561,7 @@ msgid "We apply the highest privacy standards, and never share or sell your cont
msgstr ""
#: src/view/com/feeds/MissingFeed.tsx:141
msgid "We could not connect to the service that provides this custom feed. It may be temporarily unavailable and experiencing issues, or permanently unavailable."
msgid "We could not connect to the service that provides this custom feed. It may be temporarily experiencing issues, or permanently unavailable."
msgstr ""
#: src/view/com/feeds/MissingFeed.tsx:147
@@ -11617,7 +11617,7 @@ msgstr ""
msgid "We ran out of posts from your follows. Here's the latest from <0/>."
msgstr ""
#: src/screens/Settings/InterestsSettings.tsx:161
#: src/screens/Settings/InterestsSettings.tsx:171
msgid "We recommend selecting at least two interests."
msgstr ""
@@ -11852,6 +11852,7 @@ msgstr ""
msgid "Would you like to save this as a draft to edit later?"
msgstr ""
#: src/screens/Messages/components/MessageComposer.tsx:167
#: src/screens/Messages/components/MessageInput.tsx:156
#: src/screens/Messages/components/MessageInput.web.tsx:213
msgid "Write a message"
@@ -12482,11 +12483,11 @@ msgstr ""
#: src/screens/Search/modules/ExploreInterestsCard.tsx:68
#: src/screens/Settings/ContentAndMediaSettings.tsx:94
#: src/screens/Settings/ContentAndMediaSettings.tsx:97
#: src/screens/Settings/InterestsSettings.tsx:47
#: src/screens/Settings/InterestsSettings.tsx:49
msgid "Your interests"
msgstr ""
#: src/screens/Settings/InterestsSettings.tsx:128
#: src/screens/Settings/InterestsSettings.tsx:138
msgctxt "toast"
msgid "Your interests have been updated!"
msgstr ""
@@ -12549,7 +12550,7 @@ msgstr ""
msgid "Your report will be sent to <0>{0}</0>."
msgstr ""
#: src/screens/Settings/InterestsSettings.tsx:61
#: src/screens/Settings/InterestsSettings.tsx:63
msgid "Your selected interests help us serve you content you care about."
msgstr ""
+1 -1
View File
@@ -8,7 +8,7 @@ msgstr ""
"Language: ast\n"
"Project-Id-Version: 49a8cb746fbc2ae5707392ee41ddec4c\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2026-04-07 21:54\n"
"PO-Revision-Date: 2026-04-08 18:50\n"
"Last-Translator: \n"
"Language-Team: Asturian\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+60 -59
View File
@@ -8,7 +8,7 @@ msgstr ""
"Language: az\n"
"Project-Id-Version: 49a8cb746fbc2ae5707392ee41ddec4c\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2026-04-07 21:54\n"
"PO-Revision-Date: 2026-04-08 18:50\n"
"Last-Translator: \n"
"Language-Team: Azerbaijani\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
@@ -971,7 +971,7 @@ msgstr ""
msgid "Add to lists"
msgstr ""
#: src/components/PostControls/BookmarkButton.tsx:142
#: src/components/PostControls/BookmarkButton.tsx:144
msgid "Add to saved posts"
msgstr ""
@@ -1837,20 +1837,20 @@ msgstr ""
msgid "Browse custom feeds"
msgstr ""
#: src/components/FeedInterstitials.tsx:627
#: src/components/FeedInterstitials.tsx:534
msgid "Browse more accounts"
msgstr ""
#: src/components/FeedInterstitials.tsx:757
#: src/components/FeedInterstitials.tsx:664
msgid "Browse more feeds on the Explore page"
msgstr ""
#: src/components/FeedInterstitials.tsx:738
#: src/components/FeedInterstitials.tsx:741
#: src/components/FeedInterstitials.tsx:645
#: src/components/FeedInterstitials.tsx:648
msgid "Browse more suggestions"
msgstr ""
#: src/components/FeedInterstitials.tsx:766
#: src/components/FeedInterstitials.tsx:673
msgid "Browse more suggestions on the Explore page"
msgstr ""
@@ -1894,7 +1894,7 @@ msgstr ""
#: src/components/LabelingServiceCard/index.tsx:62
#: src/components/moderation/ReportDialog/index.tsx:853
#: src/screens/Search/components/StarterPackCard.tsx:107
#: src/screens/Search/Explore.tsx:969
#: src/screens/Search/Explore.tsx:970
msgid "By {0}"
msgstr ""
@@ -1988,9 +1988,9 @@ msgstr ""
msgid "Cancel search"
msgstr ""
#: src/components/PostControls/index.tsx:109
#: src/components/PostControls/index.tsx:140
#: src/components/PostControls/index.tsx:168
#: src/components/PostControls/index.tsx:110
#: src/components/PostControls/index.tsx:141
#: src/components/PostControls/index.tsx:169
#: src/state/shell/composer/index.tsx:107
msgid "Cannot interact with a blocked user"
msgstr ""
@@ -3262,7 +3262,7 @@ msgstr ""
msgid "Discover new custom feeds"
msgstr ""
#: src/screens/Search/Explore.tsx:451
#: src/screens/Search/Explore.tsx:452
msgid "Discover new feeds"
msgstr ""
@@ -3299,7 +3299,7 @@ msgstr ""
msgid "Dismiss this section"
msgstr ""
#: src/components/FeedInterstitials.tsx:441
#: src/components/FeedInterstitials.tsx:348
msgid "Dismiss this suggestion"
msgstr ""
@@ -4024,16 +4024,16 @@ msgstr ""
msgid "Failed to load conversations"
msgstr ""
#: src/screens/Search/Explore.tsx:528
#: src/screens/Search/Explore.tsx:573
#: src/screens/Search/Explore.tsx:619
#: src/screens/Search/Explore.tsx:529
#: src/screens/Search/Explore.tsx:574
#: src/screens/Search/Explore.tsx:620
msgid "Failed to load feeds"
msgstr ""
#: src/screens/Search/Explore.tsx:487
#: src/screens/Search/Explore.tsx:542
#: src/screens/Search/Explore.tsx:587
#: src/screens/Search/Explore.tsx:633
#: src/screens/Search/Explore.tsx:488
#: src/screens/Search/Explore.tsx:543
#: src/screens/Search/Explore.tsx:588
#: src/screens/Search/Explore.tsx:634
msgid "Failed to load feeds preferences"
msgstr ""
@@ -4063,14 +4063,14 @@ msgstr ""
msgid "Failed to load preference."
msgstr ""
#: src/screens/Search/Explore.tsx:480
#: src/screens/Search/Explore.tsx:535
#: src/screens/Search/Explore.tsx:580
#: src/screens/Search/Explore.tsx:626
#: src/screens/Search/Explore.tsx:481
#: src/screens/Search/Explore.tsx:536
#: src/screens/Search/Explore.tsx:581
#: src/screens/Search/Explore.tsx:627
msgid "Failed to load suggested feeds"
msgstr ""
#: src/screens/Search/Explore.tsx:390
#: src/screens/Search/Explore.tsx:391
msgid "Failed to load suggested follows"
msgstr ""
@@ -4132,7 +4132,7 @@ msgstr ""
msgid "Failed to save settings. Please try again."
msgstr ""
#: src/screens/Settings/InterestsSettings.tsx:137
#: src/screens/Settings/InterestsSettings.tsx:147
msgctxt "toast"
msgid "Failed to save your interests."
msgstr ""
@@ -5716,7 +5716,7 @@ msgstr ""
#. Accessibility label for the like button when the post has not been liked, verb form followed by number of likes and noun form
#. placeholder {0}: post.likeCount || 0
#: src/components/PostControls/index.tsx:284
#: src/components/PostControls/index.tsx:287
msgid "Like ({0, plural, one {# like} other {# likes}})"
msgstr ""
@@ -5935,12 +5935,12 @@ msgstr ""
msgid "Live link"
msgstr ""
#: src/screens/Search/Explore.tsx:88
#: src/screens/Search/Explore.tsx:90
msgid "Load more"
msgstr ""
#: src/screens/Search/Explore.tsx:517
#: src/screens/Search/Explore.tsx:608
#: src/screens/Search/Explore.tsx:518
#: src/screens/Search/Explore.tsx:609
msgid "Load more suggested feeds"
msgstr ""
@@ -6117,10 +6117,12 @@ msgstr ""
msgid "Message from server: {0}"
msgstr ""
#: src/screens/Messages/components/MessageComposer.tsx:166
#: src/screens/Messages/components/MessageInput.tsx:154
msgid "Message input field"
msgstr ""
#: src/screens/Messages/components/MessageComposer.tsx:69
#: src/screens/Messages/components/MessageInput.tsx:79
#: src/screens/Messages/components/MessageInput.web.tsx:60
msgid "Message is too long"
@@ -6689,7 +6691,7 @@ msgid "No results"
msgstr ""
#. placeholder {0}: interestsDisplayNames[selectedInterest]
#: src/screens/Search/Explore.tsx:826
#: src/screens/Search/Explore.tsx:827
msgid "No results for \"{0}\"."
msgstr ""
@@ -6706,7 +6708,7 @@ msgstr ""
msgid "No results found for “<0>{query}</0>”."
msgstr ""
#: src/screens/Search/Explore.tsx:830
#: src/screens/Search/Explore.tsx:831
msgid "No results."
msgstr ""
@@ -6985,6 +6987,7 @@ msgstr ""
msgid "Open drawer menu"
msgstr ""
#: src/screens/Messages/components/MessageComposer.tsx:143
#: src/screens/Messages/components/MessageInput.web.tsx:180
#: src/view/com/composer/Composer.tsx:1979
msgid "Open emoji picker"
@@ -7600,7 +7603,7 @@ msgctxt "action"
msgid "Post"
msgstr ""
#: src/screens/PostThread/index.tsx:552
#: src/screens/PostThread/index.tsx:553
msgctxt "description"
msgid "Post"
msgstr ""
@@ -8127,7 +8130,7 @@ msgstr ""
msgid "Remove from saved feeds"
msgstr ""
#: src/components/PostControls/BookmarkButton.tsx:141
#: src/components/PostControls/BookmarkButton.tsx:143
#: src/screens/Bookmarks/index.tsx:262
msgid "Remove from saved posts"
msgstr ""
@@ -8288,7 +8291,7 @@ msgstr ""
#. Accessibility label for the reply button, verb form followed by number of replies and noun form
#. placeholder {0}: post.replyCount || 0
#: src/components/PostControls/index.tsx:242
#: src/components/PostControls/index.tsx:243
msgid "Reply ({0, plural, one {# reply} other {# replies}})"
msgstr ""
@@ -8774,23 +8777,19 @@ msgstr ""
msgid "Search for \"{interestsDisplayName}\" (active)"
msgstr ""
#: src/screens/Search/components/AutocompleteResults.tsx:47
#: src/screens/Search/components/AutocompleteResults.tsx:54
msgid "Search for \"{searchText}\""
msgstr ""
#: src/view/shell/desktop/Search.tsx:129
msgid "Search for “{tQuery}”"
msgstr ""
#: src/screens/StarterPack/Wizard/index.tsx:552
msgid "Search for feeds that you want to suggest to others."
msgstr ""
#: src/screens/Search/Explore.tsx:377
#: src/screens/Search/Explore.tsx:378
msgid "Search for more accounts"
msgstr ""
#: src/screens/Search/Explore.tsx:454
#: src/screens/Search/Explore.tsx:455
msgid "Search for more feeds"
msgstr ""
@@ -8879,12 +8878,12 @@ msgstr ""
msgid "See jobs at Bluesky"
msgstr ""
#: src/components/FeedInterstitials.tsx:583
#: src/components/FeedInterstitials.tsx:641
#: src/components/FeedInterstitials.tsx:490
#: src/components/FeedInterstitials.tsx:548
msgid "See more"
msgstr ""
#: src/components/FeedInterstitials.tsx:564
#: src/components/FeedInterstitials.tsx:471
msgid "See more suggested profiles"
msgstr ""
@@ -9052,7 +9051,7 @@ msgid "Select your date of birth"
msgstr ""
#: src/screens/Onboarding/StepInterests/index.tsx:69
#: src/screens/Settings/InterestsSettings.tsx:168
#: src/screens/Settings/InterestsSettings.tsx:178
msgid "Select your interests from the options below"
msgstr ""
@@ -9092,6 +9091,7 @@ msgstr ""
msgid "Send feedback"
msgstr ""
#: src/screens/Messages/components/MessageComposer.tsx:216
#: src/screens/Messages/components/MessageInput.tsx:194
#: src/screens/Messages/components/MessageInput.web.tsx:234
msgid "Send message"
@@ -9580,7 +9580,7 @@ msgstr ""
msgid "Some of your verifications are invalid."
msgstr ""
#: src/components/FeedInterstitials.tsx:720
#: src/components/FeedInterstitials.tsx:627
msgid "Some other feeds you might like"
msgstr ""
@@ -9640,7 +9640,7 @@ msgstr ""
msgid "Sorry, we're unable to load account suggestions at this time."
msgstr ""
#: src/App.native.tsx:141
#: src/App.native.tsx:140
#: src/App.web.tsx:119
msgid "Sorry! Your session expired. Please sign in again."
msgstr ""
@@ -9726,7 +9726,7 @@ msgstr ""
msgid "Starter pack is invalid"
msgstr ""
#: src/screens/Search/Explore.tsx:663
#: src/screens/Search/Explore.tsx:664
#: src/view/screens/Profile.tsx:241
msgid "Starter Packs"
msgstr ""
@@ -9842,12 +9842,12 @@ msgstr ""
msgid "Suggested"
msgstr ""
#: src/screens/Search/Explore.tsx:374
#: src/screens/Search/Explore.tsx:375
msgid "Suggested accounts"
msgstr ""
#. Accounts suggested to the user for them to follow
#: src/components/FeedInterstitials.tsx:561
#: src/components/FeedInterstitials.tsx:468
#: src/screens/Onboarding/StepSuggestedAccounts/index.tsx:180
msgid "Suggested for you"
msgstr ""
@@ -10189,7 +10189,7 @@ msgstr ""
msgid "There was an issue fetching notifications. Tap here to try again."
msgstr ""
#: src/screens/Search/Explore.tsx:1025
#: src/screens/Search/Explore.tsx:1026
#: src/view/com/posts/PostFeed.tsx:773
msgid "There was an issue fetching posts. Tap here to try again."
msgstr ""
@@ -10892,7 +10892,7 @@ msgstr ""
#. Accessibility label for the like button when the post has been liked, verb followed by number of likes and noun
#. placeholder {0}: post.likeCount || 0
#: src/components/PostControls/index.tsx:276
#: src/components/PostControls/index.tsx:279
msgid "Unlike ({0, plural, one {# like} other {# likes}})"
msgstr ""
@@ -11491,7 +11491,7 @@ msgid "View your default post interaction settings"
msgstr ""
#: src/view/com/home/HomeHeaderLayout.web.tsx:57
#: src/view/com/home/HomeHeaderLayoutMobile.tsx:75
#: src/view/com/home/HomeHeaderLayoutMobile.tsx:82
msgid "View your feeds and explore more"
msgstr ""
@@ -11561,7 +11561,7 @@ msgid "We apply the highest privacy standards, and never share or sell your cont
msgstr ""
#: src/view/com/feeds/MissingFeed.tsx:141
msgid "We could not connect to the service that provides this custom feed. It may be temporarily unavailable and experiencing issues, or permanently unavailable."
msgid "We could not connect to the service that provides this custom feed. It may be temporarily experiencing issues, or permanently unavailable."
msgstr ""
#: src/view/com/feeds/MissingFeed.tsx:147
@@ -11617,7 +11617,7 @@ msgstr ""
msgid "We ran out of posts from your follows. Here's the latest from <0/>."
msgstr ""
#: src/screens/Settings/InterestsSettings.tsx:161
#: src/screens/Settings/InterestsSettings.tsx:171
msgid "We recommend selecting at least two interests."
msgstr ""
@@ -11852,6 +11852,7 @@ msgstr ""
msgid "Would you like to save this as a draft to edit later?"
msgstr ""
#: src/screens/Messages/components/MessageComposer.tsx:167
#: src/screens/Messages/components/MessageInput.tsx:156
#: src/screens/Messages/components/MessageInput.web.tsx:213
msgid "Write a message"
@@ -12482,11 +12483,11 @@ msgstr ""
#: src/screens/Search/modules/ExploreInterestsCard.tsx:68
#: src/screens/Settings/ContentAndMediaSettings.tsx:94
#: src/screens/Settings/ContentAndMediaSettings.tsx:97
#: src/screens/Settings/InterestsSettings.tsx:47
#: src/screens/Settings/InterestsSettings.tsx:49
msgid "Your interests"
msgstr ""
#: src/screens/Settings/InterestsSettings.tsx:128
#: src/screens/Settings/InterestsSettings.tsx:138
msgctxt "toast"
msgid "Your interests have been updated!"
msgstr ""
@@ -12549,7 +12550,7 @@ msgstr ""
msgid "Your report will be sent to <0>{0}</0>."
msgstr ""
#: src/screens/Settings/InterestsSettings.tsx:61
#: src/screens/Settings/InterestsSettings.tsx:63
msgid "Your selected interests help us serve you content you care about."
msgstr ""
+60 -59
View File
@@ -8,7 +8,7 @@ msgstr ""
"Language: bn\n"
"Project-Id-Version: 49a8cb746fbc2ae5707392ee41ddec4c\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2026-04-07 21:54\n"
"PO-Revision-Date: 2026-04-08 18:50\n"
"Last-Translator: \n"
"Language-Team: Bengali\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
@@ -971,7 +971,7 @@ msgstr ""
msgid "Add to lists"
msgstr ""
#: src/components/PostControls/BookmarkButton.tsx:142
#: src/components/PostControls/BookmarkButton.tsx:144
msgid "Add to saved posts"
msgstr ""
@@ -1837,20 +1837,20 @@ msgstr ""
msgid "Browse custom feeds"
msgstr ""
#: src/components/FeedInterstitials.tsx:627
#: src/components/FeedInterstitials.tsx:534
msgid "Browse more accounts"
msgstr ""
#: src/components/FeedInterstitials.tsx:757
#: src/components/FeedInterstitials.tsx:664
msgid "Browse more feeds on the Explore page"
msgstr ""
#: src/components/FeedInterstitials.tsx:738
#: src/components/FeedInterstitials.tsx:741
#: src/components/FeedInterstitials.tsx:645
#: src/components/FeedInterstitials.tsx:648
msgid "Browse more suggestions"
msgstr ""
#: src/components/FeedInterstitials.tsx:766
#: src/components/FeedInterstitials.tsx:673
msgid "Browse more suggestions on the Explore page"
msgstr ""
@@ -1894,7 +1894,7 @@ msgstr ""
#: src/components/LabelingServiceCard/index.tsx:62
#: src/components/moderation/ReportDialog/index.tsx:853
#: src/screens/Search/components/StarterPackCard.tsx:107
#: src/screens/Search/Explore.tsx:969
#: src/screens/Search/Explore.tsx:970
msgid "By {0}"
msgstr ""
@@ -1988,9 +1988,9 @@ msgstr ""
msgid "Cancel search"
msgstr ""
#: src/components/PostControls/index.tsx:109
#: src/components/PostControls/index.tsx:140
#: src/components/PostControls/index.tsx:168
#: src/components/PostControls/index.tsx:110
#: src/components/PostControls/index.tsx:141
#: src/components/PostControls/index.tsx:169
#: src/state/shell/composer/index.tsx:107
msgid "Cannot interact with a blocked user"
msgstr ""
@@ -3262,7 +3262,7 @@ msgstr ""
msgid "Discover new custom feeds"
msgstr ""
#: src/screens/Search/Explore.tsx:451
#: src/screens/Search/Explore.tsx:452
msgid "Discover new feeds"
msgstr ""
@@ -3299,7 +3299,7 @@ msgstr ""
msgid "Dismiss this section"
msgstr ""
#: src/components/FeedInterstitials.tsx:441
#: src/components/FeedInterstitials.tsx:348
msgid "Dismiss this suggestion"
msgstr ""
@@ -4024,16 +4024,16 @@ msgstr ""
msgid "Failed to load conversations"
msgstr ""
#: src/screens/Search/Explore.tsx:528
#: src/screens/Search/Explore.tsx:573
#: src/screens/Search/Explore.tsx:619
#: src/screens/Search/Explore.tsx:529
#: src/screens/Search/Explore.tsx:574
#: src/screens/Search/Explore.tsx:620
msgid "Failed to load feeds"
msgstr ""
#: src/screens/Search/Explore.tsx:487
#: src/screens/Search/Explore.tsx:542
#: src/screens/Search/Explore.tsx:587
#: src/screens/Search/Explore.tsx:633
#: src/screens/Search/Explore.tsx:488
#: src/screens/Search/Explore.tsx:543
#: src/screens/Search/Explore.tsx:588
#: src/screens/Search/Explore.tsx:634
msgid "Failed to load feeds preferences"
msgstr ""
@@ -4063,14 +4063,14 @@ msgstr ""
msgid "Failed to load preference."
msgstr ""
#: src/screens/Search/Explore.tsx:480
#: src/screens/Search/Explore.tsx:535
#: src/screens/Search/Explore.tsx:580
#: src/screens/Search/Explore.tsx:626
#: src/screens/Search/Explore.tsx:481
#: src/screens/Search/Explore.tsx:536
#: src/screens/Search/Explore.tsx:581
#: src/screens/Search/Explore.tsx:627
msgid "Failed to load suggested feeds"
msgstr ""
#: src/screens/Search/Explore.tsx:390
#: src/screens/Search/Explore.tsx:391
msgid "Failed to load suggested follows"
msgstr ""
@@ -4132,7 +4132,7 @@ msgstr ""
msgid "Failed to save settings. Please try again."
msgstr ""
#: src/screens/Settings/InterestsSettings.tsx:137
#: src/screens/Settings/InterestsSettings.tsx:147
msgctxt "toast"
msgid "Failed to save your interests."
msgstr ""
@@ -5716,7 +5716,7 @@ msgstr ""
#. Accessibility label for the like button when the post has not been liked, verb form followed by number of likes and noun form
#. placeholder {0}: post.likeCount || 0
#: src/components/PostControls/index.tsx:284
#: src/components/PostControls/index.tsx:287
msgid "Like ({0, plural, one {# like} other {# likes}})"
msgstr ""
@@ -5935,12 +5935,12 @@ msgstr ""
msgid "Live link"
msgstr ""
#: src/screens/Search/Explore.tsx:88
#: src/screens/Search/Explore.tsx:90
msgid "Load more"
msgstr ""
#: src/screens/Search/Explore.tsx:517
#: src/screens/Search/Explore.tsx:608
#: src/screens/Search/Explore.tsx:518
#: src/screens/Search/Explore.tsx:609
msgid "Load more suggested feeds"
msgstr ""
@@ -6117,10 +6117,12 @@ msgstr ""
msgid "Message from server: {0}"
msgstr ""
#: src/screens/Messages/components/MessageComposer.tsx:166
#: src/screens/Messages/components/MessageInput.tsx:154
msgid "Message input field"
msgstr ""
#: src/screens/Messages/components/MessageComposer.tsx:69
#: src/screens/Messages/components/MessageInput.tsx:79
#: src/screens/Messages/components/MessageInput.web.tsx:60
msgid "Message is too long"
@@ -6689,7 +6691,7 @@ msgid "No results"
msgstr ""
#. placeholder {0}: interestsDisplayNames[selectedInterest]
#: src/screens/Search/Explore.tsx:826
#: src/screens/Search/Explore.tsx:827
msgid "No results for \"{0}\"."
msgstr ""
@@ -6706,7 +6708,7 @@ msgstr ""
msgid "No results found for “<0>{query}</0>”."
msgstr ""
#: src/screens/Search/Explore.tsx:830
#: src/screens/Search/Explore.tsx:831
msgid "No results."
msgstr ""
@@ -6985,6 +6987,7 @@ msgstr ""
msgid "Open drawer menu"
msgstr ""
#: src/screens/Messages/components/MessageComposer.tsx:143
#: src/screens/Messages/components/MessageInput.web.tsx:180
#: src/view/com/composer/Composer.tsx:1979
msgid "Open emoji picker"
@@ -7600,7 +7603,7 @@ msgctxt "action"
msgid "Post"
msgstr ""
#: src/screens/PostThread/index.tsx:552
#: src/screens/PostThread/index.tsx:553
msgctxt "description"
msgid "Post"
msgstr ""
@@ -8127,7 +8130,7 @@ msgstr ""
msgid "Remove from saved feeds"
msgstr ""
#: src/components/PostControls/BookmarkButton.tsx:141
#: src/components/PostControls/BookmarkButton.tsx:143
#: src/screens/Bookmarks/index.tsx:262
msgid "Remove from saved posts"
msgstr ""
@@ -8288,7 +8291,7 @@ msgstr ""
#. Accessibility label for the reply button, verb form followed by number of replies and noun form
#. placeholder {0}: post.replyCount || 0
#: src/components/PostControls/index.tsx:242
#: src/components/PostControls/index.tsx:243
msgid "Reply ({0, plural, one {# reply} other {# replies}})"
msgstr ""
@@ -8774,23 +8777,19 @@ msgstr ""
msgid "Search for \"{interestsDisplayName}\" (active)"
msgstr ""
#: src/screens/Search/components/AutocompleteResults.tsx:47
#: src/screens/Search/components/AutocompleteResults.tsx:54
msgid "Search for \"{searchText}\""
msgstr ""
#: src/view/shell/desktop/Search.tsx:129
msgid "Search for “{tQuery}”"
msgstr ""
#: src/screens/StarterPack/Wizard/index.tsx:552
msgid "Search for feeds that you want to suggest to others."
msgstr ""
#: src/screens/Search/Explore.tsx:377
#: src/screens/Search/Explore.tsx:378
msgid "Search for more accounts"
msgstr ""
#: src/screens/Search/Explore.tsx:454
#: src/screens/Search/Explore.tsx:455
msgid "Search for more feeds"
msgstr ""
@@ -8879,12 +8878,12 @@ msgstr ""
msgid "See jobs at Bluesky"
msgstr ""
#: src/components/FeedInterstitials.tsx:583
#: src/components/FeedInterstitials.tsx:641
#: src/components/FeedInterstitials.tsx:490
#: src/components/FeedInterstitials.tsx:548
msgid "See more"
msgstr ""
#: src/components/FeedInterstitials.tsx:564
#: src/components/FeedInterstitials.tsx:471
msgid "See more suggested profiles"
msgstr ""
@@ -9052,7 +9051,7 @@ msgid "Select your date of birth"
msgstr ""
#: src/screens/Onboarding/StepInterests/index.tsx:69
#: src/screens/Settings/InterestsSettings.tsx:168
#: src/screens/Settings/InterestsSettings.tsx:178
msgid "Select your interests from the options below"
msgstr ""
@@ -9092,6 +9091,7 @@ msgstr ""
msgid "Send feedback"
msgstr ""
#: src/screens/Messages/components/MessageComposer.tsx:216
#: src/screens/Messages/components/MessageInput.tsx:194
#: src/screens/Messages/components/MessageInput.web.tsx:234
msgid "Send message"
@@ -9580,7 +9580,7 @@ msgstr ""
msgid "Some of your verifications are invalid."
msgstr ""
#: src/components/FeedInterstitials.tsx:720
#: src/components/FeedInterstitials.tsx:627
msgid "Some other feeds you might like"
msgstr ""
@@ -9640,7 +9640,7 @@ msgstr ""
msgid "Sorry, we're unable to load account suggestions at this time."
msgstr ""
#: src/App.native.tsx:141
#: src/App.native.tsx:140
#: src/App.web.tsx:119
msgid "Sorry! Your session expired. Please sign in again."
msgstr ""
@@ -9726,7 +9726,7 @@ msgstr ""
msgid "Starter pack is invalid"
msgstr ""
#: src/screens/Search/Explore.tsx:663
#: src/screens/Search/Explore.tsx:664
#: src/view/screens/Profile.tsx:241
msgid "Starter Packs"
msgstr ""
@@ -9842,12 +9842,12 @@ msgstr ""
msgid "Suggested"
msgstr ""
#: src/screens/Search/Explore.tsx:374
#: src/screens/Search/Explore.tsx:375
msgid "Suggested accounts"
msgstr ""
#. Accounts suggested to the user for them to follow
#: src/components/FeedInterstitials.tsx:561
#: src/components/FeedInterstitials.tsx:468
#: src/screens/Onboarding/StepSuggestedAccounts/index.tsx:180
msgid "Suggested for you"
msgstr ""
@@ -10189,7 +10189,7 @@ msgstr ""
msgid "There was an issue fetching notifications. Tap here to try again."
msgstr ""
#: src/screens/Search/Explore.tsx:1025
#: src/screens/Search/Explore.tsx:1026
#: src/view/com/posts/PostFeed.tsx:773
msgid "There was an issue fetching posts. Tap here to try again."
msgstr ""
@@ -10892,7 +10892,7 @@ msgstr ""
#. Accessibility label for the like button when the post has been liked, verb followed by number of likes and noun
#. placeholder {0}: post.likeCount || 0
#: src/components/PostControls/index.tsx:276
#: src/components/PostControls/index.tsx:279
msgid "Unlike ({0, plural, one {# like} other {# likes}})"
msgstr ""
@@ -11491,7 +11491,7 @@ msgid "View your default post interaction settings"
msgstr ""
#: src/view/com/home/HomeHeaderLayout.web.tsx:57
#: src/view/com/home/HomeHeaderLayoutMobile.tsx:75
#: src/view/com/home/HomeHeaderLayoutMobile.tsx:82
msgid "View your feeds and explore more"
msgstr ""
@@ -11561,7 +11561,7 @@ msgid "We apply the highest privacy standards, and never share or sell your cont
msgstr ""
#: src/view/com/feeds/MissingFeed.tsx:141
msgid "We could not connect to the service that provides this custom feed. It may be temporarily unavailable and experiencing issues, or permanently unavailable."
msgid "We could not connect to the service that provides this custom feed. It may be temporarily experiencing issues, or permanently unavailable."
msgstr ""
#: src/view/com/feeds/MissingFeed.tsx:147
@@ -11617,7 +11617,7 @@ msgstr ""
msgid "We ran out of posts from your follows. Here's the latest from <0/>."
msgstr ""
#: src/screens/Settings/InterestsSettings.tsx:161
#: src/screens/Settings/InterestsSettings.tsx:171
msgid "We recommend selecting at least two interests."
msgstr ""
@@ -11852,6 +11852,7 @@ msgstr ""
msgid "Would you like to save this as a draft to edit later?"
msgstr ""
#: src/screens/Messages/components/MessageComposer.tsx:167
#: src/screens/Messages/components/MessageInput.tsx:156
#: src/screens/Messages/components/MessageInput.web.tsx:213
msgid "Write a message"
@@ -12482,11 +12483,11 @@ msgstr ""
#: src/screens/Search/modules/ExploreInterestsCard.tsx:68
#: src/screens/Settings/ContentAndMediaSettings.tsx:94
#: src/screens/Settings/ContentAndMediaSettings.tsx:97
#: src/screens/Settings/InterestsSettings.tsx:47
#: src/screens/Settings/InterestsSettings.tsx:49
msgid "Your interests"
msgstr ""
#: src/screens/Settings/InterestsSettings.tsx:128
#: src/screens/Settings/InterestsSettings.tsx:138
msgctxt "toast"
msgid "Your interests have been updated!"
msgstr ""
@@ -12549,7 +12550,7 @@ msgstr ""
msgid "Your report will be sent to <0>{0}</0>."
msgstr ""
#: src/screens/Settings/InterestsSettings.tsx:61
#: src/screens/Settings/InterestsSettings.tsx:63
msgid "Your selected interests help us serve you content you care about."
msgstr ""
+1 -1
View File
@@ -8,7 +8,7 @@ msgstr ""
"Language: ca\n"
"Project-Id-Version: 49a8cb746fbc2ae5707392ee41ddec4c\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2026-04-07 21:54\n"
"PO-Revision-Date: 2026-04-08 18:50\n"
"Last-Translator: \n"
"Language-Team: Catalan\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+1 -1
View File
@@ -8,7 +8,7 @@ msgstr ""
"Language: cy\n"
"Project-Id-Version: 49a8cb746fbc2ae5707392ee41ddec4c\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2026-04-07 21:54\n"
"PO-Revision-Date: 2026-04-08 18:50\n"
"Last-Translator: \n"
"Language-Team: Welsh\n"
"Plural-Forms: nplurals=6; plural=(n == 0) ? 0 : ((n == 1) ? 1 : ((n == 2) ? 2 : ((n == 3) ? 3 : ((n == 6) ? 4 : 5))));\n"
+1 -1
View File
@@ -8,7 +8,7 @@ msgstr ""
"Language: da\n"
"Project-Id-Version: 49a8cb746fbc2ae5707392ee41ddec4c\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2026-04-07 21:54\n"
"PO-Revision-Date: 2026-04-08 18:50\n"
"Last-Translator: \n"
"Language-Team: Danish\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+1 -1
View File
@@ -8,7 +8,7 @@ msgstr ""
"Language: de\n"
"Project-Id-Version: 49a8cb746fbc2ae5707392ee41ddec4c\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2026-04-07 21:54\n"
"PO-Revision-Date: 2026-04-08 18:50\n"
"Last-Translator: \n"
"Language-Team: German\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+1 -1
View File
@@ -8,7 +8,7 @@ msgstr ""
"Language: el\n"
"Project-Id-Version: 49a8cb746fbc2ae5707392ee41ddec4c\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2026-04-07 21:54\n"
"PO-Revision-Date: 2026-04-08 18:50\n"
"Last-Translator: \n"
"Language-Team: Greek\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+1 -1
View File
@@ -8,7 +8,7 @@ msgstr ""
"Language: en_GB\n"
"Project-Id-Version: 49a8cb746fbc2ae5707392ee41ddec4c\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2026-04-07 21:54\n"
"PO-Revision-Date: 2026-04-08 18:50\n"
"Last-Translator: \n"
"Language-Team: English, United Kingdom\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -8,7 +8,7 @@ msgstr ""
"Language: eo\n"
"Project-Id-Version: 49a8cb746fbc2ae5707392ee41ddec4c\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2026-04-07 21:54\n"
"PO-Revision-Date: 2026-04-08 18:50\n"
"Last-Translator: \n"
"Language-Team: Esperanto\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+1 -1
View File
@@ -8,7 +8,7 @@ msgstr ""
"Language: es\n"
"Project-Id-Version: 49a8cb746fbc2ae5707392ee41ddec4c\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2026-04-07 21:54\n"
"PO-Revision-Date: 2026-04-08 18:50\n"
"Last-Translator: \n"
"Language-Team: Spanish\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+2 -2
View File
@@ -8,7 +8,7 @@ msgstr ""
"Language: eu\n"
"Project-Id-Version: 49a8cb746fbc2ae5707392ee41ddec4c\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2026-04-07 21:54\n"
"PO-Revision-Date: 2026-04-08 18:50\n"
"Last-Translator: \n"
"Language-Team: Basque\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
@@ -11562,7 +11562,7 @@ msgstr "Pribatutasun-estandar gorenak aplikatzen ditugu, eta ez dugu inoiz zure
#: src/view/com/feeds/MissingFeed.tsx:141
msgid "We could not connect to the service that provides this custom feed. It may be temporarily experiencing issues, or permanently unavailable."
msgstr ""
msgstr "Ezin izan dugu pertsonalizatutako feed hau eskaintzen duen zerbitzura konektatu. Baliteke aldi baterako arazoa izatea edo betirako ez erabilgarri egotea."
#: src/view/com/feeds/MissingFeed.tsx:147
msgid "We could not find this list. It was probably deleted."
+1 -1
View File
@@ -8,7 +8,7 @@ msgstr ""
"Language: fi\n"
"Project-Id-Version: 49a8cb746fbc2ae5707392ee41ddec4c\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2026-04-07 21:54\n"
"PO-Revision-Date: 2026-04-08 18:50\n"
"Last-Translator: \n"
"Language-Team: Finnish\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+1 -1
View File
@@ -8,7 +8,7 @@ msgstr ""
"Language: fr\n"
"Project-Id-Version: 49a8cb746fbc2ae5707392ee41ddec4c\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2026-04-07 21:54\n"
"PO-Revision-Date: 2026-04-08 18:50\n"
"Last-Translator: \n"
"Language-Team: French\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
+1 -1
View File
@@ -8,7 +8,7 @@ msgstr ""
"Language: fy\n"
"Project-Id-Version: 49a8cb746fbc2ae5707392ee41ddec4c\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2026-04-07 21:54\n"
"PO-Revision-Date: 2026-04-08 18:50\n"
"Last-Translator: \n"
"Language-Team: Frisian\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+1 -1
View File
@@ -8,7 +8,7 @@ msgstr ""
"Language: ga\n"
"Project-Id-Version: 49a8cb746fbc2ae5707392ee41ddec4c\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2026-04-07 21:54\n"
"PO-Revision-Date: 2026-04-08 18:50\n"
"Last-Translator: \n"
"Language-Team: Irish\n"
"Plural-Forms: nplurals=5; plural=(n==1 ? 0 : n==2 ? 1 : n<7 ? 2 : n<11 ? 3 : 4);\n"
+1 -1
View File
@@ -8,7 +8,7 @@ msgstr ""
"Language: gd\n"
"Project-Id-Version: 49a8cb746fbc2ae5707392ee41ddec4c\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2026-04-07 21:54\n"
"PO-Revision-Date: 2026-04-08 18:50\n"
"Last-Translator: \n"
"Language-Team: Scottish Gaelic\n"
"Plural-Forms: nplurals=4; plural=(n==1 || n==11) ? 0 : (n==2 || n==12) ? 1 : (n>2 && n<20) ? 2 : 3;\n"
+1 -1
View File
@@ -8,7 +8,7 @@ msgstr ""
"Language: gl\n"
"Project-Id-Version: 49a8cb746fbc2ae5707392ee41ddec4c\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2026-04-07 21:54\n"
"PO-Revision-Date: 2026-04-08 18:50\n"
"Last-Translator: \n"
"Language-Team: Galician\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+1 -1
View File
@@ -8,7 +8,7 @@ msgstr ""
"Language: hi\n"
"Project-Id-Version: 49a8cb746fbc2ae5707392ee41ddec4c\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2026-04-07 21:54\n"
"PO-Revision-Date: 2026-04-08 18:50\n"
"Last-Translator: \n"
"Language-Team: Hindi\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+2 -2
View File
@@ -8,7 +8,7 @@ msgstr ""
"Language: hu\n"
"Project-Id-Version: 49a8cb746fbc2ae5707392ee41ddec4c\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2026-04-07 21:54\n"
"PO-Revision-Date: 2026-04-08 18:50\n"
"Last-Translator: \n"
"Language-Team: Hungarian\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
@@ -11564,7 +11564,7 @@ msgstr "Eleget teszünk a legszorosabb adatvédelmi előírásoknak, és soha se
#: src/view/com/feeds/MissingFeed.tsx:141
msgid "We could not connect to the service that provides this custom feed. It may be temporarily experiencing issues, or permanently unavailable."
msgstr ""
msgstr "A hírfolyam szolgáltatójával történő kapcsolódás meghiúsult. Lehetséges, hogy átmenetileg nem elérhető vagy véglegesen megszűnt."
#: src/view/com/feeds/MissingFeed.tsx:147
msgid "We could not find this list. It was probably deleted."
+1 -1
View File
@@ -8,7 +8,7 @@ msgstr ""
"Language: ia\n"
"Project-Id-Version: 49a8cb746fbc2ae5707392ee41ddec4c\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2026-04-07 21:54\n"
"PO-Revision-Date: 2026-04-08 18:50\n"
"Last-Translator: \n"
"Language-Team: Interlingua\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
+1 -1
View File
@@ -8,7 +8,7 @@ msgstr ""
"Language: id\n"
"Project-Id-Version: 49a8cb746fbc2ae5707392ee41ddec4c\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2026-04-07 21:54\n"
"PO-Revision-Date: 2026-04-08 18:50\n"
"Last-Translator: \n"
"Language-Team: Indonesian\n"
"Plural-Forms: nplurals=1; plural=0;\n"
+2 -2
View File
@@ -8,7 +8,7 @@ msgstr ""
"Language: it\n"
"Project-Id-Version: 49a8cb746fbc2ae5707392ee41ddec4c\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2026-04-07 21:54\n"
"PO-Revision-Date: 2026-04-08 18:50\n"
"Last-Translator: \n"
"Language-Team: Italian\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
@@ -11562,7 +11562,7 @@ msgstr "Applichiamo i più elevati standard relativi alla privacy e non condivid
#: src/view/com/feeds/MissingFeed.tsx:141
msgid "We could not connect to the service that provides this custom feed. It may be temporarily experiencing issues, or permanently unavailable."
msgstr ""
msgstr "Non è stato possibile collegarsi al servizio che fornisce questo feed personalizzato. Potrebbe avere problemi temporanei oppure non essere disponibile in modo permanente."
#: src/view/com/feeds/MissingFeed.tsx:147
msgid "We could not find this list. It was probably deleted."
+2 -2
View File
@@ -8,7 +8,7 @@ msgstr ""
"Language: ja\n"
"Project-Id-Version: 49a8cb746fbc2ae5707392ee41ddec4c\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2026-04-07 21:54\n"
"PO-Revision-Date: 2026-04-08 18:50\n"
"Last-Translator: \n"
"Language-Team: Japanese\n"
"Plural-Forms: nplurals=1; plural=0;\n"
@@ -11562,7 +11562,7 @@ msgstr "当社は最高のプライバシー基準を適用し、お客様の連
#: src/view/com/feeds/MissingFeed.tsx:141
msgid "We could not connect to the service that provides this custom feed. It may be temporarily experiencing issues, or permanently unavailable."
msgstr ""
msgstr "このカスタムフィードを提供しているサービスに接続できませんでした。一時的な問題が発生しているか、永久に利用できなくなっているのかもしれません。"
#: src/view/com/feeds/MissingFeed.tsx:147
msgid "We could not find this list. It was probably deleted."
+60 -59
View File
@@ -8,7 +8,7 @@ msgstr ""
"Language: kab\n"
"Project-Id-Version: 49a8cb746fbc2ae5707392ee41ddec4c\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2026-04-07 21:54\n"
"PO-Revision-Date: 2026-04-08 18:50\n"
"Last-Translator: \n"
"Language-Team: Kabyle\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
@@ -971,7 +971,7 @@ msgstr ""
msgid "Add to lists"
msgstr ""
#: src/components/PostControls/BookmarkButton.tsx:142
#: src/components/PostControls/BookmarkButton.tsx:144
msgid "Add to saved posts"
msgstr ""
@@ -1837,20 +1837,20 @@ msgstr ""
msgid "Browse custom feeds"
msgstr ""
#: src/components/FeedInterstitials.tsx:627
#: src/components/FeedInterstitials.tsx:534
msgid "Browse more accounts"
msgstr ""
#: src/components/FeedInterstitials.tsx:757
#: src/components/FeedInterstitials.tsx:664
msgid "Browse more feeds on the Explore page"
msgstr ""
#: src/components/FeedInterstitials.tsx:738
#: src/components/FeedInterstitials.tsx:741
#: src/components/FeedInterstitials.tsx:645
#: src/components/FeedInterstitials.tsx:648
msgid "Browse more suggestions"
msgstr ""
#: src/components/FeedInterstitials.tsx:766
#: src/components/FeedInterstitials.tsx:673
msgid "Browse more suggestions on the Explore page"
msgstr ""
@@ -1894,7 +1894,7 @@ msgstr ""
#: src/components/LabelingServiceCard/index.tsx:62
#: src/components/moderation/ReportDialog/index.tsx:853
#: src/screens/Search/components/StarterPackCard.tsx:107
#: src/screens/Search/Explore.tsx:969
#: src/screens/Search/Explore.tsx:970
msgid "By {0}"
msgstr ""
@@ -1988,9 +1988,9 @@ msgstr ""
msgid "Cancel search"
msgstr ""
#: src/components/PostControls/index.tsx:109
#: src/components/PostControls/index.tsx:140
#: src/components/PostControls/index.tsx:168
#: src/components/PostControls/index.tsx:110
#: src/components/PostControls/index.tsx:141
#: src/components/PostControls/index.tsx:169
#: src/state/shell/composer/index.tsx:107
msgid "Cannot interact with a blocked user"
msgstr ""
@@ -3262,7 +3262,7 @@ msgstr ""
msgid "Discover new custom feeds"
msgstr ""
#: src/screens/Search/Explore.tsx:451
#: src/screens/Search/Explore.tsx:452
msgid "Discover new feeds"
msgstr ""
@@ -3299,7 +3299,7 @@ msgstr ""
msgid "Dismiss this section"
msgstr ""
#: src/components/FeedInterstitials.tsx:441
#: src/components/FeedInterstitials.tsx:348
msgid "Dismiss this suggestion"
msgstr ""
@@ -4024,16 +4024,16 @@ msgstr ""
msgid "Failed to load conversations"
msgstr ""
#: src/screens/Search/Explore.tsx:528
#: src/screens/Search/Explore.tsx:573
#: src/screens/Search/Explore.tsx:619
#: src/screens/Search/Explore.tsx:529
#: src/screens/Search/Explore.tsx:574
#: src/screens/Search/Explore.tsx:620
msgid "Failed to load feeds"
msgstr ""
#: src/screens/Search/Explore.tsx:487
#: src/screens/Search/Explore.tsx:542
#: src/screens/Search/Explore.tsx:587
#: src/screens/Search/Explore.tsx:633
#: src/screens/Search/Explore.tsx:488
#: src/screens/Search/Explore.tsx:543
#: src/screens/Search/Explore.tsx:588
#: src/screens/Search/Explore.tsx:634
msgid "Failed to load feeds preferences"
msgstr ""
@@ -4063,14 +4063,14 @@ msgstr ""
msgid "Failed to load preference."
msgstr ""
#: src/screens/Search/Explore.tsx:480
#: src/screens/Search/Explore.tsx:535
#: src/screens/Search/Explore.tsx:580
#: src/screens/Search/Explore.tsx:626
#: src/screens/Search/Explore.tsx:481
#: src/screens/Search/Explore.tsx:536
#: src/screens/Search/Explore.tsx:581
#: src/screens/Search/Explore.tsx:627
msgid "Failed to load suggested feeds"
msgstr ""
#: src/screens/Search/Explore.tsx:390
#: src/screens/Search/Explore.tsx:391
msgid "Failed to load suggested follows"
msgstr ""
@@ -4132,7 +4132,7 @@ msgstr ""
msgid "Failed to save settings. Please try again."
msgstr ""
#: src/screens/Settings/InterestsSettings.tsx:137
#: src/screens/Settings/InterestsSettings.tsx:147
msgctxt "toast"
msgid "Failed to save your interests."
msgstr ""
@@ -5716,7 +5716,7 @@ msgstr ""
#. Accessibility label for the like button when the post has not been liked, verb form followed by number of likes and noun form
#. placeholder {0}: post.likeCount || 0
#: src/components/PostControls/index.tsx:284
#: src/components/PostControls/index.tsx:287
msgid "Like ({0, plural, one {# like} other {# likes}})"
msgstr ""
@@ -5935,12 +5935,12 @@ msgstr ""
msgid "Live link"
msgstr ""
#: src/screens/Search/Explore.tsx:88
#: src/screens/Search/Explore.tsx:90
msgid "Load more"
msgstr ""
#: src/screens/Search/Explore.tsx:517
#: src/screens/Search/Explore.tsx:608
#: src/screens/Search/Explore.tsx:518
#: src/screens/Search/Explore.tsx:609
msgid "Load more suggested feeds"
msgstr ""
@@ -6117,10 +6117,12 @@ msgstr ""
msgid "Message from server: {0}"
msgstr ""
#: src/screens/Messages/components/MessageComposer.tsx:166
#: src/screens/Messages/components/MessageInput.tsx:154
msgid "Message input field"
msgstr ""
#: src/screens/Messages/components/MessageComposer.tsx:69
#: src/screens/Messages/components/MessageInput.tsx:79
#: src/screens/Messages/components/MessageInput.web.tsx:60
msgid "Message is too long"
@@ -6689,7 +6691,7 @@ msgid "No results"
msgstr ""
#. placeholder {0}: interestsDisplayNames[selectedInterest]
#: src/screens/Search/Explore.tsx:826
#: src/screens/Search/Explore.tsx:827
msgid "No results for \"{0}\"."
msgstr ""
@@ -6706,7 +6708,7 @@ msgstr ""
msgid "No results found for “<0>{query}</0>”."
msgstr ""
#: src/screens/Search/Explore.tsx:830
#: src/screens/Search/Explore.tsx:831
msgid "No results."
msgstr ""
@@ -6985,6 +6987,7 @@ msgstr ""
msgid "Open drawer menu"
msgstr ""
#: src/screens/Messages/components/MessageComposer.tsx:143
#: src/screens/Messages/components/MessageInput.web.tsx:180
#: src/view/com/composer/Composer.tsx:1979
msgid "Open emoji picker"
@@ -7600,7 +7603,7 @@ msgctxt "action"
msgid "Post"
msgstr ""
#: src/screens/PostThread/index.tsx:552
#: src/screens/PostThread/index.tsx:553
msgctxt "description"
msgid "Post"
msgstr ""
@@ -8127,7 +8130,7 @@ msgstr ""
msgid "Remove from saved feeds"
msgstr ""
#: src/components/PostControls/BookmarkButton.tsx:141
#: src/components/PostControls/BookmarkButton.tsx:143
#: src/screens/Bookmarks/index.tsx:262
msgid "Remove from saved posts"
msgstr ""
@@ -8288,7 +8291,7 @@ msgstr ""
#. Accessibility label for the reply button, verb form followed by number of replies and noun form
#. placeholder {0}: post.replyCount || 0
#: src/components/PostControls/index.tsx:242
#: src/components/PostControls/index.tsx:243
msgid "Reply ({0, plural, one {# reply} other {# replies}})"
msgstr ""
@@ -8774,23 +8777,19 @@ msgstr ""
msgid "Search for \"{interestsDisplayName}\" (active)"
msgstr ""
#: src/screens/Search/components/AutocompleteResults.tsx:47
#: src/screens/Search/components/AutocompleteResults.tsx:54
msgid "Search for \"{searchText}\""
msgstr ""
#: src/view/shell/desktop/Search.tsx:129
msgid "Search for “{tQuery}”"
msgstr ""
#: src/screens/StarterPack/Wizard/index.tsx:552
msgid "Search for feeds that you want to suggest to others."
msgstr ""
#: src/screens/Search/Explore.tsx:377
#: src/screens/Search/Explore.tsx:378
msgid "Search for more accounts"
msgstr ""
#: src/screens/Search/Explore.tsx:454
#: src/screens/Search/Explore.tsx:455
msgid "Search for more feeds"
msgstr ""
@@ -8879,12 +8878,12 @@ msgstr ""
msgid "See jobs at Bluesky"
msgstr ""
#: src/components/FeedInterstitials.tsx:583
#: src/components/FeedInterstitials.tsx:641
#: src/components/FeedInterstitials.tsx:490
#: src/components/FeedInterstitials.tsx:548
msgid "See more"
msgstr ""
#: src/components/FeedInterstitials.tsx:564
#: src/components/FeedInterstitials.tsx:471
msgid "See more suggested profiles"
msgstr ""
@@ -9052,7 +9051,7 @@ msgid "Select your date of birth"
msgstr ""
#: src/screens/Onboarding/StepInterests/index.tsx:69
#: src/screens/Settings/InterestsSettings.tsx:168
#: src/screens/Settings/InterestsSettings.tsx:178
msgid "Select your interests from the options below"
msgstr ""
@@ -9092,6 +9091,7 @@ msgstr ""
msgid "Send feedback"
msgstr ""
#: src/screens/Messages/components/MessageComposer.tsx:216
#: src/screens/Messages/components/MessageInput.tsx:194
#: src/screens/Messages/components/MessageInput.web.tsx:234
msgid "Send message"
@@ -9580,7 +9580,7 @@ msgstr ""
msgid "Some of your verifications are invalid."
msgstr ""
#: src/components/FeedInterstitials.tsx:720
#: src/components/FeedInterstitials.tsx:627
msgid "Some other feeds you might like"
msgstr ""
@@ -9640,7 +9640,7 @@ msgstr ""
msgid "Sorry, we're unable to load account suggestions at this time."
msgstr ""
#: src/App.native.tsx:141
#: src/App.native.tsx:140
#: src/App.web.tsx:119
msgid "Sorry! Your session expired. Please sign in again."
msgstr ""
@@ -9726,7 +9726,7 @@ msgstr ""
msgid "Starter pack is invalid"
msgstr ""
#: src/screens/Search/Explore.tsx:663
#: src/screens/Search/Explore.tsx:664
#: src/view/screens/Profile.tsx:241
msgid "Starter Packs"
msgstr ""
@@ -9842,12 +9842,12 @@ msgstr ""
msgid "Suggested"
msgstr ""
#: src/screens/Search/Explore.tsx:374
#: src/screens/Search/Explore.tsx:375
msgid "Suggested accounts"
msgstr ""
#. Accounts suggested to the user for them to follow
#: src/components/FeedInterstitials.tsx:561
#: src/components/FeedInterstitials.tsx:468
#: src/screens/Onboarding/StepSuggestedAccounts/index.tsx:180
msgid "Suggested for you"
msgstr ""
@@ -10189,7 +10189,7 @@ msgstr ""
msgid "There was an issue fetching notifications. Tap here to try again."
msgstr ""
#: src/screens/Search/Explore.tsx:1025
#: src/screens/Search/Explore.tsx:1026
#: src/view/com/posts/PostFeed.tsx:773
msgid "There was an issue fetching posts. Tap here to try again."
msgstr ""
@@ -10892,7 +10892,7 @@ msgstr ""
#. Accessibility label for the like button when the post has been liked, verb followed by number of likes and noun
#. placeholder {0}: post.likeCount || 0
#: src/components/PostControls/index.tsx:276
#: src/components/PostControls/index.tsx:279
msgid "Unlike ({0, plural, one {# like} other {# likes}})"
msgstr ""
@@ -11491,7 +11491,7 @@ msgid "View your default post interaction settings"
msgstr ""
#: src/view/com/home/HomeHeaderLayout.web.tsx:57
#: src/view/com/home/HomeHeaderLayoutMobile.tsx:75
#: src/view/com/home/HomeHeaderLayoutMobile.tsx:82
msgid "View your feeds and explore more"
msgstr ""
@@ -11561,7 +11561,7 @@ msgid "We apply the highest privacy standards, and never share or sell your cont
msgstr ""
#: src/view/com/feeds/MissingFeed.tsx:141
msgid "We could not connect to the service that provides this custom feed. It may be temporarily unavailable and experiencing issues, or permanently unavailable."
msgid "We could not connect to the service that provides this custom feed. It may be temporarily experiencing issues, or permanently unavailable."
msgstr ""
#: src/view/com/feeds/MissingFeed.tsx:147
@@ -11617,7 +11617,7 @@ msgstr ""
msgid "We ran out of posts from your follows. Here's the latest from <0/>."
msgstr ""
#: src/screens/Settings/InterestsSettings.tsx:161
#: src/screens/Settings/InterestsSettings.tsx:171
msgid "We recommend selecting at least two interests."
msgstr ""
@@ -11852,6 +11852,7 @@ msgstr ""
msgid "Would you like to save this as a draft to edit later?"
msgstr ""
#: src/screens/Messages/components/MessageComposer.tsx:167
#: src/screens/Messages/components/MessageInput.tsx:156
#: src/screens/Messages/components/MessageInput.web.tsx:213
msgid "Write a message"
@@ -12482,11 +12483,11 @@ msgstr ""
#: src/screens/Search/modules/ExploreInterestsCard.tsx:68
#: src/screens/Settings/ContentAndMediaSettings.tsx:94
#: src/screens/Settings/ContentAndMediaSettings.tsx:97
#: src/screens/Settings/InterestsSettings.tsx:47
#: src/screens/Settings/InterestsSettings.tsx:49
msgid "Your interests"
msgstr ""
#: src/screens/Settings/InterestsSettings.tsx:128
#: src/screens/Settings/InterestsSettings.tsx:138
msgctxt "toast"
msgid "Your interests have been updated!"
msgstr ""
@@ -12549,7 +12550,7 @@ msgstr ""
msgid "Your report will be sent to <0>{0}</0>."
msgstr ""
#: src/screens/Settings/InterestsSettings.tsx:61
#: src/screens/Settings/InterestsSettings.tsx:63
msgid "Your selected interests help us serve you content you care about."
msgstr ""
+1 -1
View File
@@ -8,7 +8,7 @@ msgstr ""
"Language: km\n"
"Project-Id-Version: 49a8cb746fbc2ae5707392ee41ddec4c\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2026-04-07 21:54\n"
"PO-Revision-Date: 2026-04-08 18:50\n"
"Last-Translator: \n"
"Language-Team: Khmer\n"
"Plural-Forms: nplurals=1; plural=0;\n"
+2 -2
View File
@@ -8,7 +8,7 @@ msgstr ""
"Language: ko\n"
"Project-Id-Version: 49a8cb746fbc2ae5707392ee41ddec4c\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2026-04-07 21:54\n"
"PO-Revision-Date: 2026-04-08 18:50\n"
"Last-Translator: \n"
"Language-Team: Korean\n"
"Plural-Forms: nplurals=1; plural=0;\n"
@@ -11562,7 +11562,7 @@ msgstr "Bluesky는 최고 수준의 개인정보 보호 기준을 준수하며,
#: src/view/com/feeds/MissingFeed.tsx:141
msgid "We could not connect to the service that provides this custom feed. It may be temporarily experiencing issues, or permanently unavailable."
msgstr ""
msgstr "이 맞춤 피드를 제공하는 서비스에 연결할 수 없습니다. 일시적인 문제가 발생했거나 서비스가 영구적으로 중단되었을 수 있습니다."
#: src/view/com/feeds/MissingFeed.tsx:147
msgid "We could not find this list. It was probably deleted."
+60 -59
View File
@@ -8,7 +8,7 @@ msgstr ""
"Language: lt\n"
"Project-Id-Version: 49a8cb746fbc2ae5707392ee41ddec4c\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2026-04-07 21:54\n"
"PO-Revision-Date: 2026-04-08 18:50\n"
"Last-Translator: \n"
"Language-Team: Lithuanian\n"
"Plural-Forms: nplurals=4; plural=(n%10==1 && (n%100>19 || n%100<11) ? 0 : (n%10>=2 && n%10<=9) && (n%100>19 || n%100<11) ? 1 : n%1!=0 ? 2: 3);\n"
@@ -971,7 +971,7 @@ msgstr ""
msgid "Add to lists"
msgstr ""
#: src/components/PostControls/BookmarkButton.tsx:142
#: src/components/PostControls/BookmarkButton.tsx:144
msgid "Add to saved posts"
msgstr ""
@@ -1837,20 +1837,20 @@ msgstr ""
msgid "Browse custom feeds"
msgstr ""
#: src/components/FeedInterstitials.tsx:627
#: src/components/FeedInterstitials.tsx:534
msgid "Browse more accounts"
msgstr ""
#: src/components/FeedInterstitials.tsx:757
#: src/components/FeedInterstitials.tsx:664
msgid "Browse more feeds on the Explore page"
msgstr ""
#: src/components/FeedInterstitials.tsx:738
#: src/components/FeedInterstitials.tsx:741
#: src/components/FeedInterstitials.tsx:645
#: src/components/FeedInterstitials.tsx:648
msgid "Browse more suggestions"
msgstr ""
#: src/components/FeedInterstitials.tsx:766
#: src/components/FeedInterstitials.tsx:673
msgid "Browse more suggestions on the Explore page"
msgstr ""
@@ -1894,7 +1894,7 @@ msgstr ""
#: src/components/LabelingServiceCard/index.tsx:62
#: src/components/moderation/ReportDialog/index.tsx:853
#: src/screens/Search/components/StarterPackCard.tsx:107
#: src/screens/Search/Explore.tsx:969
#: src/screens/Search/Explore.tsx:970
msgid "By {0}"
msgstr ""
@@ -1988,9 +1988,9 @@ msgstr ""
msgid "Cancel search"
msgstr ""
#: src/components/PostControls/index.tsx:109
#: src/components/PostControls/index.tsx:140
#: src/components/PostControls/index.tsx:168
#: src/components/PostControls/index.tsx:110
#: src/components/PostControls/index.tsx:141
#: src/components/PostControls/index.tsx:169
#: src/state/shell/composer/index.tsx:107
msgid "Cannot interact with a blocked user"
msgstr ""
@@ -3262,7 +3262,7 @@ msgstr ""
msgid "Discover new custom feeds"
msgstr ""
#: src/screens/Search/Explore.tsx:451
#: src/screens/Search/Explore.tsx:452
msgid "Discover new feeds"
msgstr ""
@@ -3299,7 +3299,7 @@ msgstr ""
msgid "Dismiss this section"
msgstr ""
#: src/components/FeedInterstitials.tsx:441
#: src/components/FeedInterstitials.tsx:348
msgid "Dismiss this suggestion"
msgstr ""
@@ -4024,16 +4024,16 @@ msgstr ""
msgid "Failed to load conversations"
msgstr ""
#: src/screens/Search/Explore.tsx:528
#: src/screens/Search/Explore.tsx:573
#: src/screens/Search/Explore.tsx:619
#: src/screens/Search/Explore.tsx:529
#: src/screens/Search/Explore.tsx:574
#: src/screens/Search/Explore.tsx:620
msgid "Failed to load feeds"
msgstr ""
#: src/screens/Search/Explore.tsx:487
#: src/screens/Search/Explore.tsx:542
#: src/screens/Search/Explore.tsx:587
#: src/screens/Search/Explore.tsx:633
#: src/screens/Search/Explore.tsx:488
#: src/screens/Search/Explore.tsx:543
#: src/screens/Search/Explore.tsx:588
#: src/screens/Search/Explore.tsx:634
msgid "Failed to load feeds preferences"
msgstr ""
@@ -4063,14 +4063,14 @@ msgstr ""
msgid "Failed to load preference."
msgstr ""
#: src/screens/Search/Explore.tsx:480
#: src/screens/Search/Explore.tsx:535
#: src/screens/Search/Explore.tsx:580
#: src/screens/Search/Explore.tsx:626
#: src/screens/Search/Explore.tsx:481
#: src/screens/Search/Explore.tsx:536
#: src/screens/Search/Explore.tsx:581
#: src/screens/Search/Explore.tsx:627
msgid "Failed to load suggested feeds"
msgstr ""
#: src/screens/Search/Explore.tsx:390
#: src/screens/Search/Explore.tsx:391
msgid "Failed to load suggested follows"
msgstr ""
@@ -4132,7 +4132,7 @@ msgstr ""
msgid "Failed to save settings. Please try again."
msgstr ""
#: src/screens/Settings/InterestsSettings.tsx:137
#: src/screens/Settings/InterestsSettings.tsx:147
msgctxt "toast"
msgid "Failed to save your interests."
msgstr ""
@@ -5716,7 +5716,7 @@ msgstr ""
#. Accessibility label for the like button when the post has not been liked, verb form followed by number of likes and noun form
#. placeholder {0}: post.likeCount || 0
#: src/components/PostControls/index.tsx:284
#: src/components/PostControls/index.tsx:287
msgid "Like ({0, plural, one {# like} other {# likes}})"
msgstr ""
@@ -5935,12 +5935,12 @@ msgstr ""
msgid "Live link"
msgstr ""
#: src/screens/Search/Explore.tsx:88
#: src/screens/Search/Explore.tsx:90
msgid "Load more"
msgstr ""
#: src/screens/Search/Explore.tsx:517
#: src/screens/Search/Explore.tsx:608
#: src/screens/Search/Explore.tsx:518
#: src/screens/Search/Explore.tsx:609
msgid "Load more suggested feeds"
msgstr ""
@@ -6117,10 +6117,12 @@ msgstr ""
msgid "Message from server: {0}"
msgstr ""
#: src/screens/Messages/components/MessageComposer.tsx:166
#: src/screens/Messages/components/MessageInput.tsx:154
msgid "Message input field"
msgstr ""
#: src/screens/Messages/components/MessageComposer.tsx:69
#: src/screens/Messages/components/MessageInput.tsx:79
#: src/screens/Messages/components/MessageInput.web.tsx:60
msgid "Message is too long"
@@ -6689,7 +6691,7 @@ msgid "No results"
msgstr ""
#. placeholder {0}: interestsDisplayNames[selectedInterest]
#: src/screens/Search/Explore.tsx:826
#: src/screens/Search/Explore.tsx:827
msgid "No results for \"{0}\"."
msgstr ""
@@ -6706,7 +6708,7 @@ msgstr ""
msgid "No results found for “<0>{query}</0>”."
msgstr ""
#: src/screens/Search/Explore.tsx:830
#: src/screens/Search/Explore.tsx:831
msgid "No results."
msgstr ""
@@ -6985,6 +6987,7 @@ msgstr ""
msgid "Open drawer menu"
msgstr ""
#: src/screens/Messages/components/MessageComposer.tsx:143
#: src/screens/Messages/components/MessageInput.web.tsx:180
#: src/view/com/composer/Composer.tsx:1979
msgid "Open emoji picker"
@@ -7600,7 +7603,7 @@ msgctxt "action"
msgid "Post"
msgstr ""
#: src/screens/PostThread/index.tsx:552
#: src/screens/PostThread/index.tsx:553
msgctxt "description"
msgid "Post"
msgstr ""
@@ -8127,7 +8130,7 @@ msgstr ""
msgid "Remove from saved feeds"
msgstr ""
#: src/components/PostControls/BookmarkButton.tsx:141
#: src/components/PostControls/BookmarkButton.tsx:143
#: src/screens/Bookmarks/index.tsx:262
msgid "Remove from saved posts"
msgstr ""
@@ -8288,7 +8291,7 @@ msgstr ""
#. Accessibility label for the reply button, verb form followed by number of replies and noun form
#. placeholder {0}: post.replyCount || 0
#: src/components/PostControls/index.tsx:242
#: src/components/PostControls/index.tsx:243
msgid "Reply ({0, plural, one {# reply} other {# replies}})"
msgstr ""
@@ -8774,23 +8777,19 @@ msgstr ""
msgid "Search for \"{interestsDisplayName}\" (active)"
msgstr ""
#: src/screens/Search/components/AutocompleteResults.tsx:47
#: src/screens/Search/components/AutocompleteResults.tsx:54
msgid "Search for \"{searchText}\""
msgstr ""
#: src/view/shell/desktop/Search.tsx:129
msgid "Search for “{tQuery}”"
msgstr ""
#: src/screens/StarterPack/Wizard/index.tsx:552
msgid "Search for feeds that you want to suggest to others."
msgstr ""
#: src/screens/Search/Explore.tsx:377
#: src/screens/Search/Explore.tsx:378
msgid "Search for more accounts"
msgstr ""
#: src/screens/Search/Explore.tsx:454
#: src/screens/Search/Explore.tsx:455
msgid "Search for more feeds"
msgstr ""
@@ -8879,12 +8878,12 @@ msgstr ""
msgid "See jobs at Bluesky"
msgstr ""
#: src/components/FeedInterstitials.tsx:583
#: src/components/FeedInterstitials.tsx:641
#: src/components/FeedInterstitials.tsx:490
#: src/components/FeedInterstitials.tsx:548
msgid "See more"
msgstr ""
#: src/components/FeedInterstitials.tsx:564
#: src/components/FeedInterstitials.tsx:471
msgid "See more suggested profiles"
msgstr ""
@@ -9052,7 +9051,7 @@ msgid "Select your date of birth"
msgstr ""
#: src/screens/Onboarding/StepInterests/index.tsx:69
#: src/screens/Settings/InterestsSettings.tsx:168
#: src/screens/Settings/InterestsSettings.tsx:178
msgid "Select your interests from the options below"
msgstr ""
@@ -9092,6 +9091,7 @@ msgstr ""
msgid "Send feedback"
msgstr ""
#: src/screens/Messages/components/MessageComposer.tsx:216
#: src/screens/Messages/components/MessageInput.tsx:194
#: src/screens/Messages/components/MessageInput.web.tsx:234
msgid "Send message"
@@ -9580,7 +9580,7 @@ msgstr ""
msgid "Some of your verifications are invalid."
msgstr ""
#: src/components/FeedInterstitials.tsx:720
#: src/components/FeedInterstitials.tsx:627
msgid "Some other feeds you might like"
msgstr ""
@@ -9640,7 +9640,7 @@ msgstr ""
msgid "Sorry, we're unable to load account suggestions at this time."
msgstr ""
#: src/App.native.tsx:141
#: src/App.native.tsx:140
#: src/App.web.tsx:119
msgid "Sorry! Your session expired. Please sign in again."
msgstr ""
@@ -9726,7 +9726,7 @@ msgstr ""
msgid "Starter pack is invalid"
msgstr ""
#: src/screens/Search/Explore.tsx:663
#: src/screens/Search/Explore.tsx:664
#: src/view/screens/Profile.tsx:241
msgid "Starter Packs"
msgstr ""
@@ -9842,12 +9842,12 @@ msgstr ""
msgid "Suggested"
msgstr ""
#: src/screens/Search/Explore.tsx:374
#: src/screens/Search/Explore.tsx:375
msgid "Suggested accounts"
msgstr ""
#. Accounts suggested to the user for them to follow
#: src/components/FeedInterstitials.tsx:561
#: src/components/FeedInterstitials.tsx:468
#: src/screens/Onboarding/StepSuggestedAccounts/index.tsx:180
msgid "Suggested for you"
msgstr ""
@@ -10189,7 +10189,7 @@ msgstr ""
msgid "There was an issue fetching notifications. Tap here to try again."
msgstr ""
#: src/screens/Search/Explore.tsx:1025
#: src/screens/Search/Explore.tsx:1026
#: src/view/com/posts/PostFeed.tsx:773
msgid "There was an issue fetching posts. Tap here to try again."
msgstr ""
@@ -10892,7 +10892,7 @@ msgstr ""
#. Accessibility label for the like button when the post has been liked, verb followed by number of likes and noun
#. placeholder {0}: post.likeCount || 0
#: src/components/PostControls/index.tsx:276
#: src/components/PostControls/index.tsx:279
msgid "Unlike ({0, plural, one {# like} other {# likes}})"
msgstr ""
@@ -11491,7 +11491,7 @@ msgid "View your default post interaction settings"
msgstr ""
#: src/view/com/home/HomeHeaderLayout.web.tsx:57
#: src/view/com/home/HomeHeaderLayoutMobile.tsx:75
#: src/view/com/home/HomeHeaderLayoutMobile.tsx:82
msgid "View your feeds and explore more"
msgstr ""
@@ -11561,7 +11561,7 @@ msgid "We apply the highest privacy standards, and never share or sell your cont
msgstr ""
#: src/view/com/feeds/MissingFeed.tsx:141
msgid "We could not connect to the service that provides this custom feed. It may be temporarily unavailable and experiencing issues, or permanently unavailable."
msgid "We could not connect to the service that provides this custom feed. It may be temporarily experiencing issues, or permanently unavailable."
msgstr ""
#: src/view/com/feeds/MissingFeed.tsx:147
@@ -11617,7 +11617,7 @@ msgstr ""
msgid "We ran out of posts from your follows. Here's the latest from <0/>."
msgstr ""
#: src/screens/Settings/InterestsSettings.tsx:161
#: src/screens/Settings/InterestsSettings.tsx:171
msgid "We recommend selecting at least two interests."
msgstr ""
@@ -11852,6 +11852,7 @@ msgstr ""
msgid "Would you like to save this as a draft to edit later?"
msgstr ""
#: src/screens/Messages/components/MessageComposer.tsx:167
#: src/screens/Messages/components/MessageInput.tsx:156
#: src/screens/Messages/components/MessageInput.web.tsx:213
msgid "Write a message"
@@ -12482,11 +12483,11 @@ msgstr ""
#: src/screens/Search/modules/ExploreInterestsCard.tsx:68
#: src/screens/Settings/ContentAndMediaSettings.tsx:94
#: src/screens/Settings/ContentAndMediaSettings.tsx:97
#: src/screens/Settings/InterestsSettings.tsx:47
#: src/screens/Settings/InterestsSettings.tsx:49
msgid "Your interests"
msgstr ""
#: src/screens/Settings/InterestsSettings.tsx:128
#: src/screens/Settings/InterestsSettings.tsx:138
msgctxt "toast"
msgid "Your interests have been updated!"
msgstr ""
@@ -12549,7 +12550,7 @@ msgstr ""
msgid "Your report will be sent to <0>{0}</0>."
msgstr ""
#: src/screens/Settings/InterestsSettings.tsx:61
#: src/screens/Settings/InterestsSettings.tsx:63
msgid "Your selected interests help us serve you content you care about."
msgstr ""
+1 -1
View File
@@ -8,7 +8,7 @@ msgstr ""
"Language: ne\n"
"Project-Id-Version: 49a8cb746fbc2ae5707392ee41ddec4c\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2026-04-07 21:54\n"
"PO-Revision-Date: 2026-04-08 18:50\n"
"Last-Translator: \n"
"Language-Team: Nepali\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+1 -1
View File
@@ -8,7 +8,7 @@ msgstr ""
"Language: nl\n"
"Project-Id-Version: 49a8cb746fbc2ae5707392ee41ddec4c\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2026-04-07 21:54\n"
"PO-Revision-Date: 2026-04-08 18:50\n"
"Last-Translator: \n"
"Language-Team: Dutch\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+1 -1
View File
@@ -8,7 +8,7 @@ msgstr ""
"Language: pl\n"
"Project-Id-Version: 49a8cb746fbc2ae5707392ee41ddec4c\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2026-04-07 21:54\n"
"PO-Revision-Date: 2026-04-08 18:50\n"
"Last-Translator: \n"
"Language-Team: Polish\n"
"Plural-Forms: nplurals=4; plural=(n==1 ? 0 : (n%10>=2 && n%10<=4) && (n%100<12 || n%100>14) ? 1 : n!=1 && (n%10>=0 && n%10<=1) || (n%10>=5 && n%10<=9) || (n%100>=12 && n%100<=14) ? 2 : 3);\n"
+1 -1
View File
@@ -8,7 +8,7 @@ msgstr ""
"Language: pt\n"
"Project-Id-Version: 49a8cb746fbc2ae5707392ee41ddec4c\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2026-04-07 21:54\n"
"PO-Revision-Date: 2026-04-08 18:50\n"
"Last-Translator: \n"
"Language-Team: Portuguese, Brazilian\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+2 -2
View File
@@ -8,7 +8,7 @@ msgstr ""
"Language: pt\n"
"Project-Id-Version: 49a8cb746fbc2ae5707392ee41ddec4c\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2026-04-07 21:54\n"
"PO-Revision-Date: 2026-04-08 18:50\n"
"Last-Translator: \n"
"Language-Team: Portuguese\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
@@ -11562,7 +11562,7 @@ msgstr "Aplicamos os mais altos padrões de privacidade e nunca compartilhamos o
#: src/view/com/feeds/MissingFeed.tsx:141
msgid "We could not connect to the service that provides this custom feed. It may be temporarily experiencing issues, or permanently unavailable."
msgstr ""
msgstr "Não conseguimos conectar-nos ao serviço que fornece este feed personalizado. Pode estar temporariamente com problemas ou permanentemente indisponível."
#: src/view/com/feeds/MissingFeed.tsx:147
msgid "We could not find this list. It was probably deleted."
+2 -2
View File
@@ -8,7 +8,7 @@ msgstr ""
"Language: ro\n"
"Project-Id-Version: 49a8cb746fbc2ae5707392ee41ddec4c\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2026-04-07 21:54\n"
"PO-Revision-Date: 2026-04-08 18:50\n"
"Last-Translator: \n"
"Language-Team: Romanian\n"
"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : (n==0 || (n%100>0 && n%100<20)) ? 1 : 2);\n"
@@ -11562,7 +11562,7 @@ msgstr "Aplicăm cele mai înalte standarde de confidențialitate și nu distrib
#: src/view/com/feeds/MissingFeed.tsx:141
msgid "We could not connect to the service that provides this custom feed. It may be temporarily experiencing issues, or permanently unavailable."
msgstr ""
msgstr "Nu ne-am putut conecta la serviciul care oferă acest flux personalizat. Este posibil să întâmpine probleme temporar sau să fie permanent indisponibil."
#: src/view/com/feeds/MissingFeed.tsx:147
msgid "We could not find this list. It was probably deleted."
+1 -1
View File
@@ -8,7 +8,7 @@ msgstr ""
"Language: ru\n"
"Project-Id-Version: 49a8cb746fbc2ae5707392ee41ddec4c\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2026-04-07 21:54\n"
"PO-Revision-Date: 2026-04-08 18:50\n"
"Last-Translator: \n"
"Language-Team: Russian\n"
"Plural-Forms: nplurals=4; plural=((n%10==1 && n%100!=11) ? 0 : ((n%10 >= 2 && n%10 <=4 && (n%100 < 12 || n%100 > 14)) ? 1 : ((n%10 == 0 || (n%10 >= 5 && n%10 <=9)) || (n%100 >= 11 && n%100 <= 14)) ? 2 : 3));\n"
+2 -2
View File
@@ -8,7 +8,7 @@ msgstr ""
"Language: sv\n"
"Project-Id-Version: 49a8cb746fbc2ae5707392ee41ddec4c\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2026-04-08 01:38\n"
"PO-Revision-Date: 2026-04-08 18:50\n"
"Last-Translator: \n"
"Language-Team: Swedish\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
@@ -11562,7 +11562,7 @@ msgstr "Vi tillämpar de högsta standarderna för integritet och kommer aldrig
#: src/view/com/feeds/MissingFeed.tsx:141
msgid "We could not connect to the service that provides this custom feed. It may be temporarily experiencing issues, or permanently unavailable."
msgstr ""
msgstr "Vi kunde inte ansluta till tjänsten som tillhandahåller det här anpassade flödet. Det kan bero ett tillfälligt fel, eller så är den permanent otillgänglig."
#: src/view/com/feeds/MissingFeed.tsx:147
msgid "We could not find this list. It was probably deleted."
+1 -1
View File
@@ -8,7 +8,7 @@ msgstr ""
"Language: th\n"
"Project-Id-Version: 49a8cb746fbc2ae5707392ee41ddec4c\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2026-04-07 21:54\n"
"PO-Revision-Date: 2026-04-08 18:50\n"
"Last-Translator: \n"
"Language-Team: Thai\n"
"Plural-Forms: nplurals=1; plural=0;\n"
+1 -1
View File
@@ -8,7 +8,7 @@ msgstr ""
"Language: tr\n"
"Project-Id-Version: 49a8cb746fbc2ae5707392ee41ddec4c\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2026-04-07 21:54\n"
"PO-Revision-Date: 2026-04-08 18:50\n"
"Last-Translator: \n"
"Language-Team: Turkish\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+1 -1
View File
@@ -8,7 +8,7 @@ msgstr ""
"Language: uk\n"
"Project-Id-Version: 49a8cb746fbc2ae5707392ee41ddec4c\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2026-04-07 21:54\n"
"PO-Revision-Date: 2026-04-08 18:50\n"
"Last-Translator: \n"
"Language-Team: Ukrainian\n"
"Plural-Forms: nplurals=4; plural=((n%10==1 && n%100!=11) ? 0 : ((n%10 >= 2 && n%10 <=4 && (n%100 < 12 || n%100 > 14)) ? 1 : ((n%10 == 0 || (n%10 >= 5 && n%10 <=9)) || (n%100 >= 11 && n%100 <= 14)) ? 2 : 3));\n"
+1 -1
View File
@@ -8,7 +8,7 @@ msgstr ""
"Language: vi\n"
"Project-Id-Version: 49a8cb746fbc2ae5707392ee41ddec4c\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2026-04-07 21:54\n"
"PO-Revision-Date: 2026-04-08 18:50\n"
"Last-Translator: \n"
"Language-Team: Vietnamese\n"
"Plural-Forms: nplurals=1; plural=0;\n"
+71 -71
View File
@@ -8,7 +8,7 @@ msgstr ""
"Language: zh\n"
"Project-Id-Version: 49a8cb746fbc2ae5707392ee41ddec4c\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2026-04-07 21:54\n"
"PO-Revision-Date: 2026-04-08 18:50\n"
"Last-Translator: \n"
"Language-Team: Chinese Simplified\n"
"Plural-Forms: nplurals=1; plural=0;\n"
@@ -441,13 +441,13 @@ msgstr "{following} 位正在关注"
#: src/components/dialogs/SearchablePeopleList.tsx:414
msgid "{handle} can't be messaged"
msgstr "目前无法向 {handle} 发送私信"
msgstr "目前无法向 {handle} 发送信息"
#: src/components/dms/InitiateChatFlow.tsx:805
#: src/components/dms/InitiateChatFlow.tsx:858
#: src/components/dms/InitiateChatFlow.tsx:897
msgid "{handle} can’t be messaged"
msgstr "目前无法向 {handle} 发送私信"
msgstr "目前无法向 {handle} 发送信息"
#: src/features/liveNow/utils.ts:15
msgid "{hours, plural, one {# hour {minutesString}} other {# hours {minutesString}}}"
@@ -659,7 +659,7 @@ msgstr "你可以在 Bluesky 上找到一系列热门动态源,包括 News、B
#. If last message does not contain text, fall back to "{user} reacted to {a message}"
#: src/screens/Messages/components/ChatListItem.tsx:209
msgid "a message"
msgstr "一条私信"
msgstr "一条信息"
#: src/components/contacts/screens/PhoneInput.tsx:102
msgid "A network error occurred. Please check your internet connection"
@@ -715,7 +715,7 @@ msgstr "接受"
#: src/screens/Messages/components/RequestButtons.tsx:280
msgid "Accept chat request"
msgstr "接受私信请求"
msgstr "接受对话请求"
#. Accept a chat request
#: src/screens/Messages/components/RequestListItem.tsx:42
@@ -1102,7 +1102,7 @@ msgstr "以下是你已保存的动态源。"
#: src/screens/Settings/components/AddAppPasswordDialog.tsx:146
#: src/screens/Settings/components/AddAppPasswordDialog.tsx:153
msgid "Allow access to your direct messages"
msgstr "允许读取你的私信"
msgstr "允许读取你的私人信息"
#: src/components/dialogs/PostInteractionSettingsDialog.tsx:451
msgid "Allow anyone to reply"
@@ -1116,7 +1116,7 @@ msgstr "允许读取位置信息"
#: src/screens/Messages/Settings.tsx:78
#: src/screens/Messages/Settings.tsx:81
msgid "Allow new messages from"
msgstr "允许以下来源向你发送私信"
msgstr "允许以下来源向你发起对话"
#: src/screens/Settings/ActivityPrivacySettings.tsx:53
#: src/screens/Settings/PrivacyAndSecuritySettings.tsx:92
@@ -1146,7 +1146,7 @@ msgstr "允许你的关注者回复"
#: src/screens/Settings/AppPasswords.tsx:201
msgid "Allows access to direct messages"
msgstr "允许读取私信"
msgstr "允许读取私人信息"
#: src/screens/Login/ForgotPasswordForm.tsx:180
#: src/screens/Settings/components/ChangePasswordDialog.tsx:237
@@ -1290,11 +1290,11 @@ msgstr "不在这些选项中的问题"
#: src/components/dms/dialogs/NewChatDialog.tsx:41
msgid "An issue occurred starting the chat"
msgstr "发起私信时出现问题"
msgstr "发起对话时出现问题"
#: src/components/dms/dialogs/ShareViaChatDialog.tsx:50
msgid "An issue occurred while trying to open the chat"
msgstr "开启私信时出现问题"
msgstr "开启对话时出现问题"
#: src/components/hooks/useFollowMethods.ts:35
#: src/components/hooks/useFollowMethods.ts:52
@@ -1478,7 +1478,7 @@ msgstr "你确定要删除此应用密码“{0}”吗?"
#: src/components/dms/MessageContextMenu.tsx:206
msgid "Are you sure you want to delete this message? The message will be deleted for you, but not for the other participant."
msgstr "你确定要删除这条私信吗?此操作将仅为你删除私信,而不会为其他参与者删除。"
msgstr "你确定要删除这条信息吗?此操作将仅为你删除信息,而不会为其他参与者删除。"
#: src/screens/StarterPack/StarterPackScreen.tsx:686
msgid "Are you sure you want to delete this starter pack?"
@@ -1587,7 +1587,7 @@ msgstr "返回"
#: src/screens/Messages/Inbox.tsx:262
msgid "Back to Chats"
msgstr "返回私信"
msgstr "返回对话"
#: src/components/moderation/ReportDialog/utils/useReportOptions.ts:216
msgid "Banned activities or security violations"
@@ -1615,7 +1615,7 @@ msgstr "在创建新手包之前,你必须首先验证你的电子邮箱。"
#: src/screens/Messages/components/RequestButtons.tsx:266
msgid "Before you can accept this chat request, you must first verify your email."
msgstr "在你接受此私信请求之前,你必须首先验证你的电子邮箱。"
msgstr "在你接受此对话邀请之前,你必须首先验证你的电子邮箱。"
#: src/components/activity-notifications/SubscribeProfileButton.tsx:59
msgid "Before you can get notifications for {name}'s posts, you must first verify your email."
@@ -2085,37 +2085,37 @@ msgstr "列表在创建后将是一个独立副本,任何对新手包的更改
#: src/view/shell/desktop/LeftNav.tsx:611
#: src/view/shell/Drawer.tsx:454
msgid "Chat"
msgstr "私信"
msgstr "对话"
#: src/screens/Messages/components/RequestButtons.tsx:86
#: src/screens/Messages/components/RequestButtons.tsx:335
msgctxt "toast"
msgid "Chat deleted"
msgstr "已删除私信"
msgstr "已删除对话"
#: src/lib/hooks/useNotificationHandler.ts:117
msgid "Chat messages - silent"
msgstr "私信 - 静音"
msgstr "对话信息 - 静音"
#: src/lib/hooks/useNotificationHandler.ts:108
msgid "Chat messages - sound"
msgstr "私信 - 提示音"
msgstr "对话信息 - 提示音"
#: src/components/dms/ConvoMenu.tsx:202
msgctxt "toast"
msgid "Chat muted"
msgstr "已隐藏私信"
msgstr "已隐藏对话"
#: src/Navigation.tsx:579
#: src/screens/Messages/components/InboxPreview.tsx:23
msgid "Chat request inbox"
msgstr "私信请求收件箱"
msgstr "对话邀请收件箱"
#: src/screens/Messages/components/InboxPreview.tsx:63
#: src/screens/Messages/Inbox.tsx:57
#: src/screens/Messages/Inbox.tsx:99
msgid "Chat requests"
msgstr "私信请求"
msgstr "对话邀请"
#: src/components/dms/ConvoMenu.tsx:84
#: src/Navigation.tsx:574
@@ -2123,22 +2123,22 @@ msgstr "私信请求"
#: src/screens/Messages/ChatList.tsx:86
#: src/screens/Messages/ChatList.tsx:385
msgid "Chat settings"
msgstr "私信设置"
msgstr "对话设置"
#: src/screens/Messages/Settings.tsx:70
msgid "Chat Settings"
msgstr "私信设置"
msgstr "对话设置"
#: src/components/dms/ConvoMenu.tsx:204
msgctxt "toast"
msgid "Chat unmuted"
msgstr "已取消隐藏私信"
msgstr "已取消隐藏对话"
#: src/screens/Messages/ChatList.tsx:77
#: src/screens/Messages/ChatList.tsx:401
#: src/screens/Messages/ChatList.tsx:425
msgid "Chats"
msgstr "私信"
msgstr "对话"
#: src/screens/Settings/components/DeleteAccountDialog.tsx:236
msgid "Check <0>{currentEmail}</0> for an email with the confirmation code to enter below:"
@@ -2267,7 +2267,7 @@ msgstr "点按以开启 {0} 的标签菜单"
#: src/components/dms/MessageItem.tsx:318
msgid "Click to retry failed message"
msgstr "点击以重试发送私信"
msgstr "点击以重试发送信息"
#: src/components/dms/ChatEmptyPill.tsx:39
msgid "Clip 🐴 clop 🐴"
@@ -2726,7 +2726,7 @@ msgstr "复制新手包链接"
#: src/components/dms/MessageContextMenu.tsx:150
#: src/components/dms/MessageContextMenu.tsx:153
msgid "Copy message text"
msgstr "复制私信文字"
msgstr "复制信息文本"
#: src/components/PostControls/ShareMenu/ShareMenuItems.web.tsx:145
#: src/components/PostControls/ShareMenu/ShareMenuItems.web.tsx:148
@@ -2897,7 +2897,7 @@ msgstr "再创建一个"
#: src/components/dms/InitiateChatFlow.tsx:445
msgid "Create group chat"
msgstr "创建群组私信"
msgstr "创建群组对话"
#: src/components/dialogs/lists/CreateListFromStarterPackDialog.tsx:180
#: src/components/dialogs/lists/CreateListFromStarterPackDialog.tsx:188
@@ -3050,7 +3050,7 @@ msgstr "要删除应用密码吗?"
#: src/screens/Messages/components/RequestButtons.tsx:349
#: src/screens/Messages/components/RequestButtons.tsx:355
msgid "Delete chat"
msgstr "删除私信"
msgstr "删除对话"
#: src/screens/Settings/Settings.tsx:472
msgid "Delete chat declaration record"
@@ -3082,11 +3082,11 @@ msgstr "删除列表"
#: src/components/dms/MessageContextMenu.tsx:204
msgid "Delete message"
msgstr "删除私信"
msgstr "删除信息"
#: src/components/dms/MessageContextMenu.tsx:162
msgid "Delete message for me"
msgstr "仅为我删除私信"
msgstr "仅为我删除信息"
#: src/screens/Settings/components/DeleteAccountDialog.tsx:316
msgid "Delete my account"
@@ -3387,7 +3387,7 @@ msgstr "完成"
#: src/components/dms/MessageItem.tsx:161
msgid "Double tap or long press the message to add a reaction"
msgstr "双击或长按私信以添加反应"
msgstr "双击或长按信息以添加反应"
#: src/components/Dialog/index.tsx:402
msgid "Double tap to close the dialog"
@@ -3409,7 +3409,7 @@ msgstr "下载 CAR 文件"
#: src/screens/Settings/components/ExportCarDialog.tsx:136
#: src/screens/Settings/components/ExportCarDialog.tsx:141
msgid "Download chat data"
msgstr "下载私信数据"
msgstr "下载对话数据"
#: src/view/com/lightbox/Lightbox.web.tsx:278
#: src/view/com/lightbox/Lightbox.web.tsx:290
@@ -3952,7 +3952,7 @@ msgstr "极端主义内容"
#: src/screens/Messages/components/RequestButtons.tsx:249
msgctxt "toast"
msgid "Failed to accept chat"
msgstr "无法接受私信请求"
msgstr "无法接受对话邀请"
#: src/components/dms/ActionsWrapper.web.tsx:64
#: src/components/dms/MessageContextMenu.tsx:103
@@ -3984,11 +3984,11 @@ msgstr "无法创建新手包"
#: src/screens/Messages/components/RequestButtons.tsx:320
msgctxt "toast"
msgid "Failed to delete chat"
msgstr "无法删除私信"
msgstr "无法删除对话"
#: src/components/dms/MessageContextMenu.tsx:85
msgid "Failed to delete message"
msgstr "无法删除私信"
msgstr "无法删除信息"
#: src/components/PostControls/PostMenu/PostMenuItems.tsx:219
msgid "Failed to delete post, please try again"
@@ -4057,7 +4057,7 @@ msgstr "无法加载通知设置。"
#: src/screens/Messages/components/MessageListError.tsx:23
msgid "Failed to load past messages"
msgstr "无法加载过去的私信"
msgstr "无法加载之前的信息"
#: src/screens/Settings/ActivityPrivacySettings.tsx:66
msgid "Failed to load preference."
@@ -4139,7 +4139,7 @@ msgstr "无法保存你的兴趣。"
#: src/components/dms/MessageItem.tsx:311
msgid "Failed to send"
msgstr "无法发送私信"
msgstr "无法发送"
#: src/components/dialogs/EmailDialog/screens/Manage2FA/Disable.tsx:123
#: src/components/dialogs/EmailDialog/screens/Verify.tsx:137
@@ -6095,22 +6095,22 @@ msgstr "菜单"
#. placeholder {0}: profile.handle
#: src/components/dms/MessageProfileButton.tsx:99
msgid "Message {0}"
msgstr "传送私信给 {0}"
msgstr "发送信息给 {0}"
#: src/screens/Messages/components/ChatListItem.tsx:196
msgid "Message deleted"
msgstr "已删除私信"
msgstr "已删除信息"
#: src/components/dms/MessageContextMenu.tsx:83
msgctxt "toast"
msgid "Message deleted"
msgstr "已删除私信"
msgstr "已删除信息"
#. placeholder {0}: sender?.handle ?? 'unknown'
#. placeholder {1}: message.text
#: src/components/dms/MessageContextMenu.tsx:131
msgid "Message from @{0}: {1}"
msgstr "来自 @{0} 的私信:{1}"
msgstr "来自 @{0} 的信息:{1}"
#. placeholder {0}: rawError.message
#: src/view/com/posts/PostFeedErrorMessage.tsx:209
@@ -6120,21 +6120,21 @@ msgstr "来自服务器的信息:{0}"
#: src/screens/Messages/components/MessageComposer.tsx:166
#: src/screens/Messages/components/MessageInput.tsx:154
msgid "Message input field"
msgstr "私信输入框"
msgstr "信息输入框"
#: src/screens/Messages/components/MessageComposer.tsx:69
#: src/screens/Messages/components/MessageInput.tsx:79
#: src/screens/Messages/components/MessageInput.web.tsx:60
msgid "Message is too long"
msgstr "私信过长"
msgstr "信息过长"
#: src/components/dms/MessageContextMenu.tsx:129
msgid "Message options"
msgstr "私信选项"
msgstr "信息选项"
#: src/Navigation.tsx:828
msgid "Messages"
msgstr "私信"
msgstr "信息"
#: src/screens/Settings/AppIconSettings/useAppIconSets.ts:101
msgctxt "Name of app icon variant"
@@ -6433,7 +6433,7 @@ msgstr "来自 {firstAuthorName} 的新{postsCount, plural, one {帖文} other {
#: src/screens/Messages/ChatList.tsx:408
#: src/screens/Messages/ChatList.tsx:415
msgid "New chat"
msgstr "新私信"
msgstr "新对话"
#: src/components/dialogs/EmailDialog/screens/Update.tsx:223
msgid "New email address"
@@ -6460,11 +6460,11 @@ msgstr "新关注者"
#: src/components/dms/InitiateChatFlow.tsx:709
#: src/components/dms/InitiateChatFlow.tsx:737
msgid "New group chat"
msgstr "创建群组私信"
msgstr "创建群组对话"
#: src/components/dms/InitiateChatFlow.tsx:261
msgid "New group chat with:"
msgstr "与这些人开始群组私信:"
msgstr "与这些人开始群组对话:"
#: src/screens/Settings/components/ChangeHandleDialog.tsx:228
#: src/screens/Settings/components/ChangeHandleDialog.tsx:236
@@ -6479,7 +6479,7 @@ msgstr "新列表"
#: src/components/dms/NewMessagesPill.tsx:92
msgid "New messages"
msgstr "新私信"
msgstr "新信息"
#: src/screens/Login/SetNewPasswordForm.tsx:143
#: src/screens/Settings/components/ChangePasswordDialog.tsx:204
@@ -6627,7 +6627,7 @@ msgstr "目前还没有媒体内容"
#: src/screens/Messages/components/ChatListItem.tsx:138
msgid "No messages yet"
msgstr "目前还没有私信"
msgstr "目前还没有信息"
#: src/screens/Onboarding/StepFinished/ValuePropositionPager.shared.tsx:14
msgid "No more doomscrolling junk-filled algorithms. Find feeds that work for you, not against you."
@@ -7016,7 +7016,7 @@ msgstr "开启指向 {niceUrl} 的链接"
#: src/components/dms/ActionsWrapper.tsx:35
msgid "Open message options"
msgstr "开启私信选项"
msgstr "开启信息选项"
#: src/screens/Settings/Settings.tsx:463
msgid "Open moderation debug page"
@@ -7240,7 +7240,7 @@ msgstr "我们的审核团队已收到你的举报。"
#: src/screens/Messages/components/ChatDisabled.tsx:35
msgid "Our moderators have reviewed reports and decided to disable your access to chats on Bluesky."
msgstr "内容审核服务提供方已收到相关举报,并决定停用你的 Bluesky 私信功能。"
msgstr "内容审核服务提供方已收到相关举报,并决定停用你的 Bluesky 对话功能。"
#: src/components/dialogs/StarterPackDialog.tsx:384
msgid "Owner"
@@ -7540,7 +7540,7 @@ msgstr "请说明你为什么认为 {0} 错误应用了此标记"
#: src/screens/Messages/components/ChatDisabled.tsx:125
msgid "Please explain why you think your chats were incorrectly disabled"
msgstr "请说明你为什么认为你的私信被错误停用"
msgstr "请说明你为什么认为你的对话被错误停用"
#: src/features/liveNow/components/GoLiveDisabledDialog.tsx:124
msgid "Please explain why you think your Go Live access was incorrectly disabled."
@@ -8037,7 +8037,7 @@ msgstr "拒绝"
#: src/screens/Messages/components/RequestButtons.tsx:125
msgid "Reject chat request"
msgstr "拒绝私信请求"
msgstr "拒绝对话邀请"
#: src/screens/Messages/ChatList.tsx:291
#: src/screens/Messages/Inbox.tsx:214
@@ -8061,7 +8061,7 @@ msgstr "移除"
#: src/components/dms/ChatProfileTabs.tsx:153
msgid "Remove {displayName} from group chat"
msgstr "从群组私信中移除 {displayName}"
msgstr "从群组对话中移除 {displayName}"
#: src/components/StarterPack/Wizard/WizardListCard.tsx:62
msgid "Remove {displayName} from starter pack"
@@ -8363,7 +8363,7 @@ msgstr "举报列表"
#: src/components/dms/MessageContextMenu.tsx:170
msgid "Report message"
msgstr "举报私信"
msgstr "举报信息"
#: src/components/PostControls/PostMenu/PostMenuItems.tsx:756
#: src/components/PostControls/PostMenu/PostMenuItems.tsx:758
@@ -8399,7 +8399,7 @@ msgstr "举报这场直播"
#: src/components/moderation/ReportDialog/copy.ts:57
msgid "Report this message"
msgstr "举报这条私信"
msgstr "举报这条信息"
#: src/components/moderation/ReportDialog/copy.ts:25
msgid "Report this post"
@@ -8985,7 +8985,7 @@ msgstr "选择 GIF “{0}”"
#: src/components/dms/InitiateChatFlow.tsx:645
msgid "Select group chat members"
msgstr "选择群组私信成员"
msgstr "选择群组对话成员"
#: src/components/dialogs/MutedWords.tsx:151
msgid "Select how long to mute this word for."
@@ -9095,7 +9095,7 @@ msgstr "提交反馈"
#: src/screens/Messages/components/MessageInput.tsx:194
#: src/screens/Messages/components/MessageInput.web.tsx:234
msgid "Send message"
msgstr "发送私信"
msgstr "发送信息"
#: src/components/PostControls/ShareMenu/RecentChats.tsx:121
msgid "Send post to {name}"
@@ -9103,7 +9103,7 @@ msgstr "发送帖文给 {name}"
#: src/components/dms/dialogs/ShareViaChatDialog.tsx:65
msgid "Send post to..."
msgstr "发送私信给……"
msgstr "发送帖文给……"
#: src/components/moderation/ReportDialog/index.tsx:824
msgid "Send report to {title}"
@@ -9124,7 +9124,7 @@ msgstr "发送验证电子邮件"
#: src/components/PostControls/ShareMenu/ShareMenuItems.web.tsx:105
#: src/components/PostControls/ShareMenu/ShareMenuItems.web.tsx:111
msgid "Send via direct message"
msgstr "通过私信发送"
msgstr "通过私人信息发送"
#: src/components/contacts/screens/PhoneInput.tsx:283
msgid "Sent to our phone number verification provider Plivo"
@@ -9681,7 +9681,7 @@ msgstr "当你和其他人开始聊天后,对话就会出现在这里。"
#: src/components/dms/dialogs/NewChatDialog.tsx:95
msgid "Start a new chat"
msgstr "开始一个新私信"
msgstr "开始一个新对话"
#: src/screens/ProfileList/AboutSection.tsx:104
#: src/screens/ProfileList/FeedSection.tsx:82
@@ -9695,12 +9695,12 @@ msgstr "开始添加用户!"
#: src/components/dms/InitiateChatFlow.tsx:646
msgid "Start chat"
msgstr "开始私信"
msgstr "开始对话"
#: src/components/dialogs/SearchablePeopleList.tsx:383
#: src/components/dms/InitiateChatFlow.tsx:773
msgid "Start chat with {displayName}"
msgstr "与 {displayName} 开始私信"
msgstr "与 {displayName} 开始对话"
#: src/Navigation.tsx:599
#: src/Navigation.tsx:604
@@ -10614,7 +10614,7 @@ msgstr "需要登出请<0>点击此处</0>。或者如果需要的话,你也
#: src/components/dms/ReportConversationPrompt.tsx:19
msgid "To report a conversation, please report one of its messages via the conversation screen. This lets our moderators understand the context of your issue."
msgstr "要举报对话,请在会话界面里选择一条私信并提交举报。这将有助于使内容审核服务提供方了解相关问题的前因后果。"
msgstr "要举报对话,请在会话界面里选择一条信息并提交举报。这将有助于使内容审核服务提供方了解相关问题的前因后果。"
#: src/components/dms/DateDivider.tsx:45
msgid "Today"
@@ -11826,7 +11826,7 @@ msgstr "此直播为何需要受到审核?"
#: src/components/moderation/ReportDialog/copy.ts:58
msgid "Why should this message be reviewed?"
msgstr "此私信为何需要受到审核?"
msgstr "此信息为何需要受到审核?"
#: src/components/moderation/ReportDialog/copy.ts:26
msgid "Why should this post be reviewed?"
@@ -11856,7 +11856,7 @@ msgstr "需要把现有内容保存为草稿,以便日后编辑吗?"
#: src/screens/Messages/components/MessageInput.tsx:156
#: src/screens/Messages/components/MessageInput.web.tsx:213
msgid "Write a message"
msgstr "撰写私信"
msgstr "撰写信息"
#: src/view/screens/Profile.tsx:436
#: src/view/screens/Profile.tsx:437
@@ -11992,7 +11992,7 @@ msgstr "你也可以探索并关注新的自定义动态源。"
#: src/screens/Settings/components/ExportCarDialog.tsx:126
msgid "You can also download your chat data as a \"JSONL\" file. This file only includes chat messages that you have sent and does not include chat messages that you have received."
msgstr "你可以将你的私信数据导出为一个“JSONL”文件。此文件仅包含你发送的私信,不包含接收到的私信数据。"
msgstr "你可以将你的对话数据导出为一个“JSONL”文件。此文件仅包含你发送的对话信息,不包含接收到的对话信息数据。"
#: src/components/contacts/screens/GetContacts.tsx:250
msgid "You can always opt out and delete your data"
@@ -12000,7 +12000,7 @@ msgstr "你可以随时选择退出并删除数据"
#: src/lib/hooks/useNotificationHandler.ts:104
msgid "You can choose whether chat notifications have sound in the chat settings within the app"
msgstr "你可以自定义应用内的私信通知是否播放提示音"
msgstr "你可以自定义应用内的对话通知是否播放提示音"
#: src/screens/Messages/Settings.tsx:121
msgid "You can continue ongoing conversations regardless of which setting you choose."
@@ -12057,7 +12057,7 @@ msgstr "你没有关注任何同样关注 @{name} 的用户。"
#: src/screens/Messages/Inbox.tsx:244
msgid "You don't have any chat requests at the moment."
msgstr "你目前没有任何私信请求。"
msgstr "你目前没有收到对话邀请。"
#: src/components/dialogs/PostInteractionSettingsDialog.tsx:589
msgid "You don't have any lists yet."
@@ -12418,7 +12418,7 @@ msgstr "你的浏览器不支持此视频格式,请试试其他浏览器。"
#: src/screens/Messages/components/ChatDisabled.tsx:32
msgid "Your chats have been disabled"
msgstr "你的私信功能已被停用"
msgstr "你的对话功能已被停用"
#: src/components/dialogs/InAppBrowserConsent.tsx:70
msgid "Your choice will be remembered for future links. You can change it at any time in settings."
+1 -1
View File
@@ -8,7 +8,7 @@ msgstr ""
"Language: zh\n"
"Project-Id-Version: 49a8cb746fbc2ae5707392ee41ddec4c\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2026-04-07 21:54\n"
"PO-Revision-Date: 2026-04-08 18:50\n"
"Last-Translator: \n"
"Language-Team: Chinese Traditional, Hong Kong\n"
"Plural-Forms: nplurals=1; plural=0;\n"
+1 -1
View File
@@ -8,7 +8,7 @@ msgstr ""
"Language: zh\n"
"Project-Id-Version: 49a8cb746fbc2ae5707392ee41ddec4c\n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2026-04-07 21:54\n"
"PO-Revision-Date: 2026-04-08 18:50\n"
"Last-Translator: \n"
"Language-Team: Chinese Traditional\n"
"Plural-Forms: nplurals=1; plural=0;\n"
+18 -14
View File
@@ -5,16 +5,19 @@ import {
moderateProfile,
type ModerationDecision,
} from '@atproto/api'
import {ScrollEdgeEffectProvider} from '@bsky.app/expo-scroll-edge-effect'
import {msg} from '@lingui/core/macro'
import {useLingui} from '@lingui/react'
import {Trans} from '@lingui/react/macro'
import {
type RouteProp,
useFocusEffect,
useIsFocused,
useNavigation,
useRoute,
} from '@react-navigation/native'
import {type NativeStackScreenProps} from '@react-navigation/native-stack'
import {RemoveScrollBar} from 'react-remove-scroll-bar'
import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback'
import {
@@ -30,7 +33,7 @@ import {useModerationOpts} from '#/state/preferences/moderation-opts'
import {useProfileQuery} from '#/state/queries/profile'
import {useSetMinimalShellMode} from '#/state/shell'
import {MessagesList} from '#/screens/Messages/components/MessagesList'
import {atoms as a, useBreakpoints, useTheme, web} from '#/alf'
import {atoms as a, useTheme, web} from '#/alf'
import {AgeRestrictedScreen} from '#/components/ageAssurance/AgeRestrictedScreen'
import {useAgeAssuranceCopy} from '#/components/ageAssurance/useAgeAssuranceCopy'
import {
@@ -62,7 +65,6 @@ export function MessagesConversationScreen(props: Props) {
}
export function MessagesConversationScreenInner({route}: Props) {
const {gtMobile} = useBreakpoints()
const setMinimalShellMode = useSetMinimalShellMode()
const convoId = route.params.conversation
@@ -71,25 +73,22 @@ export function MessagesConversationScreenInner({route}: Props) {
useFocusEffect(
useCallback(() => {
setCurrentConvoId(convoId)
if (IS_WEB && !gtMobile) {
setMinimalShellMode(true)
} else {
setMinimalShellMode(false)
}
setMinimalShellMode(true)
return () => {
setCurrentConvoId(undefined)
setMinimalShellMode(false)
}
}, [gtMobile, convoId, setCurrentConvoId, setMinimalShellMode]),
}, [convoId, setCurrentConvoId, setMinimalShellMode]),
)
return (
<Layout.Screen testID="convoScreen" style={web([{minHeight: 0}, a.flex_1])}>
<ConvoProvider key={convoId} convoId={convoId}>
<Inner />
</ConvoProvider>
<ScrollEdgeEffectProvider>
<ConvoProvider key={convoId} convoId={convoId}>
<Inner />
</ConvoProvider>
</ScrollEdgeEffectProvider>
</Layout.Screen>
)
}
@@ -98,6 +97,7 @@ function Inner() {
const t = useTheme()
const convoState = useConvo()
const {_} = useLingui()
const isFocused = useIsFocused()
const moderationOpts = useModerationOpts()
const {data: recipientUnshadowed} = useProfileQuery({
@@ -122,11 +122,13 @@ function Inner() {
// Any time that we re-render the `Initializing` state, we have to reset `hasScrolled` to false. After entering this
// state, we know that we're resetting the list of messages and need to re-scroll to the bottom when they get added.
useEffect(() => {
const [prevState, setPrevState] = useState(convoState.status)
if (prevState !== convoState.status) {
setPrevState(convoState.status)
if (convoState.status === ConvoStatus.Initializing) {
setHasScrolled(false)
}
}, [convoState.status])
}
if (convoState.status === ConvoStatus.Error) {
return (
@@ -150,6 +152,8 @@ function Inner() {
return (
<Layout.Center style={[a.flex_1]}>
{/* MessagesList does not use the body scroll */}
{isFocused && IS_WEB && <RemoveScrollBar />}
{!readyToShow &&
(moderation ? (
<MessagesListHeader moderation={moderation} profile={recipient} />
@@ -1,5 +1,19 @@
import {useEffect, useState} from 'react'
import {Pressable, View} from 'react-native'
import {
useKeyboardHandler,
useReanimatedKeyboardAnimation,
} from 'react-native-keyboard-controller'
import Animated, {
Extrapolation,
interpolate,
runOnJS,
useAnimatedStyle,
} from 'react-native-reanimated'
import {useSafeAreaInsets} from 'react-native-safe-area-context'
import {GlassContainer} from 'expo-glass-effect'
import {LinearGradient} from 'expo-linear-gradient'
import {ScrollEdgeEffect} from '@bsky.app/expo-scroll-edge-effect'
import {useLingui} from '@lingui/react/macro'
import {countGraphemes} from 'unicode-segmenter/grapheme'
@@ -17,20 +31,24 @@ import {
EmojiPicker,
type EmojiPickerState,
} from '#/view/com/composer/text-input/web/EmojiPicker'
import {atoms as a, useTheme} from '#/alf'
import {atoms as a, native, platform, tokens, useTheme, utils} from '#/alf'
import {Composer, useComposerInternalApiRef} from '#/components/Composer'
import {useInteractionState} from '#/components/hooks/useInteractionState'
import {EmojiArc_Stroke2_Corner0_Rounded as EmojiSmile} from '#/components/icons/Emoji'
import {PaperPlane_Stroke2_Corner0_Rounded as PaperPlane} from '#/components/icons/PaperPlane'
import {GlassView} from '#/components/GlassView'
import {EmojiArc_Stroke2_Corner0_Rounded as EmojiSmileIcon} from '#/components/icons/Emoji'
import {PaperPlaneVertical_Filled_Stroke2_Corner1_Rounded as PaperPlaneIcon} from '#/components/icons/PaperPlane'
import * as Toast from '#/components/Toast'
import {IS_WEB} from '#/env'
import {IS_ANDROID, IS_LIQUID_GLASS, IS_NATIVE, IS_WEB} from '#/env'
const MIN_HEIGHT = 40
export function MessageComposer({
textInputId,
onSendMessage,
hasEmbed,
setEmbed,
children,
}: {
textInputId?: string
onSendMessage: (message: string) => void
hasEmbed: boolean
setEmbed: (embedUrl: string | undefined) => void
@@ -48,16 +66,25 @@ export function MessageComposer({
})
const composerInternalApiRef = useComposerInternalApiRef()
const {state: focused, onIn: onFocus, onOut: onBlur} = useInteractionState()
const {
state: hovered,
onIn: onHoverIn,
onOut: onHoverOut,
} = useInteractionState()
const [text, setText] = useState(getDraft)
useSaveMessageDraft(text)
// Android interactive dismiss sometimes doesn't blur the input
const blur = () => {
composerInternalApiRef.current?.input?.blur()
}
useKeyboardHandler({
onEnd: evt => {
'worklet'
if (IS_ANDROID && evt.progress === 0) {
runOnJS(blur)()
}
},
})
const submitDisabled = !editable || (!hasEmbed && text.trim().length === 0)
const openEmojiPicker = (pos: any) => {
setEmojiPickerState({isOpen: true, pos})
}
@@ -65,10 +92,12 @@ export function MessageComposer({
const onSubmit = () => {
if (!editable) return
if (!hasEmbed && text.trim() === '') return
if (countGraphemes(text) > MAX_DM_GRAPHEME_LENGTH) {
Toast.show(l`Message is too long`, {
type: 'error',
})
const graphemeCount = countGraphemes(text)
if (graphemeCount > MAX_DM_GRAPHEME_LENGTH) {
Toast.show(
l`Message is too long (${graphemeCount}/${MAX_DM_GRAPHEME_LENGTH})`,
{type: 'error'},
)
return
}
@@ -91,152 +120,111 @@ export function MessageComposer({
return () => {
textInputWebEmitter.removeListener('emoji-inserted', onEmojiInserted)
}
}, [])
}, [composerInternalApiRef])
return (
<>
<View style={[a.px_md, a.pb_sm, a.pt_xs]}>
{children}
<ComposerContainer>
{children}
<View
collapsable={false}
ref={
IS_WEB
? undefined
: node => {
composerInternalApiRef.current?.setAutocompleteAnchor(node)
}
}
// @ts-expect-error web only
onMouseEnter={onHoverIn}
onMouseLeave={onHoverOut}
style={[a.w_full, a.flex_row, a.gap_sm]}>
{IS_WEB && (
<Pressable
onPress={e => {
e.currentTarget.measure((_fx, _fy, _width, _height, px, py) => {
openEmojiPicker?.({
top: py,
left: px,
right: px,
bottom: py,
nextFocusRef: {
current: composerInternalApiRef.current?.input?.element,
<View
collapsable={false}
ref={native(
(node: View) =>
void composerInternalApiRef.current?.setAutocompleteAnchor(node),
)}>
<GlassContainer
style={[a.w_full, a.flex_row, a.gap_sm, a.align_end]}
spacing={tokens.space.sm}>
<GlassView
isInteractive
glassEffectStyle="regular"
style={[a.flex_1, a.rounded_xl, {minHeight: MIN_HEIGHT}]}
tintColor={t.palette.contrast_50}
fallbackStyle={[t.atoms.bg_contrast_50]}>
{IS_WEB && (
<Pressable
onPress={e => {
e.currentTarget.measure(
(_fx, _fy, _width, _height, px, py) => {
// TODO: rip this horrible system out
openEmojiPicker?.({
top: py,
left: px - 400,
right: px - 400,
bottom: py,
nextFocusRef: {
current:
composerInternalApiRef.current?.input?.element,
},
})
},
})
})
)
}}
style={[
a.overflow_hidden,
a.absolute,
a.rounded_full,
a.align_center,
a.justify_center,
a.z_30,
{
height: 20,
width: 20,
top: 10,
right: 10,
},
]}
accessibilityLabel={l`Open emoji picker`}
accessibilityHint="">
{state => (
<EmojiSmileIcon
size="md"
style={
state.hovered ||
state.focused ||
state.pressed ||
emojiPickerState.isOpen
? {color: t.palette.primary_500}
: t.atoms.text_contrast_high
}
/>
)}
</Pressable>
)}
<Composer
nativeID={textInputId}
label={l`Message input field`}
placeholder={l`Message`}
autocompletePlacement="top-start"
internalApiRef={composerInternalApiRef}
defaultValue={text}
editable={editable}
autoFocus={IS_WEB}
maxRows={12}
outerStyle={[a.flex_1]}
contentTextStyle={[a.text_md, a.leading_snug]}
contentPaddingStyle={{
paddingLeft: 16,
paddingTop: 10,
paddingBottom: 10,
paddingRight: 16 + platform({web: 20, default: 0}),
}}
style={[
a.overflow_hidden,
a.absolute,
a.rounded_full,
a.align_center,
a.justify_center,
a.z_30,
{
height: 30,
width: 30,
top: 8,
left: 8,
},
]}
accessibilityLabel={l`Open emoji picker`}
accessibilityHint="">
{state => (
<View
style={[
a.absolute,
a.inset_0,
a.align_center,
a.justify_center,
{
backgroundColor:
state.hovered || state.focused || state.pressed
? t.atoms.bg.backgroundColor
: undefined,
},
]}>
<EmojiSmile size="lg" />
</View>
)}
</Pressable>
)}
<Composer
label={l`Message input field`}
placeholder={l`Write a message`}
autocompletePlacement="top-start"
internalApiRef={composerInternalApiRef}
defaultValue={text}
editable={editable}
autoFocus={IS_WEB}
maxRows={12}
outerStyle={[
a.flex_1,
t.atoms.bg_contrast_25,
{
borderWidth: 1,
borderColor: 'transparent',
borderRadius: 22,
},
editable &&
hovered && {
borderColor: t.atoms.border_contrast_medium.borderColor,
},
editable &&
focused && {
borderColor: t.palette.primary_500,
},
]}
contentTextStyle={[a.text_md, a.leading_snug]}
contentPaddingStyle={{
paddingLeft: IS_WEB ? 30 + 12 : 12,
paddingTop: 12,
paddingBottom: 12,
paddingRight: 12,
}}
onFocus={onFocus}
onBlur={onBlur}
onChange={setText}
onFacetCommitted={facet => {
if (facet.type === 'url' && isBskyPostUrl(facet.value)) {
setEmbed(facet.value)
}
}}
onRequestSubmit={req => {
if (req.platform === 'web' && req.shiftKey) return
req.nativeEvent.preventDefault()
onSubmit()
}}
/>
{focused || text.length ? (
<Pressable
accessibilityRole="button"
accessibilityLabel={l`Send message`}
accessibilityHint=""
hitSlop={HITSLOP_10}
style={[
a.rounded_full,
a.align_center,
a.justify_center,
a.self_end,
a.z_30,
{
height: 44,
width: 44,
backgroundColor: t.palette.primary_500,
},
]}
onPress={onSubmit}
disabled={!editable}>
<PaperPlane
fill={t.palette.white}
style={[a.relative, {left: 1}]}
/>
</Pressable>
) : null}
</View>
onChange={setText}
onFacetCommitted={facet => {
if (facet.type === 'url' && isBskyPostUrl(facet.value)) {
setEmbed(facet.value)
}
}}
onRequestSubmit={req => {
if (req.platform === 'web' && req.shiftKey) return
req.nativeEvent.preventDefault()
onSubmit()
}}
/>
</GlassView>
<SubmitButton onPress={onSubmit} disabled={submitDisabled} />
</GlassContainer>
</View>
{IS_WEB && (
@@ -246,6 +234,114 @@ export function MessageComposer({
close={() => setEmojiPickerState(prev => ({...prev, isOpen: false}))}
/>
)}
</>
</ComposerContainer>
)
}
function SubmitButton({
onPress,
disabled,
}: {
onPress: () => void
disabled: boolean
}) {
const {t: l} = useLingui()
const t = useTheme()
return (
<GlassView
isInteractive
glassEffectStyle="regular"
style={[a.rounded_full]}
tintColor={disabled ? t.palette.contrast_100 : t.palette.primary_500}
fallbackStyle={{
backgroundColor: disabled
? t.palette.contrast_100
: t.palette.primary_500,
}}>
<Pressable
accessibilityRole="button"
accessibilityLabel={l`Send message`}
accessibilityHint=""
hitSlop={HITSLOP_10}
style={[
a.rounded_full,
a.align_center,
a.justify_center,
{height: MIN_HEIGHT, width: MIN_HEIGHT},
]}
onPress={onPress}
disabled={disabled}>
<PaperPlaneIcon size="md" fill={t.palette.white} style={[a.mb_2xs]} />
</Pressable>
</GlassView>
)
}
// TODO: remove export when MessageInput is deleted
export function ComposerContainer({children}: {children: React.ReactNode}) {
const {bottom: bottomInset} = useSafeAreaInsets()
const {progress} = useReanimatedKeyboardAnimation()
const t = useTheme()
const animatedContainerStyle = useAnimatedStyle(() => ({
paddingHorizontal: interpolate(
progress.get(),
[0, 1],
[bottomInset, tokens.space.sm],
{
extrapolateRight: Extrapolation.CLAMP,
extrapolateLeft: Extrapolation.CLAMP,
},
),
}))
if (IS_LIQUID_GLASS) {
return (
<ScrollEdgeEffect edge="bottom">
<Animated.View style={[a.w_full, animatedContainerStyle, a.pb_lg]}>
{children}
</Animated.View>
</ScrollEdgeEffect>
)
} else {
return (
<>
<LinearGradient
style={platform({
native: [a.pt_sm, a.px_lg, , a.pb_lg, a.w_full],
web: [
a.pt_xs,
a.pl_lg,
a.pb_lg,
// prevent overlap with the scrollbar, which looks ugly
a.pr_xs, // xs + md = lg
{width: `calc(100% - ${tokens.space.md}px)` as '100%'},
],
})}
key={t.name} // android does not update when you change the colors. sigh.
start={[0.5, 0]}
end={[0.5, 1]}
colors={[
utils.alpha(t.atoms.bg.backgroundColor, 0),
utils.alpha(t.atoms.bg.backgroundColor, 0.8),
t.atoms.bg.backgroundColor,
]}>
{children}
</LinearGradient>
{/* covers the gap between the keyboard and the input during keyboard animation */}
{IS_NATIVE && (
<View
style={[
t.atoms.bg,
a.absolute,
a.left_0,
a.right_0,
{top: '100%', height: bottomInset + 1, marginTop: -1},
]}
/>
)}
</>
)
}
}
+120 -78
View File
@@ -1,17 +1,20 @@
import {useCallback, useState} from 'react'
import {Pressable, TextInput, useWindowDimensions, View} from 'react-native'
import {Pressable, TextInput, useWindowDimensions} from 'react-native'
import {
useFocusedInputHandler,
useKeyboardHandler,
useReanimatedKeyboardAnimation,
} from 'react-native-keyboard-controller'
import Animated, {
measure,
runOnJS,
useAnimatedProps,
useAnimatedRef,
useAnimatedStyle,
useSharedValue,
} from 'react-native-reanimated'
import {useSafeAreaInsets} from 'react-native-safe-area-context'
import {GlassContainer} from 'expo-glass-effect'
import {msg} from '@lingui/core/macro'
import {useLingui} from '@lingui/react'
import {countGraphemes} from 'unicode-segmenter/grapheme'
@@ -24,21 +27,26 @@ import {
useSaveMessageDraft,
} from '#/state/messages/message-drafts'
import {type EmojiPickerPosition} from '#/view/com/composer/text-input/web/EmojiPicker'
import {android, atoms as a, useTheme} from '#/alf'
import {useSharedInputStyles} from '#/components/forms/TextField'
import {PaperPlane_Stroke2_Corner0_Rounded as PaperPlane} from '#/components/icons/PaperPlane'
import {atoms as a, platform, tokens, useTheme} from '#/alf'
import {GlassView} from '#/components/GlassView'
import {PaperPlaneVertical_Filled_Stroke2_Corner1_Rounded as PaperPlaneIcon} from '#/components/icons/PaperPlane'
import * as Toast from '#/components/Toast'
import {IS_IOS, IS_WEB} from '#/env'
import {IS_ANDROID, IS_IOS, IS_WEB} from '#/env'
import {ComposerContainer} from './MessageComposer'
import {useExtractEmbedFromFacets} from './MessageInputEmbed'
const AnimatedTextInput = Animated.createAnimatedComponent(TextInput)
const MIN_HEIGHT = 40
export function MessageInput({
textInputId,
onSendMessage,
hasEmbed,
setEmbed,
children,
}: {
textInputId?: string
onSendMessage: (message: string) => void
hasEmbed: boolean
setEmbed: (embedUrl: string | undefined) => void
@@ -57,8 +65,6 @@ export function MessageInput({
const maxHeight = useSharedValue<undefined | number>(undefined)
const isInputScrollable = useSharedValue(false)
const inputStyles = useSharedInputStyles()
const [isFocused, setIsFocused] = useState(false)
const [message, setMessage] = useState(getDraft)
const inputRef = useAnimatedRef<TextInput>()
const [shouldEnforceClear, setShouldEnforceClear] = useState(false)
@@ -133,78 +139,114 @@ export function MessageInput({
scrollEnabled: isInputScrollable.get(),
}))
const submitDisabled = needsEmailVerification || message.trim().length === 0
const blur = useCallback(() => {
inputRef.current?.blur()
}, [inputRef])
useKeyboardHandler({
onEnd: evt => {
'worklet'
// small hack: interactive dismiss on Android sometimes doesn't blur the input
if (IS_ANDROID && evt.progress === 0) {
runOnJS(blur)()
}
},
})
return (
<View style={[a.px_md, a.pb_sm, a.pt_xs]}>
<ComposerContainer>
{children}
<View
style={[
a.w_full,
a.flex_row,
t.atoms.bg_contrast_25,
{
padding: a.p_sm.padding - 2,
paddingLeft: a.p_md.padding - 2,
borderWidth: 1,
borderRadius: 23,
borderColor: 'transparent',
},
isFocused && inputStyles.chromeFocus,
]}>
<AnimatedTextInput
accessibilityLabel={_(msg`Message input field`)}
accessibilityHint={_(msg`Type your message here`)}
placeholder={_(msg`Write a message`)}
placeholderTextColor={t.palette.contrast_500}
value={message}
onChange={evt => {
// bit of a hack: iOS automatically accepts autocomplete suggestions when you tap anywhere on the screen
// including the button we just pressed - and this overrides clearing the input! so we watch for the
// next change and double make sure the input is cleared. It should *always* send an onChange event after
// clearing via setMessage('') that happens in onSubmit()
// -sfn
if (IS_IOS && shouldEnforceClear) {
setShouldEnforceClear(false)
setMessage('')
return
}
const text = evt.nativeEvent.text
setMessage(text)
}}
multiline={true}
style={[
a.flex_1,
a.text_md,
a.px_sm,
t.atoms.text,
android({paddingTop: 0}),
{paddingBottom: IS_IOS ? 5 : 0},
animatedStyle,
]}
keyboardAppearance={t.scheme}
submitBehavior="newline"
onFocus={() => setIsFocused(true)}
onBlur={() => setIsFocused(false)}
ref={inputRef}
hitSlop={HITSLOP_10}
animatedProps={animatedProps}
editable={!needsEmailVerification}
/>
<Pressable
accessibilityRole="button"
accessibilityLabel={_(msg`Send message`)}
accessibilityHint=""
hitSlop={HITSLOP_10}
style={[
a.rounded_full,
a.align_center,
a.justify_center,
{height: 30, width: 30, backgroundColor: t.palette.primary_500},
]}
onPress={onSubmit}
disabled={needsEmailVerification}>
<PaperPlane fill={t.palette.white} style={[a.relative, {left: 1}]} />
</Pressable>
</View>
</View>
<GlassContainer
style={[a.flex_row, a.align_end, a.gap_sm]}
spacing={tokens.space.xs}>
<GlassView
isInteractive
glassEffectStyle="regular"
style={[a.flex_1, a.rounded_xl, {minHeight: MIN_HEIGHT}]}
tintColor={t.palette.contrast_50}
fallbackStyle={[t.atoms.bg_contrast_50]}>
<AnimatedTextInput
nativeID={textInputId}
accessibilityLabel={_(msg`Message input field`)}
accessibilityHint={_(msg`Type your message here`)}
placeholder={_(msg`Message`)}
placeholderTextColor={t.palette.contrast_500}
value={message}
onChange={evt => {
// bit of a hack: iOS automatically accepts autocomplete suggestions when you tap anywhere on the screen
// including the button we just pressed - and this overrides clearing the input! so we watch for the
// next change and double make sure the input is cleared. It should *always* send an onChange event after
// clearing via setMessage('') that happens in onSubmit()
// -sfn
if (IS_IOS && shouldEnforceClear) {
setShouldEnforceClear(false)
setMessage('')
return
}
const text = evt.nativeEvent.text
setMessage(text)
}}
multiline={true}
style={[
{flexBasis: 'auto', minHeight: MIN_HEIGHT},
a.flex_shrink_0,
a.flex_grow,
a.text_md,
a.px_lg,
t.atoms.text,
platform({
android: {paddingTop: 2, paddingBottom: 3},
ios: {paddingTop: 10, paddingBottom: 5},
}),
animatedStyle,
]}
verticalAlign="middle"
keyboardAppearance={t.scheme}
submitBehavior="newline"
ref={inputRef}
hitSlop={HITSLOP_10}
animatedProps={animatedProps}
editable={!needsEmailVerification}
/>
</GlassView>
<GlassView
isInteractive
glassEffectStyle="regular"
style={[a.rounded_full]}
tintColor={
submitDisabled ? t.palette.contrast_100 : t.palette.primary_500
}
fallbackStyle={{
backgroundColor: submitDisabled
? t.palette.contrast_100
: t.palette.primary_500,
}}>
<Pressable
accessibilityRole="button"
accessibilityLabel={_(msg`Send message`)}
accessibilityHint=""
hitSlop={HITSLOP_10}
style={[
a.rounded_full,
a.align_center,
a.justify_center,
{
height: MIN_HEIGHT,
width: MIN_HEIGHT,
},
]}
onPress={onSubmit}
disabled={submitDisabled}>
<PaperPlaneIcon
size="md"
fill={t.palette.white}
style={[a.mb_2xs]}
/>
</Pressable>
</GlassView>
</GlassContainer>
</ComposerContainer>
)
}
+171 -139
View File
@@ -1,22 +1,28 @@
import {useCallback, useEffect, useRef, useState} from 'react'
import {type LayoutChangeEvent, View} from 'react-native'
import {useKeyboardHandler} from 'react-native-keyboard-controller'
import {useCallback, useEffect, useId, useRef, useState} from 'react'
import {type LayoutChangeEvent, type ScrollViewProps, View} from 'react-native'
import {
KeyboardChatScrollView,
type KeyboardChatScrollViewProps,
KeyboardGestureArea,
} from 'react-native-keyboard-controller'
import Animated, {
runOnJS,
scrollTo,
type ScrollEvent,
type SharedValue,
useAnimatedRef,
useAnimatedStyle,
useDerivedValue,
useSharedValue,
} from 'react-native-reanimated'
import {type ReanimatedScrollEvent} from 'react-native-reanimated/lib/typescript/hook/commonTypes'
import {useSafeAreaInsets} from 'react-native-safe-area-context'
import {
type $Typed,
type AppBskyEmbedRecord,
AppBskyRichtextFacet,
RichText,
} from '@atproto/api'
import {useScrollEdgeEffectRef} from '@bsky.app/expo-scroll-edge-effect'
import {useHideBottomBarBorderForScreen} from '#/lib/hooks/useHideBottomBarBorder'
import {mergeRefs} from '#/lib/merge-refs'
import {ScrollProvider} from '#/lib/ScrollContext'
import {shortenLinks, stripInvalidMentions} from '#/lib/strings/rich-text-manip'
import {
@@ -36,7 +42,6 @@ import {
} from '#/state/messages/convo/types'
import {useGetPost} from '#/state/queries/post'
import {useAgent} from '#/state/session'
import {useShellLayout} from '#/state/shell/shell-layout'
import {
EmojiPicker,
type EmojiPickerState,
@@ -46,15 +51,17 @@ import {ChatDisabled} from '#/screens/Messages/components/ChatDisabled'
import {MessageComposer} from '#/screens/Messages/components/MessageComposer'
import {MessageInput} from '#/screens/Messages/components/MessageInput'
import {MessageListError} from '#/screens/Messages/components/MessageListError'
import {atoms as a, platform, tokens, useTheme, web} from '#/alf'
import {ChatEmptyPill} from '#/components/dms/ChatEmptyPill'
import {MessageItem} from '#/components/dms/MessageItem'
import {NewMessagesPill} from '#/components/dms/NewMessagesPill'
import {Loader} from '#/components/Loader'
import {Text} from '#/components/Typography'
import {useAnalytics} from '#/analytics'
import {IS_NATIVE, IS_WEB} from '#/env'
import {IS_ANDROID, IS_NATIVE, IS_WEB} from '#/env'
import {ChatStatusInfo} from './ChatStatusInfo'
import {MessageInputEmbed, useMessageEmbed} from './MessageInputEmbed'
import {KeyboardStickyView} from './vendor/KeyboardStickyView'
function MaybeLoader({isLoading}: {isLoading: boolean}) {
return (
@@ -108,9 +115,9 @@ export function MessagesList({
const agent = useAgent()
const getPost = useGetPost()
const {embedUri, setEmbed} = useMessageEmbed()
const t = useTheme()
useHideBottomBarBorderForScreen()
const textInputId = 'chat-input-' + useId()
const flatListRef = useAnimatedRef<ListMethods>()
const [newMessagesPill, setNewMessagesPill] = useState({
@@ -123,6 +130,18 @@ export function MessagesList({
pos: {top: 0, left: 0, right: 0, bottom: 0, nextFocusRef: null},
})
const inputHeightUI = useSharedValue(0)
const [inputHeightJS, setInputHeightJS] = useState(0)
const onInputLayout = useCallback(
(event: LayoutChangeEvent) => {
const inputHeight = event.nativeEvent.layout.height
inputHeightUI.set(inputHeight)
setInputHeightJS(inputHeight)
},
[inputHeightUI],
)
// We need to keep track of when the scroll offset is at the bottom of the list to know when to scroll as new items
// are added to the list. For example, if the user is scrolled up to 1iew older messages, we don't want to scroll to
// the bottom.
@@ -226,12 +245,12 @@ export function MessagesList({
const onStartReached = useCallback(() => {
if (hasScrolled && prevContentHeight.current > layoutHeight.get()) {
convoState.fetchMessageHistory()
void convoState.fetchMessageHistory()
}
}, [convoState, hasScrolled, layoutHeight])
const onScroll = useCallback(
(e: ReanimatedScrollEvent) => {
(e: ScrollEvent) => {
'worklet'
layoutHeight.set(e.layoutMeasurement.height)
const bottomOffset = e.contentOffset.y + e.layoutMeasurement.height
@@ -256,56 +275,8 @@ export function MessagesList({
)
// -- Keyboard animation handling
const {footerHeight} = useShellLayout()
const keyboardHeight = useSharedValue(0)
const keyboardIsOpening = useSharedValue(false)
// In some cases - like when the emoji piker opens - we don't want to animate the scroll in the list onLayout event.
// We use this value to keep track of when we want to disable the animation.
const layoutScrollWithoutAnimation = useSharedValue(false)
useKeyboardHandler(
{
onStart: e => {
'worklet'
// Immediate updates - like opening the emoji picker - will have a duration of zero. In those cases, we should
// just update the height here instead of having the `onMove` event do it (that event will not fire!)
if (e.duration === 0) {
layoutScrollWithoutAnimation.set(true)
keyboardHeight.set(e.height)
} else {
keyboardIsOpening.set(true)
}
},
onMove: e => {
'worklet'
keyboardHeight.set(e.height)
if (e.height > footerHeight.get()) {
scrollTo(flatListRef, 0, 1e7, false)
}
},
onEnd: e => {
'worklet'
keyboardHeight.set(e.height)
if (e.height > footerHeight.get()) {
scrollTo(flatListRef, 0, 1e7, false)
}
keyboardIsOpening.set(false)
},
},
[footerHeight],
)
const animatedListStyle = useAnimatedStyle(() => ({
marginBottom: Math.max(keyboardHeight.get(), footerHeight.get()),
}))
const animatedStickyViewStyle = useAnimatedStyle(() => ({
transform: [
{translateY: -Math.max(keyboardHeight.get(), footerHeight.get())},
],
}))
const {bottom: bottomInset} = useSafeAreaInsets()
// -- Message sending
const onSendMessage = useCallback(
@@ -387,26 +358,6 @@ export function MessagesList({
[agent, convoState, embedUri, getPost, hasScrolled, setHasScrolled],
)
// -- List layout changes (opening emoji keyboard, etc.)
const onListLayout = useCallback(
(e: LayoutChangeEvent) => {
layoutHeight.set(e.nativeEvent.layout.height)
if (IS_WEB || !keyboardIsOpening.get()) {
flatListRef.current?.scrollToEnd({
animated: !layoutScrollWithoutAnimation.get(),
})
layoutScrollWithoutAnimation.set(false)
}
},
[
flatListRef,
keyboardIsOpening,
layoutScrollWithoutAnimation,
layoutHeight,
],
)
const scrollToEndOnPress = useCallback(() => {
flatListRef.current?.scrollToOffset({
offset: prevContentHeight.current,
@@ -418,66 +369,100 @@ export function MessagesList({
setEmojiPickerState({isOpen: true, pos})
}, [])
const renderScrollComponent = useCallback(
(props: ScrollViewProps) => (
<ChatScrollComponent {...props} inputHeight={inputHeightUI} />
),
[inputHeightUI],
)
return (
<>
{/* Custom scroll provider so that we can use the `onScroll` event in our custom List implementation */}
<ScrollProvider onScroll={onScroll}>
<List
ref={flatListRef}
data={convoState.items}
renderItem={renderItem}
keyExtractor={keyExtractor}
disableFullWindowScroll={true}
disableVirtualization={true}
style={animatedListStyle}
// The extra two items account for the header and the footer components
initialNumToRender={IS_NATIVE ? 32 : 62}
maxToRenderPerBatch={IS_WEB ? 32 : 62}
keyboardDismissMode="on-drag"
keyboardShouldPersistTaps="handled"
maintainVisibleContentPosition={{
minIndexForVisible: 0,
}}
removeClippedSubviews={false}
sideBorders={false}
onContentSizeChange={onContentSizeChange}
onLayout={onListLayout}
onStartReached={onStartReached}
onScrollToIndexFailed={onScrollToIndexFailed}
scrollEventThrottle={100}
ListHeaderComponent={
<MaybeLoader isLoading={convoState.isFetchingHistory} />
}
/>
</ScrollProvider>
<Animated.View style={animatedStickyViewStyle}>
{convoState.status === ConvoStatus.Disabled ? (
<ChatDisabled />
) : blocked ? (
footer
) : (
<ConversationFooter
convoState={convoState}
hasAcceptOverride={hasAcceptOverride}>
{ax.features.enabled(ax.features.DmsNewMessageComposerEnable) ? (
<MessageComposer
onSendMessage={onSendMessage}
hasEmbed={!!embedUri}
setEmbed={setEmbed}>
<MessageInputEmbed embedUri={embedUri} setEmbed={setEmbed} />
</MessageComposer>
) : (
<MessageInput
onSendMessage={onSendMessage}
hasEmbed={!!embedUri}
setEmbed={setEmbed}
openEmojiPicker={onOpenEmojiPicker}>
<MessageInputEmbed embedUri={embedUri} setEmbed={setEmbed} />
</MessageInput>
<KeyboardGestureArea
interpolator="ios"
// HACKFIX: https://github.com/kirillzyusko/react-native-keyboard-controller/issues/1419
offset={Math.round(inputHeightJS)}
textInputNativeID={textInputId}
style={[a.flex_1]}>
{/* Custom scroll provider so that we can use the `onScroll` event in our custom List implementation */}
<ScrollProvider onScroll={onScroll}>
<List
ref={flatListRef}
data={convoState.items}
renderItem={renderItem}
keyExtractor={keyExtractor}
disableFullWindowScroll={true}
disableVirtualization={true}
// The extra two items account for the header and the footer components
initialNumToRender={IS_NATIVE ? 32 : 62}
maxToRenderPerBatch={IS_WEB ? 32 : 62}
keyboardDismissMode="interactive"
keyboardShouldPersistTaps="handled"
maintainVisibleContentPosition={{minIndexForVisible: 0}}
removeClippedSubviews={false}
sideBorders={false}
onContentSizeChange={onContentSizeChange}
onStartReached={onStartReached}
onScrollToIndexFailed={onScrollToIndexFailed}
showsVerticalScrollIndicator={!IS_ANDROID}
scrollEventThrottle={100}
ListHeaderComponent={
<MaybeLoader isLoading={convoState.isFetchingHistory} />
}
// native only (prop is not supported on web)
renderScrollComponent={renderScrollComponent}
// pushes up the content under the input on web (renderScrollComponent handles it on native)
ListFooterComponent={web(
<WebInputSpacer inputHeight={inputHeightJS} />,
)}
</ConversationFooter>
)}
</Animated.View>
style={web({
scrollbarWidth: 'thin',
scrollbarColor: `${t.palette.contrast_100} transparent`,
scrollbarGutter: 'stable both-edges',
})}
/>
</ScrollProvider>
<KeyboardStickyView
style={[a.absolute, a.bottom_0, a.left_0, a.right_0]}
onLayout={onInputLayout}
minimumOffset={bottomInset}
offset={{
closed: platform({
ios: tokens.space.lg, // hide bottom padding when closed
default: 0,
}),
opened: 0,
}}>
{convoState.status === ConvoStatus.Disabled ? (
<ChatDisabled />
) : blocked ? (
footer
) : (
<ConversationFooter
convoState={convoState}
hasAcceptOverride={hasAcceptOverride}>
{ax.features.enabled(ax.features.DmsNewMessageComposerEnable) ? (
<MessageComposer
textInputId={textInputId}
onSendMessage={onSendMessage}
hasEmbed={!!embedUri}
setEmbed={setEmbed}>
<MessageInputEmbed embedUri={embedUri} setEmbed={setEmbed} />
</MessageComposer>
) : (
<MessageInput
textInputId={textInputId}
onSendMessage={onSendMessage}
hasEmbed={!!embedUri}
setEmbed={setEmbed}
openEmojiPicker={onOpenEmojiPicker}>
<MessageInputEmbed embedUri={embedUri} setEmbed={setEmbed} />
</MessageInput>
)}
</ConversationFooter>
)}
</KeyboardStickyView>
</KeyboardGestureArea>
{IS_WEB && (
<EmojiPicker
@@ -492,6 +477,53 @@ export function MessagesList({
)
}
/** Note: native only */
function ChatScrollComponent({
ref,
inputHeight,
...props
}: ScrollViewProps & {
ref?: React.RefObject<KeyboardChatScrollViewProps>
inputHeight: SharedValue<number>
}) {
const scrollEdgeRef = useScrollEdgeEffectRef()
const {bottom: bottomInset} = useSafeAreaInsets()
const offset = platform({
ios: bottomInset - tokens.space.lg,
android: bottomInset,
default: 0,
})
const inputOffset = platform({
ios: bottomInset - tokens.space.lg,
android: bottomInset,
default: 0,
})
const extraContentPadding = useDerivedValue(
() => inputHeight.get() + inputOffset,
)
return (
<KeyboardChatScrollView
ref={mergeRefs([scrollEdgeRef, ref])}
automaticallyAdjustContentInsets={false}
keyboardDismissMode="interactive"
keyboardLiftBehavior="always"
extraContentPadding={extraContentPadding}
offset={offset}
{...props}
/>
)
}
function WebInputSpacer({inputHeight}: {inputHeight: number}) {
if (!IS_WEB) return null
return <Animated.View style={{height: inputHeight}} />
}
type FooterState = 'loading' | 'new-chat' | 'request' | 'standard'
function getFooterState(
@@ -0,0 +1,48 @@
import {
type KeyboardStickyViewProps,
useReanimatedKeyboardAnimation,
} from 'react-native-keyboard-controller'
import Animated, {useAnimatedStyle} from 'react-native-reanimated'
// Vendored from https://github.com/kirillzyusko/react-native-keyboard-controller/blob/main/src/components/KeyboardStickyView/index.tsx
// Converted to Reanimated to support `minimumOffset` clamping.
export function KeyboardStickyView({
children,
offset: {closed = 0, opened = 0} = {},
style,
enabled = true,
minimumOffset,
...props
}: KeyboardStickyViewProps & {
/**
* Stop the stickyview going lower than this (i.e. bottom safe area)
*/
minimumOffset?: number
}) {
const {height, progress} = useReanimatedKeyboardAnimation()
const animatedStyle = useAnimatedStyle(() => {
const offset = closed + (opened - closed) * progress.get()
let translateY: number
if (enabled) {
let h = height.get()
if (minimumOffset != null) {
h = Math.min(h, -minimumOffset)
}
translateY = h + offset
} else {
translateY = closed
}
return {
transform: [{translateY}],
}
})
return (
<Animated.View style={[animatedStyle, style]} {...props}>
{children}
</Animated.View>
)
}
+60 -64
View File
@@ -2,9 +2,7 @@ import {useState} from 'react'
import {Alert, LayoutAnimation, Linking, Pressable, View} from 'react-native'
import {useReducedMotion} from 'react-native-reanimated'
import {type AppBskyActorDefs, moderateProfile} from '@atproto/api'
import {msg} from '@lingui/core/macro'
import {useLingui} from '@lingui/react'
import {Trans} from '@lingui/react/macro'
import {Trans, useLingui} from '@lingui/react/macro'
import {useNavigation} from '@react-navigation/native'
import {type NativeStackScreenProps} from '@react-navigation/native-stack'
@@ -73,7 +71,7 @@ import {useActivitySubscriptionsNudged} from '#/storage/hooks/activity-subscript
type Props = NativeStackScreenProps<CommonNavigatorParams, 'Settings'>
export function SettingsScreen({}: Props) {
const ax = useAnalytics()
const {_} = useLingui()
const {t: l} = useLingui()
const reducedMotion = useReducedMotion()
const {logoutEveryAccount} = useSessionApi()
const {accounts, currentAccount} = useSession()
@@ -121,10 +119,8 @@ export function SettingsScreen({}: Props) {
{accounts.length > 1 ? (
<>
<SettingsList.PressableItem
label={_(msg`Switch account`)}
accessibilityHint={_(
msg`Shows other accounts you can switch to`,
)}
label={l`Switch account`}
accessibilityHint={l`Shows other accounts you can switch to`}
onPress={() => {
if (!reducedMotion) {
LayoutAnimation.configureNext(
@@ -161,7 +157,9 @@ export function SettingsScreen({}: Props) {
p => p.did === account.did,
)}
pendingDid={pendingDid}
onPressSwitchAccount={onPressSwitchAccount}
onPressSwitchAccount={(account, logContext) =>
void onPressSwitchAccount(account, logContext)
}
/>
))}
<AddAccountRow />
@@ -172,7 +170,7 @@ export function SettingsScreen({}: Props) {
<AddAccountRow />
)}
<SettingsList.Divider />
<SettingsList.LinkItem to="/settings/account" label={_(msg`Account`)}>
<SettingsList.LinkItem to="/settings/account" label={l`Account`}>
<SettingsList.ItemIcon icon={PersonIcon} />
<SettingsList.ItemText>
<Trans>Account</Trans>
@@ -180,21 +178,23 @@ export function SettingsScreen({}: Props) {
</SettingsList.LinkItem>
<SettingsList.LinkItem
to="/settings/privacy-and-security"
label={_(msg`Privacy and security`)}>
label={l`Privacy and security`}>
<SettingsList.ItemIcon icon={LockIcon} />
<SettingsList.ItemText>
<Trans>Privacy and security</Trans>
</SettingsList.ItemText>
</SettingsList.LinkItem>
<SettingsList.LinkItem to="/moderation" label={_(msg`Moderation`)}>
<SettingsList.LinkItem
to="/moderation"
label={l`Moderation and content filters`}>
<SettingsList.ItemIcon icon={HandIcon} />
<SettingsList.ItemText>
<Trans>Moderation</Trans>
<Trans>Moderation and content filters</Trans>
</SettingsList.ItemText>
</SettingsList.LinkItem>
<SettingsList.LinkItem
to="/settings/notifications"
label={_(msg`Notifications`)}>
label={l`Notifications`}>
<SettingsList.ItemIcon icon={NotificationIcon} />
<SettingsList.ItemText>
<Trans>Notifications</Trans>
@@ -202,7 +202,7 @@ export function SettingsScreen({}: Props) {
</SettingsList.LinkItem>
<SettingsList.LinkItem
to="/settings/content-and-media"
label={_(msg`Content and media`)}>
label={l`Content and media`}>
<SettingsList.ItemIcon icon={WindowIcon} />
<SettingsList.ItemText>
<Trans>Content and media</Trans>
@@ -213,7 +213,7 @@ export function SettingsScreen({}: Props) {
!ax.features.enabled(ax.features.ImportContactsSettingsDisable) && (
<SettingsList.LinkItem
to="/settings/find-contacts"
label={_(msg`Find friends from contacts`)}>
label={l`Find friends from contacts`}>
<SettingsList.ItemIcon icon={ContactsIcon} />
<SettingsList.ItemText>
<Trans>Find friends from contacts</Trans>
@@ -222,7 +222,7 @@ export function SettingsScreen({}: Props) {
)}
<SettingsList.LinkItem
to="/settings/appearance"
label={_(msg`Appearance`)}>
label={l`Appearance`}>
<SettingsList.ItemIcon icon={PaintRollerIcon} />
<SettingsList.ItemText>
<Trans>Appearance</Trans>
@@ -230,31 +230,29 @@ export function SettingsScreen({}: Props) {
</SettingsList.LinkItem>
<SettingsList.LinkItem
to="/settings/accessibility"
label={_(msg`Accessibility`)}>
label={l`Accessibility`}>
<SettingsList.ItemIcon icon={AccessibilityIcon} />
<SettingsList.ItemText>
<Trans>Accessibility</Trans>
</SettingsList.ItemText>
</SettingsList.LinkItem>
<SettingsList.LinkItem
to="/settings/language"
label={_(msg`Languages`)}>
<SettingsList.LinkItem to="/settings/language" label={l`Languages`}>
<SettingsList.ItemIcon icon={EarthIcon} />
<SettingsList.ItemText>
<Trans>Languages</Trans>
</SettingsList.ItemText>
</SettingsList.LinkItem>
<SettingsList.PressableItem
onPress={() => Linking.openURL(HELP_DESK_URL)}
label={_(msg`Help`)}
accessibilityHint={_(msg`Opens helpdesk in browser`)}>
onPress={() => void Linking.openURL(HELP_DESK_URL)}
label={l`Help`}
accessibilityHint={l`Opens helpdesk in browser`}>
<SettingsList.ItemIcon icon={CircleQuestionIcon} />
<SettingsList.ItemText>
<Trans>Help</Trans>
</SettingsList.ItemText>
<SettingsList.Chevron />
</SettingsList.PressableItem>
<SettingsList.LinkItem to="/settings/about" label={_(msg`About`)}>
<SettingsList.LinkItem to="/settings/about" label={l`About`}>
<SettingsList.ItemIcon icon={BubbleInfoIcon} />
<SettingsList.ItemText>
<Trans>About</Trans>
@@ -264,7 +262,7 @@ export function SettingsScreen({}: Props) {
<SettingsList.PressableItem
destructive
onPress={() => signOutPromptControl.open()}
label={_(msg`Sign out`)}>
label={l`Sign out`}>
<SettingsList.ItemText>
<Trans>Sign out</Trans>
</SettingsList.ItemText>
@@ -281,7 +279,7 @@ export function SettingsScreen({}: Props) {
}
setShowDevOptions(d => !d)
}}
label={_(msg`Developer options`)}>
label={l`Developer options`}>
<SettingsList.ItemIcon icon={CodeBracketsIcon} />
<SettingsList.ItemText>
<Trans>Developer options</Trans>
@@ -295,11 +293,11 @@ export function SettingsScreen({}: Props) {
<Prompt.Basic
control={signOutPromptControl}
title={_(msg`Sign out?`)}
description={_(msg`You will be signed out of all your accounts.`)}
title={l`Sign out?`}
description={l`You will be signed out of all your accounts.`}
onConfirm={() => logoutEveryAccount('Settings')}
confirmButtonCta={_(msg`Sign out`)}
cancelButtonCta={_(msg`Cancel`)}
confirmButtonCta={l`Sign out`}
cancelButtonCta={l`Cancel`}
confirmButtonColor="negative"
/>
@@ -376,7 +374,7 @@ function ProfilePreview({
}
function DevOptions() {
const {_} = useLingui()
const {t: l} = useLingui()
const agent = useAgent()
const [override, setOverride] = useStorage(device, [
'policyUpdateDebugOverride',
@@ -392,26 +390,26 @@ function DevOptions() {
} = useApplyPullRequestOTAUpdate()
const [actyNotifNudged, setActyNotifNudged] = useActivitySubscriptionsNudged()
const resetOnboarding = async () => {
const resetOnboarding = () => {
navigation.navigate('Home')
onboardingDispatch({type: 'start'})
Toast.show(_(msg`Onboarding reset`))
Toast.show(l`Onboarding reset`)
}
const clearAllStorage = async () => {
await clearStorage()
Toast.show(_(msg`Storage cleared, you need to restart the app now.`))
Toast.show(l`Storage cleared, you need to restart the app now.`)
}
const onPressUnsnoozeReminder = () => {
const lastEmailConfirm = new Date()
// wind back 3 days
lastEmailConfirm.setDate(lastEmailConfirm.getDate() - 3)
persisted.write('reminders', {
void persisted.write('reminders', {
...persisted.get('reminders'),
lastEmailConfirm: lastEmailConfirm.toISOString(),
})
Toast.show(_(msg`You probably want to restart the app now.`))
Toast.show(l`You probably want to restart the app now.`)
}
const onPressActySubsUnNudge = () => {
@@ -431,7 +429,7 @@ function DevOptions() {
style: 'default',
text: 'Apply',
onPress: (channel?: string) => {
tryApplyUpdate(channel ?? '')
void tryApplyUpdate(channel ?? '')
},
},
],
@@ -446,42 +444,42 @@ function DevOptions() {
<>
<SettingsList.PressableItem
onPress={() => navigation.navigate('Log')}
label={_(msg`Open system log`)}>
label={l`Open system log`}>
<SettingsList.ItemText>
<Trans>System log</Trans>
</SettingsList.ItemText>
</SettingsList.PressableItem>
<SettingsList.PressableItem
onPress={() => navigation.navigate('Debug')}
label={_(msg`Open storybook page`)}>
label={l`Open storybook page`}>
<SettingsList.ItemText>
<Trans>Storybook</Trans>
</SettingsList.ItemText>
</SettingsList.PressableItem>
<SettingsList.PressableItem
onPress={() => navigation.navigate('DebugMod')}
label={_(msg`Open moderation debug page`)}>
label={l`Open moderation debug page`}>
<SettingsList.ItemText>
<Trans>Debug Moderation</Trans>
</SettingsList.ItemText>
</SettingsList.PressableItem>
<SettingsList.PressableItem
onPress={() => deleteChatDeclarationRecord()}
label={_(msg`Open storybook page`)}>
label={l`Open storybook page`}>
<SettingsList.ItemText>
<Trans>Delete chat declaration record</Trans>
</SettingsList.ItemText>
</SettingsList.PressableItem>
<SettingsList.PressableItem
onPress={() => resetOnboarding()}
label={_(msg`Reset onboarding state`)}>
onPress={() => void resetOnboarding()}
label={l`Reset onboarding state`}>
<SettingsList.ItemText>
<Trans>Reset onboarding state</Trans>
</SettingsList.ItemText>
</SettingsList.PressableItem>
<SettingsList.PressableItem
onPress={onPressUnsnoozeReminder}
label={_(msg`Unsnooze email reminder`)}>
label={l`Unsnooze email reminder`}>
<SettingsList.ItemText>
<Trans>Unsnooze email reminder</Trans>
</SettingsList.ItemText>
@@ -489,15 +487,15 @@ function DevOptions() {
{actyNotifNudged && (
<SettingsList.PressableItem
onPress={onPressActySubsUnNudge}
label={_(msg`Reset activity subscription nudge`)}>
label={l`Reset activity subscription nudge`}>
<SettingsList.ItemText>
<Trans>Reset activity subscription nudge</Trans>
</SettingsList.ItemText>
</SettingsList.PressableItem>
)}
<SettingsList.PressableItem
onPress={() => clearAllStorage()}
label={_(msg`Clear all storage data`)}>
onPress={() => void clearAllStorage()}
label={l`Clear all storage data`}>
<SettingsList.ItemText>
<Trans>Clear all storage data (restart after this)</Trans>
</SettingsList.ItemText>
@@ -505,7 +503,7 @@ function DevOptions() {
{IS_IOS ? (
<SettingsList.PressableItem
onPress={onPressApplyOta}
label={_(msg`Apply Pull Request`)}>
label={l`Apply Pull Request`}>
<SettingsList.ItemText>
<Trans>Apply Pull Request</Trans>
</SettingsList.ItemText>
@@ -513,8 +511,8 @@ function DevOptions() {
) : null}
{IS_NATIVE && isCurrentlyRunningPullRequestDeployment ? (
<SettingsList.PressableItem
onPress={revertToEmbedded}
label={_(msg`Unapply Pull Request`)}>
onPress={() => void revertToEmbedded()}
label={l`Unapply Pull Request`}>
<SettingsList.ItemText>
<Trans>Unapply Pull Request {currentChannel}</Trans>
</SettingsList.ItemText>
@@ -543,7 +541,7 @@ function DevOptions() {
<Button
onPress={() => {
device.set([PolicyUpdate202508], false)
agent.bskyAppRemoveNuxs([PolicyUpdate202508])
void agent.bskyAppRemoveNuxs([PolicyUpdate202508])
Toast.show(`Done`, {
type: 'info',
})
@@ -562,7 +560,7 @@ function DevOptions() {
}
function AddAccountRow() {
const {_} = useLingui()
const {t: l} = useLingui()
const {setShowLoggedOut} = useLoggedOutViewControls()
const closeEverything = useCloseAllActiveElements()
@@ -574,7 +572,7 @@ function AddAccountRow() {
return (
<SettingsList.PressableItem
onPress={onAddAnotherAccount}
label={_(msg`Add another account`)}>
label={l`Add another account`}>
<SettingsList.ItemIcon icon={PersonPlusIcon} />
<SettingsList.ItemText>
<Trans>Add another account</Trans>
@@ -597,7 +595,7 @@ function AccountRow({
logContext: 'Settings',
) => void
}) {
const {_} = useLingui()
const {t: l} = useLingui()
const t = useTheme()
const moderationOpts = useModerationOpts()
@@ -614,7 +612,7 @@ function AccountRow({
<View style={[a.relative]}>
<SettingsList.PressableItem
onPress={onSwitchAccount}
label={_(msg`Switch account`)}>
label={l`Switch account`}>
{moderationOpts && profile ? (
<UserAvatar
size={28}
@@ -636,7 +634,7 @@ function AccountRow({
</SettingsList.PressableItem>
{!pendingDid && (
<Menu.Root>
<Menu.Trigger label={_(msg`Account options`)}>
<Menu.Trigger label={l`Account options`}>
{({props, state}) => (
<Pressable
{...props}
@@ -653,7 +651,7 @@ function AccountRow({
</Menu.Trigger>
<Menu.Outer showCancel>
<Menu.Item
label={_(msg`Remove account`)}
label={l`Remove account`}
onPress={() => removePromptControl.open()}>
<Menu.ItemText>
<Trans>Remove account</Trans>
@@ -666,15 +664,13 @@ function AccountRow({
<Prompt.Basic
control={removePromptControl}
title={_(msg`Remove from quick access?`)}
description={_(
msg`This will remove @${account.handle} from the quick access list.`,
)}
title={l`Remove from quick access?`}
description={l`This will remove @${account.handle} from the quick access list.`}
onConfirm={() => {
removeAccount(account)
Toast.show(_(msg`Account removed from quick access`))
Toast.show(l`Account removed from quick access`)
}}
confirmButtonCta={_(msg`Remove`)}
confirmButtonCta={l`Remove`}
confirmButtonColor="negative"
/>
</View>
@@ -48,9 +48,9 @@ export function CaptchaWebView({
return
}
onSuccess(code)
} catch (e: unknown) {
} catch (e) {
// We don't actually want to record an error here, because this will happen quite a bit. We will only be able to
// get hte href of the iframe if it's on our domain, so all the hcaptcha requests will throw here, although it's
// get the href of the iframe if it's on our domain, so all the hcaptcha requests will throw here, although it's
// harmless. Our other indicators of time-to-complete and back press should be more reliable in catching issues.
}
}, [stateParam, onSuccess, onError])
+3 -2
View File
@@ -34,7 +34,7 @@ export function StepCaptchaNative() {
const [ready, setReady] = useState(false)
useEffect(() => {
;(async () => {
void (async () => {
logger.debug('trying to generate attestation token...')
try {
if (IS_IOS) {
@@ -48,7 +48,8 @@ export function StepCaptchaNative() {
setToken(token)
setPayload(base64UrlEncode(payload))
}
} catch (e: any) {
} catch (err) {
const e = err as Error
logger.error(e)
} finally {
setReady(true)
+18 -24
View File
@@ -1,8 +1,6 @@
import {useEffect, useRef, useState} from 'react'
import {type TextInput, View} from 'react-native'
import {msg} from '@lingui/core/macro'
import {useLingui} from '@lingui/react'
import {Plural, Trans} from '@lingui/react/macro'
import {Plural, Trans, useLingui} from '@lingui/react/macro'
import * as EmailValidator from 'email-validator'
import type tldts from 'tldts'
@@ -60,7 +58,7 @@ export function StepInfo({
refetchServer: () => void
isLoadingStarterPack: boolean
}) {
const {_} = useLingui()
const {t: l} = useLingui()
const ax = useAnalytics()
const {state, dispatch} = useSignupContext()
const preemptivelyCompleteActivePolicyUpdate =
@@ -94,12 +92,12 @@ export function StepInfo({
const tldtsRef = useRef<typeof tldts>(undefined)
useEffect(() => {
// @ts-expect-error - valid path
import('tldts/dist/index.cjs.min.js').then(tldts => {
void import('tldts/dist/index.cjs.min.js').then(tldts => {
tldtsRef.current = tldts
})
// This will get used in the avatar creator a few steps later, so lets preload it now
// @ts-expect-error - valid path
import('react-native-view-shot/src/index')
void import('react-native-view-shot/src/index')
}, [])
const onNextPress = () => {
@@ -115,21 +113,21 @@ export function StepInfo({
if (state.serviceDescription?.inviteCodeRequired && !inviteCode) {
return dispatch({
type: 'setError',
value: _(msg`Please enter your invite code.`),
value: l`Please enter your invite code.`,
field: 'invite-code',
})
}
if (!email) {
return dispatch({
type: 'setError',
value: _(msg`Please enter your email.`),
value: l`Please enter your email.`,
field: 'email',
})
}
if (!EmailValidator.validate(email)) {
return dispatch({
type: 'setError',
value: _(msg`Your email appears to be invalid.`),
value: l`Your email appears to be invalid.`,
field: 'email',
})
}
@@ -139,9 +137,7 @@ export function StepInfo({
setHasWarnedEmail(true)
return dispatch({
type: 'setError',
value: _(
msg`Please double-check that you have entered your email address correctly.`,
),
value: l`Please double-check that you have entered your email address correctly.`,
})
}
} else if (hasWarnedEmail) {
@@ -151,14 +147,14 @@ export function StepInfo({
if (!password) {
return dispatch({
type: 'setError',
value: _(msg`Please choose your password.`),
value: l`Please choose your password.`,
field: 'password',
})
}
if (password.length < 8) {
return dispatch({
type: 'setError',
value: _(msg`Your password must be at least 8 characters long.`),
value: l`Your password must be at least 8 characters long.`,
field: 'password',
})
}
@@ -205,7 +201,7 @@ export function StepInfo({
dispatch({type: 'clearError'})
}
}}
label={_(msg`Required for this provider`)}
label={l`Required for this provider`}
defaultValue={state.inviteCode}
autoCapitalize="none"
autoComplete="email"
@@ -241,7 +237,7 @@ export function StepInfo({
dispatch({type: 'clearError'})
}
}}
label={_(msg`Enter your email address`)}
label={l`Enter your email address`}
defaultValue={state.email}
autoCapitalize="none"
autoComplete="email"
@@ -269,7 +265,7 @@ export function StepInfo({
dispatch({type: 'clearError'})
}
}}
label={_(msg`Choose your password`)}
label={l`Choose your password`}
defaultValue={state.password}
secureTextEntry
autoComplete="new-password"
@@ -297,8 +293,8 @@ export function StepInfo({
value: sanitizeDate(new Date(date)),
})
}}
label={_(msg`Date of birth`)}
accessibilityHint={_(msg`Select your date of birth`)}
label={l`Date of birth`}
accessibilityHint={l`Select your date of birth`}
maximumDate={new Date()}
/>
</View>
@@ -331,9 +327,7 @@ export function StepInfo({
<Trans>
Have we got your location wrong?{' '}
<SimpleInlineLinkText
label={_(
msg`Tap here to confirm your location with GPS.`,
)}
label={l`Tap here to confirm your location with GPS.`}
{...createStaticClick(() => {
locationControl.open()
})}>
@@ -363,7 +357,7 @@ export function StepInfo({
props.closeDialog(() => {
// set this after close!
setDeviceGeolocation(props.geolocation)
Toast.show(_(msg`Your location has been updated.`), {
Toast.show(l`Your location has been updated.`, {
type: 'success',
})
})
@@ -380,7 +374,7 @@ export function StepInfo({
onBackPress={onPressBack}
onNextPress={onNextPress}
onRetryPress={refetchServer}
overrideNextText={hasWarnedEmail ? _(msg`It's correct`) : undefined}
overrideNextText={hasWarnedEmail ? l`It's correct` : undefined}
/>
</>
)
+12 -13
View File
@@ -3,9 +3,7 @@ import {AppState, type AppStateStatus, View} from 'react-native'
import ReactNativeDeviceAttest from 'react-native-device-attest'
import Animated, {FadeIn, LayoutAnimationConfig} from 'react-native-reanimated'
import {AppBskyGraphStarterpack} from '@atproto/api'
import {msg} from '@lingui/core/macro'
import {useLingui} from '@lingui/react'
import {Trans} from '@lingui/react/macro'
import {Trans, useLingui} from '@lingui/react/macro'
import {FEEDBACK_FORM_URL} from '#/lib/constants'
import {logger} from '#/logger'
@@ -36,7 +34,7 @@ import * as bsky from '#/types/bsky'
export function Signup({onPressBack}: {onPressBack: () => void}) {
const ax = useAnalytics()
const {_} = useLingui()
const {t: l} = useLingui()
const t = useTheme()
const [state, dispatch] = useReducer(reducer, {
...initialState,
@@ -61,6 +59,7 @@ export function Signup({onPressBack}: {onPressBack: () => void}) {
uri: activeStarterPack?.uri,
})
// eslint-disable-next-line react/hook-use-state
const [isFetchedAtMount] = useState(starterPack != null)
const showStarterPackCard =
activeStarterPack?.uri && !isFetchingStarterPack && starterPack
@@ -85,21 +84,21 @@ export function Signup({onPressBack}: {onPressBack: () => void}) {
dispatch({type: 'setServiceDescription', value: undefined})
dispatch({
type: 'setError',
value: _(
msg`Unable to contact your service. Please check your Internet connection.`,
),
value: l`Unable to contact your service. Please check your Internet connection.`,
})
} else if (serviceInfo) {
dispatch({type: 'setServiceDescription', value: serviceInfo})
dispatch({type: 'setError', value: ''})
}
}, [_, serviceInfo, isError])
}, [l, serviceInfo, isError])
useEffect(() => {
if (state.pendingSubmit) {
if (!state.pendingSubmit.mutableProcessed) {
// OK to mutate assuming it's never read in render.
// eslint-disable-next-line react-hooks/immutability, react-compiler/react-compiler
state.pendingSubmit.mutableProcessed = true
submit(state, dispatch)
void submit(state, dispatch)
}
}
}, [state, dispatch, submit])
@@ -133,8 +132,8 @@ export function Signup({onPressBack}: {onPressBack: () => void}) {
<SignupContext.Provider value={{state, dispatch}}>
<LoggedOutLayout
leadin=""
title={_(msg`Create Account`)}
description={_(msg`We're so excited to have you join us!`)}
title={l`Create account`}
description={l`We’re so excited to have you join us!`}
scrollable>
<View testID="createAccount" style={a.flex_1}>
{showStarterPackCard &&
@@ -204,7 +203,7 @@ export function Signup({onPressBack}: {onPressBack: () => void}) {
isFetchingStarterPack && !isErrorStarterPack
}
isServerError={isError}
refetchServer={refetch}
refetchServer={() => void refetch()}
/>
) : state.activeStep === SignupStep.HANDLE ? (
<StepHandle />
@@ -231,7 +230,7 @@ export function Signup({onPressBack}: {onPressBack: () => void}) {
]}>
<Trans>Having trouble?</Trans>{' '}
<InlineLinkText
label={_(msg`Contact support`)}
label={l`Contact support`}
to={FEEDBACK_FORM_URL({email: state.email})}
style={[!gtMobile && a.text_md]}>
<Trans>Contact support</Trans>
+14 -14
View File
@@ -4,8 +4,7 @@ import {
ComAtprotoServerCreateAccount,
type ComAtprotoServerDescribeServer,
} from '@atproto/api'
import {msg} from '@lingui/core/macro'
import {useLingui} from '@lingui/react'
import {useLingui} from '@lingui/react/macro'
import * as EmailValidator from 'email-validator'
import {DEFAULT_SERVICE} from '#/lib/constants'
@@ -18,7 +17,9 @@ import {type AnalyticsContextType, useAnalytics} from '#/analytics'
export type ServiceDescription = ComAtprotoServerDescribeServer.OutputSchema
const DEFAULT_DATE = new Date(Date.now() - 60e3 * 60 * 24 * 365 * 20) // default to 20 years ago
const date = new Date()
date.setFullYear(date.getFullYear() - 20) // default to 20 years ago
const DEFAULT_DATE = date
export enum SignupStep {
INFO,
@@ -256,7 +257,7 @@ export const useSignupContext = () => useContext(SignupContext)
export function useSubmitSignup() {
const ax = useAnalytics()
const {_} = useLingui()
const {t: l} = useLingui()
const {createAccount} = useSessionApi()
const onboardingDispatch = useOnboardingDispatch()
@@ -266,7 +267,7 @@ export function useSubmitSignup() {
dispatch({type: 'setStep', value: SignupStep.INFO})
return dispatch({
type: 'setError',
value: _(msg`Please enter your email.`),
value: l`Please enter your email.`,
field: 'email',
})
}
@@ -274,7 +275,7 @@ export function useSubmitSignup() {
dispatch({type: 'setStep', value: SignupStep.INFO})
return dispatch({
type: 'setError',
value: _(msg`Your email appears to be invalid.`),
value: l`Your email appears to be invalid.`,
field: 'email',
})
}
@@ -282,7 +283,7 @@ export function useSubmitSignup() {
dispatch({type: 'setStep', value: SignupStep.INFO})
return dispatch({
type: 'setError',
value: _(msg`Please choose your password.`),
value: l`Please choose your password.`,
field: 'password',
})
}
@@ -290,7 +291,7 @@ export function useSubmitSignup() {
dispatch({type: 'setStep', value: SignupStep.HANDLE})
return dispatch({
type: 'setError',
value: _(msg`Please choose your handle.`),
value: l`Please choose your handle.`,
field: 'handle',
})
}
@@ -305,7 +306,7 @@ export function useSubmitSignup() {
})
return dispatch({
type: 'setError',
value: _(msg`Please complete the verification captcha.`),
value: l`Please complete the verification captcha.`,
})
}
dispatch({type: 'setError', value: ''})
@@ -337,14 +338,13 @@ export function useSubmitSignup() {
* createAccount fails, one tab is not stuck in onboarding — Eric
*/
onboardingDispatch({type: 'start'})
} catch (e: any) {
} catch (err) {
const e = err as Error
let errMsg = e.toString()
if (e instanceof ComAtprotoServerCreateAccount.InvalidInviteCodeError) {
dispatch({
type: 'setError',
value: _(
msg`Invite code not accepted. Check that you input it correctly and try again.`,
),
value: l`Invite code not accepted. Check that you input it correctly and try again.`,
field: 'invite-code',
})
dispatch({type: 'setStep', value: SignupStep.INFO})
@@ -370,6 +370,6 @@ export function useSubmitSignup() {
dispatch({type: 'setIsLoading', value: false})
}
},
[_, onboardingDispatch, createAccount],
[l, ax.logger, createAccount, onboardingDispatch],
)
}
+1 -1
View File
@@ -14,7 +14,7 @@ const BIRTHDATE_DELAY_HOURS = IS_DEV ? 0.001 : 48
* Stores the timestamp of the birthday update locally. This is used to
* debounce birthday updates globally.
*
* Use {@link useIsBirthDateUpdateAllowed} to check if an update is allowed.
* Use {@link useIsBirthdateUpdateAllowed} to check if an update is allowed.
*/
export function snoozeBirthdateUpdateAllowedForDid(did: string) {
account.set([did, 'birthdateLastUpdatedAt'], new Date().toISOString())
+4 -2
View File
@@ -204,6 +204,7 @@ export async function compressImage(
img: ComposerImage,
options?: {
highResolution?: boolean
increasedBlobSizeLimit?: boolean
},
): Promise<PickerImage> {
const source = img.transformed || img.source
@@ -211,6 +212,7 @@ export async function compressImage(
let attempts = 0
let maxDimension = highResolution ? 4000 : POST_IMG_MAX.width
let maxBytes = options?.increasedBlobSizeLimit ? 2000000 : POST_IMG_MAX.size
let minQualityPercentage = 0
let maxQualityPercentage = 101 // exclusive
@@ -251,7 +253,7 @@ export async function compressImage(
const base64 = res.base64
const size = base64 ? getDataUriSize(base64) : 0
if (base64 && size <= POST_IMG_MAX.size) {
if (base64 && size <= maxBytes) {
minQualityPercentage = qualityPercentage
newDataUri = {
path: await moveIfNecessary(res.uri),
@@ -275,7 +277,7 @@ export async function compressImage(
async function moveIfNecessary(from: string) {
const cacheDir = IS_NATIVE && getImageCacheDirectory()
if (cacheDir && from.startsWith(cacheDir)) {
if (cacheDir && !from.startsWith(cacheDir)) {
const to = joinPath(cacheDir, nanoid(36))
await makeDirectoryAsync(cacheDir, {intermediates: true})
@@ -52,7 +52,7 @@ export function useUpdateActorDeclaration({
onError: error => {
logger.error(error)
if (currentAccount) {
queryClient.invalidateQueries({
void queryClient.invalidateQueries({
queryKey: PROFILE_RKEY(currentAccount.did),
})
}
+14 -1
View File
@@ -1,6 +1,11 @@
import {useCallback} from 'react'
import {type AppBskyActorDefs, type AppBskyFeedDefs, AtUri} from '@atproto/api'
import {useMutation, useQuery, useQueryClient} from '@tanstack/react-query'
import {
type QueryClient,
useMutation,
useQuery,
useQueryClient,
} from '@tanstack/react-query'
import {useToggleMutationQueue} from '#/lib/hooks/useToggleMutationQueue'
import {updatePostShadow} from '#/state/cache/post-shadow'
@@ -43,6 +48,14 @@ export function usePostQuery(uri: string | undefined) {
})
}
export function precachePost(
queryClient: QueryClient,
uri: string,
post: AppBskyFeedDefs.PostView,
) {
queryClient.setQueryData(RQKEY(uri), post)
}
export function useGetPost() {
const queryClient = useQueryClient()
const agent = useAgent()
+80 -83
View File
@@ -181,65 +181,64 @@ export async function createAgentAndCreateAccount(
// Not awaited so that we can still get into onboarding.
// This is OK because we won't let you toggle adult stuff until you set the date.
if (IS_PROD_SERVICE(service)) {
Promise.allSettled(
[
networkRetry(3, () => {
return agent.setPersonalDetails({
birthDate: birthdate,
})
}).catch(e => {
logger.info(`createAgentAndCreateAccount: failed to set birthDate`)
throw e
}),
networkRetry(3, () => {
return agent.upsertProfile(prev => {
const next: Un$Typed<AppBskyActorProfile.Record> = prev || {}
next.displayName = handle
next.createdAt = createdAt
return next
})
}).catch(e => {
logger.info(
`createAgentAndCreateAccount: failed to set initial profile`,
)
throw e
}),
networkRetry(1, () => {
return agent.overwriteSavedFeeds([
{
...DISCOVER_SAVED_FEED,
id: TID.nextStr(),
},
{
...TIMELINE_SAVED_FEED,
id: TID.nextStr(),
},
])
}).catch(e => {
logger.info(
`createAgentAndCreateAccount: failed to set initial feeds`,
)
throw e
}),
getAge(birthDate) < 18 &&
networkRetry(3, () => {
return agent.com.atproto.repo.putRecord({
repo: account.did,
collection: 'chat.bsky.actor.declaration',
rkey: 'self',
record: {
$type: 'chat.bsky.actor.declaration',
allowIncoming: 'none',
},
})
}).catch(e => {
logger.info(
`createAgentAndCreateAccount: failed to set chat declaration`,
)
throw e
}),
].filter(Boolean),
).then(promises => {
void Promise.allSettled([
networkRetry(3, () => {
return agent.setPersonalDetails({
birthDate: birthdate,
})
}).catch(e => {
logger.info(`createAgentAndCreateAccount: failed to set birthDate`)
throw e
}),
networkRetry(3, () => {
return agent.upsertProfile(prev => {
const next: Un$Typed<AppBskyActorProfile.Record> = prev || {}
next.displayName = handle
next.createdAt = createdAt
return next
})
}).catch(e => {
logger.info(
`createAgentAndCreateAccount: failed to set initial profile`,
)
throw e
}),
networkRetry(1, () => {
return agent.overwriteSavedFeeds([
{
...DISCOVER_SAVED_FEED,
id: TID.nextStr(),
},
{
...TIMELINE_SAVED_FEED,
id: TID.nextStr(),
},
])
}).catch(e => {
logger.info(`createAgentAndCreateAccount: failed to set initial feeds`)
throw e
}),
...(getAge(birthDate) < 18
? [
networkRetry(3, () => {
return agent.com.atproto.repo.putRecord({
repo: account.did,
collection: 'chat.bsky.actor.declaration',
rkey: 'self',
record: {
$type: 'chat.bsky.actor.declaration',
allowIncoming: 'none',
},
})
}).catch(e => {
logger.info(
`createAgentAndCreateAccount: failed to set chat declaration`,
)
throw e
}),
]
: []),
]).then(promises => {
const rejected = promises.filter(p => p.status === 'rejected')
if (rejected.length > 0) {
logger.error(
@@ -248,30 +247,28 @@ export async function createAgentAndCreateAccount(
}
})
} else {
Promise.allSettled(
[
networkRetry(3, () => {
return agent.setPersonalDetails({
birthDate: birthDate.toISOString(),
})
}).catch(e => {
logger.info(`createAgentAndCreateAccount: failed to set birthDate`)
throw e
}),
networkRetry(3, () => {
return agent.upsertProfile(prev => {
const next: Un$Typed<AppBskyActorProfile.Record> = prev || {}
next.createdAt = prev?.createdAt || new Date().toISOString()
return next
})
}).catch(e => {
logger.info(
`createAgentAndCreateAccount: failed to set initial profile`,
)
throw e
}),
].filter(Boolean),
).then(promises => {
void Promise.allSettled([
networkRetry(3, () => {
return agent.setPersonalDetails({
birthDate: birthDate.toISOString(),
})
}).catch(e => {
logger.info(`createAgentAndCreateAccount: failed to set birthDate`)
throw e
}),
networkRetry(3, () => {
return agent.upsertProfile(prev => {
const next: Un$Typed<AppBskyActorProfile.Record> = prev || {}
next.createdAt = prev?.createdAt || new Date().toISOString()
return next
})
}).catch(e => {
logger.info(
`createAgentAndCreateAccount: failed to set initial profile`,
)
throw e
}),
]).then(promises => {
const rejected = promises.filter(p => p.status === 'rejected')
if (rejected.length > 0) {
logger.error(
+10 -7
View File
@@ -8,7 +8,7 @@ import {
useState,
useSyncExternalStore,
} from 'react'
import {type AtpSessionEvent, type BskyAgent} from '@atproto/api'
import {type AtpAgent, type AtpSessionEvent} from '@atproto/api'
import * as persisted from '#/state/persisted'
import {useCloseAllActiveElements} from '#/state/util'
@@ -47,7 +47,7 @@ const StateContext = createContext<SessionStateContext>({
})
StateContext.displayName = 'SessionStateContext'
const AgentContext = createContext<BskyAgent | null>(null)
const AgentContext = createContext<AtpAgent | null>(null)
AgentContext.displayName = 'SessionAgentContext'
const ApiContext = createContext<SessionApiContext>({
@@ -96,7 +96,7 @@ class SessionStore {
),
}
addSessionDebugLog({type: 'persisted:broadcast', data: persistedData})
persisted.write('session', persistedData)
void persisted.write('session', persistedData)
}
this.listeners.forEach(listener => listener())
}
@@ -105,12 +105,13 @@ class SessionStore {
export function Provider({children}: React.PropsWithChildren<{}>) {
const ax = useAnalyticsBase()
const cancelPendingTask = useOneTaskAtATime()
// eslint-disable-next-line react/hook-use-state
const [store] = useState(() => new SessionStore())
const state = useSyncExternalStore(store.subscribe, store.getState)
const onboardingDispatch = useOnboardingDispatch()
const onAgentSessionChange = useCallback(
(agent: BskyAgent, accountDid: string, sessionEvent: AtpSessionEvent) => {
(agent: AtpAgent, accountDid: string, sessionEvent: AtpSessionEvent) => {
const refreshedAccount = agentToSessionAccount(agent) // Mutable, so snapshot it right away.
if (sessionEvent === 'expired' || sessionEvent === 'create-failed') {
emitSessionDropped()
@@ -327,10 +328,11 @@ export function Provider({children}: React.PropsWithChildren<{}>) {
* follower tabs. Follower tabs will therefore receive the fresh
* session. See APP-1960, or ask Eric.
*/
resumeSession(syncedAccount)
void resumeSession(syncedAccount)
} else {
const agent = state.currentAgentState.agent as BskyAgent
const agent = state.currentAgentState.agent as AtpAgent
const prevSession = agent.session
// eslint-disable-next-line react-compiler/react-compiler
agent.sessionManager.session = sessionAccountToSession(syncedAccount)
addSessionDebugLog({
type: 'agent:patch',
@@ -376,6 +378,7 @@ export function Provider({children}: React.PropsWithChildren<{}>) {
)
// @ts-expect-error window type is not declared, debug only
// eslint-disable-next-line react-hooks/immutability
if (__DEV__ && IS_WEB) window.agent = state.currentAgentState.agent
const agent = state.currentAgentState.agent as BskyAppAgent
@@ -448,7 +451,7 @@ export function useRequireAuth() {
)
}
export function useAgent(): BskyAgent {
export function useAgent(): AtpAgent {
const agent = useContext(AgentContext)
if (!agent) {
throw Error('useAgent() must be below <SessionProvider>.')
+3
View File
@@ -838,6 +838,9 @@ export const ComposePost = ({
highResolutionImages: ax.features.enabled(
ax.features.ImageUploadsHighResolution,
),
increasedBlobSizeLimit: ax.features.enabled(
ax.features.ImageUploadsBlobSize2mbEnabled,
),
},
)
).uris[0]