diff --git a/bskyembed/package.json b/bskyembed/package.json
index cb9a46213b..72d2b6dfcf 100644
--- a/bskyembed/package.json
+++ b/bskyembed/package.json
@@ -22,7 +22,7 @@
"eslint-plugin-simple-import-sort": "^12.0.0",
"postcss": "^8.4.38",
"tailwindcss": "^3.4.3",
- "typescript": "^4.0.5",
+ "typescript": "^5.5.4",
"vite": "^5.2.8",
"vite-tsconfig-paths": "^4.3.2"
}
diff --git a/bskyembed/src/components/post.tsx b/bskyembed/src/components/post.tsx
index 1d1e8f4d81..4db5eeb45e 100644
--- a/bskyembed/src/components/post.tsx
+++ b/bskyembed/src/components/post.tsx
@@ -11,7 +11,7 @@ import likeIcon from '../../assets/heart2_filled_stroke2_corner0_rounded.svg'
import logo from '../../assets/logo.svg'
import repostIcon from '../../assets/repost_stroke2_corner2_rounded.svg'
import {CONTENT_LABELS} from '../labels'
-import {getRkey, niceDate} from '../utils'
+import {getRkey, niceDate, prettyNumber} from '../utils'
import {Container} from './container'
import {Embed} from './embed'
import {Link} from './link'
@@ -78,7 +78,7 @@ export function Post({thread}: Props) {
- {post.likeCount}
+ {prettyNumber(post.likeCount)}
)}
@@ -86,7 +86,7 @@ export function Post({thread}: Props) {
- {post.repostCount}
+ {prettyNumber(post.repostCount)}
)}
@@ -97,7 +97,7 @@ export function Post({thread}: Props) {
{post.replyCount
- ? `Read ${post.replyCount} ${
+ ? `Read ${prettyNumber(post.replyCount)} ${
post.replyCount > 1 ? 'replies' : 'reply'
} on Bluesky`
: `View on Bluesky`}
diff --git a/bskyembed/src/utils.ts b/bskyembed/src/utils.ts
index 1f6fd5061c..cfa4a525bf 100644
--- a/bskyembed/src/utils.ts
+++ b/bskyembed/src/utils.ts
@@ -16,3 +16,13 @@ export function getRkey({uri}: {uri: string}): string {
const at = new AtUri(uri)
return at.rkey
}
+
+const formatter = new Intl.NumberFormat('en-US', {
+ notation: 'compact',
+ maximumFractionDigits: 1,
+ roundingMode: 'trunc',
+})
+
+export function prettyNumber(number: number) {
+ return formatter.format(number)
+}
diff --git a/bskyembed/tsconfig.json b/bskyembed/tsconfig.json
index 44c516ed11..b3b6055ccd 100644
--- a/bskyembed/tsconfig.json
+++ b/bskyembed/tsconfig.json
@@ -20,5 +20,5 @@
"jsxFragmentFactory": "Fragment",
"downlevelIteration": true
},
- "include": ["src"]
+ "include": ["src", "vite.config.ts"]
}
diff --git a/bskyembed/tsconfig.snippet.json b/bskyembed/tsconfig.snippet.json
index a6b6071dd6..fee21964a1 100644
--- a/bskyembed/tsconfig.snippet.json
+++ b/bskyembed/tsconfig.snippet.json
@@ -6,5 +6,5 @@
"strict": true,
"outDir": "dist"
},
- "include": ["snippet"],
+ "include": ["snippet"]
}
diff --git a/bskyembed/yarn.lock b/bskyembed/yarn.lock
index ca52dc0747..3c5ef5aec0 100644
--- a/bskyembed/yarn.lock
+++ b/bskyembed/yarn.lock
@@ -4024,10 +4024,10 @@ typed-array-length@^1.0.6:
is-typed-array "^1.1.13"
possible-typed-array-names "^1.0.0"
-typescript@^4.0.5:
- version "4.9.5"
- resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.9.5.tgz#095979f9bcc0d09da324d58d03ce8f8374cbe65a"
- integrity sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==
+typescript@^5.5.4:
+ version "5.5.4"
+ resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.5.4.tgz#d9852d6c82bad2d2eda4fd74a5762a8f5909e9ba"
+ integrity sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==
uint8arrays@3.0.0:
version "3.0.0"
diff --git a/scripts/post-web-build.js b/scripts/post-web-build.js
index 5db3788545..baaa7cb8b7 100644
--- a/scripts/post-web-build.js
+++ b/scripts/post-web-build.js
@@ -2,7 +2,6 @@ const path = require('path')
const fs = require('fs')
const projectRoot = path.join(__dirname, '..')
-const webBuildJs = path.join(projectRoot, 'web-build', 'static', 'js')
const templateFile = path.join(
projectRoot,
'bskyweb',
@@ -10,18 +9,18 @@ const templateFile = path.join(
'scripts.html',
)
-const jsFiles = fs.readdirSync(webBuildJs).filter(name => name.endsWith('.js'))
-jsFiles.sort((a, b) => {
- // make sure main is written last
- if (a.startsWith('main')) return 1
- if (b.startsWith('main')) return -1
- return a.localeCompare(b)
-})
+const {entrypoints} = require(path.join(
+ projectRoot,
+ 'web-build/asset-manifest.json',
+))
-console.log(`Found ${jsFiles.length} js files in web-build`)
+console.log(`Found ${entrypoints.length} entrypoints`)
console.log(`Writing ${templateFile}`)
-const outputFile = jsFiles
- .map(name => ``)
+const outputFile = entrypoints
+ .map(name => {
+ const file = path.basename(name)
+ return ``
+ })
.join('\n')
fs.writeFileSync(templateFile, outputFile)
diff --git a/src/App.native.tsx b/src/App.native.tsx
index c26052a92d..609d316d4b 100644
--- a/src/App.native.tsx
+++ b/src/App.native.tsx
@@ -175,25 +175,25 @@ function App() {
-
-
-
-
-
-
-
+
+
+
+
+
+
+
-
-
-
-
-
-
-
+
+
+
+
+
+
+
diff --git a/src/App.web.tsx b/src/App.web.tsx
index fa1fba031b..8531dc88d6 100644
--- a/src/App.web.tsx
+++ b/src/App.web.tsx
@@ -153,25 +153,25 @@ function App() {
return (
-
-
-
-
-
-
-
+
+
+
+
+
+
+
-
-
-
-
-
-
-
+
+
+
+
+
+
+
)
diff --git a/src/Navigation.tsx b/src/Navigation.tsx
index 960e66bbad..0bf0e9f93e 100644
--- a/src/Navigation.tsx
+++ b/src/Navigation.tsx
@@ -490,6 +490,7 @@ function MyProfileTabNavigator() {
getComponent={() => ProfileScreen}
initialParams={{
name: 'me',
+ hideBackButton: true,
}}
/>
{commonScreens(MyProfileTab as typeof HomeTab)}
diff --git a/src/alf/atoms.ts b/src/alf/atoms.ts
index e918e370d8..429a060729 100644
--- a/src/alf/atoms.ts
+++ b/src/alf/atoms.ts
@@ -853,6 +853,7 @@ export const atoms = {
mr_auto: {
marginRight: 'auto',
},
+
/*
* Pointer events & user select
*/
@@ -871,6 +872,7 @@ export const atoms = {
user_select_all: {
userSelect: 'all',
},
+
/*
* Text decoration
*/
@@ -880,4 +882,11 @@ export const atoms = {
strike_through: {
textDecorationLine: 'line-through',
},
+
+ /*
+ * Display
+ */
+ hidden: {
+ display: 'none',
+ },
} as const
diff --git a/src/alf/index.tsx b/src/alf/index.tsx
index ade2ce1451..5fa7d3b1a1 100644
--- a/src/alf/index.tsx
+++ b/src/alf/index.tsx
@@ -1,5 +1,5 @@
import React from 'react'
-import {Dimensions} from 'react-native'
+import {useMediaQuery} from 'react-responsive'
import {createThemes, defaultTheme} from '#/alf/themes'
import {Theme, ThemeName} from '#/alf/types'
@@ -12,52 +12,15 @@ export * from '#/alf/util/flatten'
export * from '#/alf/util/platform'
export * from '#/alf/util/themeSelector'
-type BreakpointName = keyof typeof breakpoints
-
-/*
- * Breakpoints
- */
-const breakpoints: {
- [key: string]: number
-} = {
- gtPhone: 500,
- gtMobile: 800,
- gtTablet: 1300,
-}
-function getActiveBreakpoints({width}: {width: number}) {
- const active: (keyof typeof breakpoints)[] = Object.keys(breakpoints).filter(
- breakpoint => width >= breakpoints[breakpoint],
- )
-
- return {
- active: active[active.length - 1],
- gtPhone: active.includes('gtPhone'),
- gtMobile: active.includes('gtMobile'),
- gtTablet: active.includes('gtTablet'),
- }
-}
-
/*
* Context
*/
export const Context = React.createContext<{
themeName: ThemeName
theme: Theme
- breakpoints: {
- active: BreakpointName | undefined
- gtPhone: boolean
- gtMobile: boolean
- gtTablet: boolean
- }
}>({
themeName: 'light',
theme: defaultTheme,
- breakpoints: {
- active: undefined,
- gtPhone: false,
- gtMobile: false,
- gtTablet: false,
- },
})
export function ThemeProvider({
@@ -74,18 +37,6 @@ export function ThemeProvider({
})
}, [])
const theme = themes[themeName]
- const [breakpoints, setBreakpoints] = React.useState(() =>
- getActiveBreakpoints({width: Dimensions.get('window').width}),
- )
-
- React.useEffect(() => {
- const listener = Dimensions.addEventListener('change', ({window}) => {
- const bp = getActiveBreakpoints({width: window.width})
- if (bp.active !== breakpoints.active) setBreakpoints(bp)
- })
-
- return listener.remove
- }, [breakpoints, setBreakpoints])
return (
({
themeName: themeName,
theme: theme,
- breakpoints,
}),
- [theme, themeName, breakpoints],
+ [theme, themeName],
)}>
{children}
@@ -107,5 +57,12 @@ export function useTheme() {
}
export function useBreakpoints() {
- return React.useContext(Context).breakpoints
+ const gtPhone = useMediaQuery({minWidth: 500})
+ const gtMobile = useMediaQuery({minWidth: 800})
+ const gtTablet = useMediaQuery({minWidth: 1300})
+ return {
+ gtPhone,
+ gtMobile,
+ gtTablet,
+ }
}
diff --git a/src/components/FeedInterstitials.tsx b/src/components/FeedInterstitials.tsx
index e37d2c3e0e..65e981f77a 100644
--- a/src/components/FeedInterstitials.tsx
+++ b/src/components/FeedInterstitials.tsx
@@ -1,18 +1,21 @@
import React from 'react'
import {View} from 'react-native'
import {ScrollView} from 'react-native-gesture-handler'
-import {AppBskyFeedDefs, AtUri} from '@atproto/api'
+import {AppBskyActorDefs, AppBskyFeedDefs, AtUri} from '@atproto/api'
import {msg, Trans} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {useNavigation} from '@react-navigation/native'
import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries'
import {NavigationProp} from '#/lib/routes/types'
+import {useGate} from '#/lib/statsig/statsig'
import {logEvent} from '#/lib/statsig/statsig'
import {logger} from '#/logger'
import {useModerationOpts} from '#/state/preferences/moderation-opts'
import {useGetPopularFeedsQuery} from '#/state/queries/feed'
+import {FeedDescriptor} from '#/state/queries/post-feed'
import {useProfilesQuery} from '#/state/queries/profile'
+import {useSuggestedFollowsByActorQuery} from '#/state/queries/suggested-follows'
import {useSession} from '#/state/session'
import {useProgressGuide} from '#/state/shell/progress-guide'
import * as userActionHistory from '#/state/userActionHistory'
@@ -173,14 +176,63 @@ function useExperimentalSuggestedUsersQuery() {
}
}
-export function SuggestedFollows() {
- const t = useTheme()
- const {_} = useLingui()
+export function SuggestedFollows({feed}: {feed: FeedDescriptor}) {
+ const gate = useGate()
+ const [feedType, feedUri] = feed.split('|')
+ if (feedType === 'author') {
+ if (gate('show_follow_suggestions_in_profile')) {
+ return
+ } else {
+ return null
+ }
+ } else {
+ return
+ }
+}
+
+export function SuggestedFollowsProfile({did}: {did: string}) {
+ const {
+ isLoading: isSuggestionsLoading,
+ data,
+ error,
+ } = useSuggestedFollowsByActorQuery({
+ did,
+ })
+ return (
+
+ )
+}
+
+export function SuggestedFollowsHome() {
const {
isLoading: isSuggestionsLoading,
profiles,
error,
} = useExperimentalSuggestedUsersQuery()
+ return (
+
+ )
+}
+
+export function ProfileGrid({
+ isSuggestionsLoading,
+ error,
+ profiles,
+}: {
+ isSuggestionsLoading: boolean
+ profiles: AppBskyActorDefs.ProfileViewDetailed[]
+ error: Error | null
+}) {
+ const t = useTheme()
+ const {_} = useLingui()
const moderationOpts = useModerationOpts()
const navigation = useNavigation()
const {gtMobile} = useBreakpoints()
diff --git a/src/components/ProfileHoverCard/index.web.tsx b/src/components/ProfileHoverCard/index.web.tsx
index 9280009887..3890790dbe 100644
--- a/src/components/ProfileHoverCard/index.web.tsx
+++ b/src/components/ProfileHoverCard/index.web.tsx
@@ -377,7 +377,7 @@ function Inner({
hide: () => void
}) {
const t = useTheme()
- const {_} = useLingui()
+ const {_, i18n} = useLingui()
const {currentAccount} = useSession()
const moderation = React.useMemo(
() => moderateProfile(profile, moderationOpts),
@@ -393,8 +393,8 @@ function Inner({
profile.viewer?.blocking ||
profile.viewer?.blockedBy ||
profile.viewer?.blockingByList
- const following = formatCount(profile.followsCount || 0)
- const followers = formatCount(profile.followersCount || 0)
+ const following = formatCount(i18n, profile.followsCount || 0)
+ const followers = formatCount(i18n, profile.followersCount || 0)
const pluralizedFollowers = plural(profile.followersCount || 0, {
one: 'follower',
other: 'followers',
diff --git a/src/components/StarterPack/QrCode.tsx b/src/components/StarterPack/QrCode.tsx
index a8a2e34917..8ce5cbbb13 100644
--- a/src/components/StarterPack/QrCode.tsx
+++ b/src/components/StarterPack/QrCode.tsx
@@ -59,20 +59,24 @@ export const QrCode = React.forwardRef(function QrCode(
-
-
- on
-
-
-
-
-
-
+
+
+ on
+
+
+
+
+
+
+
+
diff --git a/src/components/dialogs/Embed.tsx b/src/components/dialogs/Embed.tsx
index 7d858cae40..f43c3c6fe6 100644
--- a/src/components/dialogs/Embed.tsx
+++ b/src/components/dialogs/Embed.tsx
@@ -43,7 +43,7 @@ function EmbedDialogInner({
timestamp,
}: Omit) {
const t = useTheme()
- const {_} = useLingui()
+ const {_, i18n} = useLingui()
const ref = useRef(null)
const [copied, setCopied] = useState(false)
@@ -86,9 +86,9 @@ function EmbedDialogInner({
)} (@${escapeHtml(
postAuthor.handle,
)}) ${escapeHtml(
- niceDate(timestamp),
+ niceDate(i18n, timestamp),
)}`
- }, [postUri, postCid, record, timestamp, postAuthor])
+ }, [i18n, postUri, postCid, record, timestamp, postAuthor])
return (
diff --git a/src/components/dms/MessageItem.tsx b/src/components/dms/MessageItem.tsx
index 573c24f77d..c5c472cf08 100644
--- a/src/components/dms/MessageItem.tsx
+++ b/src/components/dms/MessageItem.tsx
@@ -11,6 +11,7 @@ import {
ChatBskyConvoDefs,
RichText as RichTextAPI,
} from '@atproto/api'
+import {I18n} from '@lingui/core'
import {msg} from '@lingui/macro'
import {useLingui} from '@lingui/react'
@@ -153,14 +154,14 @@ let MessageItemMetadata = ({
)
const relativeTimestamp = useCallback(
- (timestamp: string) => {
+ (i18n: I18n, timestamp: string) => {
const date = new Date(timestamp)
const now = new Date()
- const time = new Intl.DateTimeFormat(undefined, {
+ const time = i18n.date(date, {
hour: 'numeric',
minute: 'numeric',
- }).format(date)
+ })
const diff = now.getTime() - date.getTime()
@@ -182,13 +183,13 @@ let MessageItemMetadata = ({
return _(msg`Yesterday, ${time}`)
}
- return new Intl.DateTimeFormat(undefined, {
+ return i18n.date(date, {
hour: 'numeric',
minute: 'numeric',
day: 'numeric',
month: 'numeric',
year: 'numeric',
- }).format(date)
+ })
},
[_],
)
diff --git a/src/components/forms/DateField/index.shared.tsx b/src/components/forms/DateField/index.shared.tsx
index 1f54bdc8be..814bbed7cc 100644
--- a/src/components/forms/DateField/index.shared.tsx
+++ b/src/components/forms/DateField/index.shared.tsx
@@ -1,12 +1,12 @@
import React from 'react'
import {Pressable, View} from 'react-native'
+import {useLingui} from '@lingui/react'
import {android, atoms as a, useTheme, web} from '#/alf'
import * as TextField from '#/components/forms/TextField'
import {useInteractionState} from '#/components/hooks/useInteractionState'
import {CalendarDays_Stroke2_Corner0_Rounded as CalendarDays} from '#/components/icons/CalendarDays'
import {Text} from '#/components/Typography'
-import {localizeDate} from './utils'
// looks like a TextField.Input, but is just a button. It'll do something different on each platform on press
// iOS: open a dialog with an inline date picker
@@ -25,6 +25,7 @@ export function DateFieldButton({
isInvalid?: boolean
accessibilityHint?: string
}) {
+ const {i18n} = useLingui()
const t = useTheme()
const {
@@ -91,7 +92,7 @@ export function DateFieldButton({
t.atoms.text,
{lineHeight: a.text_md.fontSize * 1.1875},
]}>
- {localizeDate(value)}
+ {i18n.date(value, {timeZone: 'UTC'})}
diff --git a/src/components/forms/DateField/utils.ts b/src/components/forms/DateField/utils.ts
index c787272fe8..04bb482ce1 100644
--- a/src/components/forms/DateField/utils.ts
+++ b/src/components/forms/DateField/utils.ts
@@ -1,16 +1,5 @@
-import {getLocales} from 'expo-localization'
-
-const LOCALE = getLocales()[0]
-
// we need the date in the form yyyy-MM-dd to pass to the input
export function toSimpleDateString(date: Date | string): string {
const _date = typeof date === 'string' ? new Date(date) : date
return _date.toISOString().split('T')[0]
}
-
-export function localizeDate(date: Date | string): string {
- const _date = typeof date === 'string' ? new Date(date) : date
- return new Intl.DateTimeFormat(LOCALE.languageTag, {
- timeZone: 'UTC',
- }).format(_date)
-}
diff --git a/src/lib/api/feed-manip.ts b/src/lib/api/feed-manip.ts
index a0ee647b79..d81f250b80 100644
--- a/src/lib/api/feed-manip.ts
+++ b/src/lib/api/feed-manip.ts
@@ -81,7 +81,15 @@ export class FeedViewPostsSlice {
isParentBlocked,
isParentNotFound,
})
- if (!reply || reason) {
+ if (!reply) {
+ if (post.record.reply) {
+ // This reply wasn't properly hydrated by the AppView.
+ this.isOrphan = true
+ this.items[0].isParentNotFound = true
+ }
+ return
+ }
+ if (reason) {
return
}
if (
@@ -366,11 +374,7 @@ export class FeedTuner {
): FeedViewPostsSlice[] => {
for (let i = 0; i < slices.length; i++) {
const slice = slices[i]
- if (
- slice.isReply &&
- !slice.isRepost &&
- !shouldDisplayReplyInFollowing(slice.getAuthors(), userDid)
- ) {
+ if (slice.isReply && !shouldDisplayReplyInFollowing(slice, userDid)) {
slices.splice(i, 1)
i--
}
@@ -434,9 +438,13 @@ function areSameAuthor(authors: AuthorContext): boolean {
}
function shouldDisplayReplyInFollowing(
- authors: AuthorContext,
+ slice: FeedViewPostsSlice,
userDid: string,
): boolean {
+ if (slice.isRepost) {
+ return true
+ }
+ const authors = slice.getAuthors()
const {author, parentAuthor, grandparentAuthor, rootAuthor} = authors
if (!isSelfOrFollowing(author, userDid)) {
// Only show replies from self or people you follow.
@@ -450,6 +458,21 @@ function shouldDisplayReplyInFollowing(
// Always show self-threads.
return true
}
+ if (
+ parentAuthor &&
+ parentAuthor.did !== author.did &&
+ rootAuthor &&
+ rootAuthor.did === author.did &&
+ slice.items.length > 2
+ ) {
+ // If you follow A, show A -> someone[>0 likes] -> A chains too.
+ // This is different from cases below because you only know one person.
+ const parentPost = slice.items[1].post
+ const parentLikeCount = parentPost.likeCount ?? 0
+ if (parentLikeCount > 0) {
+ return true
+ }
+ }
// From this point on we need at least one more reason to show it.
if (
parentAuthor &&
diff --git a/src/lib/api/index.ts b/src/lib/api/index.ts
index fa2e4ba6ce..f6537e3d1c 100644
--- a/src/lib/api/index.ts
+++ b/src/lib/api/index.ts
@@ -1,4 +1,5 @@
import {
+ AppBskyEmbedDefs,
AppBskyEmbedExternal,
AppBskyEmbedImages,
AppBskyEmbedRecord,
@@ -45,7 +46,12 @@ interface PostOpts {
uri: string
cid: string
}
- video?: BlobRef
+ video?: {
+ blobRef: BlobRef
+ altText: string
+ captions: {lang: string; file: File}[]
+ aspectRatio?: AppBskyEmbedDefs.AspectRatio
+ }
extLink?: ExternalEmbedDraft
images?: ImageModel[]
labels?: string[]
@@ -128,19 +134,35 @@ export async function post(agent: BskyAgent, opts: PostOpts) {
// add video embed if present
if (opts.video) {
+ const captions = await Promise.all(
+ opts.video.captions
+ .filter(caption => caption.lang !== '')
+ .map(async caption => {
+ const {data} = await agent.uploadBlob(caption.file, {
+ encoding: 'text/vtt',
+ })
+ return {lang: caption.lang, file: data.blob}
+ }),
+ )
if (opts.quote) {
embed = {
$type: 'app.bsky.embed.recordWithMedia',
record: embed,
media: {
$type: 'app.bsky.embed.video',
- video: opts.video,
+ video: opts.video.blobRef,
+ alt: opts.video.altText || undefined,
+ captions: captions.length === 0 ? undefined : captions,
+ aspectRatio: opts.video.aspectRatio,
} as AppBskyEmbedVideo.Main,
} as AppBskyEmbedRecordWithMedia.Main
} else {
embed = {
$type: 'app.bsky.embed.video',
- video: opts.video,
+ video: opts.video.blobRef,
+ alt: opts.video.altText || undefined,
+ captions: captions.length === 0 ? undefined : captions,
+ aspectRatio: opts.video.aspectRatio,
} as AppBskyEmbedVideo.Main
}
}
diff --git a/src/lib/custom-animations/CountWheel.tsx b/src/lib/custom-animations/CountWheel.tsx
new file mode 100644
index 0000000000..1a86767125
--- /dev/null
+++ b/src/lib/custom-animations/CountWheel.tsx
@@ -0,0 +1,177 @@
+import React from 'react'
+import {View} from 'react-native'
+import Animated, {
+ Easing,
+ LayoutAnimationConfig,
+ useReducedMotion,
+ withTiming,
+} from 'react-native-reanimated'
+import {i18n} from '@lingui/core'
+
+import {decideShouldRoll} from 'lib/custom-animations/util'
+import {s} from 'lib/styles'
+import {formatCount} from 'view/com/util/numeric/format'
+import {Text} from 'view/com/util/text/Text'
+import {atoms as a, useTheme} from '#/alf'
+
+const animationConfig = {
+ duration: 400,
+ easing: Easing.out(Easing.cubic),
+}
+
+function EnteringUp() {
+ 'worklet'
+ const animations = {
+ opacity: withTiming(1, animationConfig),
+ transform: [{translateY: withTiming(0, animationConfig)}],
+ }
+ const initialValues = {
+ opacity: 0,
+ transform: [{translateY: 18}],
+ }
+ return {
+ animations,
+ initialValues,
+ }
+}
+
+function EnteringDown() {
+ 'worklet'
+ const animations = {
+ opacity: withTiming(1, animationConfig),
+ transform: [{translateY: withTiming(0, animationConfig)}],
+ }
+ const initialValues = {
+ opacity: 0,
+ transform: [{translateY: -18}],
+ }
+ return {
+ animations,
+ initialValues,
+ }
+}
+
+function ExitingUp() {
+ 'worklet'
+ const animations = {
+ opacity: withTiming(0, animationConfig),
+ transform: [
+ {
+ translateY: withTiming(-18, animationConfig),
+ },
+ ],
+ }
+ const initialValues = {
+ opacity: 1,
+ transform: [{translateY: 0}],
+ }
+ return {
+ animations,
+ initialValues,
+ }
+}
+
+function ExitingDown() {
+ 'worklet'
+ const animations = {
+ opacity: withTiming(0, animationConfig),
+ transform: [{translateY: withTiming(18, animationConfig)}],
+ }
+ const initialValues = {
+ opacity: 1,
+ transform: [{translateY: 0}],
+ }
+ return {
+ animations,
+ initialValues,
+ }
+}
+
+export function CountWheel({
+ likeCount,
+ big,
+ isLiked,
+}: {
+ likeCount: number
+ big?: boolean
+ isLiked: boolean
+}) {
+ const t = useTheme()
+ const shouldAnimate = !useReducedMotion()
+ const shouldRoll = decideShouldRoll(isLiked, likeCount)
+
+ // Incrementing the key will cause the `Animated.View` to re-render, with the newly selected entering/exiting
+ // animation
+ // The initial entering/exiting animations will get skipped, since these will happen on screen mounts and would
+ // be unnecessary
+ const [key, setKey] = React.useState(0)
+ const [prevCount, setPrevCount] = React.useState(likeCount)
+ const prevIsLiked = React.useRef(isLiked)
+ const formattedCount = formatCount(i18n, likeCount)
+ const formattedPrevCount = formatCount(i18n, prevCount)
+
+ React.useEffect(() => {
+ if (isLiked === prevIsLiked.current) {
+ return
+ }
+
+ const newPrevCount = isLiked ? likeCount - 1 : likeCount + 1
+ setKey(prev => prev + 1)
+ setPrevCount(newPrevCount)
+ prevIsLiked.current = isLiked
+ }, [isLiked, likeCount])
+
+ const enteringAnimation =
+ shouldAnimate && shouldRoll
+ ? isLiked
+ ? EnteringUp
+ : EnteringDown
+ : undefined
+ const exitingAnimation =
+ shouldAnimate && shouldRoll
+ ? isLiked
+ ? ExitingUp
+ : ExitingDown
+ : undefined
+
+ return (
+
+ {likeCount > 0 ? (
+
+
+
+ {formattedCount}
+
+
+ {shouldAnimate && (likeCount > 1 || !isLiked) ? (
+
+
+ {formattedPrevCount}
+
+
+ ) : null}
+
+ ) : null}
+
+ )
+}
diff --git a/src/lib/custom-animations/CountWheel.web.tsx b/src/lib/custom-animations/CountWheel.web.tsx
new file mode 100644
index 0000000000..594117bfe0
--- /dev/null
+++ b/src/lib/custom-animations/CountWheel.web.tsx
@@ -0,0 +1,120 @@
+import React from 'react'
+import {View} from 'react-native'
+import {useReducedMotion} from 'react-native-reanimated'
+import {i18n} from '@lingui/core'
+
+import {decideShouldRoll} from 'lib/custom-animations/util'
+import {s} from 'lib/styles'
+import {formatCount} from 'view/com/util/numeric/format'
+import {Text} from 'view/com/util/text/Text'
+import {atoms as a, useTheme} from '#/alf'
+
+const animationConfig = {
+ duration: 400,
+ easing: 'cubic-bezier(0.4, 0, 0.2, 1)',
+ fill: 'forwards' as FillMode,
+}
+
+const enteringUpKeyframe = [
+ {opacity: 0, transform: 'translateY(18px)'},
+ {opacity: 1, transform: 'translateY(0)'},
+]
+
+const enteringDownKeyframe = [
+ {opacity: 0, transform: 'translateY(-18px)'},
+ {opacity: 1, transform: 'translateY(0)'},
+]
+
+const exitingUpKeyframe = [
+ {opacity: 1, transform: 'translateY(0)'},
+ {opacity: 0, transform: 'translateY(-18px)'},
+]
+
+const exitingDownKeyframe = [
+ {opacity: 1, transform: 'translateY(0)'},
+ {opacity: 0, transform: 'translateY(18px)'},
+]
+
+export function CountWheel({
+ likeCount,
+ big,
+ isLiked,
+}: {
+ likeCount: number
+ big?: boolean
+ isLiked: boolean
+}) {
+ const t = useTheme()
+ const shouldAnimate = !useReducedMotion()
+ const shouldRoll = decideShouldRoll(isLiked, likeCount)
+
+ const countView = React.useRef(null)
+ const prevCountView = React.useRef(null)
+
+ const [prevCount, setPrevCount] = React.useState(likeCount)
+ const prevIsLiked = React.useRef(isLiked)
+ const formattedCount = formatCount(i18n, likeCount)
+ const formattedPrevCount = formatCount(i18n, prevCount)
+
+ React.useEffect(() => {
+ if (isLiked === prevIsLiked.current) {
+ return
+ }
+
+ const newPrevCount = isLiked ? likeCount - 1 : likeCount + 1
+ if (shouldAnimate && shouldRoll) {
+ countView.current?.animate?.(
+ isLiked ? enteringUpKeyframe : enteringDownKeyframe,
+ animationConfig,
+ )
+ prevCountView.current?.animate?.(
+ isLiked ? exitingUpKeyframe : exitingDownKeyframe,
+ animationConfig,
+ )
+ setPrevCount(newPrevCount)
+ }
+ prevIsLiked.current = isLiked
+ }, [isLiked, likeCount, shouldAnimate, shouldRoll])
+
+ if (likeCount < 1) {
+ return null
+ }
+
+ return (
+
+
+
+ {formattedCount}
+
+
+ {shouldAnimate && (likeCount > 1 || !isLiked) ? (
+
+
+ {formattedPrevCount}
+
+
+ ) : null}
+
+ )
+}
diff --git a/src/lib/custom-animations/LikeIcon.tsx b/src/lib/custom-animations/LikeIcon.tsx
new file mode 100644
index 0000000000..f5802eccb4
--- /dev/null
+++ b/src/lib/custom-animations/LikeIcon.tsx
@@ -0,0 +1,135 @@
+import React from 'react'
+import {View} from 'react-native'
+import Animated, {
+ Keyframe,
+ LayoutAnimationConfig,
+ useReducedMotion,
+} from 'react-native-reanimated'
+
+import {s} from 'lib/styles'
+import {useTheme} from '#/alf'
+import {
+ Heart2_Filled_Stroke2_Corner0_Rounded as HeartIconFilled,
+ Heart2_Stroke2_Corner0_Rounded as HeartIconOutline,
+} from '#/components/icons/Heart2'
+
+const keyframe = new Keyframe({
+ 0: {
+ transform: [{scale: 1}],
+ },
+ 10: {
+ transform: [{scale: 0.7}],
+ },
+ 40: {
+ transform: [{scale: 1.2}],
+ },
+ 100: {
+ transform: [{scale: 1}],
+ },
+})
+
+const circle1Keyframe = new Keyframe({
+ 0: {
+ opacity: 0,
+ transform: [{scale: 0}],
+ },
+ 10: {
+ opacity: 0.4,
+ },
+ 40: {
+ transform: [{scale: 1.5}],
+ },
+ 95: {
+ opacity: 0.4,
+ },
+ 100: {
+ opacity: 0,
+ transform: [{scale: 1.5}],
+ },
+})
+
+const circle2Keyframe = new Keyframe({
+ 0: {
+ opacity: 0,
+ transform: [{scale: 0}],
+ },
+ 10: {
+ opacity: 1,
+ },
+ 40: {
+ transform: [{scale: 0}],
+ },
+ 95: {
+ opacity: 1,
+ },
+ 100: {
+ opacity: 0,
+ transform: [{scale: 1.5}],
+ },
+})
+
+export function AnimatedLikeIcon({
+ isLiked,
+ big,
+}: {
+ isLiked: boolean
+ big?: boolean
+}) {
+ const t = useTheme()
+ const size = big ? 22 : 18
+ const shouldAnimate = !useReducedMotion()
+
+ return (
+
+
+ {isLiked ? (
+
+
+
+ ) : (
+
+ )}
+ {isLiked ? (
+ <>
+
+
+ >
+ ) : null}
+
+
+ )
+}
diff --git a/src/lib/custom-animations/LikeIcon.web.tsx b/src/lib/custom-animations/LikeIcon.web.tsx
new file mode 100644
index 0000000000..6dc94c2917
--- /dev/null
+++ b/src/lib/custom-animations/LikeIcon.web.tsx
@@ -0,0 +1,117 @@
+import React from 'react'
+import {View} from 'react-native'
+import {useReducedMotion} from 'react-native-reanimated'
+
+import {s} from 'lib/styles'
+import {useTheme} from '#/alf'
+import {
+ Heart2_Filled_Stroke2_Corner0_Rounded as HeartIconFilled,
+ Heart2_Stroke2_Corner0_Rounded as HeartIconOutline,
+} from '#/components/icons/Heart2'
+
+const animationConfig = {
+ duration: 400,
+ easing: 'cubic-bezier(0.4, 0, 0.2, 1)',
+ fill: 'forwards' as FillMode,
+}
+
+const keyframe = [
+ {transform: 'scale(1)'},
+ {transform: 'scale(0.7)'},
+ {transform: 'scale(1.2)'},
+ {transform: 'scale(1)'},
+]
+
+const circle1Keyframe = [
+ {opacity: 0, transform: 'scale(0)'},
+ {opacity: 0.4},
+ {transform: 'scale(1.5)'},
+ {opacity: 0.4},
+ {opacity: 0, transform: 'scale(1.5)'},
+]
+
+const circle2Keyframe = [
+ {opacity: 0, transform: 'scale(0)'},
+ {opacity: 1},
+ {transform: 'scale(0)'},
+ {opacity: 1},
+ {opacity: 0, transform: 'scale(1.5)'},
+]
+
+export function AnimatedLikeIcon({
+ isLiked,
+ big,
+}: {
+ isLiked: boolean
+ big?: boolean
+}) {
+ const t = useTheme()
+ const size = big ? 22 : 18
+ const shouldAnimate = !useReducedMotion()
+ const prevIsLiked = React.useRef(isLiked)
+
+ const likeIconRef = React.useRef(null)
+ const circle1Ref = React.useRef(null)
+ const circle2Ref = React.useRef(null)
+
+ React.useEffect(() => {
+ if (prevIsLiked.current === isLiked) {
+ return
+ }
+
+ if (shouldAnimate && isLiked) {
+ likeIconRef.current?.animate?.(keyframe, animationConfig)
+ circle1Ref.current?.animate?.(circle1Keyframe, animationConfig)
+ circle2Ref.current?.animate?.(circle2Keyframe, animationConfig)
+ }
+ prevIsLiked.current = isLiked
+ }, [shouldAnimate, isLiked])
+
+ return (
+
+ {isLiked ? (
+ // @ts-expect-error is div
+
+
+
+ ) : (
+
+ )}
+
+
+
+ )
+}
diff --git a/src/lib/custom-animations/util.ts b/src/lib/custom-animations/util.ts
new file mode 100644
index 0000000000..0aebab57bb
--- /dev/null
+++ b/src/lib/custom-animations/util.ts
@@ -0,0 +1,21 @@
+// It should roll when:
+// - We're going from 1 to 0 (roll backwards)
+// - The count is anywhere between 1 and 999
+// - The count is going up and is a multiple of 100
+// - The count is going down and is 1 less than a multiple of 100
+export function decideShouldRoll(isSet: boolean, count: number) {
+ let shouldRoll = false
+ if (!isSet && count === 0) {
+ shouldRoll = true
+ } else if (count > 0 && count < 1000) {
+ shouldRoll = true
+ } else if (count > 0) {
+ const mod = count % 100
+ if (isSet && mod === 0) {
+ shouldRoll = true
+ } else if (!isSet && mod === 99) {
+ shouldRoll = true
+ }
+ }
+ return shouldRoll
+}
diff --git a/src/lib/generate-starterpack.ts b/src/lib/generate-starterpack.ts
index 64d30a954f..dba98b942a 100644
--- a/src/lib/generate-starterpack.ts
+++ b/src/lib/generate-starterpack.ts
@@ -65,7 +65,6 @@ export function useGenerateStarterPackMutation({
}) {
const {_} = useLingui()
const agent = useAgent()
- const starterPackString = _(msg`Starter Pack`)
return useMutation<{uri: string; cid: string}, Error, void>({
mutationFn: async () => {
@@ -106,7 +105,7 @@ export function useGenerateStarterPackMutation({
25,
true,
)
- const starterPackName = `${displayName}'s ${starterPackString}`
+ const starterPackName = _(msg`${displayName}'s Starter Pack`)
const list = await createStarterPackList({
name: starterPackName,
diff --git a/src/lib/hooks/__tests__/useTimeAgo.test.ts b/src/lib/hooks/__tests__/useTimeAgo.test.ts
index e74f9c62db..68eb6e43a9 100644
--- a/src/lib/hooks/__tests__/useTimeAgo.test.ts
+++ b/src/lib/hooks/__tests__/useTimeAgo.test.ts
@@ -1,102 +1,213 @@
import {describe, expect, it} from '@jest/globals'
-import {MessageDescriptor} from '@lingui/core'
import {addDays, subDays, subHours, subMinutes, subSeconds} from 'date-fns'
import {dateDiff} from '../useTimeAgo'
-const lingui: any = (obj: MessageDescriptor) => obj.message
-
const base = new Date('2024-06-17T00:00:00Z')
describe('dateDiff', () => {
it(`works with numbers`, () => {
- expect(dateDiff(subDays(base, 3), Number(base), {lingui})).toEqual('3d')
+ const earlier = subDays(base, 3)
+ expect(dateDiff(earlier, Number(base))).toEqual({
+ value: 3,
+ unit: 'day',
+ earlier,
+ later: base,
+ })
})
it(`works with strings`, () => {
- expect(dateDiff(subDays(base, 3), base.toString(), {lingui})).toEqual('3d')
+ const earlier = subDays(base, 3)
+ expect(dateDiff(earlier, base.toString())).toEqual({
+ value: 3,
+ unit: 'day',
+ earlier,
+ later: base,
+ })
})
it(`works with dates`, () => {
- expect(dateDiff(subDays(base, 3), base, {lingui})).toEqual('3d')
+ const earlier = subDays(base, 3)
+ expect(dateDiff(earlier, base)).toEqual({
+ value: 3,
+ unit: 'day',
+ earlier,
+ later: base,
+ })
})
it(`equal values return now`, () => {
- expect(dateDiff(base, base, {lingui})).toEqual('now')
+ expect(dateDiff(base, base)).toEqual({
+ value: 0,
+ unit: 'now',
+ earlier: base,
+ later: base,
+ })
})
it(`future dates return now`, () => {
- expect(dateDiff(addDays(base, 3), base, {lingui})).toEqual('now')
+ const earlier = addDays(base, 3)
+ expect(dateDiff(earlier, base)).toEqual({
+ value: 0,
+ unit: 'now',
+ earlier,
+ later: base,
+ })
})
it(`values < 5 seconds ago return now`, () => {
const then = subSeconds(base, 4)
- expect(dateDiff(then, base, {lingui})).toEqual('now')
+ expect(dateDiff(then, base)).toEqual({
+ value: 0,
+ unit: 'now',
+ earlier: then,
+ later: base,
+ })
})
it(`values >= 5 seconds ago return seconds`, () => {
const then = subSeconds(base, 5)
- expect(dateDiff(then, base, {lingui})).toEqual('5s')
+ expect(dateDiff(then, base)).toEqual({
+ value: 5,
+ unit: 'second',
+ earlier: then,
+ later: base,
+ })
})
it(`values < 1 min return seconds`, () => {
const then = subSeconds(base, 59)
- expect(dateDiff(then, base, {lingui})).toEqual('59s')
+ expect(dateDiff(then, base)).toEqual({
+ value: 59,
+ unit: 'second',
+ earlier: then,
+ later: base,
+ })
})
it(`values >= 1 min return minutes`, () => {
const then = subSeconds(base, 60)
- expect(dateDiff(then, base, {lingui})).toEqual('1m')
+ expect(dateDiff(then, base)).toEqual({
+ value: 1,
+ unit: 'minute',
+ earlier: then,
+ later: base,
+ })
})
it(`minutes round down`, () => {
const then = subSeconds(base, 119)
- expect(dateDiff(then, base, {lingui})).toEqual('1m')
+ expect(dateDiff(then, base)).toEqual({
+ value: 1,
+ unit: 'minute',
+ earlier: then,
+ later: base,
+ })
})
it(`values < 1 hour return minutes`, () => {
const then = subMinutes(base, 59)
- expect(dateDiff(then, base, {lingui})).toEqual('59m')
+ expect(dateDiff(then, base)).toEqual({
+ value: 59,
+ unit: 'minute',
+ earlier: then,
+ later: base,
+ })
})
it(`values >= 1 hour return hours`, () => {
const then = subMinutes(base, 60)
- expect(dateDiff(then, base, {lingui})).toEqual('1h')
+ expect(dateDiff(then, base)).toEqual({
+ value: 1,
+ unit: 'hour',
+ earlier: then,
+ later: base,
+ })
})
it(`hours round down`, () => {
const then = subMinutes(base, 119)
- expect(dateDiff(then, base, {lingui})).toEqual('1h')
+ expect(dateDiff(then, base)).toEqual({
+ value: 1,
+ unit: 'hour',
+ earlier: then,
+ later: base,
+ })
})
it(`values < 1 day return hours`, () => {
const then = subHours(base, 23)
- expect(dateDiff(then, base, {lingui})).toEqual('23h')
+ expect(dateDiff(then, base)).toEqual({
+ value: 23,
+ unit: 'hour',
+ earlier: then,
+ later: base,
+ })
})
it(`values >= 1 day return days`, () => {
const then = subHours(base, 24)
- expect(dateDiff(then, base, {lingui})).toEqual('1d')
+ expect(dateDiff(then, base)).toEqual({
+ value: 1,
+ unit: 'day',
+ earlier: then,
+ later: base,
+ })
})
it(`days round down`, () => {
const then = subHours(base, 47)
- expect(dateDiff(then, base, {lingui})).toEqual('1d')
+ expect(dateDiff(then, base)).toEqual({
+ value: 1,
+ unit: 'day',
+ earlier: then,
+ later: base,
+ })
})
it(`values < 30 days return days`, () => {
const then = subDays(base, 29)
- expect(dateDiff(then, base, {lingui})).toEqual('29d')
+ expect(dateDiff(then, base)).toEqual({
+ value: 29,
+ unit: 'day',
+ earlier: then,
+ later: base,
+ })
})
it(`values >= 30 days return months`, () => {
const then = subDays(base, 30)
- expect(dateDiff(then, base, {lingui})).toEqual('1mo')
+ expect(dateDiff(then, base)).toEqual({
+ value: 1,
+ unit: 'month',
+ earlier: then,
+ later: base,
+ })
})
it(`months round down`, () => {
const then = subDays(base, 59)
- expect(dateDiff(then, base, {lingui})).toEqual('1mo')
+ expect(dateDiff(then, base)).toEqual({
+ value: 1,
+ unit: 'month',
+ earlier: then,
+ later: base,
+ })
})
it(`values are rounded by increments of 30`, () => {
const then = subDays(base, 61)
- expect(dateDiff(then, base, {lingui})).toEqual('2mo')
+ expect(dateDiff(then, base)).toEqual({
+ value: 2,
+ unit: 'month',
+ earlier: then,
+ later: base,
+ })
})
it(`values < 360 days return months`, () => {
const then = subDays(base, 359)
- expect(dateDiff(then, base, {lingui})).toEqual('11mo')
+ expect(dateDiff(then, base)).toEqual({
+ value: 11,
+ unit: 'month',
+ earlier: then,
+ later: base,
+ })
})
it(`values >= 360 days return the earlier value`, () => {
const then = subDays(base, 360)
- expect(dateDiff(then, base, {lingui})).toEqual(then.toLocaleDateString())
+ expect(dateDiff(then, base)).toEqual({
+ value: 12,
+ unit: 'month',
+ earlier: then,
+ later: base,
+ })
})
})
diff --git a/src/lib/hooks/useInitialNumToRender.ts b/src/lib/hooks/useInitialNumToRender.ts
index 82bc89c0f8..f729cbffa6 100644
--- a/src/lib/hooks/useInitialNumToRender.ts
+++ b/src/lib/hooks/useInitialNumToRender.ts
@@ -15,5 +15,10 @@ export function useInitialNumToRender({
const finalHeight =
screenHeight - screenHeightOffset - topInset - bottomBarHeight
- return Math.floor(finalHeight / minItemHeight) + 1
+
+ const minItems = Math.floor(finalHeight / minItemHeight)
+ if (minItems < 1) {
+ return 1
+ }
+ return minItems
}
diff --git a/src/lib/hooks/useTimeAgo.ts b/src/lib/hooks/useTimeAgo.ts
index efcb4754bb..3a8bf49bc6 100644
--- a/src/lib/hooks/useTimeAgo.ts
+++ b/src/lib/hooks/useTimeAgo.ts
@@ -1,25 +1,16 @@
import {useCallback} from 'react'
-import {msg, plural} from '@lingui/macro'
-import {I18nContext, useLingui} from '@lingui/react'
+import {I18n} from '@lingui/core'
+import {defineMessage, msg, plural} from '@lingui/macro'
+import {useLingui} from '@lingui/react'
import {differenceInSeconds} from 'date-fns'
-export type TimeAgoOptions = {
- lingui: I18nContext['_']
- format?: 'long' | 'short'
-}
+export type DateDiffFormat = 'long' | 'short'
-export function useGetTimeAgo() {
- const {_} = useLingui()
- return useCallback(
- (
- earlier: number | string | Date,
- later: number | string | Date,
- options?: Omit,
- ) => {
- return dateDiff(earlier, later, {lingui: _, format: options?.format})
- },
- [_],
- )
+type DateDiff = {
+ value: number
+ unit: 'now' | 'second' | 'minute' | 'hour' | 'day' | 'month'
+ earlier: Date
+ later: Date
}
const NOW = 5
@@ -28,59 +19,160 @@ const HOUR = MINUTE * 60
const DAY = HOUR * 24
const MONTH_30 = DAY * 30
+export function useGetTimeAgo() {
+ const {i18n} = useLingui()
+ return useCallback(
+ (
+ earlier: number | string | Date,
+ later: number | string | Date,
+ options?: {format: DateDiffFormat},
+ ) => {
+ const diff = dateDiff(earlier, later)
+ return formatDateDiff({diff, i18n, format: options?.format})
+ },
+ [i18n],
+ )
+}
+
/**
- * Returns the difference between `earlier` and `later` dates, formatted as a
- * natural language string.
+ * Returns the difference between `earlier` and `later` dates, based on
+ * opinionated rules.
+ *
+ * - All month are considered exactly 30 days.
+ * - Dates assume `earlier` <= `later`, and will otherwise return 'now'.
+ * - All values round down
+ */
+export function dateDiff(
+ earlier: number | string | Date,
+ later: number | string | Date,
+): DateDiff {
+ let diff = {
+ value: 0,
+ unit: 'now' as DateDiff['unit'],
+ }
+ const e = new Date(earlier)
+ const l = new Date(later)
+ const diffSeconds = differenceInSeconds(l, e)
+
+ if (diffSeconds < NOW) {
+ diff = {
+ value: 0,
+ unit: 'now' as DateDiff['unit'],
+ }
+ } else if (diffSeconds < MINUTE) {
+ diff = {
+ value: diffSeconds,
+ unit: 'second' as DateDiff['unit'],
+ }
+ } else if (diffSeconds < HOUR) {
+ const value = Math.floor(diffSeconds / MINUTE)
+ diff = {
+ value,
+ unit: 'minute' as DateDiff['unit'],
+ }
+ } else if (diffSeconds < DAY) {
+ const value = Math.floor(diffSeconds / HOUR)
+ diff = {
+ value,
+ unit: 'hour' as DateDiff['unit'],
+ }
+ } else if (diffSeconds < MONTH_30) {
+ const value = Math.floor(diffSeconds / DAY)
+ diff = {
+ value,
+ unit: 'day' as DateDiff['unit'],
+ }
+ } else {
+ const value = Math.floor(diffSeconds / MONTH_30)
+ diff = {
+ value,
+ unit: 'month' as DateDiff['unit'],
+ }
+ }
+
+ return {
+ ...diff,
+ earlier: e,
+ later: l,
+ }
+}
+
+/**
+ * Accepts a `DateDiff` and teturns the difference between `earlier` and
+ * `later` dates, formatted as a natural language string.
*
* - All month are considered exactly 30 days.
* - Dates assume `earlier` <= `later`, and will otherwise return 'now'.
* - Differences >= 360 days are returned as the "M/D/YYYY" string
* - All values round down
*/
-export function dateDiff(
- earlier: number | string | Date,
- later: number | string | Date,
- options: TimeAgoOptions,
-): string {
- const _ = options.lingui
- const format = options?.format || 'short'
+export function formatDateDiff({
+ diff,
+ format = 'short',
+ i18n,
+}: {
+ diff: DateDiff
+ format?: DateDiffFormat
+ i18n: I18n
+}): string {
const long = format === 'long'
- const diffSeconds = differenceInSeconds(new Date(later), new Date(earlier))
- if (diffSeconds < NOW) {
- return _(msg`now`)
- } else if (diffSeconds < MINUTE) {
- return `${diffSeconds}${
- long ? ` ${plural(diffSeconds, {one: 'second', other: 'seconds'})}` : 's'
- }`
- } else if (diffSeconds < HOUR) {
- const diff = Math.floor(diffSeconds / MINUTE)
- return `${diff}${
- long ? ` ${plural(diff, {one: 'minute', other: 'minutes'})}` : 'm'
- }`
- } else if (diffSeconds < DAY) {
- const diff = Math.floor(diffSeconds / HOUR)
- return `${diff}${
- long ? ` ${plural(diff, {one: 'hour', other: 'hours'})}` : 'h'
- }`
- } else if (diffSeconds < MONTH_30) {
- const diff = Math.floor(diffSeconds / DAY)
- return `${diff}${
- long ? ` ${plural(diff, {one: 'day', other: 'days'})}` : 'd'
- }`
- } else {
- const diff = Math.floor(diffSeconds / MONTH_30)
- if (diff < 12) {
- return `${diff}${
- long ? ` ${plural(diff, {one: 'month', other: 'months'})}` : 'mo'
- }`
- } else {
- const str = new Date(earlier).toLocaleDateString()
-
- if (long) {
- return _(msg`on ${str}`)
+ switch (diff.unit) {
+ case 'now': {
+ return i18n._(msg`now`)
+ }
+ case 'second': {
+ return long
+ ? i18n._(plural(diff.value, {one: '# second', other: '# seconds'}))
+ : i18n._(
+ defineMessage({
+ message: `${diff.value}s`,
+ comment: `How many seconds have passed, displayed in a narrow form`,
+ }),
+ )
+ }
+ case 'minute': {
+ return long
+ ? i18n._(plural(diff.value, {one: '# minute', other: '# minutes'}))
+ : i18n._(
+ defineMessage({
+ message: `${diff.value}m`,
+ comment: `How many minutes have passed, displayed in a narrow form`,
+ }),
+ )
+ }
+ case 'hour': {
+ return long
+ ? i18n._(plural(diff.value, {one: '# hour', other: '# hours'}))
+ : i18n._(
+ defineMessage({
+ message: `${diff.value}h`,
+ comment: `How many hours have passed, displayed in a narrow form`,
+ }),
+ )
+ }
+ case 'day': {
+ return long
+ ? i18n._(plural(diff.value, {one: '# day', other: '# days'}))
+ : i18n._(
+ defineMessage({
+ message: `${diff.value}d`,
+ comment: `How many days have passed, displayed in a narrow form`,
+ }),
+ )
+ }
+ case 'month': {
+ if (diff.value < 12) {
+ return long
+ ? i18n._(plural(diff.value, {one: '# month', other: '# months'}))
+ : i18n._(
+ defineMessage({
+ message: `${diff.value}mo`,
+ comment: `How many months have passed, displayed in a narrow form`,
+ }),
+ )
}
- return str
+ return i18n.date(new Date(diff.earlier))
}
}
}
diff --git a/src/lib/media/video/compress.ts b/src/lib/media/video/compress.ts
index 9576175962..709f2a77a7 100644
--- a/src/lib/media/video/compress.ts
+++ b/src/lib/media/video/compress.ts
@@ -1,9 +1,6 @@
import {getVideoMetaData, Video} from 'react-native-compressor'
-export type CompressedVideo = {
- uri: string
- size: number
-}
+import {CompressedVideo} from './types'
export async function compressVideo(
file: string,
diff --git a/src/lib/media/video/compress.web.ts b/src/lib/media/video/compress.web.ts
index 11ccb51041..c087025349 100644
--- a/src/lib/media/video/compress.web.ts
+++ b/src/lib/media/video/compress.web.ts
@@ -1,12 +1,8 @@
import {VideoTooLargeError} from 'lib/media/video/errors'
+import {CompressedVideo} from './types'
const MAX_VIDEO_SIZE = 1024 * 1024 * 100 // 100MB
-export type CompressedVideo = {
- uri: string
- size: number
-}
-
// doesn't actually compress, but throws if >100MB
export async function compressVideo(
file: string,
@@ -15,8 +11,9 @@ export async function compressVideo(
onProgress?: (progress: number) => void
},
): Promise {
- const blob = await fetch(file).then(res => res.blob())
- const video = URL.createObjectURL(blob)
+ const {mimeType, base64} = parseDataUrl(file)
+ const blob = base64ToBlob(base64, mimeType)
+ const uri = URL.createObjectURL(blob)
if (blob.size > MAX_VIDEO_SIZE) {
throw new VideoTooLargeError()
@@ -24,6 +21,34 @@ export async function compressVideo(
return {
size: blob.size,
- uri: video,
+ uri,
+ bytes: await blob.arrayBuffer(),
}
}
+
+function parseDataUrl(dataUrl: string) {
+ const [mimeType, base64] = dataUrl.slice('data:'.length).split(';base64,')
+ if (!mimeType || !base64) {
+ throw new Error('Invalid data URL')
+ }
+ return {mimeType, base64}
+}
+
+function base64ToBlob(base64: string, mimeType: string) {
+ const byteCharacters = atob(base64)
+ const byteArrays = []
+
+ for (let offset = 0; offset < byteCharacters.length; offset += 512) {
+ const slice = byteCharacters.slice(offset, offset + 512)
+ const byteNumbers = new Array(slice.length)
+
+ for (let i = 0; i < slice.length; i++) {
+ byteNumbers[i] = slice.charCodeAt(i)
+ }
+
+ const byteArray = new Uint8Array(byteNumbers)
+ byteArrays.push(byteArray)
+ }
+
+ return new Blob(byteArrays, {type: mimeType})
+}
diff --git a/src/lib/media/video/types.ts b/src/lib/media/video/types.ts
new file mode 100644
index 0000000000..ba0070054d
--- /dev/null
+++ b/src/lib/media/video/types.ts
@@ -0,0 +1,6 @@
+export type CompressedVideo = {
+ uri: string
+ size: number
+ // web only, can fall back to uri if missing
+ bytes?: ArrayBuffer
+}
diff --git a/src/lib/moderation/useLabelInfo.ts b/src/lib/moderation/useLabelInfo.ts
index b1cffe1e71..0ff7e1246a 100644
--- a/src/lib/moderation/useLabelInfo.ts
+++ b/src/lib/moderation/useLabelInfo.ts
@@ -1,9 +1,9 @@
import {
- ComAtprotoLabelDefs,
AppBskyLabelerDefs,
- LABELS,
- interpretLabelValueDefinition,
+ ComAtprotoLabelDefs,
InterpretedLabelValueDefinition,
+ interpretLabelValueDefinition,
+ LABELS,
} from '@atproto/api'
import {useLingui} from '@lingui/react'
import * as bcp47Match from 'bcp-47-match'
diff --git a/src/lib/statsig/gates.ts b/src/lib/statsig/gates.ts
index d4478477b3..be40548adf 100644
--- a/src/lib/statsig/gates.ts
+++ b/src/lib/statsig/gates.ts
@@ -4,5 +4,7 @@ export type Gate =
| 'fixed_bottom_bar'
| 'onboarding_minimum_interests'
| 'suggested_feeds_interstitial'
- | 'video_debug'
- | 'videos'
+ | 'show_follow_suggestions_in_profile'
+ | 'video_debug' // not recommended
+ | 'video_upload' // upload videos
+ | 'video_view_on_posts' // see posted videos
diff --git a/src/lib/strings/helpers.ts b/src/lib/strings/helpers.ts
index b4ce64fa5f..acd55da2d2 100644
--- a/src/lib/strings/helpers.ts
+++ b/src/lib/strings/helpers.ts
@@ -1,3 +1,6 @@
+import {useCallback, useMemo} from 'react'
+import Graphemer from 'graphemer'
+
export function enforceLen(
str: string,
len: number,
@@ -23,6 +26,21 @@ export function enforceLen(
return str
}
+export function useEnforceMaxGraphemeCount() {
+ const splitter = useMemo(() => new Graphemer(), [])
+
+ return useCallback(
+ (text: string, maxCount: number) => {
+ if (splitter.countGraphemes(text) > maxCount) {
+ return splitter.splitGraphemes(text).slice(0, maxCount).join('')
+ } else {
+ return text
+ }
+ },
+ [splitter],
+ )
+}
+
// https://stackoverflow.com/a/52171480
export function toHashCode(str: string, seed = 0): number {
let h1 = 0xdeadbeef ^ seed,
diff --git a/src/lib/strings/time.ts b/src/lib/strings/time.ts
index bfefea9bc3..e505b7892e 100644
--- a/src/lib/strings/time.ts
+++ b/src/lib/strings/time.ts
@@ -1,13 +1,12 @@
-export function niceDate(date: number | string | Date) {
+import {I18n} from '@lingui/core'
+
+export function niceDate(i18n: I18n, date: number | string | Date) {
const d = new Date(date)
- return `${d.toLocaleDateString('en-us', {
- year: 'numeric',
- month: 'short',
- day: 'numeric',
- })} at ${d.toLocaleTimeString(undefined, {
- hour: 'numeric',
- minute: '2-digit',
- })}`
+
+ return i18n.date(d, {
+ dateStyle: 'long',
+ timeStyle: 'short',
+ })
}
export function getAge(birthDate: Date): number {
diff --git a/src/lib/strings/url-helpers.ts b/src/lib/strings/url-helpers.ts
index 95c6bceadb..4c8db83999 100644
--- a/src/lib/strings/url-helpers.ts
+++ b/src/lib/strings/url-helpers.ts
@@ -340,7 +340,7 @@ export function shortLinkToHref(url: string): string {
}
}
-export function getHostnameFromUrl(url: string): string | null {
+export function getHostnameFromUrl(url: string | URL): string | null {
let urlp
try {
urlp = new URL(url)
@@ -350,7 +350,7 @@ export function getHostnameFromUrl(url: string): string | null {
return urlp.hostname
}
-export function getServiceAuthAudFromUrl(url: string): string | null {
+export function getServiceAuthAudFromUrl(url: string | URL): string | null {
const hostname = getHostnameFromUrl(url)
if (!hostname) {
return null
diff --git a/src/locale/locales/pt-BR/messages.po b/src/locale/locales/pt-BR/messages.po
index 9093609966..aa6258cf3b 100644
--- a/src/locale/locales/pt-BR/messages.po
+++ b/src/locale/locales/pt-BR/messages.po
@@ -9,13 +9,13 @@ msgstr ""
"Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n"
"PO-Revision-Date: 2024-05-13 11:41\n"
-"Last-Translator: gildaswise\n"
-"Language-Team: maisondasilva, MightyLoggor, gildaswise, gleydson, faeriarum\n"
+"Last-Translator: fabiohcnobre\n"
+"Language-Team: maisondasilva, MightyLoggor, gildaswise, gleydson, faeriarum, fabiohcnobre\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: src/screens/Messages/List/ChatListItem.tsx:120
msgid "(contains embedded content)"
-msgstr ""
+msgstr "(contém conteúdo incorporado)"
#: src/view/com/modals/VerifyEmail.tsx:150
msgid "(no email)"
@@ -105,11 +105,11 @@ msgstr ""
#: src/screens/StarterPack/StarterPackLandingScreen.tsx:228
msgid "{0} joined this week"
-msgstr ""
+msgstr "{0} entrou esta semana"
#: src/screens/StarterPack/StarterPackScreen.tsx:467
msgid "{0} people have used this starter pack!"
-msgstr ""
+msgstr "{0} pessoas já usaram este pacote inicial!"
#: src/view/screens/ProfileList.tsx:286
#~ msgid "{0} your feeds"
@@ -117,15 +117,15 @@ msgstr ""
#: src/view/com/util/UserAvatar.tsx:419
msgid "{0}'s avatar"
-msgstr ""
+msgstr "Avatar de {0}"
#: src/screens/StarterPack/Wizard/StepDetails.tsx:68
msgid "{0}'s favorite feeds and people - join me!"
-msgstr ""
+msgstr "Os feeds e pessoas favoritas de {0} - junte-se a mim!"
#: src/screens/StarterPack/Wizard/StepDetails.tsx:47
msgid "{0}'s starter pack"
-msgstr ""
+msgstr "O kit inicial de {0}"
#: src/components/LabelingServiceCard/index.tsx:71
msgid "{count, plural, one {Liked by # user} other {Liked by # users}}"
@@ -153,7 +153,7 @@ msgstr ""
#: src/screens/StarterPack/Wizard/index.tsx:174
msgid "{displayName}'s Starter Pack"
-msgstr ""
+msgstr "O Kit Inicial de {displayName}"
#: src/screens/SignupQueued.tsx:207
msgid "{estimatedTimeHrs, plural, one {hour} other {hours}}"
@@ -170,7 +170,7 @@ msgstr "{following} seguindo"
#: src/components/dms/dialogs/SearchablePeopleList.tsx:405
msgid "{handle} can't be messaged"
-msgstr ""
+msgstr "{handle} não pode receber mensagens"
#: src/screens/Profile/Header/ProfileHeaderLabeler.tsx:286
#: src/screens/Profile/Header/ProfileHeaderLabeler.tsx:299
@@ -205,12 +205,12 @@ msgstr ""
#: src/screens/StarterPack/Wizard/index.tsx:466
msgctxt "profiles"
msgid "<0>{0}, 0><1>{1}, 1>and {2, plural, one {# other} other {# others}} are included in your starter pack"
-msgstr ""
+msgstr "<0>{0}, 0><1>{1}, 1>e {2, plural, one {# outro} other {# outros}} estão incluídos no seu kit inicial"
#: src/screens/StarterPack/Wizard/index.tsx:519
msgctxt "feeds"
msgid "<0>{0}, 0><1>{1}, 1>and {2, plural, one {# other} other {# others}} are included in your starter pack"
-msgstr ""
+msgstr "<0>{0}, 0><1>{1}, 1>e {2, plural, one {# outro} other {# outros}} estão incluídos no seu kit inicial"
#: src/screens/StarterPack/Wizard/index.tsx:497
#~ msgid "<0>{0}, 0><1>{1}, 1>and {2} {3, plural, one {other} other {others}} are included in your starter pack"
@@ -226,7 +226,7 @@ msgstr "<0>{0}0> {1, plural, one {seguindo} other {seguindo}}"
#: src/screens/StarterPack/Wizard/index.tsx:507
msgid "<0>{0}0> and<1> 1><2>{1} 2>are included in your starter pack"
-msgstr ""
+msgstr "<0>{0}0> e<1> 1><2>{1} 2>estão incluídos no seu kit inicial"
#: src/view/shell/Drawer.tsx:96
#~ msgid "<0>{0}0> following"
@@ -234,11 +234,11 @@ msgstr ""
#: src/screens/StarterPack/Wizard/index.tsx:500
msgid "<0>{0}0> is included in your starter pack"
-msgstr ""
+msgstr "<0>{0}0> está incluído no seu kit inicial"
#: src/components/WhoCanReply.tsx:274
msgid "<0>{0}0> members"
-msgstr ""
+msgstr "<0>{0}0> membros"
#: src/components/ProfileHoverCard/index.web.tsx:437
#~ msgid "<0>{followers} 0><1>{pluralizedFollowers}1>"
@@ -267,7 +267,7 @@ msgstr "<0>Não se aplica.0> Este aviso só funciona para posts com mídia."
#: src/screens/StarterPack/Wizard/index.tsx:457
msgid "<0>You0> and<1> 1><2>{0} 2>are included in your starter pack"
-msgstr ""
+msgstr "<0>Você0> e<1> 1><2>{0} 2>estão incluídos no seu kit inicial"
#: src/screens/Profile/Header/Handle.tsx:50
msgid "⚠Invalid Handle"
@@ -275,7 +275,7 @@ msgstr "⚠Usuário Inválido"
#: src/components/dialogs/MutedWords.tsx:193
msgid "24 hours"
-msgstr ""
+msgstr "24 horas"
#: src/screens/Login/LoginForm.tsx:266
msgid "2FA Confirmation"
@@ -283,15 +283,15 @@ msgstr "Confirmação do 2FA"
#: src/components/dialogs/MutedWords.tsx:232
msgid "30 days"
-msgstr ""
+msgstr "30 dias"
#: src/components/dialogs/MutedWords.tsx:217
msgid "7 days"
-msgstr ""
+msgstr "7 dias"
#: src/tours/Tooltip.tsx:70
msgid "A help tooltip"
-msgstr ""
+msgstr "Uma sugestão de ajuda"
#: src/view/com/util/ViewHeader.tsx:92
#: src/view/screens/Search/Search.tsx:684
@@ -377,11 +377,11 @@ msgstr "Adicionar"
#: src/screens/StarterPack/Wizard/index.tsx:568
msgid "Add {0} more to continue"
-msgstr ""
+msgstr "Adicione mais {0} para continuar"
#: src/components/StarterPack/Wizard/WizardListCard.tsx:59
msgid "Add {displayName} to starter pack"
-msgstr ""
+msgstr "Adicione {displayName} ao kit inicial"
#: src/view/com/modals/SelfLabel.tsx:57
msgid "Add a content warning"
@@ -435,7 +435,7 @@ msgstr "Adicionar palavras/tags silenciadas"
#: src/screens/StarterPack/Wizard/index.tsx:197
#~ msgid "Add people to your starter pack that you think others will enjoy following"
-#~ msgstr ""
+#~ msgstr "Adicione pessoas ao seu kit inicial que você acha que outros gostarão de seguir"
#: src/screens/Home/NoFeedsPinned.tsx:99
msgid "Add recommended feeds"
@@ -443,7 +443,7 @@ msgstr "Utilizar feeds recomendados"
#: src/screens/StarterPack/Wizard/index.tsx:488
msgid "Add some feeds to your starter pack!"
-msgstr ""
+msgstr "Adicione alguns feeds ao seu kit inicial!"
#: src/screens/Feeds/NoFollowingFeed.tsx:41
msgid "Add the default feed of only people you follow"
@@ -455,7 +455,7 @@ msgstr "Adicione o seguinte registro DNS ao seu domínio:"
#: src/components/FeedCard.tsx:293
msgid "Add this feed to your feeds"
-msgstr ""
+msgstr "Adicione este feed aos seus feeds"
#: src/view/com/profile/ProfileMenu.tsx:267
#: src/view/com/profile/ProfileMenu.tsx:270
@@ -491,7 +491,7 @@ msgstr "Conteúdo Adulto"
#: src/screens/Moderation/index.tsx:365
msgid "Adult content can only be enabled via the Web at <0>bsky.app0>."
-msgstr ""
+msgstr "Conteúdo adulto só pode ser habilitado pela web em <0>bsky.app0>."
#: src/components/moderation/LabelPreference.tsx:242
msgid "Adult content is disabled."
@@ -504,11 +504,11 @@ msgstr "Avançado"
#: src/state/shell/progress-guide.tsx:171
msgid "Algorithm training complete!"
-msgstr ""
+msgstr "Treinamento de algoritmo concluído!"
#: src/screens/StarterPack/StarterPackScreen.tsx:370
msgid "All accounts have been followed!"
-msgstr ""
+msgstr "Todas as contas foram seguidas!"
#: src/view/screens/Feeds.tsx:733
msgid "All the feeds you've saved, right in one place."
@@ -517,25 +517,25 @@ msgstr "Todos os feeds que você salvou, em um único lugar."
#: src/view/com/modals/AddAppPasswords.tsx:188
#: src/view/com/modals/AddAppPasswords.tsx:195
msgid "Allow access to your direct messages"
-msgstr ""
+msgstr "Permitir acesso às suas mensagens diretas"
#: src/screens/Messages/Settings.tsx:61
#: src/screens/Messages/Settings.tsx:64
#~ msgid "Allow messages from"
-#~ msgstr ""
+#~ msgstr "Permitir mensagens de"
#: src/screens/Messages/Settings.tsx:62
#: src/screens/Messages/Settings.tsx:65
msgid "Allow new messages from"
-msgstr ""
+msgstr "Permitir novas mensagens de"
#: src/components/dialogs/PostInteractionSettingsDialog.tsx:359
msgid "Allow replies from:"
-msgstr ""
+msgstr "Permitir respostas de:"
#: src/view/screens/AppPasswords.tsx:271
msgid "Allows access to direct messages"
-msgstr ""
+msgstr "Permite acesso a mensagens diretas"
#: src/screens/Login/ForgotPasswordForm.tsx:178
#: src/view/com/modals/ChangePassword.tsx:171
@@ -577,7 +577,7 @@ msgstr "Um email foi enviado para seu email anterior, {0}. Ele inclui um código
#: src/components/dialogs/GifSelect.tsx:254
msgid "An error has occurred"
-msgstr ""
+msgstr "Ocorreu um erro"
#: src/components/dialogs/GifSelect.tsx:252
#~ msgid "An error occured"
@@ -585,25 +585,25 @@ msgstr ""
#: src/view/com/util/post-embeds/VideoEmbedInner/VideoWebControls.tsx:314
msgid "An error occurred"
-msgstr ""
+msgstr "Ocorreu um erro"
#: src/components/StarterPack/ProfileStarterPacks.tsx:315
msgid "An error occurred while generating your starter pack. Want to try again?"
-msgstr ""
+msgstr "Ocorreu um erro ao gerar seu kit inicial. Quer tentar novamente?"
#: src/view/com/util/post-embeds/VideoEmbed.tsx:69
#: src/view/com/util/post-embeds/VideoEmbed.web.tsx:150
msgid "An error occurred while loading the video. Please try again later."
-msgstr ""
+msgstr "Ocorreu um erro ao carregar o vídeo. Tente novamente mais tarde."
#: src/components/StarterPack/ShareDialog.tsx:79
#~ msgid "An error occurred while saving the image."
-#~ msgstr ""
+#~ msgstr "Ocorreu um erro ao salvar a imagem."
#: src/components/StarterPack/QrCodeDialog.tsx:71
#: src/components/StarterPack/ShareDialog.tsx:79
msgid "An error occurred while saving the QR code!"
-msgstr ""
+msgstr "Ocorreu um erro ao salvar o QR code!"
#: src/components/dms/MessageMenu.tsx:134
#~ msgid "An error occurred while trying to delete the message. Please try again."
@@ -612,11 +612,11 @@ msgstr ""
#: src/screens/StarterPack/StarterPackScreen.tsx:336
#: src/screens/StarterPack/StarterPackScreen.tsx:358
msgid "An error occurred while trying to follow all"
-msgstr ""
+msgstr "Ocorreu um erro ao tentar seguir todos"
#: src/state/queries/video/video.ts:112
msgid "An error occurred while uploading the video."
-msgstr ""
+msgstr "Ocorreu um erro ao enviar o vídeo."
#: src/lib/moderation/useReportOptions.ts:28
msgid "An issue not included in these options"
@@ -624,11 +624,11 @@ msgstr "Outro problema"
#: src/components/dms/dialogs/NewChatDialog.tsx:36
msgid "An issue occurred starting the chat"
-msgstr ""
+msgstr "Ocorreu um problema ao iniciar o chat"
#: src/components/dms/dialogs/ShareViaChatDialog.tsx:49
msgid "An issue occurred while trying to open the chat"
-msgstr ""
+msgstr "Ocorreu um problema ao tentar abrir o chat"
#: src/components/hooks/useFollowMethods.ts:35
#: src/components/hooks/useFollowMethods.ts:50
@@ -646,7 +646,7 @@ msgstr "ocorreu um erro desconhecido"
#: src/components/moderation/ModerationDetailsDialog.tsx:151
#: src/components/moderation/ModerationDetailsDialog.tsx:147
msgid "an unknown labeler"
-msgstr ""
+msgstr "um rotulador desconhecido"
#: src/components/WhoCanReply.tsx:295
#: src/view/com/notifications/FeedItem.tsx:235
@@ -669,7 +669,7 @@ msgstr "Comportamento anti-social"
#: src/view/com/composer/threadgate/ThreadgateBtn.tsx:54
msgid "Anybody can interact"
-msgstr ""
+msgstr "Qualquer pessoa pode interagir"
#: src/view/screens/LanguageSettings.tsx:96
msgid "App Language"
@@ -720,7 +720,7 @@ msgstr "Contestação enviada."
#: src/screens/Messages/Conversation/ChatDisabled.tsx:99
#: src/screens/Messages/Conversation/ChatDisabled.tsx:101
msgid "Appeal this decision"
-msgstr ""
+msgstr "Recorrer desta decisão"
#: src/screens/Settings/AppearanceSettings.tsx:69
#: src/view/screens/Settings/index.tsx:484
@@ -729,11 +729,11 @@ msgstr "Aparência"
#: src/view/screens/Settings/index.tsx:475
msgid "Appearance settings"
-msgstr ""
+msgstr "Configurações de aparência"
#: src/Navigation.tsx:326
msgid "Appearance Settings"
-msgstr ""
+msgstr "Configurações de aparência"
#: src/screens/Feeds/NoSavedFeedsOfAnyType.tsx:47
#: src/screens/Home/NoFeedsPinned.tsx:93
@@ -742,7 +742,7 @@ msgstr "Utilizar feeds recomendados"
#: src/screens/StarterPack/StarterPackScreen.tsx:610
#~ msgid "Are you sure you want delete this starter pack?"
-#~ msgstr ""
+#~ msgstr "Tem certeza de que deseja excluir este pacote inicial?"
#: src/view/screens/AppPasswords.tsx:282
msgid "Are you sure you want to delete the app password \"{name}\"?"
@@ -754,11 +754,11 @@ msgstr "Tem certeza de que deseja excluir a senha do aplicativo \"{name}\"?"
#: src/components/dms/MessageMenu.tsx:149
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 "Tem certeza de que deseja excluir esta mensagem? A mensagem será excluída para você, mas não para o outro participante."
#: src/screens/StarterPack/StarterPackScreen.tsx:621
msgid "Are you sure you want to delete this starter pack?"
-msgstr ""
+msgstr "Tem certeza de que deseja excluir este kit inicial?"
#: src/components/dms/ConvoMenu.tsx:189
#~ msgid "Are you sure you want to leave this conversation? Your messages will be deleted for you, but not for other participants."
@@ -766,7 +766,7 @@ msgstr ""
#: src/components/dms/LeaveConvoPrompt.tsx:48
msgid "Are you sure you want to leave this conversation? Your messages will be deleted for you, but not for the other participant."
-msgstr ""
+msgstr "Tem certeza de que deseja sair desta conversa? Suas mensagens serão excluídas para você, mas não para os outros participantes."
#: src/view/com/feeds/FeedSourceCard.tsx:313
msgid "Are you sure you want to remove {0} from your feeds?"
@@ -774,7 +774,7 @@ msgstr "Tem certeza que deseja remover {0} dos seus feeds?"
#: src/components/FeedCard.tsx:310
msgid "Are you sure you want to remove this from your feeds?"
-msgstr ""
+msgstr "Tem certeza que deseja remover isto de seus feeds?"
#: src/view/com/composer/Composer.tsx:772
msgid "Are you sure you'd like to discard this draft?"
@@ -920,7 +920,7 @@ msgstr "Bluesky é uma rede aberta que permite a escolha do seu provedor de hosp
#: src/components/ProgressGuide/List.tsx:55
msgid "Bluesky is better with friends!"
-msgstr ""
+msgstr "Bluesky é melhor com amigos!"
#: src/view/com/auth/onboarding/WelcomeDesktop.tsx:80
#: src/view/com/auth/onboarding/WelcomeMobile.tsx:82
@@ -939,7 +939,7 @@ msgstr ""
#: src/components/StarterPack/ProfileStarterPacks.tsx:282
msgid "Bluesky will choose a set of recommended accounts from people in your network."
-msgstr ""
+msgstr "O Bluesky escolherá um conjunto de contas recomendadas de pessoas em sua rede."
#: src/screens/Moderation/index.tsx:567
msgid "Bluesky will not show your profile and posts to logged-out users. Other apps may not honor this request. This does not make your account private."
@@ -960,23 +960,23 @@ msgstr "Livros"
#: src/components/FeedInterstitials.tsx:300
msgid "Browse more accounts on the Explore page"
-msgstr ""
+msgstr "Navegue por mais contas na página Explorar"
#: src/components/FeedInterstitials.tsx:433
msgid "Browse more feeds on the Explore page"
-msgstr ""
+msgstr "Navegue por mais feeds na página Explorar"
#: src/components/FeedInterstitials.tsx:282
#: src/components/FeedInterstitials.tsx:285
#: src/components/FeedInterstitials.tsx:415
#: src/components/FeedInterstitials.tsx:418
msgid "Browse more suggestions"
-msgstr ""
+msgstr "Veja mais sugestões"
#: src/components/FeedInterstitials.tsx:308
#: src/components/FeedInterstitials.tsx:442
msgid "Browse more suggestions on the Explore page"
-msgstr ""
+msgstr "Navegue por mais sugestões na página Explorar"
#: src/screens/Home/NoFeedsPinned.tsx:103
#: src/screens/Home/NoFeedsPinned.tsx:109
@@ -1080,7 +1080,7 @@ msgstr "Cancelar citação"
#: src/screens/Deactivated.tsx:155
msgid "Cancel reactivation and log out"
-msgstr ""
+msgstr "Cancelar reativação e sair"
#: src/view/com/modals/ListAddRemoveUsers.tsx:88
msgid "Cancel search"
@@ -1150,7 +1150,7 @@ msgstr "Configurações do Chat"
#: src/screens/Messages/Settings.tsx:59
#: src/view/screens/Settings/index.tsx:613
msgid "Chat Settings"
-msgstr ""
+msgstr "Configurações do Chat"
#: src/components/dms/ConvoMenu.tsx:84
msgid "Chat unmuted"
@@ -1187,23 +1187,23 @@ msgstr "Verifique em sua caixa de entrada um e-mail com o código de confirmaç
#: src/screens/Onboarding/StepInterests/index.tsx:191
msgid "Choose 3 or more:"
-msgstr ""
+msgstr "Escolha 3 ou mais:"
#: src/screens/Onboarding/StepInterests/index.tsx:326
msgid "Choose at least {0} more"
-msgstr ""
+msgstr "Escolha pelo menos mais {0}"
#: src/screens/StarterPack/Wizard/index.tsx:190
msgid "Choose Feeds"
-msgstr ""
+msgstr "Escolha os feeds"
#: src/components/StarterPack/ProfileStarterPacks.tsx:290
msgid "Choose for me"
-msgstr ""
+msgstr "Escolha para mim"
#: src/screens/StarterPack/Wizard/index.tsx:186
msgid "Choose People"
-msgstr ""
+msgstr "Escolha Pessoas"
#: src/view/com/auth/server-input/index.tsx:79
msgid "Choose Service"
@@ -1225,7 +1225,7 @@ msgstr "Selecionar esta cor como seu avatar"
#: src/components/dialogs/ThreadgateEditor.tsx:91
#: src/components/dialogs/ThreadgateEditor.tsx:95
#~ msgid "Choose who can reply"
-#~ msgstr ""
+#~ msgstr "Escolha quem pode responder"
#: src/screens/Onboarding/StepAlgoFeeds/index.tsx:104
#~ msgid "Choose your main feeds"
@@ -1270,11 +1270,11 @@ msgstr "clique aqui"
#: src/view/com/modals/DeleteAccount.tsx:208
msgid "Click here for more information on deactivating your account"
-msgstr ""
+msgstr "Clique aqui para obter mais informações sobre como desativar sua conta"
#: src/view/com/modals/DeleteAccount.tsx:216
msgid "Click here for more information."
-msgstr ""
+msgstr "Clique aqui para mais informações."
#: src/screens/Feeds/NoFollowingFeed.tsx:46
#~ msgid "Click here to add one."
@@ -1290,15 +1290,15 @@ msgstr "Clique aqui para abrir o menu da tag {tag}"
#: src/components/dialogs/PostInteractionSettingsDialog.tsx:303
msgid "Click to disable quote posts of this post."
-msgstr ""
+msgstr "Clique para desabilitar as citações desta publicação."
#: src/components/dialogs/PostInteractionSettingsDialog.tsx:304
msgid "Click to enable quote posts of this post."
-msgstr ""
+msgstr "Clique para habilitar citações desta publicação."
#: src/components/dms/MessageItem.tsx:231
msgid "Click to retry failed message"
-msgstr ""
+msgstr "Clique para tentar novamente a mensagem que falhou"
#: src/screens/Onboarding/index.tsx:32
msgid "Climate"
@@ -1353,7 +1353,7 @@ msgstr "Fechar visualizador de imagens"
#: src/components/dms/MessagesNUX.tsx:162
msgid "Close modal"
-msgstr ""
+msgstr "Fechar janela"
#: src/view/shell/index.web.tsx:61
msgid "Close navigation footer"
@@ -1382,7 +1382,7 @@ msgstr "Fechar o visualizador de banner"
#: src/view/com/notifications/FeedItem.tsx:269
msgid "Collapse list of users"
-msgstr ""
+msgstr "Recolher lista de usuários"
#: src/view/com/notifications/FeedItem.tsx:470
msgid "Collapses list of users for a given notification"
@@ -1421,7 +1421,7 @@ msgstr "Escrever resposta"
#: src/view/com/composer/videos/VideoTranscodeProgress.tsx:51
msgid "Compressing..."
-msgstr ""
+msgstr "Comprimindo..."
#: src/screens/Onboarding/StepModeration/ModerationOption.tsx:81
#~ msgid "Configure content filtering setting for category: {0}"
@@ -1533,7 +1533,7 @@ msgstr "Continuar como {0} (já conectado)"
#: src/view/com/post-thread/PostThreadLoadMore.tsx:52
msgid "Continue thread..."
-msgstr ""
+msgstr "Continuar o tópico..."
#: src/screens/Onboarding/StepInterests/index.tsx:275
#: src/screens/Onboarding/StepProfile/index.tsx:266
@@ -1551,7 +1551,7 @@ msgstr "Continuar para o próximo passo"
#: src/screens/Messages/List/ChatListItem.tsx:154
msgid "Conversation deleted"
-msgstr ""
+msgstr "Conversa apagada"
#: src/screens/Onboarding/index.tsx:41
msgid "Cooking"
@@ -1599,11 +1599,11 @@ msgstr "Copiar código"
#: src/components/StarterPack/ShareDialog.tsx:124
msgid "Copy link"
-msgstr ""
+msgstr "Copiar link"
#: src/components/StarterPack/ShareDialog.tsx:131
msgid "Copy Link"
-msgstr ""
+msgstr "Copiar Link"
#: src/view/screens/ProfileList.tsx:484
msgid "Copy link to list"
@@ -1626,7 +1626,7 @@ msgstr "Copiar texto do post"
#: src/components/StarterPack/QrCodeDialog.tsx:171
msgid "Copy QR code"
-msgstr ""
+msgstr "Copiar QR code"
#: src/Navigation.tsx:281
#: src/view/screens/CopyrightPolicy.tsx:29
@@ -1635,7 +1635,7 @@ msgstr "Política de Direitos Autorais"
#: src/view/com/composer/videos/state.ts:31
#~ msgid "Could not compress video"
-#~ msgstr ""
+#~ msgstr "Não foi possível compactar o vídeo"
#: src/components/dms/LeaveConvoPrompt.tsx:39
msgid "Could not leave chat"
@@ -1663,7 +1663,7 @@ msgstr "Não foi possível silenciar este chat"
#: src/components/StarterPack/ProfileStarterPacks.tsx:272
msgid "Create"
-msgstr ""
+msgstr "Criar"
#: src/view/com/auth/SplashScreen.tsx:57
#: src/view/com/auth/SplashScreen.web.tsx:106
@@ -1676,17 +1676,17 @@ msgstr "Criar uma nova conta do Bluesky"
#: src/components/StarterPack/QrCodeDialog.tsx:154
msgid "Create a QR code for a starter pack"
-msgstr ""
+msgstr "Crie o QR code para um kit inicial"
#: src/components/StarterPack/ProfileStarterPacks.tsx:165
#: src/components/StarterPack/ProfileStarterPacks.tsx:259
#: src/Navigation.tsx:368
msgid "Create a starter pack"
-msgstr ""
+msgstr "Crie um kit inicial"
#: src/components/StarterPack/ProfileStarterPacks.tsx:246
msgid "Create a starter pack for me"
-msgstr ""
+msgstr "Crie um kit inicial para mim"
#: src/screens/Signup/index.tsx:99
msgid "Create Account"
@@ -1703,7 +1703,7 @@ msgstr "Criar um avatar"
#: src/components/StarterPack/ProfileStarterPacks.tsx:172
msgid "Create another"
-msgstr ""
+msgstr "Crie outra"
#: src/view/com/modals/AddAppPasswords.tsx:243
msgid "Create App Password"
@@ -1716,7 +1716,7 @@ msgstr "Criar uma nova conta"
#: src/components/StarterPack/ShareDialog.tsx:158
#~ msgid "Create QR code"
-#~ msgstr ""
+#~ msgstr "Criar QR code"
#: src/components/ReportDialog/SelectReportOptionView.tsx:101
msgid "Create report for {0}"
@@ -1755,7 +1755,7 @@ msgstr "Configurar mídia de sites externos."
#: src/components/dialogs/PostInteractionSettingsDialog.tsx:288
msgid "Customize who can interact with this post."
-msgstr ""
+msgstr "Personalize quem pode interagir com esta postagem."
#: src/screens/Settings/AppearanceSettings.tsx:95
#: src/screens/Settings/AppearanceSettings.tsx:97
@@ -1772,7 +1772,7 @@ msgstr "Modo escuro"
#: src/screens/Settings/AppearanceSettings.tsx:109
#: src/screens/Settings/AppearanceSettings.tsx:114
msgid "Dark theme"
-msgstr ""
+msgstr "Tema escuro"
#: src/view/screens/Settings/index.tsx:473
#~ msgid "Dark Theme"
@@ -1785,11 +1785,11 @@ msgstr "Data de nascimento"
#: src/screens/Settings/components/DeactivateAccountDialog.tsx:73
#: src/view/screens/Settings/index.tsx:772
msgid "Deactivate account"
-msgstr ""
+msgstr "Desativar conta"
#: src/view/screens/Settings/index.tsx:784
msgid "Deactivate my account"
-msgstr ""
+msgstr "Desativar minha conta"
#: src/view/screens/Settings/index.tsx:839
msgid "Debug Moderation"
@@ -1832,7 +1832,7 @@ msgstr "Excluir senha de aplicativo?"
#: src/view/screens/Settings/index.tsx:856
#: src/view/screens/Settings/index.tsx:859
msgid "Delete chat declaration record"
-msgstr ""
+msgstr "Excluir registro da declaração de chat"
#: src/components/dms/MessageMenu.tsx:124
msgid "Delete for me"
@@ -1866,11 +1866,11 @@ msgstr "Excluir post"
#: src/screens/StarterPack/StarterPackScreen.tsx:567
#: src/screens/StarterPack/StarterPackScreen.tsx:723
msgid "Delete starter pack"
-msgstr ""
+msgstr "Excluir kit inicial"
#: src/screens/StarterPack/StarterPackScreen.tsx:618
msgid "Delete starter pack?"
-msgstr ""
+msgstr "Excluir kit inicial?"
#: src/view/screens/ProfileList.tsx:718
msgid "Delete this list?"
@@ -1890,7 +1890,7 @@ msgstr "Post excluído."
#: src/view/screens/Settings/index.tsx:857
msgid "Deletes the chat declaration record"
-msgstr ""
+msgstr "Exclui o registro de declaração de chat"
#: src/view/com/modals/CreateOrEditList.tsx:289
#: src/view/com/modals/CreateOrEditList.tsx:310
@@ -1906,15 +1906,15 @@ msgstr "Texto alternativo"
#: src/view/com/util/forms/PostDropdownBtn.tsx:544
#: src/view/com/util/forms/PostDropdownBtn.tsx:554
msgid "Detach quote"
-msgstr ""
+msgstr "Desanexar citação"
#: src/view/com/util/forms/PostDropdownBtn.tsx:687
msgid "Detach quote post?"
-msgstr ""
+msgstr "Desanexar postagem de citação?"
#: src/components/WhoCanReply.tsx:175
msgid "Dialog: adjust who can interact with this post"
-msgstr ""
+msgstr "Diálogo: ajuste quem pode interagir com esta postagem"
#: src/view/com/composer/Composer.tsx:327
msgid "Did you want to say anything?"
@@ -1927,7 +1927,7 @@ msgstr "Menos escuro"
#: src/components/dms/MessagesNUX.tsx:88
msgid "Direct messages are here!"
-msgstr ""
+msgstr "As mensagens diretas estão aqui!"
#: src/view/screens/AccessibilitySettings.tsx:111
msgid "Disable autoplay for GIFs"
@@ -1947,7 +1947,7 @@ msgstr "Desabilitar feedback tátil"
#: src/view/com/util/post-embeds/VideoEmbedInner/VideoWebControls.tsx:242
msgid "Disable subtitles"
-msgstr ""
+msgstr "Desativar legendas"
#: src/view/screens/Settings/index.tsx:697
#~ msgid "Disable vibrations"
@@ -1977,7 +1977,7 @@ msgstr "Desencorajar aplicativos a mostrar minha conta para usuários desautenti
#: src/tours/HomeTour.tsx:70
msgid "Discover learns which posts you like as you browse."
-msgstr ""
+msgstr "Descubra quais postagens você gosta enquanto navega."
#: src/view/com/posts/FollowingEmptyState.tsx:70
#: src/view/com/posts/FollowingEndOfFeed.tsx:71
@@ -1986,7 +1986,7 @@ msgstr "Descubra novos feeds"
#: src/view/screens/Search/Explore.tsx:389
msgid "Discover new feeds"
-msgstr ""
+msgstr "Descubra novos feeds"
#: src/view/screens/Feeds.tsx:756
msgid "Discover New Feeds"
@@ -1994,19 +1994,19 @@ msgstr "Descubra Novos Feeds"
#: src/view/com/profile/ProfileHeaderSuggestedFollows.tsx:108
msgid "Dismiss"
-msgstr ""
+msgstr "Ocultar"
#: src/view/com/composer/Composer.tsx:612
msgid "Dismiss error"
-msgstr ""
+msgstr "Ocultar erro"
#: src/components/ProgressGuide/List.tsx:40
msgid "Dismiss getting started guide"
-msgstr ""
+msgstr "Ignorar guia de primeiros passos"
#: src/view/screens/AccessibilitySettings.tsx:99
msgid "Display larger alt text badges"
-msgstr ""
+msgstr "Exibir emblemas de texto alternativo maiores"
#: src/view/com/modals/EditProfile.tsx:193
msgid "Display name"
@@ -2022,7 +2022,7 @@ msgstr "Painel DNS"
#: src/components/dialogs/MutedWords.tsx:302
msgid "Do not apply this mute word to users you follow"
-msgstr ""
+msgstr "Não aplique esta palavra ocultada aos usuários que você segue"
#: src/lib/moderation/useGlobalLabelStrings.ts:39
msgid "Does not include nudity."
@@ -2072,7 +2072,7 @@ msgstr "Feito{extraText}"
#: src/screens/StarterPack/StarterPackLandingScreen.tsx:324
msgid "Download Bluesky"
-msgstr ""
+msgstr "Baixe o Bluesky"
#: src/view/screens/Settings/ExportCarDialog.tsx:77
#: src/view/screens/Settings/ExportCarDialog.tsx:81
@@ -2089,7 +2089,7 @@ msgstr "Solte para adicionar imagens"
#: src/components/dialogs/MutedWords.tsx:153
msgid "Duration:"
-msgstr ""
+msgstr "Duração:"
#: src/view/com/modals/ChangeHandle.tsx:252
msgid "e.g. alice"
@@ -2137,7 +2137,7 @@ msgstr "Cada convite só funciona uma vez. Você receberá mais convites periodi
#: src/view/screens/Feeds.tsx:385
#: src/view/screens/Feeds.tsx:453
msgid "Edit"
-msgstr ""
+msgstr "Editar"
#: src/view/com/lists/ListMembers.tsx:149
msgctxt "action"
@@ -2151,7 +2151,7 @@ msgstr "Editar avatar"
#: src/components/StarterPack/Wizard/WizardEditListDialog.tsx:119
msgid "Edit Feeds"
-msgstr ""
+msgstr "Editar Feeds"
#: src/view/com/composer/photos/Gallery.tsx:151
#: src/view/com/modals/EditImage.tsx:208
@@ -2161,7 +2161,7 @@ msgstr "Editar imagem"
#: src/view/com/util/forms/PostDropdownBtn.tsx:590
#: src/view/com/util/forms/PostDropdownBtn.tsx:603
msgid "Edit interaction settings"
-msgstr ""
+msgstr "Editar configurações de interação"
#: src/view/screens/ProfileList.tsx:515
msgid "Edit list details"
@@ -2184,12 +2184,12 @@ msgstr "Editar meu perfil"
#: src/components/StarterPack/Wizard/WizardEditListDialog.tsx:117
msgid "Edit People"
-msgstr ""
+msgstr "Editar Pessoas"
#: src/components/dialogs/PostInteractionSettingsDialog.tsx:66
#: src/components/dialogs/PostInteractionSettingsDialog.tsx:204
msgid "Edit post interaction settings"
-msgstr ""
+msgstr "Editar configurações de interação de postagem"
#: src/screens/Profile/Header/ProfileHeaderLabeler.tsx:184
#: src/screens/Profile/Header/ProfileHeaderStandard.tsx:179
@@ -2208,7 +2208,7 @@ msgstr "Editar Perfil"
#: src/screens/StarterPack/StarterPackScreen.tsx:554
msgid "Edit starter pack"
-msgstr ""
+msgstr "Editar kit incial"
#: src/view/com/modals/CreateOrEditList.tsx:234
msgid "Edit User List"
@@ -2216,7 +2216,7 @@ msgstr "Editar lista de usuários"
#: src/components/WhoCanReply.tsx:87
msgid "Edit who can reply"
-msgstr ""
+msgstr "Editar quem pode responder"
#: src/view/com/modals/EditProfile.tsx:194
msgid "Edit your display name"
@@ -2228,7 +2228,7 @@ msgstr "Editar sua descrição"
#: src/Navigation.tsx:373
msgid "Edit your starter pack"
-msgstr ""
+msgstr "Editar kit inicial"
#: src/screens/Onboarding/index.tsx:31
#: src/screens/Onboarding/state.ts:86
@@ -2237,7 +2237,7 @@ msgstr "Educação"
#: src/components/dialogs/ThreadgateEditor.tsx:98
#~ msgid "Either choose \"Everybody\" or \"Nobody\""
-#~ msgstr ""
+#~ msgstr "Escolha entre \"Todos\" ou \"Ninguém\""
#: src/screens/Signup/StepInfo/index.tsx:143
#: src/view/com/modals/ChangeEmail.tsx:136
@@ -2312,11 +2312,11 @@ msgstr "Habilitar mídia para"
#: src/view/screens/NotificationsSettings.tsx:65
#: src/view/screens/NotificationsSettings.tsx:68
msgid "Enable priority notifications"
-msgstr ""
+msgstr "Habilitar notificações prioritárias"
#: src/view/com/util/post-embeds/VideoEmbedInner/VideoWebControls.tsx:242
msgid "Enable subtitles"
-msgstr ""
+msgstr "Habilitar legendas"
#: src/view/screens/PreferencesFollowingFeed.tsx:145
#~ msgid "Enable this setting to only see replies between people you follow."
@@ -2338,11 +2338,11 @@ msgstr "Fim do feed"
#: src/components/Lists.tsx:52
#~ msgid "End of list"
-#~ msgstr ""
+#~ msgstr "Fim da lista"
#: src/tours/Tooltip.tsx:159
msgid "End of onboarding tour window. Do not move forward. Instead, go backward for more options, or press to skip."
-msgstr ""
+msgstr "Fim da integração da sua janela. Não avance. Em vez disso, volte para mais opções ou pressione para pular."
#: src/view/com/modals/AddAppPasswords.tsx:161
msgid "Enter a name for this App Password"
@@ -2413,18 +2413,18 @@ msgstr "Todos"
#: src/components/WhoCanReply.tsx:67
msgid "Everybody can reply"
-msgstr ""
+msgstr "Todos podem responder"
#: src/components/WhoCanReply.tsx:213
msgid "Everybody can reply to this post."
-msgstr ""
+msgstr "Todos podem responder esta postagem."
#: src/components/dms/MessagesNUX.tsx:131
#: src/components/dms/MessagesNUX.tsx:134
#: src/screens/Messages/Settings.tsx:75
#: src/screens/Messages/Settings.tsx:78
msgid "Everyone"
-msgstr ""
+msgstr "Todos"
#: src/lib/moderation/useReportOptions.ts:68
msgid "Excessive mentions or replies"
@@ -2436,11 +2436,11 @@ msgstr "Mensagens excessivas ou indesejadas"
#: src/components/dialogs/MutedWords.tsx:311
msgid "Exclude users you follow"
-msgstr ""
+msgstr "Excluir usuário que você segue"
#: src/components/dialogs/MutedWords.tsx:514
msgid "Excludes users you follow"
-msgstr ""
+msgstr "Excluir usuário que você segue"
#: src/view/com/modals/DeleteAccount.tsx:293
msgid "Exits account deletion process"
@@ -2468,7 +2468,7 @@ msgstr "Expandir texto alternativo"
#: src/view/com/notifications/FeedItem.tsx:270
msgid "Expand list of users"
-msgstr ""
+msgstr "Expandir lista de usuário"
#: src/view/com/composer/ComposerReplyTo.tsx:82
#: src/view/com/composer/ComposerReplyTo.tsx:85
@@ -2477,15 +2477,15 @@ msgstr "Mostrar ou esconder o post a que você está respondendo"
#: src/view/screens/NotificationsSettings.tsx:83
msgid "Experimental: When this preference is enabled, you'll only receive reply and quote notifications from users you follow. We'll continue to add more controls here over time."
-msgstr ""
+msgstr "Experimental: Quando essa preferência estiver habilitada, você receberá apenas notificações de resposta e citação de usuários que você segue. Continuaremos adicionando mais controles aqui ao longo do tempo"
#: src/components/dialogs/MutedWords.tsx:500
msgid "Expired"
-msgstr ""
+msgstr "Expirado "
#: src/components/dialogs/MutedWords.tsx:502
msgid "Expires {0}"
-msgstr ""
+msgstr "Expirada {0}"
#: src/lib/moderation/useGlobalLabelStrings.ts:47
msgid "Explicit or potentially disturbing media."
@@ -2532,7 +2532,7 @@ msgstr "Não foi possível criar senha de aplicativo."
#: src/screens/StarterPack/Wizard/index.tsx:229
#: src/screens/StarterPack/Wizard/index.tsx:237
msgid "Failed to create starter pack"
-msgstr ""
+msgstr "Falha ao criar o pacote inicial"
#: src/view/com/modals/CreateOrEditList.tsx:194
msgid "Failed to create the list. Check your internet connection and try again."
@@ -2548,12 +2548,12 @@ msgstr "Não foi possível excluir o post, por favor tente novamente."
#: src/screens/StarterPack/StarterPackScreen.tsx:686
msgid "Failed to delete starter pack"
-msgstr ""
+msgstr "Falha ao excluir o pacote inicial"
#: src/view/screens/Search/Explore.tsx:427
#: src/view/screens/Search/Explore.tsx:455
msgid "Failed to load feeds preferences"
-msgstr ""
+msgstr "Falha ao carregar preferências de feeds"
#: src/components/dialogs/GifSelect.ios.tsx:196
#: src/components/dialogs/GifSelect.tsx:212
@@ -2562,7 +2562,7 @@ msgstr "Não foi possível carregar os GIFs"
#: src/screens/Messages/Conversation/MessageListError.tsx:23
msgid "Failed to load past messages"
-msgstr ""
+msgstr "Não foi possível carregar mensagens antigas."
#: src/screens/Messages/Conversation/MessageListError.tsx:28
#~ msgid "Failed to load past messages."
@@ -2576,11 +2576,11 @@ msgstr ""
#: src/view/screens/Search/Explore.tsx:420
#: src/view/screens/Search/Explore.tsx:448
msgid "Failed to load suggested feeds"
-msgstr ""
+msgstr "Falha ao carregar feeds sugeridos"
#: src/view/screens/Search/Explore.tsx:378
msgid "Failed to load suggested follows"
-msgstr ""
+msgstr "Falha ao carregar sugestões a seguir"
#: src/view/com/lightbox/Lightbox.tsx:90
msgid "Failed to save image: {0}"
@@ -2588,11 +2588,11 @@ msgstr "Não foi possível salvar a imagem: {0}"
#: src/state/queries/notifications/settings.ts:39
msgid "Failed to save notification preferences, please try again"
-msgstr ""
+msgstr "Falha ao salvar as preferências de notificação, tente novamente"
#: src/components/dms/MessageItem.tsx:224
msgid "Failed to send"
-msgstr ""
+msgstr "Falha ao enviar"
#: src/screens/Messages/Conversation/MessageListError.tsx:29
#~ msgid "Failed to send message(s)."
@@ -2601,20 +2601,20 @@ msgstr ""
#: src/components/moderation/LabelsOnMeDialog.tsx:234
#: src/screens/Messages/Conversation/ChatDisabled.tsx:87
msgid "Failed to submit appeal, please try again."
-msgstr ""
+msgstr "Falha ao enviar o recurso, tente novamente."
#: src/view/com/util/forms/PostDropdownBtn.tsx:223
msgid "Failed to toggle thread mute, please try again"
-msgstr ""
+msgstr "Falha ao alternar o silenciamento do tópico, tente novamente"
#: src/components/FeedCard.tsx:273
msgid "Failed to update feeds"
-msgstr ""
+msgstr "Falha ao atualizar os feeds""
#: src/components/dms/MessagesNUX.tsx:60
#: src/screens/Messages/Settings.tsx:35
msgid "Failed to update settings"
-msgstr ""
+msgstr "Falha ao atualizar as configurações"
#: src/Navigation.tsx:226
msgid "Feed"
@@ -2631,7 +2631,7 @@ msgstr "Feed por {0}"
#: src/components/StarterPack/Wizard/WizardListCard.tsx:55
msgid "Feed toggle"
-msgstr ""
+msgstr "Alternar Feed"
#: src/view/shell/desktop/RightNav.tsx:70
#: src/view/shell/Drawer.tsx:346
@@ -2664,7 +2664,7 @@ msgstr "Os feeds são algoritmos personalizados que os usuários com um pouco de
#: src/components/FeedCard.tsx:270
msgid "Feeds updated!"
-msgstr ""
+msgstr "Feeds atualizados!"
#: src/view/com/modals/ChangeHandle.tsx:475
msgid "File Contents"
@@ -2690,7 +2690,7 @@ msgstr "Encontre contas para seguir"
#: src/tours/HomeTour.tsx:88
msgid "Find more feeds and accounts to follow in the Explore page."
-msgstr ""
+msgstr "Encontre mais feeds e contas para seguir na página Explorar."
#: src/view/screens/Search/Search.tsx:439
msgid "Find posts and users on Bluesky"
@@ -2718,11 +2718,11 @@ msgstr "Ajuste as threads."
#: src/screens/StarterPack/Wizard/index.tsx:191
msgid "Finish"
-msgstr ""
+msgstr "Finalizar"
#: src/tours/Tooltip.tsx:149
msgid "Finish tour and begin using the application"
-msgstr ""
+msgstr "Conclua o tour e comece a usar o aplicativo"
#: src/screens/Onboarding/index.tsx:35
msgid "Fitness"
@@ -2762,11 +2762,11 @@ msgstr "Seguir {0}"
#: src/view/com/posts/AviFollowButton.tsx:69
msgid "Follow {name}"
-msgstr ""
+msgstr "Seguir {name}"
#: src/components/ProgressGuide/List.tsx:54
msgid "Follow 7 accounts"
-msgstr ""
+msgstr "Siga 7 contas"
#: src/view/com/profile/ProfileMenu.tsx:246
#: src/view/com/profile/ProfileMenu.tsx:257
@@ -2776,7 +2776,7 @@ msgstr "Seguir Conta"
#: src/screens/StarterPack/StarterPackScreen.tsx:416
#: src/screens/StarterPack/StarterPackScreen.tsx:423
msgid "Follow all"
-msgstr ""
+msgstr "Siga todos"
#: src/screens/Onboarding/StepSuggestedAccounts/index.tsx:187
#~ msgid "Follow All"
@@ -2788,7 +2788,7 @@ msgstr "Seguir De Volta"
#: src/view/screens/Search/Explore.tsx:334
msgid "Follow more accounts to get connected to your interests and build your network."
-msgstr ""
+msgstr "Siga mais contas para se conectar aos seus interesses e construir sua rede."
#: src/screens/Onboarding/StepSuggestedAccounts/index.tsx:182
#~ msgid "Follow selected accounts and continue to the next step"
@@ -2800,7 +2800,7 @@ msgstr ""
#: src/components/KnownFollowers.tsx:169
#~ msgid "Followed by"
-#~ msgstr ""
+#~ msgstr "Seguido por"
#: src/view/com/profile/ProfileCard.tsx:190
#~ msgid "Followed by {0}"
@@ -2808,19 +2808,19 @@ msgstr ""
#: src/components/KnownFollowers.tsx:231
msgid "Followed by <0>{0}0>"
-msgstr ""
+msgstr "Seguido por <0>{0}0>"
#: src/components/KnownFollowers.tsx:217
msgid "Followed by <0>{0}0> and {1, plural, one {# other} other {# others}}"
-msgstr ""
+msgstr "Seguido por <0>{0}0> e {1, plural, one {# other} other {# others}}"
#: src/components/KnownFollowers.tsx:204
msgid "Followed by <0>{0}0> and <1>{1}1>"
-msgstr ""
+msgstr "Seguido por <0>{0}0> e <1>{1}1>"
#: src/components/KnownFollowers.tsx:186
msgid "Followed by <0>{0}0>, <1>{1}1>, and {2, plural, one {# other} other {# others}}"
-msgstr ""
+msgstr "Seguido por <0>{0}0>, <1>{1}1>, e {2, plural, one {# other} other {# others}}"
#: src/components/dialogs/PostInteractionSettingsDialog.tsx:403
msgid "Followed users"
@@ -2836,7 +2836,7 @@ msgstr "seguiu você"
#: src/view/com/notifications/FeedItem.tsx:209
msgid "followed you back"
-msgstr ""
+msgstr "seguiu você de volta"
#: src/view/com/profile/ProfileFollowers.tsx:104
#: src/view/screens/ProfileFollowers.tsx:25
@@ -2845,12 +2845,12 @@ msgstr "Seguidores"
#: src/Navigation.tsx:187
msgid "Followers of @{0} that you know"
-msgstr ""
+msgstr "Seguidores de @{0} que você conhece"
#: src/screens/Profile/KnownFollowers.tsx:108
#: src/screens/Profile/KnownFollowers.tsx:118
msgid "Followers you know"
-msgstr ""
+msgstr "Seguidores que você conhece"
#. User is following this account, click to unfollow
#: src/components/ProfileCard.tsx:345
@@ -2872,7 +2872,7 @@ msgstr "Seguindo {0}"
#: src/view/com/posts/AviFollowButton.tsx:51
msgid "Following {name}"
-msgstr ""
+msgstr "Seguindo {name}"
#: src/view/screens/Settings/index.tsx:539
msgid "Following feed preferences"
@@ -2886,7 +2886,7 @@ msgstr "Configurações do feed principal"
#: src/tours/HomeTour.tsx:59
msgid "Following shows the latest posts from people you follow."
-msgstr ""
+msgstr "Seguir mostra as postagens mais recentes das pessoas que você segue."
#: src/screens/Profile/Header/Handle.tsx:31
msgid "Follows you"
@@ -2911,7 +2911,7 @@ msgstr "Por motivos de segurança, você não poderá ver esta senha novamente.
#: src/components/dialogs/MutedWords.tsx:178
msgid "Forever"
-msgstr ""
+msgstr "Para sempre"
#: src/screens/Login/index.tsx:129
#: src/screens/Login/index.tsx:144
@@ -2945,15 +2945,15 @@ msgstr "Galeria"
#: src/components/StarterPack/ProfileStarterPacks.tsx:279
msgid "Generate a starter pack"
-msgstr ""
+msgstr "Gere um kit inicial"
#: src/view/shell/Drawer.tsx:350
msgid "Get help"
-msgstr ""
+msgstr "Obter ajuda"
#: src/components/dms/MessagesNUX.tsx:168
msgid "Get started"
-msgstr ""
+msgstr "Começar"
#: src/view/com/modals/VerifyEmail.tsx:197
#: src/view/com/modals/VerifyEmail.tsx:199
@@ -2962,7 +2962,7 @@ msgstr "Vamos começar"
#: src/components/ProgressGuide/List.tsx:33
msgid "Getting started"
-msgstr ""
+msgstr "Começando"
#: src/view/com/util/images/ImageHorzList.tsx:35
msgid "GIF"
@@ -3000,7 +3000,7 @@ msgstr "Voltar"
#: src/screens/StarterPack/StarterPackLandingScreen.tsx:189
#~ msgid "Go back to previous screen"
-#~ msgstr ""
+#~ msgstr "Voltar para a tela anterior"
#: src/components/dms/ReportDialog.tsx:154
#: src/components/ReportDialog/SelectReportOptionView.tsx:80
@@ -3013,7 +3013,7 @@ msgstr "Voltar para o passo anterior"
#: src/screens/StarterPack/Wizard/index.tsx:299
msgid "Go back to the previous step"
-msgstr ""
+msgstr "Voltar para a etapa anterior"
#: src/view/screens/NotFound.tsx:55
msgid "Go home"
@@ -3030,7 +3030,7 @@ msgstr "Voltar para a tela inicial"
#: src/screens/Messages/List/ChatListItem.tsx:211
msgid "Go to conversation with {0}"
-msgstr ""
+msgstr "Ir para a conversa com {0}"
#: src/screens/Login/ForgotPasswordForm.tsx:172
#: src/view/com/modals/ChangePassword.tsx:168
@@ -3043,7 +3043,7 @@ msgstr "Ir para este perfil"
#: src/tours/Tooltip.tsx:138
msgid "Go to the next step of the tour"
-msgstr ""
+msgstr "Vá para a próxima etapa do tour"
#: src/components/dms/ConvoMenu.tsx:164
msgid "Go to user's profile"
@@ -3055,7 +3055,7 @@ msgstr "Conteúdo Gráfico"
#: src/state/shell/progress-guide.tsx:161
msgid "Half way there!"
-msgstr ""
+msgstr "Metade do caminho!"
#: src/view/com/modals/ChangeHandle.tsx:260
msgid "Handle"
@@ -3108,7 +3108,7 @@ msgstr "Aqui está a sua senha de aplicativo."
#: src/components/ListCard.tsx:128
msgid "Hidden list"
-msgstr ""
+msgstr "Lista oculta"
#: src/components/moderation/ContentHider.tsx:116
#: src/components/moderation/LabelPreference.tsx:134
@@ -3134,17 +3134,17 @@ msgstr "Esconder"
#: src/view/com/util/forms/PostDropdownBtn.tsx:501
#: src/view/com/util/forms/PostDropdownBtn.tsx:507
msgid "Hide post for me"
-msgstr ""
+msgstr "Ocultar postagem para mim"
#: src/view/com/util/forms/PostDropdownBtn.tsx:518
#: src/view/com/util/forms/PostDropdownBtn.tsx:528
msgid "Hide reply for everyone"
-msgstr ""
+msgstr "Ocultar resposta para todos"
#: src/view/com/util/forms/PostDropdownBtn.tsx:500
#: src/view/com/util/forms/PostDropdownBtn.tsx:506
msgid "Hide reply for me"
-msgstr ""
+msgstr "Ocultar resposta para mim"
#: src/components/moderation/ContentHider.tsx:68
#: src/components/moderation/PostHider.tsx:79
@@ -3158,7 +3158,7 @@ msgstr "Ocultar este post?"
#: src/view/com/util/forms/PostDropdownBtn.tsx:635
#: src/view/com/util/forms/PostDropdownBtn.tsx:697
msgid "Hide this reply?"
-msgstr ""
+msgstr "Ocultar esta resposta?"
#: src/view/com/notifications/FeedItem.tsx:468
msgid "Hide user list"
@@ -3261,7 +3261,7 @@ msgstr "Se você quiser alterar sua senha, enviaremos um código que para verifi
#: src/screens/Settings/components/DeactivateAccountDialog.tsx:92
msgid "If you're trying to change your handle or email, do so before you deactivate."
-msgstr ""
+msgstr "Se você estiver tentando alterar seu endereço ou e-mail, faça isso antes de desativar."
#: src/lib/moderation/useReportOptions.ts:38
msgid "Illegal and Urgent"
@@ -3277,7 +3277,7 @@ msgstr "Texto alternativo da imagem"
#: src/components/StarterPack/ShareDialog.tsx:76
msgid "Image saved to your camera roll!"
-msgstr ""
+msgstr "Imagem salva no rolo da câmera!"
#: src/lib/moderation/useReportOptions.ts:49
msgid "Impersonation or false claims about identity or affiliation"
@@ -3285,7 +3285,7 @@ msgstr "Falsificação de identidade ou alegações falsas sobre identidade ou f
#: src/lib/moderation/useReportOptions.ts:86
msgid "Inappropriate messages or explicit links"
-msgstr ""
+msgstr "Mensagens inapropriadas ou links explícitos"
#: src/screens/Login/SetNewPasswordForm.tsx:127
msgid "Input code sent to your email for password reset"
@@ -3333,11 +3333,11 @@ msgstr "Insira o usuário"
#: src/view/com/composer/threadgate/ThreadgateBtn.tsx:55
msgid "Interaction limited"
-msgstr ""
+msgstr "Interação limitada"
#: src/components/dms/MessagesNUX.tsx:82
msgid "Introducing Direct Messages"
-msgstr ""
+msgstr "Apresentando Mensagens Diretas"
#: src/screens/Login/LoginForm.tsx:140
#: src/view/screens/Settings/DisableEmail2FADialog.tsx:70
@@ -3374,15 +3374,15 @@ msgstr "Convites: 1 disponível"
#: src/components/StarterPack/ShareDialog.tsx:97
msgid "Invite people to this starter pack!"
-msgstr ""
+msgstr "Convide pessoas para este kit inicial!"
#: src/screens/StarterPack/Wizard/StepDetails.tsx:35
msgid "Invite your friends to follow your favorite feeds and people"
-msgstr ""
+msgstr "Convide seus amigos para seguir seus feeds e pessoas favoritas"
#: src/screens/StarterPack/Wizard/StepDetails.tsx:32
msgid "Invites, but personal"
-msgstr ""
+msgstr "Convites, mas pessoais"
#: src/screens/Onboarding/StepFollowingFeed.tsx:65
#~ msgid "It shows posts from the people you follow as they happen."
@@ -3390,7 +3390,7 @@ msgstr ""
#: src/screens/StarterPack/Wizard/index.tsx:452
msgid "It's just you right now! Add more people to your starter pack by searching above."
-msgstr ""
+msgstr "É só você por enquanto! Adicione mais pessoas ao seu kit inicial pesquisando acima."
#: src/view/com/auth/SplashScreen.web.tsx:164
msgid "Jobs"
@@ -3401,11 +3401,11 @@ msgstr "Carreiras"
#: src/screens/StarterPack/StarterPackScreen.tsx:443
#: src/screens/StarterPack/StarterPackScreen.tsx:454
msgid "Join Bluesky"
-msgstr ""
+msgstr "Crie uma conta no Bluesky"
#: src/components/StarterPack/QrCode.tsx:56
msgid "Join the conversation"
-msgstr ""
+msgstr "Participe da conversa"
#: src/screens/Onboarding/index.tsx:21
#: src/screens/Onboarding/state.ts:89
@@ -3472,7 +3472,7 @@ msgstr "Saiba Mais"
#: src/view/com/auth/SplashScreen.web.tsx:152
msgid "Learn more about Bluesky"
-msgstr ""
+msgstr "Saiba mais sobre Bluesky"
#: src/components/moderation/ContentHider.tsx:66
#: src/components/moderation/ContentHider.tsx:131
@@ -3500,7 +3500,7 @@ msgstr "Sair"
#: src/components/dms/MessagesListBlockedFooter.tsx:66
#: src/components/dms/MessagesListBlockedFooter.tsx:73
msgid "Leave chat"
-msgstr ""
+msgstr "Sair do chat"
#: src/components/dms/ConvoMenu.tsx:138
#: src/components/dms/ConvoMenu.tsx:141
@@ -3528,7 +3528,7 @@ msgstr "na sua frente."
#: src/components/StarterPack/ProfileStarterPacks.tsx:295
msgid "Let me choose"
-msgstr ""
+msgstr "Deixe-me escolher"
#: src/screens/Login/index.tsx:130
#: src/screens/Login/index.tsx:145
@@ -3551,12 +3551,12 @@ msgstr "Claro"
#: src/components/ProgressGuide/List.tsx:48
msgid "Like 10 posts"
-msgstr ""
+msgstr "Curtir 10 postagens"
#: src/state/shell/progress-guide.tsx:157
#: src/state/shell/progress-guide.tsx:162
msgid "Like 10 posts to train the Discover feed"
-msgstr ""
+msgstr "Curtir 10 posts para treinar o feed de Descobertas"
#: src/screens/Profile/Header/ProfileHeaderLabeler.tsx:267
#: src/view/screens/ProfileFeed.tsx:575
@@ -3629,11 +3629,11 @@ msgstr "Lista excluída"
#: src/screens/List/ListHiddenScreen.tsx:126
msgid "List has been hidden"
-msgstr ""
+msgstr "Lista foi ocultada"
#: src/view/screens/ProfileList.tsx:159
msgid "List Hidden"
-msgstr ""
+msgstr "Lista Oculta"
#: src/view/screens/ProfileList.tsx:386
msgid "List muted"
@@ -3662,19 +3662,19 @@ msgstr "Listas"
#: src/components/dms/BlockedByListDialog.tsx:39
msgid "Lists blocking this user:"
-msgstr ""
+msgstr "Listas bloqueando este usuário:"
#: src/view/screens/Search/Explore.tsx:131
msgid "Load more"
-msgstr ""
+msgstr "Carregar mais"
#: src/view/screens/Search/Explore.tsx:219
msgid "Load more suggested feeds"
-msgstr ""
+msgstr "Carregar mais sugestões de feeds"
#: src/view/screens/Search/Explore.tsx:217
msgid "Load more suggested follows"
-msgstr ""
+msgstr "Carregar mais sugestões de seguidores"
#: src/view/screens/Notifications.tsx:219
msgid "Load new notifications"
@@ -3698,7 +3698,7 @@ msgstr "Registros"
#: src/screens/Deactivated.tsx:214
#: src/screens/Deactivated.tsx:220
msgid "Log in or sign up"
-msgstr ""
+msgstr "Entre ou registre-se"
#: src/screens/SignupQueued.tsx:155
#: src/screens/SignupQueued.tsx:158
@@ -3737,11 +3737,11 @@ msgstr "Parece que você desafixou todos os seus feeds, mas não esquenta, dá u
#: src/screens/Feeds/NoFollowingFeed.tsx:37
msgid "Looks like you're missing a following feed. <0>Click here to add one.0>"
-msgstr ""
+msgstr "Parece que está faltando um feed a seguir. <0>Clique aqui para adicionar um.0>"
#: src/components/StarterPack/ProfileStarterPacks.tsx:254
msgid "Make one for me"
-msgstr ""
+msgstr "Faça um para mim "
#: src/view/com/modals/LinkWarning.tsx:79
msgid "Make sure this is where you intend to go!"
@@ -3776,7 +3776,7 @@ msgstr "Menu"
#: src/components/dms/MessageProfileButton.tsx:67
msgid "Message {0}"
-msgstr ""
+msgstr "Mensagem {0}"
#: src/components/dms/MessageMenu.tsx:72
#: src/screens/Messages/List/ChatListItem.tsx:155
@@ -3817,7 +3817,7 @@ msgstr "Conta Enganosa"
#: src/screens/Settings/AppearanceSettings.tsx:78
msgid "Mode"
-msgstr ""
+msgstr "Modo"
#: src/Navigation.tsx:135
#: src/screens/Moderation/index.tsx:105
@@ -3862,7 +3862,7 @@ msgstr "Listas de Moderação"
#: src/components/moderation/LabelPreference.tsx:247
msgid "moderation settings"
-msgstr ""
+msgstr "configurações de Moderação"
#: src/view/screens/Settings/index.tsx:521
msgid "Moderation settings"
@@ -3899,11 +3899,11 @@ msgstr "Respostas mais curtidas primeiro"
#: src/screens/Onboarding/state.ts:90
msgid "Movies"
-msgstr ""
+msgstr "Filmes"
#: src/screens/Onboarding/state.ts:91
msgid "Music"
-msgstr ""
+msgstr "Música"
#: src/components/TagMenu/index.tsx:263
#: src/view/com/util/post-embeds/VideoEmbedInner/VideoWebControls.tsx:254
@@ -3931,7 +3931,7 @@ msgstr "Silenciar posts com {displayTag}"
#: src/components/dms/ConvoMenu.tsx:172
#: src/components/dms/ConvoMenu.tsx:178
msgid "Mute conversation"
-msgstr ""
+msgstr "Silenciar conversa"
#: src/components/dialogs/MutedWords.tsx:148
#~ msgid "Mute in tags only"
@@ -3943,7 +3943,7 @@ msgstr ""
#: src/components/dialogs/MutedWords.tsx:253
msgid "Mute in:"
-msgstr ""
+msgstr "Silenciar em:"
#: src/view/screens/ProfileList.tsx:734
msgid "Mute list"
@@ -3960,15 +3960,15 @@ msgstr "Silenciar estas contas?"
#: src/components/dialogs/MutedWords.tsx:185
msgid "Mute this word for 24 hours"
-msgstr ""
+msgstr "Silencie esta palavra por 24 horas"
#: src/components/dialogs/MutedWords.tsx:224
msgid "Mute this word for 30 days"
-msgstr ""
+msgstr "Silencie esta palavra por 30 dias"
#: src/components/dialogs/MutedWords.tsx:209
msgid "Mute this word for 7 days"
-msgstr ""
+msgstr "Silencie esta palavra por 7 dias"
#: src/components/dialogs/MutedWords.tsx:258
msgid "Mute this word in post text and tags"
@@ -3980,7 +3980,7 @@ msgstr "Silenciar esta palavra apenas nas tags de um post"
#: src/components/dialogs/MutedWords.tsx:170
msgid "Mute this word until you unmute it"
-msgstr ""
+msgstr "Oculte esta palavra até que você a reative"
#: src/view/com/util/forms/PostDropdownBtn.tsx:465
#: src/view/com/util/forms/PostDropdownBtn.tsx:471
@@ -4065,11 +4065,11 @@ msgstr "Natureza"
#: src/components/StarterPack/StarterPackCard.tsx:121
msgid "Navigate to {0}"
-msgstr ""
+msgstr "Navegar para {0}"
#: src/view/com/util/post-embeds/ExternalLinkEmbed.tsx:73
msgid "Navigate to starter pack"
-msgstr ""
+msgstr "Navegue até o pacote inicial"
#: src/screens/Login/ForgotPasswordForm.tsx:173
#: src/screens/Login/LoginForm.tsx:332
@@ -4115,7 +4115,7 @@ msgstr "Novo chat"
#: src/components/dms/NewMessagesPill.tsx:92
msgid "New messages"
-msgstr ""
+msgstr "Novas mensagens"
#: src/view/com/modals/CreateOrEditList.tsx:241
msgid "New Moderation List"
@@ -4151,7 +4151,7 @@ msgstr "Novo Post"
#: src/components/NewskieDialog.tsx:83
msgid "New user info dialog"
-msgstr ""
+msgstr "Novo diálogo de informações do usuário"
#: src/view/com/modals/CreateOrEditList.tsx:236
msgid "New User List"
@@ -4217,7 +4217,7 @@ msgstr "Nenhum GIF em destaque encontrado."
#: src/screens/StarterPack/Wizard/StepFeeds.tsx:120
msgid "No feeds found. Try searching for something else."
-msgstr ""
+msgstr "Nenhum feed encontrado. Tente pesquisar por outra coisa."
#: src/components/ProfileCard.tsx:331
#: src/screens/Profile/Header/ProfileHeaderStandard.tsx:120
@@ -4234,7 +4234,7 @@ msgstr "Nenhuma mensagem ainda"
#: src/screens/Messages/List/index.tsx:274
msgid "No more conversations to show"
-msgstr ""
+msgstr "Não há mais conversas para mostrar"
#: src/view/com/notifications/Feed.tsx:121
msgid "No notifications yet!"
@@ -4245,15 +4245,15 @@ msgstr "Nenhuma notificação!"
#: src/screens/Messages/Settings.tsx:93
#: src/screens/Messages/Settings.tsx:96
msgid "No one"
-msgstr ""
+msgstr "Ninguém"
#: src/components/WhoCanReply.tsx:237
msgid "No one but the author can quote this post."
-msgstr ""
+msgstr "Ninguém além do autor pode citar esta postagem."
#: src/screens/Profile/Sections/Feed.tsx:59
msgid "No posts yet."
-msgstr ""
+msgstr "Nenhuma postagem ainda."
#: src/view/com/composer/text-input/mobile/Autocomplete.tsx:101
#: src/view/com/composer/text-input/web/Autocomplete.tsx:195
@@ -4262,7 +4262,7 @@ msgstr "Nenhum resultado"
#: src/components/dms/dialogs/SearchablePeopleList.tsx:202
msgid "No results"
-msgstr ""
+msgstr "Nenhum resultados"
#: src/components/Lists.tsx:215
msgid "No results found"
@@ -4299,7 +4299,7 @@ msgstr "Ninguém"
#: src/view/com/composer/threadgate/ThreadgateBtn.tsx:46
#~ msgid "Nobody can reply"
-#~ msgstr ""
+#~ msgstr "Ninguém pode responder"
#: src/components/LikedByList.tsx:79
#: src/components/LikesDialog.tsx:99
@@ -4308,7 +4308,7 @@ msgstr "Ninguém curtiu isso ainda. Você pode ser o primeiro!"
#: src/screens/StarterPack/Wizard/StepProfiles.tsx:103
msgid "Nobody was found. Try searching for someone else."
-msgstr ""
+msgstr "Ninguém foi encontrado. Tente procurar por outra pessoa."
#: src/lib/moderation/useGlobalLabelStrings.ts:42
msgid "Non-sexual Nudity"
@@ -4340,28 +4340,28 @@ msgstr "Nota: o Bluesky é uma rede aberta e pública. Esta configuração limit
#: src/screens/Messages/List/index.tsx:215
msgid "Nothing here"
-msgstr ""
+msgstr "Não há nada aqui"
#: src/view/screens/NotificationsSettings.tsx:54
msgid "Notification filters"
-msgstr ""
+msgstr "Filtros de notificação"
#: src/Navigation.tsx:348
#: src/view/screens/Notifications.tsx:119
msgid "Notification settings"
-msgstr ""
+msgstr "Configurações de notificação"
#: src/view/screens/NotificationsSettings.tsx:39
msgid "Notification Settings"
-msgstr ""
+msgstr "Configurações de notificação"
#: src/screens/Messages/Settings.tsx:124
msgid "Notification sounds"
-msgstr ""
+msgstr "Sons de notificação"
#: src/screens/Messages/Settings.tsx:121
msgid "Notification Sounds"
-msgstr ""
+msgstr "Sons de Notificação"
#: src/Navigation.tsx:559
#: src/view/screens/Notifications.tsx:145
@@ -4376,7 +4376,7 @@ msgstr "Notificações"
#: src/lib/hooks/useTimeAgo.ts:51
msgid "now"
-msgstr ""
+msgstr "agora"
#: src/components/dms/MessageItem.tsx:169
msgid "Now"
@@ -4434,7 +4434,7 @@ msgstr "Resetar tutoriais"
#: src/tours/Tooltip.tsx:118
msgid "Onboarding tour step {0}: {1}"
-msgstr ""
+msgstr "Etapa do tour de integração {0}: {1}"
#: src/view/com/composer/Composer.tsx:589
msgid "One or more images is missing alt text."
@@ -4446,7 +4446,7 @@ msgstr "Apenas imagens .jpg ou .png são permitidas"
#: src/components/WhoCanReply.tsx:245
#~ msgid "Only {0} can reply"
-#~ msgstr ""
+#~ msgstr "Apenas {0} pode responder"
#: src/components/WhoCanReply.tsx:217
msgid "Only {0} can reply."
@@ -4475,7 +4475,7 @@ msgstr "Abrir"
#: src/view/com/posts/AviFollowButton.tsx:87
msgid "Open {name} profile shortcut menu"
-msgstr ""
+msgstr "Abra o menu de atalho perfil de {name}"
#: src/screens/Onboarding/StepProfile/index.tsx:277
msgid "Open avatar creator"
@@ -4484,7 +4484,7 @@ msgstr "Abrir criador de avatar"
#: src/screens/Messages/List/ChatListItem.tsx:219
#: src/screens/Messages/List/ChatListItem.tsx:220
msgid "Open conversation options"
-msgstr ""
+msgstr "Abrir opções de conversa"
#: src/view/com/composer/Composer.tsx:754
#: src/view/com/composer/Composer.tsx:755
@@ -4501,7 +4501,7 @@ msgstr "Abrir links no navegador interno"
#: src/components/dms/ActionsWrapper.tsx:87
msgid "Open message options"
-msgstr ""
+msgstr "Abrir opções de mensagem"
#: src/screens/Moderation/index.tsx:230
msgid "Open muted words and tags settings"
@@ -4517,7 +4517,7 @@ msgstr "Abrir opções do post"
#: src/screens/StarterPack/StarterPackScreen.tsx:540
msgid "Open starter pack menu"
-msgstr ""
+msgstr "Abra o menu do kit inicial"
#: src/view/screens/Settings/index.tsx:826
#: src/view/screens/Settings/index.tsx:836
@@ -4534,7 +4534,7 @@ msgstr "Abre {numItems} opções"
#: src/view/com/composer/threadgate/ThreadgateBtn.tsx:68
msgid "Opens a dialog to choose who can reply to this thread"
-msgstr ""
+msgstr "Abre uma caixa de diálogo para escolher quem pode responder a este tópico"
#: src/view/screens/Settings/index.tsx:455
msgid "Opens accessibility settings"
@@ -4550,7 +4550,7 @@ msgstr "Abre detalhes adicionais para um registro de depuração"
#: src/view/screens/Settings/index.tsx:476
msgid "Opens appearance settings"
-msgstr ""
+msgstr "Abre as configurações de aparência"
#: src/view/com/composer/photos/OpenCameraBtn.tsx:74
msgid "Opens camera on device"
@@ -4558,7 +4558,7 @@ msgstr "Abre a câmera do dispositivo"
#: src/view/screens/Settings/index.tsx:605
msgid "Opens chat settings"
-msgstr ""
+msgstr "Abre as configurações de chat"
#: src/view/com/post-thread/PostThreadComposePrompt.tsx:30
msgid "Opens composer"
@@ -4596,7 +4596,7 @@ msgstr "Abre a lista de códigos de convite"
#: src/view/screens/Settings/index.tsx:774
msgid "Opens modal for account deactivation confirmation"
-msgstr ""
+msgstr "Abre janela para confirmação da desativação da conta"
#: src/view/screens/Settings/index.tsx:796
msgid "Opens modal for account deletion confirmation. Requires email code"
@@ -4671,11 +4671,11 @@ msgstr "Abre as preferências de threads"
#: src/view/com/notifications/FeedItem.tsx:555
#: src/view/com/util/UserAvatar.tsx:420
msgid "Opens this profile"
-msgstr ""
+msgstr "Abre este perfil"
#: src/view/com/composer/videos/SelectVideoBtn.tsx:54
msgid "Opens video picker"
-msgstr ""
+msgstr "Abre seletor de vídeos"
#: src/view/com/util/forms/DropdownButton.tsx:293
msgid "Option {0} of {numItems}"
@@ -4688,7 +4688,7 @@ msgstr "Se quiser adicionar mais informações, digite abaixo:"
#: src/components/dialogs/MutedWords.tsx:299
msgid "Options:"
-msgstr ""
+msgstr "Opções:"
#: src/components/dialogs/PostInteractionSettingsDialog.tsx:388
msgid "Or combine these options:"
@@ -4696,11 +4696,11 @@ msgstr "Ou combine estas opções:"
#: src/screens/Deactivated.tsx:211
msgid "Or, continue with another account."
-msgstr ""
+msgstr "Ou continue com outra conta."
#: src/screens/Deactivated.tsx:194
msgid "Or, log into one of your other accounts."
-msgstr ""
+msgstr "Ou faça login em uma de suas outras contas."
#: src/lib/moderation/useReportOptions.ts:27
msgid "Other"
@@ -4712,7 +4712,7 @@ msgstr "Outra conta"
#: src/view/screens/Settings/index.tsx:379
msgid "Other accounts"
-msgstr ""
+msgstr "Outras contas"
#: src/view/com/composer/select-language/SelectLangBtn.tsx:92
msgid "Other..."
@@ -4720,7 +4720,7 @@ msgstr "Outro..."
#: src/screens/Messages/Conversation/ChatDisabled.tsx:28
msgid "Our moderators have reviewed reports and decided to disable your access to chats on Bluesky."
-msgstr ""
+msgstr "Nossos moderadores analisaram os relatórios e decidiram desabilitar seu acesso aos chats no Bluesky."
#: src/components/Lists.tsx:216
#: src/view/screens/NotFound.tsx:45
@@ -4757,7 +4757,7 @@ msgstr "Pausar"
#: src/view/com/util/post-embeds/VideoEmbedInner/VideoWebControls.tsx:203
msgid "Pause video"
-msgstr ""
+msgstr "Pausar vídeo"
#: src/screens/StarterPack/StarterPackScreen.tsx:171
#: src/view/screens/Search/Search.tsx:369
@@ -4782,7 +4782,7 @@ msgstr "A permissão de galeria foi recusada. Por favor, habilite-a nas configur
#: src/components/StarterPack/Wizard/WizardListCard.tsx:55
msgid "Person toggle"
-msgstr ""
+msgstr "Alternar pessoa"
#: src/screens/Onboarding/index.tsx:28
#: src/screens/Onboarding/state.ts:94
@@ -4791,7 +4791,7 @@ msgstr "Pets"
#: src/screens/Onboarding/state.ts:95
msgid "Photography"
-msgstr ""
+msgstr "Fotografia"
#: src/view/com/modals/SelfLabel.tsx:122
msgid "Pictures meant for adults."
@@ -4812,7 +4812,7 @@ msgstr "Feeds Fixados"
#: src/view/screens/ProfileList.tsx:345
msgid "Pinned to your feeds"
-msgstr ""
+msgstr "Fixado em seus feeds"
#: src/view/com/util/post-embeds/GifEmbed.tsx:44
#: src/view/com/util/post-embeds/VideoEmbedInner/VideoWebControls.tsx:226
@@ -4826,7 +4826,7 @@ msgstr "Reproduzir {0}"
#: src/screens/Messages/Settings.tsx:97
#: src/screens/Messages/Settings.tsx:104
#~ msgid "Play notification sounds"
-#~ msgstr ""
+#~ msgstr "Reproduzir sons de notificação"
#: src/view/com/util/post-embeds/GifEmbed.tsx:43
msgid "Play or pause the GIF"
@@ -4835,7 +4835,7 @@ msgstr "Tocar ou pausar o GIF"
#: src/view/com/util/post-embeds/VideoEmbed.tsx:52
#: src/view/com/util/post-embeds/VideoEmbedInner/VideoWebControls.tsx:204
msgid "Play video"
-msgstr ""
+msgstr "Reproduzir vídeo"
#: src/view/com/util/post-embeds/ExternalPlayerEmbed.tsx:57
#: src/view/com/util/post-embeds/ExternalPlayerEmbed.tsx:58
@@ -4882,7 +4882,7 @@ msgstr "Por favor, digite o seu e-mail."
#: src/screens/Signup/StepInfo/index.tsx:63
msgid "Please enter your invite code."
-msgstr ""
+msgstr "Por favor, insira seu código de convite."
#: src/view/com/modals/DeleteAccount.tsx:253
msgid "Please enter your password as well:"
@@ -4894,7 +4894,7 @@ msgstr "Por favor, explique por que você acha que este rótulo foi aplicado inc
#: src/screens/Messages/Conversation/ChatDisabled.tsx:110
msgid "Please explain why you think your chats were incorrectly disabled"
-msgstr ""
+msgstr "Por favor, explique por que você acha que seus chats foram desativados incorretamente"
#: src/lib/hooks/useAccountSwitcher.ts:48
#: src/lib/hooks/useAccountSwitcher.ts:58
@@ -4960,7 +4960,7 @@ msgstr "Post Escondido por Você"
#: src/components/dialogs/PostInteractionSettingsDialog.tsx:283
msgid "Post interaction settings"
-msgstr ""
+msgstr "Configurações de interação de postagem"
#: src/view/com/composer/select-language/SelectLangBtn.tsx:88
msgid "Post language"
@@ -4990,7 +4990,7 @@ msgstr "Posts"
#: src/components/dialogs/MutedWords.tsx:115
msgid "Posts can be muted based on their text, their tags, or both. We recommend avoiding common words that appear in many posts, since it can result in no posts being shown."
-msgstr ""
+msgstr "As postagens podem ser silenciadas com base em seu texto, suas tags ou ambos. Recomendamos evitar palavras comuns que aparecem em muitas postagens, pois isso pode resultar em nenhuma postagem sendo exibida."
#: src/view/com/posts/FeedErrorMessage.tsx:68
msgid "Posts hidden"
@@ -5002,11 +5002,11 @@ msgstr "Link Potencialmente Enganoso"
#: src/state/queries/notifications/settings.ts:44
msgid "Preference saved"
-msgstr ""
+msgstr "Preferência salva"
#: src/screens/Messages/Conversation/MessageListError.tsx:19
msgid "Press to attempt reconnection"
-msgstr ""
+msgstr "Pressione para tentar reconectar"
#: src/components/forms/HostingProvider.tsx:46
msgid "Press to change hosting provider"
@@ -5026,7 +5026,7 @@ msgstr "Tentar novamente"
#: src/components/KnownFollowers.tsx:124
msgid "Press to view followers of this account that you also follow"
-msgstr ""
+msgstr "Pressione para ver os seguidores desta conta que você também segue"
#: src/view/com/lightbox/Lightbox.web.tsx:150
msgid "Previous image"
@@ -5042,7 +5042,7 @@ msgstr "Priorizar seus Seguidores"
#: src/view/screens/NotificationsSettings.tsx:57
msgid "Priority notifications"
-msgstr ""
+msgstr "Notificações prioritárias"
#: src/view/screens/Settings/index.tsx:620
#: src/view/shell/desktop/RightNav.tsx:81
@@ -5059,7 +5059,7 @@ msgstr "Política de Privacidade"
#: src/components/dms/MessagesNUX.tsx:91
msgid "Privately chat with other users."
-msgstr ""
+msgstr "Converse em particular com outros usuários."
#: src/screens/Login/ForgotPasswordForm.tsx:156
msgid "Processing..."
@@ -5108,19 +5108,19 @@ msgstr "Publicar resposta"
#: src/components/StarterPack/QrCodeDialog.tsx:128
msgid "QR code copied to your clipboard!"
-msgstr ""
+msgstr "QR code copiado para sua área de transferência!"
#: src/components/StarterPack/QrCodeDialog.tsx:106
msgid "QR code has been downloaded!"
-msgstr ""
+msgstr "QR code foi baixado!"
#: src/components/StarterPack/QrCodeDialog.tsx:107
msgid "QR code saved to your camera roll!"
-msgstr ""
+msgstr "QR code salvo no rolo da sua câmera!"
#: src/tours/Tooltip.tsx:111
msgid "Quick tip"
-msgstr ""
+msgstr "Dica rápida"
#: src/view/com/util/post-ctrls/RepostButton.tsx:122
#: src/view/com/util/post-ctrls/RepostButton.tsx:149
@@ -5141,11 +5141,11 @@ msgstr "Citar post"
#: src/view/com/util/forms/PostDropdownBtn.tsx:302
msgid "Quote post was re-attached"
-msgstr ""
+msgstr "A postagem de citação foi anexada novamente"
#: src/view/com/util/forms/PostDropdownBtn.tsx:301
msgid "Quote post was successfully detached"
-msgstr ""
+msgstr "A postagem de citação foi desanexada com sucesso"
#: src/components/dialogs/PostInteractionSettingsDialog.tsx:313
#: src/view/com/util/post-ctrls/RepostButton.tsx:121
@@ -5153,24 +5153,24 @@ msgstr ""
#: src/view/com/util/post-ctrls/RepostButton.web.tsx:84
#: src/view/com/util/post-ctrls/RepostButton.web.tsx:91
msgid "Quote posts disabled"
-msgstr ""
+msgstr "Postagens de citações desabilitadas"
#: src/components/dialogs/PostInteractionSettingsDialog.tsx:311
msgid "Quote posts enabled"
-msgstr ""
+msgstr "Postagens de citações habilitadas"
#: src/components/dialogs/PostInteractionSettingsDialog.tsx:295
msgid "Quote settings"
-msgstr ""
+msgstr "Configurações de citações"
#: src/screens/Post/PostQuotes.tsx:29
#: src/view/com/post-thread/PostQuotes.tsx:122
msgid "Quotes"
-msgstr ""
+msgstr "Citações"
#: src/view/com/post-thread/PostThreadItem.tsx:230
msgid "Quotes of this post"
-msgstr ""
+msgstr "Citações desta postagem"
#: src/view/screens/PreferencesThreads.tsx:80
msgid "Random (aka \"Poster's Roulette\")"
@@ -5183,27 +5183,27 @@ msgstr "Índices"
#: src/view/com/util/forms/PostDropdownBtn.tsx:543
#: src/view/com/util/forms/PostDropdownBtn.tsx:553
msgid "Re-attach quote"
-msgstr ""
+msgstr "Reanexar citação"
#: src/screens/Deactivated.tsx:144
msgid "Reactivate your account"
-msgstr ""
+msgstr "Reative sua conta"
#: src/view/com/auth/SplashScreen.web.tsx:157
msgid "Read the Bluesky blog"
-msgstr ""
+msgstr "Leia o blog Bluesky"
#: src/screens/Signup/StepInfo/Policies.tsx:59
msgid "Read the Bluesky Privacy Policy"
-msgstr ""
+msgstr "Leia a Política de Privacidade do Bluesky"
#: src/screens/Signup/StepInfo/Policies.tsx:49
msgid "Read the Bluesky Terms of Service"
-msgstr ""
+msgstr "Leia os Termos de Serviço do Bluesky"
#: src/components/dms/ReportDialog.tsx:174
msgid "Reason:"
-msgstr ""
+msgstr "Motivo:"
#: src/components/dms/MessageReportDialog.tsx:149
#~ msgid "Reason: {0}"
@@ -5223,15 +5223,15 @@ msgstr "Buscas Recentes"
#: src/screens/Messages/Conversation/MessageListError.tsx:20
msgid "Reconnect"
-msgstr ""
+msgstr "Reconectar"
#: src/view/screens/Notifications.tsx:146
msgid "Refresh notifications"
-msgstr ""
+msgstr "Atualizar notificações"
#: src/screens/Messages/List/index.tsx:200
msgid "Reload conversations"
-msgstr ""
+msgstr "Recarregar conversas"
#: src/components/dialogs/MutedWords.tsx:438
#: src/components/FeedCard.tsx:313
@@ -5248,7 +5248,7 @@ msgstr "Remover"
#: src/components/StarterPack/Wizard/WizardListCard.tsx:58
msgid "Remove {displayName} from starter pack"
-msgstr ""
+msgstr "Remover {displayName} do pacote inicial"
#: src/view/com/util/AccountDropdownBtn.tsx:26
msgid "Remove account"
@@ -5264,7 +5264,7 @@ msgstr "Remover banner"
#: src/screens/Messages/Conversation/MessageInputEmbed.tsx:218
msgid "Remove embed"
-msgstr ""
+msgstr "Remover incorporação"
#: src/view/com/posts/FeedErrorMessage.tsx:169
#: src/view/com/posts/FeedShutdownMsg.tsx:116
@@ -5291,11 +5291,11 @@ msgstr "Remover dos meus feeds?"
#: src/view/com/util/AccountDropdownBtn.tsx:53
msgid "Remove from quick access?"
-msgstr ""
+msgstr "Remover do acesso rápido?"
#: src/screens/List/ListHiddenScreen.tsx:156
msgid "Remove from saved feeds"
-msgstr ""
+msgstr "Remover dos feeds salvos"
#: src/view/com/composer/photos/Gallery.tsx:174
msgid "Remove image"
@@ -5311,11 +5311,11 @@ msgstr "Remover palavra silenciada da lista"
#: src/view/screens/Search/Search.tsx:969
msgid "Remove profile"
-msgstr ""
+msgstr "Remover perfil"
#: src/view/screens/Search/Search.tsx:971
msgid "Remove profile from search history"
-msgstr ""
+msgstr "Remover perfil do histórico de pesquisa"
#: src/view/com/util/post-embeds/QuoteEmbed.tsx:255
msgid "Remove quote"
@@ -5332,11 +5332,11 @@ msgstr "Remover este feed dos feeds salvos"
#: src/view/com/util/post-embeds/QuoteEmbed.tsx:100
msgid "Removed by author"
-msgstr ""
+msgstr "Removido pelo autor"
#: src/view/com/util/post-embeds/QuoteEmbed.tsx:98
msgid "Removed by you"
-msgstr ""
+msgstr Removido por você"
#: src/view/com/modals/ListAddRemoveUsers.tsx:200
#: src/view/com/modals/UserAddRemoveLists.tsx:164
@@ -5350,7 +5350,7 @@ msgstr "Removido dos meus feeds"
#: src/screens/List/ListHiddenScreen.tsx:94
#: src/screens/List/ListHiddenScreen.tsx:160
msgid "Removed from saved feeds"
-msgstr ""
+msgstr "Removido dos feeds salvos"
#: src/view/com/posts/FeedShutdownMsg.tsx:44
#: src/view/screens/ProfileFeed.tsx:192
@@ -5368,7 +5368,7 @@ msgstr "Remove o post citado"
#: src/view/com/composer/ExternalEmbedRemoveBtn.tsx:29
msgid "Removes the image preview"
-msgstr ""
+msgstr "Remove a pré-visualização da imagem"
#: src/view/com/posts/FeedShutdownMsg.tsx:129
#: src/view/com/posts/FeedShutdownMsg.tsx:133
@@ -5381,15 +5381,15 @@ msgstr "Respostas"
#: src/components/WhoCanReply.tsx:69
msgid "Replies disabled"
-msgstr ""
+msgstr "Respostas desabilitadas"
#: src/view/com/threadgate/WhoCanReply.tsx:123
#~ msgid "Replies on this thread are disabled"
-#~ msgstr ""
+#~ msgstr "As respostas neste tópico estão desabilitadas"
#: src/components/WhoCanReply.tsx:215
msgid "Replies to this post are disabled."
-msgstr ""
+msgstr "Respostas para esta postagem estão desativadas."
#: src/components/WhoCanReply.tsx:243
#~ msgid "Replies to this thread are disabled"
@@ -5407,20 +5407,20 @@ msgstr "Responder"
#: src/components/moderation/ModerationDetailsDialog.tsx:115
#: src/lib/moderation/useModerationCauseDescription.ts:123
msgid "Reply Hidden by Thread Author"
-msgstr ""
+msgstr "Resposta Oculta pelo Autor da Thread"
#: src/components/moderation/ModerationDetailsDialog.tsx:114
#: src/lib/moderation/useModerationCauseDescription.ts:122
msgid "Reply Hidden by You"
-msgstr ""
+msgstr "Responder Oculto por Você"
#: src/components/dialogs/PostInteractionSettingsDialog.tsx:355
msgid "Reply settings"
-msgstr ""
+msgstr "Configurações de resposta"
#: src/components/dialogs/PostInteractionSettingsDialog.tsx:340
msgid "Reply settings are chosen by the author of the thread"
-msgstr ""
+msgstr "Configurações de resposta são escolhidas pelo autor da thread"
#: src/view/com/post/Post.tsx:177
#: src/view/com/posts/FeedItem.tsx:285
@@ -5437,26 +5437,26 @@ msgstr "Responder <0><1/>0>"
#: src/view/com/posts/FeedItem.tsx:513
msgctxt "description"
msgid "Reply to a blocked post"
-msgstr ""
+msgstr "Responder a uma postagem bloqueada"
#: src/view/com/posts/FeedItem.tsx:515
msgctxt "description"
msgid "Reply to a post"
-msgstr ""
+msgstr "Responder a uma postagem"
#: src/view/com/post/Post.tsx:194
#: src/view/com/posts/FeedItem.tsx:519
msgctxt "description"
msgid "Reply to you"
-msgstr ""
+msgstr "Responder para você"
#: src/view/com/util/forms/PostDropdownBtn.tsx:332
msgid "Reply visibility updated"
-msgstr ""
+msgstr "Visibilidade da resposta atualizada"
#: src/view/com/util/forms/PostDropdownBtn.tsx:331
msgid "Reply was successfully hidden"
-msgstr ""
+msgstr "Resposta foi ocultada com sucesso"
#: src/components/dms/MessageMenu.tsx:132
#: src/components/dms/MessagesListBlockedFooter.tsx:77
@@ -5505,7 +5505,7 @@ msgstr "Denunciar post"
#: src/screens/StarterPack/StarterPackScreen.tsx:593
#: src/screens/StarterPack/StarterPackScreen.tsx:596
msgid "Report starter pack"
-msgstr ""
+msgstr "Denunciar kit inicial"
#: src/components/ReportDialog/SelectReportOptionView.tsx:43
msgid "Report this content"
@@ -5531,7 +5531,7 @@ msgstr "Denunciar este post"
#: src/components/ReportDialog/SelectReportOptionView.tsx:59
msgid "Report this starter pack"
-msgstr ""
+msgstr "Denunciar este kit inicial"
#: src/components/ReportDialog/SelectReportOptionView.tsx:47
msgid "Report this user"
@@ -5576,7 +5576,7 @@ msgstr "Repostado por <0><1/>0>"
#: src/view/com/posts/FeedItem.tsx:292
#: src/view/com/posts/FeedItem.tsx:311
msgid "Reposted by you"
-msgstr ""
+msgstr "repostou para você"
#: src/view/com/notifications/FeedItem.tsx:184
msgid "reposted your post"
@@ -5726,7 +5726,7 @@ msgstr "Salvar usuário"
#: src/components/StarterPack/ShareDialog.tsx:151
#: src/components/StarterPack/ShareDialog.tsx:158
msgid "Save image"
-msgstr ""
+msgstr "Salvar imagem"
#: src/view/com/modals/crop-image/CropImage.web.tsx:169
msgid "Save image crop"
@@ -5734,7 +5734,7 @@ msgstr "Salvar corte de imagem"
#: src/components/StarterPack/QrCodeDialog.tsx:181
msgid "Save QR code"
-msgstr ""
+msgstr "Salvar QR code"
#: src/view/screens/ProfileFeed.tsx:334
#: src/view/screens/ProfileFeed.tsx:340
@@ -5775,7 +5775,7 @@ msgstr "Salva o corte da imagem"
#: src/view/com/notifications/FeedItem.tsx:416
#: src/view/com/notifications/FeedItem.tsx:441
msgid "Say hello!"
-msgstr ""
+msgstr "Diga olá!"
#: src/screens/Onboarding/index.tsx:33
#: src/screens/Onboarding/state.ts:97
@@ -5819,7 +5819,7 @@ msgstr "Pesquisar por posts com a tag {displayTag}"
#: src/screens/StarterPack/Wizard/index.tsx:491
msgid "Search for feeds that you want to suggest to others."
-msgstr ""
+msgstr "Procure por feeds que você queira sugerir para outros."
#: src/components/dms/NewChat.tsx:226
#~ msgid "Search for someone to start a conversation with."
@@ -5866,7 +5866,7 @@ msgstr "Ver posts com <0>{displayTag}0> deste usuário"
#: src/view/com/auth/SplashScreen.web.tsx:162
msgid "See jobs at Bluesky"
-msgstr ""
+msgstr "Veja empregos na Bluesky"
#: src/view/com/notifications/FeedItem.tsx:411
#: src/view/com/util/UserAvatar.tsx:402
@@ -5915,7 +5915,7 @@ msgstr "Selecionar GIF \"{0}\""
#: src/components/dialogs/MutedWords.tsx:142
msgid "Select how long to mute this word for."
-msgstr ""
+msgstr "Selecione por quanto tempo essa palavra deve ser silenciada."
#: src/view/screens/LanguageSettings.tsx:303
msgid "Select languages"
@@ -5951,11 +5951,11 @@ msgstr "Selecione o serviço que hospeda seus dados."
#: src/view/com/composer/videos/SelectVideoBtn.tsx:53
msgid "Select video"
-msgstr ""
+msgstr "Selecione o vídeo"
#: src/components/dialogs/MutedWords.tsx:242
msgid "Select what content this mute word should apply to."
-msgstr ""
+msgstr "Selecione a qual conteúdo esta palavra silenciada deve ser aplicada."
#: src/screens/Onboarding/StepModeration/index.tsx:63
#~ msgid "Select what you want to see (or not see), and we’ll handle the rest."
@@ -5991,7 +5991,7 @@ msgstr "Selecione seu idioma preferido para as traduções no seu feed."
#: src/components/dms/ChatEmptyPill.tsx:38
msgid "Send a neat website!"
-msgstr ""
+msgstr "Envie um site bacana!"
#: src/view/com/modals/VerifyEmail.tsx:210
#: src/view/com/modals/VerifyEmail.tsx:212
@@ -6018,7 +6018,7 @@ msgstr "Enviar mensagem"
#: src/components/dms/dialogs/ShareViaChatDialog.tsx:64
msgid "Send post to..."
-msgstr ""
+msgstr "Enviar postagem para..."
#: src/components/dms/ReportDialog.tsx:234
#: src/components/dms/ReportDialog.tsx:237
@@ -6039,7 +6039,7 @@ msgstr "Enviar e-mail de verificação"
#: src/view/com/util/forms/PostDropdownBtn.tsx:399
#: src/view/com/util/forms/PostDropdownBtn.tsx:402
msgid "Send via direct message"
-msgstr ""
+msgstr "Enviar por mensagem direta"
#: src/view/com/modals/DeleteAccount.tsx:151
msgid "Sends email with confirmation code for account deletion"
@@ -6156,11 +6156,11 @@ msgstr "Compartilhar"
#: src/components/dms/ChatEmptyPill.tsx:37
msgid "Share a cool story!"
-msgstr ""
+msgstr "Compartilhe uma história legal!"
#: src/components/dms/ChatEmptyPill.tsx:36
msgid "Share a fun fact!"
-msgstr ""
+msgstr "Compartilhe um fato divertido!"
#: src/view/com/profile/ProfileMenu.tsx:377
#: src/view/com/util/forms/PostDropdownBtn.tsx:659
@@ -6177,7 +6177,7 @@ msgstr "Compartilhar feed"
#: src/components/StarterPack/ShareDialog.tsx:131
#: src/screens/StarterPack/StarterPackScreen.tsx:586
msgid "Share link"
-msgstr ""
+msgstr "Compartilhar link"
#: src/view/com/modals/LinkWarning.tsx:89
#: src/view/com/modals/LinkWarning.tsx:95
@@ -6186,28 +6186,28 @@ msgstr "Compartilhar Link"
#: src/components/StarterPack/ShareDialog.tsx:88
msgid "Share link dialog"
-msgstr ""
+msgstr "Compartilhar Link de diálogo"
#: src/components/StarterPack/ShareDialog.tsx:135
#: src/components/StarterPack/ShareDialog.tsx:146
msgid "Share QR code"
-msgstr ""
+msgstr "Compartilhar QR code"
#: src/screens/StarterPack/StarterPackScreen.tsx:404
msgid "Share this starter pack"
-msgstr ""
+msgstr "Compartilhar este kit inicial"
#: src/components/StarterPack/ShareDialog.tsx:100
msgid "Share this starter pack and help people join your community on Bluesky."
-msgstr ""
+msgstr "Compartilhe este kit inicial e ajude as pessoas a se juntarem à sua comunidade no Bluesky."
#: src/components/dms/ChatEmptyPill.tsx:34
msgid "Share your favorite feed!"
-msgstr ""
+msgstr "Compartilhe este kit inicial e ajude as pessoas a se juntarem à sua comunidade no Bluesky."
#: src/Navigation.tsx:251
msgid "Shared Preferences Tester"
-msgstr ""
+msgstr "Compartilhe Preferências de Testador"
#: src/view/com/modals/LinkWarning.tsx:92
msgid "Shares the linked website"
@@ -6249,7 +6249,7 @@ msgstr "Mostrar usuários parecidos com {0}"
#: src/view/com/post-thread/PostThreadShowHiddenReplies.tsx:23
msgid "Show hidden replies"
-msgstr ""
+msgstr "Mostrar respostas ocultas"
#: src/view/com/util/forms/PostDropdownBtn.tsx:449
#: src/view/com/util/forms/PostDropdownBtn.tsx:451
@@ -6258,7 +6258,7 @@ msgstr "Mostrar menos disso"
#: src/screens/List/ListHiddenScreen.tsx:172
msgid "Show list anyway"
-msgstr ""
+msgstr "Mostrar lista de qualquer maneira"
#: src/view/com/post-thread/PostThreadItem.tsx:584
#: src/view/com/post/Post.tsx:234
@@ -6273,7 +6273,7 @@ msgstr "Mostrar mais disso"
#: src/view/com/post-thread/PostThreadShowHiddenReplies.tsx:23
msgid "Show muted replies"
-msgstr ""
+msgstr "Mostrar respostas silenciadas"
#: src/view/screens/PreferencesFollowingFeed.tsx:154
msgid "Show Posts from My Feeds"
@@ -6318,7 +6318,7 @@ msgstr "Mostrar as respostas de pessoas que você segue antes de todas as outras
#: src/view/com/util/forms/PostDropdownBtn.tsx:517
#: src/view/com/util/forms/PostDropdownBtn.tsx:527
msgid "Show reply for everyone"
-msgstr ""
+msgstr "Mostrar resposta para todos"
#: src/view/screens/PreferencesFollowingFeed.tsx:84
msgid "Show Reposts"
@@ -6393,7 +6393,7 @@ msgstr "Sair"
#: src/view/screens/Settings/index.tsx:420
#: src/view/screens/Settings/index.tsx:430
msgid "Sign out of all accounts"
-msgstr ""
+msgstr "Sair de todas as contas"
#: src/view/shell/bottom-bar/BottomBar.tsx:305
#: src/view/shell/bottom-bar/BottomBar.tsx:306
@@ -6427,16 +6427,16 @@ msgstr "autenticado como @{0}"
#: src/view/com/notifications/FeedItem.tsx:222
msgid "signed up with your starter pack"
-msgstr ""
+msgstr "se inscreveu com seu kit inicial"
#: src/screens/StarterPack/StarterPackLandingScreen.tsx:306
#: src/screens/StarterPack/StarterPackLandingScreen.tsx:313
msgid "Signup without a starter pack"
-msgstr ""
+msgstr "Inscreva-se sem um kit inicial"
#: src/view/com/profile/ProfileHeaderSuggestedFollows.tsx:102
msgid "Similar accounts"
-msgstr ""
+msgstr "Contas semelhantes"
#: src/screens/Onboarding/StepInterests/index.tsx:265
#: src/screens/StarterPack/Wizard/index.tsx:191
@@ -6454,11 +6454,11 @@ msgstr "Desenvolvimento de software"
#: src/components/FeedInterstitials.tsx:397
msgid "Some other feeds you might like"
-msgstr ""
+msgstr "Alguns outros feeds que você pode gostar"
#: src/components/WhoCanReply.tsx:70
msgid "Some people can reply"
-msgstr ""
+msgstr "Algumas pessoas podem responder"
#: src/screens/StarterPack/Wizard/index.tsx:203
#~ msgid "Some subtitle"
@@ -6471,7 +6471,7 @@ msgstr "Algo deu errado"
#: src/screens/Deactivated.tsx:94
#: src/screens/Settings/components/DeactivateAccountDialog.tsx:59
msgid "Something went wrong, please try again"
-msgstr ""
+msgstr "Algo deu errado, tente novamente"
#: src/components/ReportDialog/index.tsx:59
#: src/screens/Moderation/index.tsx:115
@@ -6482,7 +6482,7 @@ msgstr "Algo deu errado. Por favor, tente novamente."
#: src/components/Lists.tsx:200
#: src/view/screens/NotificationsSettings.tsx:46
msgid "Something went wrong!"
-msgstr ""
+msgstr "Algo deu errado!"
#: src/App.native.tsx:102
#: src/App.web.tsx:83
@@ -6507,7 +6507,7 @@ msgstr "Classificar respostas de um post por:"
#: src/components/moderation/LabelsOnMeDialog.tsx:171
msgid "Source: <0>{sourceName}0>"
-msgstr ""
+msgstr "Fonte: <0>{sourceName}0>"
#: src/lib/moderation/useReportOptions.ts:67
#: src/lib/moderation/useReportOptions.ts:80
@@ -6533,38 +6533,38 @@ msgstr "Começar um novo chat"
#: src/components/dms/dialogs/SearchablePeopleList.tsx:371
msgid "Start chat with {displayName}"
-msgstr ""
+msgstr "Comece a conversar com {displayName}"
#: src/components/dms/MessagesNUX.tsx:161
msgid "Start chatting"
-msgstr ""
+msgstr "Comece a conversar"
#: src/tours/Tooltip.tsx:99
msgid "Start of onboarding tour window. Do not move backward. Instead, go forward for more options, or press to skip."
-msgstr ""
+msgstr "Início da integração da sua janela. Não retroceda. Em vez disso, avance para mais opções ou pressione para pular."
#: src/lib/generate-starterpack.ts:68
#: src/Navigation.tsx:358
#: src/Navigation.tsx:363
#: src/screens/StarterPack/Wizard/index.tsx:182
msgid "Starter Pack"
-msgstr ""
+msgstr "Kit Inicial"
#: src/components/StarterPack/StarterPackCard.tsx:73
msgid "Starter pack by {0}"
-msgstr ""
+msgstr "Kit inicial por {0}"
#: src/screens/StarterPack/StarterPackScreen.tsx:703
msgid "Starter pack is invalid"
-msgstr ""
+msgstr "Kit inicial é inválido"
#: src/view/screens/Profile.tsx:214
msgid "Starter Packs"
-msgstr ""
+msgstr "Kits Iniciais"
#: src/components/StarterPack/ProfileStarterPacks.tsx:238
msgid "Starter packs let you easily share your favorite feeds and people with your friends."
-msgstr ""
+msgstr "Kits iniciais permitem que você compartilhe facilmente seus feeds e pessoas favoritas com seus amigos."
#: src/view/screens/Settings/index.tsx:862
#~ msgid "Status page"
@@ -6625,7 +6625,7 @@ msgstr "Inscreva-se nesta lista"
#: src/view/screens/Search/Explore.tsx:332
msgid "Suggested accounts"
-msgstr ""
+msgstr "Contas sugeridas"
#: src/view/screens/Search/Search.tsx:425
#~ msgid "Suggested Follows"
@@ -6652,7 +6652,7 @@ msgstr "Alterar Conta"
#: src/tours/HomeTour.tsx:48
msgid "Switch between feeds to control your experience."
-msgstr ""
+msgstr "Alterne entre feeds para controlar sua experiência."
#: src/view/screens/Settings/index.tsx:126
msgid "Switch to {0}"
@@ -6681,7 +6681,7 @@ msgstr "Menu da tag: {displayTag}"
#: src/components/dialogs/MutedWords.tsx:282
msgid "Tags only"
-msgstr ""
+msgstr "Apenas Tags"
#: src/view/com/modals/crop-image/CropImage.web.tsx:135
msgid "Tall"
@@ -6689,15 +6689,15 @@ msgstr "Alto"
#: src/components/ProgressGuide/Toast.tsx:150
msgid "Tap to dismiss"
-msgstr ""
+msgstr "Toque para dispensar"
#: src/view/com/util/post-embeds/VideoEmbedInner/VideoEmbedInnerNative.tsx:181
msgid "Tap to enter full screen"
-msgstr ""
+msgstr "Toque para entrar em tela cheia"
#: src/view/com/util/post-embeds/VideoEmbedInner/VideoEmbedInnerNative.tsx:202
msgid "Tap to toggle sound"
-msgstr ""
+msgstr "Toque para alternar o som"
#: src/view/com/util/images/AutoSizedImage.tsx:70
msgid "Tap to view fully"
@@ -6705,11 +6705,11 @@ msgstr "Toque para ver tudo"
#: src/state/shell/progress-guide.tsx:166
msgid "Task complete - 10 likes!"
-msgstr ""
+msgstr "Tarefa concluída - 10 curtidas!"
#: src/components/ProgressGuide/List.tsx:49
msgid "Teach our algorithm what you like"
-msgstr ""
+msgstr "Ensine ao nosso algoritmo o que você curte"
#: src/screens/Onboarding/index.tsx:36
#: src/screens/Onboarding/state.ts:99
@@ -6718,11 +6718,11 @@ msgstr "Tecnologia"
#: src/components/dms/ChatEmptyPill.tsx:35
msgid "Tell a joke!"
-msgstr ""
+msgstr "Conte uma piada!"
#: src/screens/StarterPack/Wizard/StepDetails.tsx:63
msgid "Tell us a little more"
-msgstr ""
+msgstr "Conte-nos um pouco mais"
#: src/view/shell/desktop/RightNav.tsx:90
msgid "Terms"
@@ -6749,7 +6749,7 @@ msgstr "Termos utilizados violam as diretrizes da comunidade"
#: src/components/dialogs/MutedWords.tsx:266
msgid "Text & tags"
-msgstr ""
+msgstr "Texto e tags"
#: src/components/moderation/LabelsOnMeDialog.tsx:266
#: src/screens/Messages/Conversation/ChatDisabled.tsx:108
@@ -6776,11 +6776,11 @@ msgstr "Este identificador de usuário já está sendo usado."
#: src/screens/StarterPack/Wizard/index.tsx:105
#: src/screens/StarterPack/Wizard/index.tsx:113
msgid "That starter pack could not be found."
-msgstr ""
+msgstr "Esse kit inicial não pôde ser encontrado."
#: src/view/com/post-thread/PostQuotes.tsx:129
msgid "That's all, folks!"
-msgstr ""
+msgstr "É isso, pessoal!"
#: src/screens/Profile/Header/ProfileHeaderStandard.tsx:310
#: src/view/com/profile/ProfileMenu.tsx:353
@@ -6794,11 +6794,11 @@ msgstr "A conta poderá interagir com você após o desbloqueio."
#: src/components/moderation/ModerationDetailsDialog.tsx:118
#: src/lib/moderation/useModerationCauseDescription.ts:126
msgid "The author of this thread has hidden this reply."
-msgstr ""
+msgstr "O autor deste tópico ocultou esta resposta."
#: src/screens/Moderation/index.tsx:368
msgid "The Bluesky web application"
-msgstr ""
+msgstr "A aplicação web Bluesky"
#: src/view/screens/CommunityGuidelines.tsx:36
msgid "The Community Guidelines have been moved to <0/>"
@@ -6810,16 +6810,16 @@ msgstr "A Política de Direitos Autorais foi movida para <0/>"
#: src/view/com/posts/FeedShutdownMsg.tsx:102
msgid "The Discover feed"
-msgstr ""
+msgstr "O feed Discover"
#: src/state/shell/progress-guide.tsx:167
#: src/state/shell/progress-guide.tsx:172
msgid "The Discover feed now knows what you like"
-msgstr ""
+msgstr "O feed Discover agora sabe o que você curte"
#: src/screens/StarterPack/StarterPackLandingScreen.tsx:327
msgid "The experience is better in the app. Download Bluesky now and we'll pick back up where you left off."
-msgstr ""
+msgstr "A experiência é melhor no aplicativo. Baixe o Bluesky agora e retomaremos de onde você parou."
#: src/view/com/posts/FeedShutdownMsg.tsx:67
msgid "The feed has been replaced with Discover."
@@ -6848,11 +6848,11 @@ msgstr "A Política de Privacidade foi movida para <0/>"
#: src/state/queries/video/video.ts:129
msgid "The selected video is larger than 100MB."
-msgstr ""
+msgstr "Vídeo selecionado é maior que 100 MB."
#: src/screens/StarterPack/StarterPackScreen.tsx:713
msgid "The starter pack that you are trying to view is invalid. You may delete this starter pack instead."
-msgstr ""
+msgstr "O kit inicial que você está tentando visualizar é inválido. Você pode excluir este kit inicial em vez disso."
#: src/view/screens/Support.tsx:36
msgid "The support form has been moved. If you need help, please <0/> or visit {HELP_DESK_URL} to get in touch with us."
@@ -6868,7 +6868,7 @@ msgstr "Os Termos de Serviço foram movidos para"
#: src/screens/Settings/components/DeactivateAccountDialog.tsx:86
msgid "There is no time limit for account deactivation, come back any time."
-msgstr ""
+msgstr "Não há limite de tempo para desativação da conta, volte quando quiser."
#: src/screens/Profile/Header/ProfileHeaderLabeler.tsx:117
#: src/view/screens/ProfileFeed.tsx:545
@@ -6987,7 +6987,7 @@ msgstr "Esta conta solicitou que os usuários fizessem login para visualizar seu
#: src/components/dms/BlockedByListDialog.tsx:34
msgid "This account is blocked by one or more of your moderation lists. To unblock, please visit the lists directly and remove this user."
-msgstr ""
+msgstr "This account is blocked by one or more of your moderation lists. To unblock, please visit the lists directly and remove this user."
#: src/components/moderation/LabelsOnMeDialog.tsx:260
#~ msgid "This appeal will be sent to <0>{0}0>."
@@ -6995,15 +6995,15 @@ msgstr ""
#: src/components/moderation/LabelsOnMeDialog.tsx:250
msgid "This appeal will be sent to <0>{sourceName}0>."
-msgstr ""
+msgstr "Este apelo será enviado para <0>{sourceName}0>."
#: src/screens/Messages/Conversation/ChatDisabled.tsx:104
msgid "This appeal will be sent to Bluesky's moderation service."
-msgstr ""
+msgstr "Este apelo será enviado ao serviço de moderação da Bluesky."
#: src/screens/Messages/Conversation/MessageListError.tsx:18
msgid "This chat was disconnected"
-msgstr ""
+msgstr "Este chat foi desconectado"
#: src/screens/Messages/Conversation/MessageListError.tsx:26
#~ msgid "This chat was disconnected due to a network error."
@@ -7032,7 +7032,7 @@ msgstr "Este conteúdo não é visível sem uma conta do Bluesky."
#: src/screens/Messages/List/ChatListItem.tsx:213
msgid "This conversation is with a deleted or a deactivated account. Press for options."
-msgstr ""
+msgstr "Esta conversa é com uma conta excluída ou desativada. Pressione para opções."
#: src/view/screens/Settings/ExportCarDialog.tsx:93
msgid "This feature is in beta. You can read more about repository exports in <0>this blogpost0>."
@@ -7056,7 +7056,7 @@ msgstr "Este feed está vazio! Talvez você precise seguir mais usuários ou con
#: src/view/screens/ProfileFeed.tsx:474
#: src/view/screens/ProfileList.tsx:785
msgid "This feed is empty."
-msgstr ""
+msgstr "Este feed está vazio."
#: src/view/com/posts/FeedShutdownMsg.tsx:99
msgid "This feed is no longer online. We are showing <0>Discover0> instead."
@@ -7088,7 +7088,7 @@ msgstr "Este rótulo foi aplicado pelo autor."
#: src/components/moderation/LabelsOnMeDialog.tsx:169
msgid "This label was applied by you."
-msgstr ""
+msgstr "Esta etiqueta foi aplicada por você."
#: src/screens/Profile/Sections/Labels.tsx:188
msgid "This labeler hasn't declared what labels it publishes, and may not be active."
@@ -7100,7 +7100,7 @@ msgstr "Este link está levando você ao seguinte site:"
#: src/screens/List/ListHiddenScreen.tsx:136
msgid "This list - created by <0>{0}0> - contains possible violations of Bluesky's community guidelines in its name or description."
-msgstr ""
+msgstr "Esta lista - criada por <0>{0}0> - contém possíveis violações das diretrizes da comunidade Bluesky em seu nome ou descrição."
#: src/view/screens/ProfileList.tsx:963
msgid "This list is empty!"
@@ -7125,7 +7125,7 @@ msgstr "Este post só pode ser visto por usuários autenticados e não aparecer
#: src/view/com/util/forms/PostDropdownBtn.tsx:637
msgid "This post will be hidden from feeds and threads. This cannot be undone."
-msgstr ""
+msgstr "Este post será ocultado de feeds e threads. Isso não pode ser desfeito."
#: src/view/com/util/forms/PostDropdownBtn.tsx:443
#~ msgid "This post will be hidden from feeds."
@@ -7133,7 +7133,7 @@ msgstr ""
#: src/view/com/composer/useExternalLinkFetch.ts:67
msgid "This post's author has disabled quote posts."
-msgstr ""
+msgstr "O autor desta postagem desabilitou as postagens de citação."
#: src/view/com/profile/ProfileMenu.tsx:374
msgid "This profile is only visible to logged-in users. It won't be visible to people who aren't logged in."
@@ -7141,7 +7141,7 @@ msgstr "Este post só pode ser visto por usuários autenticados e não aparecer
#: src/view/com/util/forms/PostDropdownBtn.tsx:699
msgid "This reply will be sorted into a hidden section at the bottom of your thread and will mute notifications for subsequent replies - both for yourself and others."
-msgstr ""
+msgstr "Esta resposta será classificada em uma seção oculta na parte inferior do seu tópico e silenciará as notificações para respostas subsequentes, tanto para você quanto para outras pessoas."
#: src/screens/Signup/StepInfo/Policies.tsx:37
msgid "This service has not provided terms of service or a privacy policy."
@@ -7157,7 +7157,7 @@ msgstr "Este usuário não é seguido por ninguém ainda."
#: src/components/dms/MessagesListBlockedFooter.tsx:60
msgid "This user has blocked you"
-msgstr ""
+msgstr "Este usuário bloqueou você"
#: src/components/moderation/ModerationDetailsDialog.tsx:78
#: src/lib/moderation/useModerationCauseDescription.ts:73
@@ -7178,7 +7178,7 @@ msgstr "Este usuário está incluído na lista <0>{0}0>, que você silenciou."
#: src/components/NewskieDialog.tsx:65
msgid "This user is new here. Press for more info about when they joined."
-msgstr ""
+msgstr "Este usuário é novo aqui. Pressione para mais informações sobre quando ele entrou."
#: src/view/com/profile/ProfileFollows.tsx:87
msgid "This user isn't following anyone."
@@ -7190,7 +7190,7 @@ msgstr "Este usuário não segue ninguém ainda."
#: src/components/dialogs/MutedWords.tsx:435
msgid "This will delete \"{0}\" from your muted words. You can always add it back later."
-msgstr ""
+msgstr "Isso excluirá \"{0}\" das suas palavras silenciadas. Você sempre pode adicioná-lo novamente mais tarde."
#: src/components/dialogs/MutedWords.tsx:283
#~ msgid "This will delete {0} from your muted words. You can always add it back later."
@@ -7198,11 +7198,11 @@ msgstr ""
#: src/view/com/util/AccountDropdownBtn.tsx:55
msgid "This will remove @{0} from the quick access list."
-msgstr ""
+msgstr "Isso removerá @{0} da lista de acesso rápido."
#: src/view/com/util/forms/PostDropdownBtn.tsx:689
msgid "This will remove your post from this quote post for all users, and replace it with a placeholder."
-msgstr ""
+msgstr "Isso removerá sua postagem desta postagem de citação para todos os usuários e a substituirá por um espaço reservado."
#: src/view/screens/Settings/index.tsx:560
msgid "Thread preferences"
@@ -7215,7 +7215,7 @@ msgstr "Preferências das Threads"
#: src/components/WhoCanReply.tsx:109
#~ msgid "Thread settings updated"
-#~ msgstr ""
+#~ msgstr "Configurações de Thread atualizadas"
#: src/view/screens/PreferencesThreads.tsx:113
msgid "Threaded Mode"
@@ -7274,7 +7274,7 @@ msgstr "Tentar novamente"
#: src/screens/Onboarding/state.ts:100
msgid "TV"
-msgstr ""
+msgstr "TV"
#: src/view/screens/Settings/index.tsx:711
msgid "Two-factor authentication"
@@ -7307,7 +7307,7 @@ msgstr "Não foi possível entrar em contato com seu serviço. Por favor, verifi
#: src/screens/StarterPack/StarterPackScreen.tsx:637
msgid "Unable to delete"
-msgstr ""
+msgstr "Não foi possível excluir"
#: src/components/dms/MessagesListBlockedFooter.tsx:89
#: src/components/dms/MessagesListBlockedFooter.tsx:96
@@ -7328,7 +7328,7 @@ msgstr "Desbloquear"
#: src/components/dms/ConvoMenu.tsx:188
#: src/components/dms/ConvoMenu.tsx:192
msgid "Unblock account"
-msgstr ""
+msgstr "Desbloquear Conta"
#: src/view/com/profile/ProfileMenu.tsx:303
#: src/view/com/profile/ProfileMenu.tsx:309
@@ -7394,7 +7394,7 @@ msgstr "Dessilenciar posts com {displayTag}"
#: src/components/dms/ConvoMenu.tsx:176
msgid "Unmute conversation"
-msgstr ""
+msgstr "Desmutar conversa"
#: src/components/dms/ConvoMenu.tsx:140
#~ msgid "Unmute notifications"
@@ -7407,11 +7407,11 @@ msgstr "Dessilenciar thread"
#: src/view/com/util/post-embeds/VideoEmbedInner/VideoWebControls.tsx:201
msgid "Unmute video"
-msgstr ""
+msgstr "Desmutar vídeo"
#: src/view/com/util/post-embeds/VideoEmbedInner/VideoEmbedInnerNative.tsx:201
msgid "Unmuted"
-msgstr ""
+msgstr "Desmutar"
#: src/view/screens/ProfileFeed.tsx:292
#: src/view/screens/ProfileList.tsx:673
@@ -7428,7 +7428,7 @@ msgstr "Desafixar lista de moderação"
#: src/view/screens/ProfileList.tsx:346
msgid "Unpinned from your feeds"
-msgstr ""
+msgstr "Desfixado dos seus feeds"
#: src/screens/Profile/Header/ProfileHeaderLabeler.tsx:228
msgid "Unsubscribe"
@@ -7437,7 +7437,7 @@ msgstr "Desinscrever-se"
#: src/screens/List/ListHiddenScreen.tsx:184
#: src/screens/List/ListHiddenScreen.tsx:194
msgid "Unsubscribe from list"
-msgstr ""
+msgstr "Cancelar assinatura da lista"
#: src/screens/Profile/Header/ProfileHeaderLabeler.tsx:196
msgid "Unsubscribe from this labeler"
@@ -7445,7 +7445,7 @@ msgstr "Desinscrever-se deste rotulador"
#: src/screens/List/ListHiddenScreen.tsx:86
msgid "Unsubscribed from list"
-msgstr ""
+msgstr "Cancelada inscrição na lista"
#: src/lib/moderation/useReportOptions.ts:85
#~ msgid "Unwanted sexual content"
@@ -7466,11 +7466,11 @@ msgstr "Alterar para {handle}"
#: src/view/com/util/forms/PostDropdownBtn.tsx:305
msgid "Updating quote attachment failed"
-msgstr ""
+msgstr "Falha na atualização do anexo de cotação"
#: src/view/com/util/forms/PostDropdownBtn.tsx:335
msgid "Updating reply visibility failed"
-msgstr ""
+msgstr "Falha na atualização da visibilidade da resposta"
#: src/screens/Login/SetNewPasswordForm.tsx:186
msgid "Updating..."
@@ -7556,7 +7556,7 @@ msgstr "Usuário Bloqueado por \"{0}\""
#: src/components/dms/BlockedByListDialog.tsx:27
msgid "User blocked by list"
-msgstr ""
+msgstr "Usuário Bloqueado Por Lista"
#: src/components/moderation/ModerationDetailsDialog.tsx:56
msgid "User Blocked by List"
@@ -7609,14 +7609,14 @@ msgstr "Usuários"
#: src/components/WhoCanReply.tsx:258
msgid "users followed by <0>@{0}0>"
-msgstr ""
+msgstr "usuários seguidos por <0>@{0}0>"
#: src/components/dms/MessagesNUX.tsx:140
#: src/components/dms/MessagesNUX.tsx:143
#: src/screens/Messages/Settings.tsx:84
#: src/screens/Messages/Settings.tsx:87
msgid "Users I follow"
-msgstr ""
+msgstr "Usuários que eu sigo"
#: src/components/dialogs/PostInteractionSettingsDialog.tsx:416
msgid "Users in \"{0}\""
@@ -7673,7 +7673,7 @@ msgstr "Versão {appVersion} {bundleInfo}"
#: src/view/com/util/post-embeds/VideoEmbedInner/VideoEmbedInnerNative.tsx:180
msgid "Video"
-msgstr ""
+msgstr "Vídeo"
#: src/screens/Onboarding/index.tsx:39
#: src/screens/Onboarding/state.ts:88
@@ -7682,7 +7682,7 @@ msgstr "Games"
#: src/view/com/composer/videos/state.ts:27
#~ msgid "Videos cannot be larger than 100MB"
-#~ msgstr ""
+#~ msgstr "Vídeos não podem ter mais de 100 MB"
#: src/screens/Profile/Header/Shell.tsx:113
msgid "View {0}'s avatar"
@@ -7691,19 +7691,19 @@ msgstr "Ver o avatar de {0}"
#: src/components/ProfileCard.tsx:110
#: src/view/com/notifications/FeedItem.tsx:277
msgid "View {0}'s profile"
-msgstr ""
+msgstr "Ver perfil de {0}"
#: src/components/dms/MessagesListHeader.tsx:160
msgid "View {displayName}'s profile"
-msgstr ""
+msgstr "Ver perfil de {displayName}"
#: src/components/ProfileHoverCard/index.web.tsx:430
msgid "View blocked user's profile"
-msgstr ""
+msgstr "Ver perfil do usuário bloqueado"
#: src/view/screens/Settings/ExportCarDialog.tsx:97
msgid "View blogpost for more details"
-msgstr ""
+msgstr "Veja o blogpost para mais detalhes"
#: src/view/screens/Log.tsx:56
msgid "View debug entry"
@@ -7747,20 +7747,20 @@ msgstr "Ver usuários que curtiram este feed"
#: src/screens/Moderation/index.tsx:274
msgid "View your blocked accounts"
-msgstr ""
+msgstr "Veja suas contas bloqueadas"
#: src/view/com/home/HomeHeaderLayout.web.tsx:79
#: src/view/com/home/HomeHeaderLayoutMobile.tsx:86
msgid "View your feeds and explore more"
-msgstr ""
+msgstr "Veja seus feeds e explore mais"
#: src/screens/Moderation/index.tsx:244
msgid "View your moderation lists"
-msgstr ""
+msgstr "Veja suas listas de moderação"
#: src/screens/Moderation/index.tsx:259
msgid "View your muted accounts"
-msgstr ""
+msgstr "Veja suas contas silenciadas"
#: src/view/com/modals/LinkWarning.tsx:89
#: src/view/com/modals/LinkWarning.tsx:95
@@ -7831,7 +7831,7 @@ msgstr "Usaremos isto para customizar a sua experiência."
#: src/components/dms/dialogs/SearchablePeopleList.tsx:90
msgid "We're having network issues, try again"
-msgstr ""
+msgstr "Estamos com problemas de rede, tente novamente"
#: src/screens/Signup/index.tsx:100
msgid "We're so excited to have you join us!"
@@ -7851,7 +7851,7 @@ msgstr "Lamentamos, mas sua busca não pôde ser concluída. Por favor, tente no
#: src/view/com/composer/Composer.tsx:380
msgid "We're sorry! The post you are replying to has been deleted."
-msgstr ""
+msgstr "Sentimos muito! A postagem que você está respondendo foi excluída."
#: src/components/Lists.tsx:220
#: src/view/screens/NotFound.tsx:48
@@ -7864,11 +7864,11 @@ msgstr "Sentimos muito! Não conseguimos encontrar a página que você estava pr
#: src/screens/Profile/Header/ProfileHeaderLabeler.tsx:333
msgid "We're sorry! You can only subscribe to twenty labelers, and you've reached your limit of twenty."
-msgstr ""
+msgstr "Sentimos muito! Você só pode assinar vinte rotuladores, e você atingiu seu limite de vinte."
#: src/screens/Deactivated.tsx:128
msgid "Welcome back!"
-msgstr ""
+msgstr "Bem vindo de volta!"
#: src/view/com/auth/onboarding/WelcomeMobile.tsx:48
#~ msgid "Welcome to <0>Bluesky0>"
@@ -7876,7 +7876,7 @@ msgstr ""
#: src/components/NewskieDialog.tsx:103
msgid "Welcome, friend!"
-msgstr ""
+msgstr "Bem-vindo, amigo!"
#: src/screens/Onboarding/StepInterests/index.tsx:155
msgid "What are your interests?"
@@ -7884,7 +7884,7 @@ msgstr "Do que você gosta?"
#: src/screens/StarterPack/Wizard/StepDetails.tsx:42
msgid "What do you want to call your starter pack?"
-msgstr ""
+msgstr "Como você quer chamar seu kit inicial?"
#: src/view/com/auth/SplashScreen.tsx:40
#: src/view/com/auth/SplashScreen.web.tsx:86
@@ -7902,12 +7902,12 @@ msgstr "Quais idiomas você gostaria de ver nos seus feeds?"
#: src/components/WhoCanReply.tsx:179
msgid "Who can interact with this post?"
-msgstr ""
+msgstr "Quem pode interagir com esta postagem?"
#: src/components/dms/MessagesNUX.tsx:110
#: src/components/dms/MessagesNUX.tsx:124
msgid "Who can message you?"
-msgstr ""
+msgstr "Quem pode enviar mensagens para você?"
#: src/components/WhoCanReply.tsx:87
msgid "Who can reply"
@@ -7915,11 +7915,11 @@ msgstr "Quem pode responder"
#: src/components/WhoCanReply.tsx:212
#~ msgid "Who can reply dialog"
-#~ msgstr ""
+#~ msgstr "Quem pode responder ao diálogo"
#: src/components/WhoCanReply.tsx:216
#~ msgid "Who can reply?"
-#~ msgstr ""
+#~ msgstr "Quem pode responder?"
#: src/screens/Home/NoFeedsPinned.tsx:79
#: src/screens/Messages/List/index.tsx:185
@@ -7948,7 +7948,7 @@ msgstr "Por que este post deve ser analisado?"
#: src/components/ReportDialog/SelectReportOptionView.tsx:60
msgid "Why should this starter pack be reviewed?"
-msgstr ""
+msgstr "Por que este pacote inicial deve ser analisado?"
#: src/components/ReportDialog/SelectReportOptionView.tsx:48
msgid "Why should this user be reviewed?"
@@ -7990,23 +7990,23 @@ msgstr "Sim"
#: src/screens/Settings/components/DeactivateAccountDialog.tsx:106
#: src/screens/Settings/components/DeactivateAccountDialog.tsx:108
msgid "Yes, deactivate"
-msgstr ""
+msgstr "Sim, desativar"
#: src/screens/StarterPack/StarterPackScreen.tsx:649
msgid "Yes, delete this starter pack"
-msgstr ""
+msgstr "Sim, exclua este kit inicial"
#: src/view/com/util/forms/PostDropdownBtn.tsx:692
msgid "Yes, detach"
-msgstr ""
+msgstr "Sim, desvincule"
#: src/view/com/util/forms/PostDropdownBtn.tsx:702
msgid "Yes, hide"
-msgstr ""
+msgstr "Sim, ocultar"
#: src/screens/Deactivated.tsx:150
msgid "Yes, reactivate my account"
-msgstr ""
+msgstr "Sim, reative minha conta"
#: src/components/dms/MessageItem.tsx:182
msgid "Yesterday, {time}"
@@ -8015,11 +8015,11 @@ msgstr "Ontem, {time}"
#: src/components/StarterPack/StarterPackCard.tsx:76
#: src/screens/List/ListHiddenScreen.tsx:140
msgid "you"
-msgstr ""
+msgstr "você"
#: src/components/NewskieDialog.tsx:43
msgid "You"
-msgstr ""
+msgstr "Você"
#: src/screens/SignupQueued.tsx:136
msgid "You are in line."
@@ -8036,7 +8036,7 @@ msgstr "Você também pode descobrir novos feeds para seguir."
#: src/view/com/modals/DeleteAccount.tsx:202
msgid "You can also temporarily deactivate your account instead, and reactivate it at any time."
-msgstr ""
+msgstr "Você também pode desativar temporariamente sua conta e reativá-la a qualquer momento."
#: src/screens/Onboarding/StepFollowingFeed.tsx:143
#~ msgid "You can change these settings later."
@@ -8044,11 +8044,11 @@ msgstr ""
#: src/components/dms/MessagesNUX.tsx:119
msgid "You can change this at any time."
-msgstr ""
+msgstr "Você pode alterar isso a qualquer momento."
#: src/screens/Messages/Settings.tsx:111
msgid "You can continue ongoing conversations regardless of which setting you choose."
-msgstr ""
+msgstr "Você pode continuar conversas em andamento, independentemente da configuração que escolher."
#: src/screens/Login/index.tsx:158
#: src/screens/Login/PasswordUpdatedForm.tsx:33
@@ -8057,7 +8057,7 @@ msgstr "Agora você pode entrar com a sua nova senha."
#: src/screens/Deactivated.tsx:136
msgid "You can reactivate your account to continue logging in. Your profile and posts will be visible to other users."
-msgstr ""
+msgstr "Você pode reativar sua conta para continuar fazendo login. Seu perfil e suas postagens ficarão visíveis para outros usuários."
#: src/view/com/profile/ProfileFollowers.tsx:86
msgid "You do not have any followers."
@@ -8065,7 +8065,7 @@ msgstr "Ninguém segue você ainda."
#: src/screens/Profile/KnownFollowers.tsx:99
msgid "You don't follow any users who follow @{name}."
-msgstr ""
+msgstr "Você não segue nenhum usuário que segue @{name}."
#: src/view/com/modals/InviteCodes.tsx:67
msgid "You don't have any invite codes yet! We'll send you some when you've been on Bluesky for a little longer."
@@ -8089,7 +8089,7 @@ msgstr "Você bloqueou esta conta ou foi bloqueado por ela."
#: src/components/dms/MessagesListBlockedFooter.tsx:58
msgid "You have blocked this user"
-msgstr ""
+msgstr "Você bloqueou este usuário"
#: src/components/moderation/ModerationDetailsDialog.tsx:72
#: src/lib/moderation/useModerationCauseDescription.ts:55
@@ -8123,7 +8123,7 @@ msgstr "Você silenciou este usuário."
#: src/screens/Messages/List/index.tsx:225
msgid "You have no conversations yet. Start one!"
-msgstr ""
+msgstr "Você ainda não tem conversas. Comece uma!"
#: src/view/com/feeds/ProfileFeedgens.tsx:138
msgid "You have no feeds."
@@ -8152,11 +8152,11 @@ msgstr "Você ainda não silenciou nenhuma conta. Para silenciar uma conta, aces
#: src/components/Lists.tsx:52
msgid "You have reached the end"
-msgstr ""
+msgstr "Você chegou ao fim"
#: src/components/StarterPack/ProfileStarterPacks.tsx:235
msgid "You haven't created a starter pack yet!"
-msgstr ""
+msgstr "Você ainda não criou um kit inicial!"
#: src/components/dialogs/MutedWords.tsx:398
msgid "You haven't muted any words or tags yet"
@@ -8165,7 +8165,7 @@ msgstr "Você não silenciou nenhuma palavra ou tag ainda"
#: src/components/moderation/ModerationDetailsDialog.tsx:117
#: src/lib/moderation/useModerationCauseDescription.ts:125
msgid "You hid this reply."
-msgstr ""
+msgstr "Você ocultou esta resposta."
#: src/components/moderation/LabelsOnMeDialog.tsx:86
msgid "You may appeal non-self labels if you feel they were placed in error."
@@ -8177,19 +8177,19 @@ msgstr "Você pode contestar estes rótulos se você acha que estão errados."
#: src/screens/StarterPack/Wizard/State.tsx:79
msgid "You may only add up to {STARTER_PACK_MAX_SIZE} profiles"
-msgstr ""
+msgstr "Você pode adicionar no máximo {STARTER_PACK_MAX_SIZE} perfis ao seu kit inicial"
#: src/screens/StarterPack/Wizard/State.tsx:97
msgid "You may only add up to 3 feeds"
-msgstr ""
+msgstr "Você pode adicionar no máximo 3 feeds"
#: src/screens/StarterPack/Wizard/State.tsx:95
#~ msgid "You may only add up to 50 feeds"
-#~ msgstr ""
+#~ msgstr "Você só pode adicionar até 50 feeds"
#: src/screens/StarterPack/Wizard/State.tsx:78
#~ msgid "You may only add up to 50 profiles"
-#~ msgstr ""
+#~ msgstr "Você só pode adicionar até 50 perfis"
#: src/screens/Signup/StepInfo/Policies.tsx:85
msgid "You must be 13 years of age or older to sign up."
@@ -8201,15 +8201,15 @@ msgstr "Você precisa ter no mínimo 13 anos de idade para se cadastrar."
#: src/components/StarterPack/ProfileStarterPacks.tsx:306
msgid "You must be following at least seven other people to generate a starter pack."
-msgstr ""
+msgstr "Você deve estar seguindo pelo menos sete outras pessoas para gerar um kit inicial."
#: src/components/StarterPack/QrCodeDialog.tsx:60
msgid "You must grant access to your photo library to save a QR code"
-msgstr ""
+msgstr "Você deve conceder acesso à sua biblioteca de fotos para salvar o QR code."
#: src/components/StarterPack/ShareDialog.tsx:68
msgid "You must grant access to your photo library to save the image."
-msgstr ""
+msgstr "Você deve conceder acesso à sua biblioteca de fotos para salvar a imagem."
#: src/components/ReportDialog/SubmitView.tsx:209
msgid "You must select at least one labeler for a report"
@@ -8217,7 +8217,7 @@ msgstr "Você deve selecionar no mínimo um rotulador"
#: src/screens/Deactivated.tsx:131
msgid "You previously deactivated @{0}."
-msgstr ""
+msgstr "Você desativou @{0} anteriormente."
#: src/view/com/util/forms/PostDropdownBtn.tsx:216
msgid "You will no longer receive notifications for this thread"
@@ -8237,31 +8237,31 @@ msgstr "Você: {0}"
#: src/screens/Messages/List/ChatListItem.tsx:143
msgid "You: {defaultEmbeddedContentMessage}"
-msgstr ""
+msgstr "Você: {defaultEmbeddedContentMessage}"
#: src/screens/Messages/List/ChatListItem.tsx:136
msgid "You: {short}"
-msgstr ""
+msgstr "Você: {short}"
#: src/screens/Signup/index.tsx:113
msgid "You'll follow the suggested users and feeds once you finish creating your account!"
-msgstr ""
+msgstr "Você seguirá os usuários e feeds sugeridos depois de terminar de criar sua conta!"
#: src/screens/Signup/index.tsx:118
msgid "You'll follow the suggested users once you finish creating your account!"
-msgstr ""
+msgstr "Você seguirá os usuários sugeridos depois de terminar de criar sua conta!"
#: src/screens/StarterPack/StarterPackLandingScreen.tsx:239
msgid "You'll follow these people and {0} others"
-msgstr ""
+msgstr "Você seguirá estas pessoas e mais {0} outras"
#: src/screens/StarterPack/StarterPackLandingScreen.tsx:237
msgid "You'll follow these people right away"
-msgstr ""
+msgstr "Você seguirá estas pessoas imediatamente"
#: src/screens/StarterPack/StarterPackLandingScreen.tsx:277
msgid "You'll stay updated with these feeds"
-msgstr ""
+msgstr "Você se manterá atualizado com estes feeds"
#: src/screens/Onboarding/StepModeration/index.tsx:60
#~ msgid "You're in control"
@@ -8276,7 +8276,7 @@ msgstr "Você está na fila"
#: src/screens/Deactivated.tsx:89
#: src/screens/Settings/components/DeactivateAccountDialog.tsx:54
msgid "You're logged in with an App Password. Please log in with your main password to continue deactivating your account."
-msgstr ""
+msgstr "Você está logado com uma senha de aplicativo. Por favor, faça login com sua senha principal para continuar desativando sua conta."
#: src/screens/Onboarding/StepFinished.tsx:239
msgid "You're ready to go!"
@@ -8309,11 +8309,11 @@ msgstr "Sua data de nascimento"
#: src/view/com/util/post-embeds/VideoEmbed.web.tsx:145
msgid "Your browser does not support the video format. Please try a different browser."
-msgstr ""
+msgstr "Seu navegador não suporta o formato de vídeo. Por favor, tente um navegador diferente."
#: src/screens/Messages/Conversation/ChatDisabled.tsx:25
msgid "Your chats have been disabled"
-msgstr ""
+msgstr "Seus chats foram desativados"
#: src/view/com/modals/InAppBrowserConsent.tsx:47
msgid "Your choice will be saved, but can be changed later in settings."
@@ -8340,7 +8340,7 @@ msgstr "Seu e-mail ainda não foi verificado. Esta é uma etapa importante de se
#: src/state/shell/progress-guide.tsx:156
msgid "Your first like!"
-msgstr ""
+msgstr "Sua primeira curtida!"
#: src/view/com/posts/FollowingEmptyState.tsx:43
msgid "Your following feed is empty! Follow more users to see what's happening."
@@ -8376,7 +8376,7 @@ msgstr "Seu perfil"
#: src/screens/Settings/components/DeactivateAccountDialog.tsx:75
msgid "Your profile, posts, feeds, and lists will no longer be visible to other Bluesky users. You can reactivate your account at any time by logging in."
-msgstr ""
+msgstr "Seu perfil, postagens, feeds e listas não serão mais visíveis para outros usuários do Bluesky. Você pode reativar sua conta a qualquer momento fazendo login."
#: src/view/com/composer/Composer.tsx:425
msgid "Your reply has been published"
diff --git a/src/screens/Post/PostLikedBy.tsx b/src/screens/Post/PostLikedBy.tsx
index c29e0aa24c..eab9e2d27f 100644
--- a/src/screens/Post/PostLikedBy.tsx
+++ b/src/screens/Post/PostLikedBy.tsx
@@ -1,5 +1,4 @@
import React from 'react'
-import {View} from 'react-native'
import {msg} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {useFocusEffect} from '@react-navigation/native'
@@ -7,9 +6,12 @@ import {useFocusEffect} from '@react-navigation/native'
import {CommonNavigatorParams, NativeStackScreenProps} from '#/lib/routes/types'
import {makeRecordUri} from '#/lib/strings/url-helpers'
import {useSetMinimalShellMode} from '#/state/shell'
+import {isWeb} from 'platform/detection'
import {PostLikedBy as PostLikedByComponent} from '#/view/com/post-thread/PostLikedBy'
import {ViewHeader} from '#/view/com/util/ViewHeader'
+import {CenteredView} from 'view/com/util/Views'
import {atoms as a} from '#/alf'
+import {ListHeaderDesktop} from '#/components/Lists'
type Props = NativeStackScreenProps
export const PostLikedByScreen = ({route}: Props) => {
@@ -25,9 +27,10 @@ export const PostLikedByScreen = ({route}: Props) => {
)
return (
-
-
+
+
+
-
+
)
}
diff --git a/src/screens/Post/PostQuotes.tsx b/src/screens/Post/PostQuotes.tsx
index d670f32150..4a06639fc8 100644
--- a/src/screens/Post/PostQuotes.tsx
+++ b/src/screens/Post/PostQuotes.tsx
@@ -1,5 +1,4 @@
import React from 'react'
-import {View} from 'react-native'
import {msg} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {useFocusEffect} from '@react-navigation/native'
@@ -7,9 +6,12 @@ import {useFocusEffect} from '@react-navigation/native'
import {CommonNavigatorParams, NativeStackScreenProps} from '#/lib/routes/types'
import {makeRecordUri} from '#/lib/strings/url-helpers'
import {useSetMinimalShellMode} from '#/state/shell'
+import {isWeb} from 'platform/detection'
import {PostQuotes as PostQuotesComponent} from '#/view/com/post-thread/PostQuotes'
import {ViewHeader} from '#/view/com/util/ViewHeader'
+import {CenteredView} from 'view/com/util/Views'
import {atoms as a} from '#/alf'
+import {ListHeaderDesktop} from '#/components/Lists'
type Props = NativeStackScreenProps
export const PostQuotesScreen = ({route}: Props) => {
@@ -25,9 +27,10 @@ export const PostQuotesScreen = ({route}: Props) => {
)
return (
-
-
+
+
+
-
+
)
}
diff --git a/src/screens/Post/PostRepostedBy.tsx b/src/screens/Post/PostRepostedBy.tsx
index b15a6f6ee2..2a8ef1e0f7 100644
--- a/src/screens/Post/PostRepostedBy.tsx
+++ b/src/screens/Post/PostRepostedBy.tsx
@@ -1,5 +1,4 @@
import React from 'react'
-import {View} from 'react-native'
import {msg} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {useFocusEffect} from '@react-navigation/native'
@@ -7,9 +6,12 @@ import {useFocusEffect} from '@react-navigation/native'
import {CommonNavigatorParams, NativeStackScreenProps} from '#/lib/routes/types'
import {makeRecordUri} from '#/lib/strings/url-helpers'
import {useSetMinimalShellMode} from '#/state/shell'
+import {isWeb} from 'platform/detection'
import {PostRepostedBy as PostRepostedByComponent} from '#/view/com/post-thread/PostRepostedBy'
import {ViewHeader} from '#/view/com/util/ViewHeader'
+import {CenteredView} from 'view/com/util/Views'
import {atoms as a} from '#/alf'
+import {ListHeaderDesktop} from '#/components/Lists'
type Props = NativeStackScreenProps
export const PostRepostedByScreen = ({route}: Props) => {
@@ -25,9 +27,10 @@ export const PostRepostedByScreen = ({route}: Props) => {
)
return (
-
-
+
+
+
-
+
)
}
diff --git a/src/screens/Profile/Header/Metrics.tsx b/src/screens/Profile/Header/Metrics.tsx
index e3537f44be..756eb1f89e 100644
--- a/src/screens/Profile/Header/Metrics.tsx
+++ b/src/screens/Profile/Header/Metrics.tsx
@@ -17,9 +17,9 @@ export function ProfileHeaderMetrics({
profile: Shadow
}) {
const t = useTheme()
- const {_} = useLingui()
- const following = formatCount(profile.followsCount || 0)
- const followers = formatCount(profile.followersCount || 0)
+ const {_, i18n} = useLingui()
+ const following = formatCount(i18n, profile.followsCount || 0)
+ const followers = formatCount(i18n, profile.followersCount || 0)
const pluralizedFollowers = plural(profile.followersCount || 0, {
one: 'follower',
other: 'followers',
@@ -54,7 +54,7 @@ export function ProfileHeaderMetrics({
- {formatCount(profile.postsCount || 0)}{' '}
+ {formatCount(i18n, profile.postsCount || 0)}{' '}
{plural(profile.postsCount || 0, {one: 'post', other: 'posts'})}
diff --git a/src/screens/Profile/Header/ProfileHeaderStandard.tsx b/src/screens/Profile/Header/ProfileHeaderStandard.tsx
index 2b6353b276..2036023c30 100644
--- a/src/screens/Profile/Header/ProfileHeaderStandard.tsx
+++ b/src/screens/Profile/Header/ProfileHeaderStandard.tsx
@@ -10,6 +10,7 @@ import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
import {msg, Trans} from '@lingui/macro'
import {useLingui} from '@lingui/react'
+import {useGate} from '#/lib/statsig/statsig'
import {logger} from '#/logger'
import {isIOS} from '#/platform/detection'
import {Shadow} from '#/state/cache/types'
@@ -59,6 +60,7 @@ let ProfileHeaderStandard = ({
const profile: Shadow =
useProfileShadow(profileUnshadowed)
const t = useTheme()
+ const gate = useGate()
const {currentAccount, hasSession} = useSession()
const {_} = useLingui()
const {openModal} = useModalControls()
@@ -203,27 +205,29 @@ let ProfileHeaderStandard = ({
{hasSession && (
<>
-
+ label={_(msg`Show follows similar to ${profile.handle}`)}
+ style={{width: 36, height: 36}}>
+
+
+ )}
>
)}
diff --git a/src/screens/StarterPack/StarterPackLandingScreen.tsx b/src/screens/StarterPack/StarterPackLandingScreen.tsx
index 7dda45f968..5f1d5e0628 100644
--- a/src/screens/StarterPack/StarterPackLandingScreen.tsx
+++ b/src/screens/StarterPack/StarterPackLandingScreen.tsx
@@ -113,7 +113,7 @@ function LandingScreenLoaded({
moderationOpts: ModerationOpts
}) {
const {creator, listItemsSample, feeds} = starterPack
- const {_} = useLingui()
+ const {_, i18n} = useLingui()
const t = useTheme()
const activeStarterPack = useActiveStarterPack()
const setActiveStarterPack = useSetActiveStarterPack()
@@ -225,7 +225,9 @@ function LandingScreenLoaded({
t.atoms.text_contrast_medium,
]}
numberOfLines={1}>
- {formatCount(JOINED_THIS_WEEK)} joined this week
+
+ {formatCount(i18n, JOINED_THIS_WEEK)} joined this week
+
diff --git a/src/state/queries/video/compress-video.ts b/src/state/queries/video/compress-video.ts
index a4c17eaceb..533b584166 100644
--- a/src/state/queries/video/compress-video.ts
+++ b/src/state/queries/video/compress-video.ts
@@ -2,7 +2,8 @@ import {ImagePickerAsset} from 'expo-image-picker'
import {useMutation} from '@tanstack/react-query'
import {cancelable} from '#/lib/async/cancelable'
-import {CompressedVideo, compressVideo} from 'lib/media/video/compress'
+import {CompressedVideo} from '#/lib/media/video/types'
+import {compressVideo} from 'lib/media/video/compress'
export function useCompressVideoMutation({
onProgress,
diff --git a/src/state/queries/video/video-upload.ts b/src/state/queries/video/video-upload.ts
index 11c8390cef..6fdd9d5bbe 100644
--- a/src/state/queries/video/video-upload.ts
+++ b/src/state/queries/video/video-upload.ts
@@ -4,7 +4,7 @@ import {useMutation} from '@tanstack/react-query'
import {nanoid} from 'nanoid/non-secure'
import {cancelable} from '#/lib/async/cancelable'
-import {CompressedVideo} from '#/lib/media/video/compress'
+import {CompressedVideo} from '#/lib/media/video/types'
import {createVideoEndpointUrl} from '#/state/queries/video/util'
import {useAgent, useSession} from '#/state/session'
import {getServiceAuthAudFromUrl} from 'lib/strings/url-helpers'
@@ -28,14 +28,11 @@ export const useUploadVideoMutation = ({
mutationFn: cancelable(async (video: CompressedVideo) => {
const uri = createVideoEndpointUrl('/xrpc/app.bsky.video.uploadVideo', {
did: currentAccount!.did,
- name: `${nanoid(12)}.mp4`, // @TODO what are we limiting this to?
+ name: `${nanoid(12)}.mp4`,
})
- if (!currentAccount?.service) {
- throw new Error('User is not logged in')
- }
+ const serviceAuthAud = getServiceAuthAudFromUrl(agent.dispatchUrl)
- const serviceAuthAud = getServiceAuthAudFromUrl(currentAccount.service)
if (!serviceAuthAud) {
throw new Error('Agent does not have a PDS URL')
}
@@ -44,6 +41,7 @@ export const useUploadVideoMutation = ({
{
aud: serviceAuthAud,
lxm: 'com.atproto.repo.uploadBlob',
+ exp: Date.now() / 1000 + 60 * 30, // 30 minutes
},
)
diff --git a/src/state/queries/video/video-upload.web.ts b/src/state/queries/video/video-upload.web.ts
index 4673bc417f..c3ad392683 100644
--- a/src/state/queries/video/video-upload.web.ts
+++ b/src/state/queries/video/video-upload.web.ts
@@ -3,7 +3,7 @@ import {useMutation} from '@tanstack/react-query'
import {nanoid} from 'nanoid/non-secure'
import {cancelable} from '#/lib/async/cancelable'
-import {CompressedVideo} from '#/lib/media/video/compress'
+import {CompressedVideo} from '#/lib/media/video/types'
import {createVideoEndpointUrl} from '#/state/queries/video/util'
import {useAgent, useSession} from '#/state/session'
import {getServiceAuthAudFromUrl} from 'lib/strings/url-helpers'
@@ -30,11 +30,8 @@ export const useUploadVideoMutation = ({
name: `${nanoid(12)}.mp4`, // @TODO: make sure it's always mp4'
})
- if (!currentAccount?.service) {
- throw new Error('User is not logged in')
- }
+ const serviceAuthAud = getServiceAuthAudFromUrl(agent.dispatchUrl)
- const serviceAuthAud = getServiceAuthAudFromUrl(currentAccount.service)
if (!serviceAuthAud) {
throw new Error('Agent does not have a PDS URL')
}
@@ -43,10 +40,15 @@ export const useUploadVideoMutation = ({
{
aud: serviceAuthAud,
lxm: 'com.atproto.repo.uploadBlob',
+ exp: Date.now() / 1000 + 60 * 30, // 30 minutes
},
)
- const bytes = await fetch(video.uri).then(res => res.arrayBuffer())
+ let bytes = video.bytes
+
+ if (!bytes) {
+ bytes = await fetch(video.uri).then(res => res.arrayBuffer())
+ }
const xhr = new XMLHttpRequest()
const res = await new Promise(
diff --git a/src/state/queries/video/video.ts b/src/state/queries/video/video.ts
index 035dc50813..3c5094c71b 100644
--- a/src/state/queries/video/video.ts
+++ b/src/state/queries/video/video.ts
@@ -1,4 +1,4 @@
-import React from 'react'
+import React, {useCallback} from 'react'
import {ImagePickerAsset} from 'expo-image-picker'
import {AppBskyVideoDefs, BlobRef} from '@atproto/api'
import {msg} from '@lingui/macro'
@@ -6,8 +6,8 @@ import {useLingui} from '@lingui/react'
import {QueryClient, useQuery, useQueryClient} from '@tanstack/react-query'
import {logger} from '#/logger'
-import {CompressedVideo} from 'lib/media/video/compress'
import {VideoTooLargeError} from 'lib/media/video/errors'
+import {CompressedVideo} from 'lib/media/video/types'
import {useCompressVideoMutation} from 'state/queries/video/compress-video'
import {useVideoAgent} from 'state/queries/video/util'
import {useUploadVideoMutation} from 'state/queries/video/video-upload'
@@ -20,6 +20,7 @@ type Action =
| {type: 'SetError'; error: string | undefined}
| {type: 'Reset'}
| {type: 'SetAsset'; asset: ImagePickerAsset}
+ | {type: 'SetDimensions'; width: number; height: number}
| {type: 'SetVideo'; video: CompressedVideo}
| {type: 'SetJobStatus'; jobStatus: AppBskyVideoDefs.JobStatus}
| {type: 'SetBlobRef'; blobRef: BlobRef}
@@ -58,6 +59,13 @@ function reducer(queryClient: QueryClient) {
}
} else if (action.type === 'SetAsset') {
updatedState = {...state, asset: action.asset}
+ } else if (action.type === 'SetDimensions') {
+ updatedState = {
+ ...state,
+ asset: state.asset
+ ? {...state.asset, width: action.width, height: action.height}
+ : undefined,
+ }
} else if (action.type === 'SetVideo') {
updatedState = {...state, video: action.video}
} else if (action.type === 'SetJobStatus') {
@@ -178,11 +186,20 @@ export function useUploadVideo({
dispatch({type: 'Reset'})
}
+ const updateVideoDimensions = useCallback((width: number, height: number) => {
+ dispatch({
+ type: 'SetDimensions',
+ width,
+ height,
+ })
+ }, [])
+
return {
state,
dispatch,
selectVideo,
clearVideo,
+ updateVideoDimensions,
}
}
diff --git a/src/state/shell/composer.tsx b/src/state/shell/composer.tsx
index c990054890..74802a9930 100644
--- a/src/state/shell/composer.tsx
+++ b/src/state/shell/composer.tsx
@@ -5,8 +5,11 @@ import {
AppBskyRichtextFacet,
ModerationDecision,
} from '@atproto/api'
+import {msg} from '@lingui/macro'
+import {useLingui} from '@lingui/react'
import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback'
+import * as Toast from '#/view/com/util/Toast'
export interface ComposerOptsPostRef {
uri: string
@@ -22,12 +25,7 @@ export interface ComposerOptsQuote {
text: string
facets?: AppBskyRichtextFacet.Main[]
indexedAt: string
- author: {
- did: string
- handle: string
- displayName?: string
- avatar?: string
- }
+ author: AppBskyActorDefs.ProfileViewBasic
embeds?: AppBskyEmbedRecord.ViewRecord['embeds']
}
export interface ComposerOpts {
@@ -56,10 +54,25 @@ const controlsContext = React.createContext({
})
export function Provider({children}: React.PropsWithChildren<{}>) {
+ const {_} = useLingui()
const [state, setState] = React.useState()
const openComposer = useNonReactiveCallback((opts: ComposerOpts) => {
- setState(opts)
+ const author = opts.replyTo?.author || opts.quote?.author
+ const isBlocked = Boolean(
+ author &&
+ (author.viewer?.blocking ||
+ author.viewer?.blockedBy ||
+ author.viewer?.blockingByList),
+ )
+ if (isBlocked) {
+ Toast.show(
+ _(msg`Cannot interact with a blocked user`),
+ 'exclamation-circle',
+ )
+ } else {
+ setState(opts)
+ }
})
const closeComposer = useNonReactiveCallback(() => {
diff --git a/src/view/com/composer/Composer.tsx b/src/view/com/composer/Composer.tsx
index 7c11f0a9ab..8a8fa66b75 100644
--- a/src/view/com/composer/Composer.tsx
+++ b/src/view/com/composer/Composer.tsx
@@ -108,6 +108,7 @@ import {TextInput, TextInputRef} from './text-input/TextInput'
import {ThreadgateBtn} from './threadgate/ThreadgateBtn'
import {useExternalLinkFetch} from './useExternalLinkFetch'
import {SelectVideoBtn} from './videos/SelectVideoBtn'
+import {SubtitleDialogBtn} from './videos/SubtitleDialog'
import {VideoPreview} from './videos/VideoPreview'
import {VideoTranscodeProgress} from './videos/VideoTranscodeProgress'
@@ -172,10 +173,14 @@ export const ComposePost = observer(function ComposePost({
initQuote,
)
+ const [videoAltText, setVideoAltText] = useState('')
+ const [captions, setCaptions] = useState<{lang: string; file: File}[]>([])
+
const {
selectVideo,
clearVideo,
state: videoUploadState,
+ updateVideoDimensions,
} = useUploadVideo({
setStatus: setProcessingState,
onSuccess: () => {
@@ -347,7 +352,19 @@ export const ComposePost = observer(function ComposePost({
postgate,
onStateChange: setProcessingState,
langs: toPostLanguages(langPrefs.postLanguage),
- video: videoUploadState.blobRef,
+ video: videoUploadState.blobRef
+ ? {
+ blobRef: videoUploadState.blobRef,
+ altText: videoAltText,
+ captions: captions,
+ aspectRatio: videoUploadState.asset
+ ? {
+ width: videoUploadState.asset?.width,
+ height: videoUploadState.asset?.height,
+ }
+ : undefined,
+ }
+ : undefined,
})
).uri
try {
@@ -694,16 +711,29 @@ export const ComposePost = observer(function ComposePost({
)}
) : null}
- {videoUploadState.status === 'compressing' &&
- videoUploadState.asset ? (
-
+ ) : videoUploadState.video ? (
+
+ ) : null)}
+ {(videoUploadState.asset || videoUploadState.video) && (
+
- ) : videoUploadState.video ? (
-
- ) : null}
+ )}
@@ -730,7 +760,7 @@ export const ComposePost = observer(function ComposePost({
) : (
- {gate('videos') && (
+ {gate('video_upload') && (
void
+ setCaptions: React.Dispatch<
+ React.SetStateAction<{lang: string; file: File}[]>
+ >
+}
+
+export function SubtitleDialogBtn(props: Props) {
+ const control = Dialog.useDialogControl()
+ const {_} = useLingui()
+
+ return (
+
+
+
+
+
+
+
+ )
+}
+
+function SubtitleDialogInner({
+ altText,
+ setAltText,
+ captions,
+ setCaptions,
+}: Props) {
+ const control = Dialog.useDialogContext()
+ const {_} = useLingui()
+ const t = useTheme()
+ const enforceLen = useEnforceMaxGraphemeCount()
+ const {primaryLanguage} = useLanguagePrefs()
+
+ const handleSelectFile = useCallback(
+ (file: File) => {
+ setCaptions(subs => [
+ ...subs,
+ {
+ lang: subs.some(s => s.lang === primaryLanguage)
+ ? ''
+ : primaryLanguage,
+ file,
+ },
+ ])
+ },
+ [setCaptions, primaryLanguage],
+ )
+
+ const subtitleMissingLanguage = captions.some(sub => sub.lang === '')
+
+ return (
+
+
+
+ Alt text
+
+
+ setAltText(enforceLen(evt, MAX_ALT_TEXT))}
+ maxLength={MAX_ALT_TEXT * 10}
+ multiline
+ numberOfLines={3}
+ onKeyPress={({nativeEvent}) => {
+ if (nativeEvent.key === 'Escape') {
+ control.close()
+ }
+ }}
+ />
+
+
+ {isWeb && (
+ <>
+
+
+ Captions (.vtt)
+
+ = 4}
+ />
+
+ {captions.map((subtitle, i) => (
+
+ langCode(lang) === subtitle.lang ||
+ !captions.some(s => s.lang === langCode(lang)),
+ )}
+ style={[i % 2 === 0 && t.atoms.bg_contrast_25]}
+ />
+ ))}
+
+ >
+ )}
+
+ {subtitleMissingLanguage && (
+
+ Ensure you have selected a language for each subtitle file.
+
+ )}
+
+
+
+
+
+
+
+ )
+}
+
+function SubtitleFileRow({
+ language,
+ file,
+ otherLanguages,
+ setCaptions,
+ style,
+}: {
+ language: string
+ file: File
+ otherLanguages: {code2: string; code3: string; name: string}[]
+ setCaptions: React.Dispatch<
+ React.SetStateAction<{lang: string; file: File}[]>
+ >
+ style: StyleProp
+}) {
+ const {_} = useLingui()
+ const t = useTheme()
+
+ const handleValueChange = useCallback(
+ (lang: string) => {
+ if (lang) {
+ setCaptions(subs =>
+ subs.map(s => (s.lang === language ? {lang, file: s.file} : s)),
+ )
+ }
+ },
+ [setCaptions, language],
+ )
+
+ return (
+
+
+
+ {language === '' ? (
+
+ ) : (
+
+ )}
+
+ {file.name}
+
+ ({
+ label: `${lang.name} (${langCode(lang)})`,
+ value: langCode(lang),
+ }))}
+ style={{viewContainer: {maxWidth: 200, flex: 1}}}
+ />
+
+
+
+
+
+ )
+}
+
+function langCode(lang: {code2: string; code3: string}) {
+ return lang.code2 || lang.code3
+}
diff --git a/src/view/com/composer/videos/SubtitleFilePicker.native.tsx b/src/view/com/composer/videos/SubtitleFilePicker.native.tsx
new file mode 100644
index 0000000000..f2b9a7b04a
--- /dev/null
+++ b/src/view/com/composer/videos/SubtitleFilePicker.native.tsx
@@ -0,0 +1,3 @@
+export function SubtitleFilePicker() {
+ throw new Error('SubtitleFilePicker is a web-only component')
+}
diff --git a/src/view/com/composer/videos/SubtitleFilePicker.tsx b/src/view/com/composer/videos/SubtitleFilePicker.tsx
new file mode 100644
index 0000000000..9e0fe0aeee
--- /dev/null
+++ b/src/view/com/composer/videos/SubtitleFilePicker.tsx
@@ -0,0 +1,63 @@
+import React, {useRef} from 'react'
+import {View} from 'react-native'
+import {msg, Trans} from '@lingui/macro'
+import {useLingui} from '@lingui/react'
+
+import * as Toast from '#/view/com/util/Toast'
+import {atoms as a} from '#/alf'
+import {Button, ButtonIcon, ButtonText} from '#/components/Button'
+import {CC_Stroke2_Corner0_Rounded as CCIcon} from '#/components/icons/CC'
+
+export function SubtitleFilePicker({
+ onSelectFile,
+ disabled,
+}: {
+ onSelectFile: (file: File) => void
+ disabled?: boolean
+}) {
+ const {_} = useLingui()
+ const ref = useRef(null)
+
+ const handleClick = () => {
+ ref.current?.click()
+ }
+
+ const handlePick = (evt: React.ChangeEvent) => {
+ const selectedFile = evt.target.files?.[0]
+ if (selectedFile) {
+ if (selectedFile.type === 'text/vtt') {
+ onSelectFile(selectedFile)
+ } else {
+ Toast.show(_(msg`Only WebVTT (.vtt) files are supported`))
+ }
+ }
+ }
+
+ return (
+
+
+
+
+
+
+ )
+}
diff --git a/src/view/com/composer/videos/VideoPreview.tsx b/src/view/com/composer/videos/VideoPreview.tsx
index 6956c8c4f8..199a1fff7f 100644
--- a/src/view/com/composer/videos/VideoPreview.tsx
+++ b/src/view/com/composer/videos/VideoPreview.tsx
@@ -1,38 +1,56 @@
/* eslint-disable @typescript-eslint/no-shadow */
import React from 'react'
import {View} from 'react-native'
+import {ImagePickerAsset} from 'expo-image-picker'
import {useVideoPlayer, VideoView} from 'expo-video'
-import {CompressedVideo} from '#/lib/media/video/compress'
+import {CompressedVideo} from '#/lib/media/video/types'
+import {clamp} from '#/lib/numbers'
import {ExternalEmbedRemoveBtn} from 'view/com/composer/ExternalEmbedRemoveBtn'
-import {atoms as a} from '#/alf'
+import {atoms as a, useTheme} from '#/alf'
export function VideoPreview({
+ asset,
video,
clear,
}: {
+ asset: ImagePickerAsset
video: CompressedVideo
+ setDimensions: (width: number, height: number) => void
clear: () => void
}) {
+ const t = useTheme()
const player = useVideoPlayer(video.uri, player => {
player.loop = true
player.muted = true
player.play()
})
+ let aspectRatio = asset.width / asset.height
+
+ if (isNaN(aspectRatio)) {
+ aspectRatio = 16 / 9
+ }
+
+ aspectRatio = clamp(aspectRatio, 1 / 1, 3 / 1)
+
return (
diff --git a/src/view/com/composer/videos/VideoPreview.web.tsx b/src/view/com/composer/videos/VideoPreview.web.tsx
index 223dbd4244..5e7f828576 100644
--- a/src/view/com/composer/videos/VideoPreview.web.tsx
+++ b/src/view/com/composer/videos/VideoPreview.web.tsx
@@ -1,27 +1,70 @@
-import React from 'react'
+import React, {useEffect, useRef} from 'react'
import {View} from 'react-native'
+import {ImagePickerAsset} from 'expo-image-picker'
-import {CompressedVideo} from '#/lib/media/video/compress'
+import {CompressedVideo} from '#/lib/media/video/types'
+import {clamp} from '#/lib/numbers'
import {ExternalEmbedRemoveBtn} from 'view/com/composer/ExternalEmbedRemoveBtn'
import {atoms as a} from '#/alf'
export function VideoPreview({
+ asset,
video,
+ setDimensions,
clear,
}: {
+ asset: ImagePickerAsset
video: CompressedVideo
+ setDimensions: (width: number, height: number) => void
clear: () => void
}) {
+ const ref = useRef(null)
+
+ useEffect(() => {
+ if (!ref.current) return
+
+ const abortController = new AbortController()
+ const {signal} = abortController
+ ref.current.addEventListener(
+ 'loadedmetadata',
+ function () {
+ setDimensions(this.videoWidth, this.videoHeight)
+ },
+ {signal},
+ )
+
+ return () => {
+ abortController.abort()
+ }
+ }, [setDimensions])
+
+ let aspectRatio = asset.width / asset.height
+
+ if (isNaN(aspectRatio)) {
+ aspectRatio = 16 / 9
+ }
+
+ aspectRatio = clamp(aspectRatio, 1 / 1, 3 / 1)
+
return (
-
+
)
}
diff --git a/src/view/com/composer/videos/VideoTranscodeBackdrop.tsx b/src/view/com/composer/videos/VideoTranscodeBackdrop.tsx
index 1f41736420..ef38e62afe 100644
--- a/src/view/com/composer/videos/VideoTranscodeBackdrop.tsx
+++ b/src/view/com/composer/videos/VideoTranscodeBackdrop.tsx
@@ -21,8 +21,8 @@ export function VideoTranscodeBackdrop({uri}: {uri: string}) {
}, [])
return (
-
- {thumbnail && (
+ thumbnail && (
+
- )}
-
+
+ )
)
}
diff --git a/src/view/com/composer/videos/VideoTranscodeBackdrop.web.tsx b/src/view/com/composer/videos/VideoTranscodeBackdrop.web.tsx
index 9b580fdf2a..d4090d8530 100644
--- a/src/view/com/composer/videos/VideoTranscodeBackdrop.web.tsx
+++ b/src/view/com/composer/videos/VideoTranscodeBackdrop.web.tsx
@@ -1,7 +1,3 @@
-import React from 'react'
-
-export function VideoTranscodeBackdrop({uri}: {uri: string}) {
- return (
-
- )
+export function VideoTranscodeBackdrop() {
+ return null
}
diff --git a/src/view/com/composer/videos/VideoTranscodeProgress.tsx b/src/view/com/composer/videos/VideoTranscodeProgress.tsx
index 8a79492d72..3e26230ffd 100644
--- a/src/view/com/composer/videos/VideoTranscodeProgress.tsx
+++ b/src/view/com/composer/videos/VideoTranscodeProgress.tsx
@@ -4,6 +4,8 @@ import {View} from 'react-native'
import ProgressPie from 'react-native-progress/Pie'
import {ImagePickerAsset} from 'expo-image-picker'
+import {clamp} from '#/lib/numbers'
+import {isWeb} from '#/platform/detection'
import {atoms as a, useTheme} from '#/alf'
import {ExternalEmbedRemoveBtn} from '../ExternalEmbedRemoveBtn'
import {VideoTranscodeBackdrop} from './VideoTranscodeBackdrop'
@@ -19,7 +21,15 @@ export function VideoTranscodeProgress({
}) {
const t = useTheme()
- const aspectRatio = asset.width / asset.height
+ if (isWeb) return null
+
+ let aspectRatio = asset.width / asset.height
+
+ if (isNaN(aspectRatio)) {
+ aspectRatio = 16 / 9
+ }
+
+ aspectRatio = clamp(aspectRatio, 1 / 1, 3 / 1)
return (
+function renderItem({item, index}: {item: GetLikes.Like; index: number}) {
+ return (
+
+ )
}
function keyExtractor(item: GetLikes.Like) {
@@ -25,7 +26,6 @@ function keyExtractor(item: GetLikes.Like) {
}
export function PostLikedBy({uri}: {uri: string}) {
- const {_} = useLingui()
const initialNumToRender = useInitialNumToRender()
const [isPTRing, setIsPTRing] = useState(false)
@@ -78,6 +78,7 @@ export function PostLikedBy({uri}: {uri: string}) {
)
}
@@ -91,7 +92,6 @@ export function PostLikedBy({uri}: {uri: string}) {
onRefresh={onRefresh}
onEndReached={onEndReached}
onEndReachedThreshold={4}
- ListHeaderComponent={}
ListFooterComponent={
)
}
diff --git a/src/view/com/post-thread/PostQuotes.tsx b/src/view/com/post-thread/PostQuotes.tsx
index f91a041d75..48c8a69efb 100644
--- a/src/view/com/post-thread/PostQuotes.tsx
+++ b/src/view/com/post-thread/PostQuotes.tsx
@@ -14,24 +14,23 @@ import {useModerationOpts} from '#/state/preferences/moderation-opts'
import {usePostQuotesQuery} from '#/state/queries/post-quotes'
import {useResolveUriQuery} from '#/state/queries/resolve-uri'
import {useInitialNumToRender} from 'lib/hooks/useInitialNumToRender'
+import {isWeb} from 'platform/detection'
import {Post} from 'view/com/post/Post'
-import {
- ListFooter,
- ListHeaderDesktop,
- ListMaybePlaceholder,
-} from '#/components/Lists'
+import {ListFooter, ListMaybePlaceholder} from '#/components/Lists'
import {List} from '../util/List'
function renderItem({
item,
+ index,
}: {
item: {
post: AppBskyFeedDefs.PostView
moderation: ModerationDecision
record: AppBskyFeedPost.Record
}
+ index: number
}) {
- return
+ return
}
function keyExtractor(item: {
@@ -45,7 +44,6 @@ function keyExtractor(item: {
export function PostQuotes({uri}: {uri: string}) {
const {_} = useLingui()
const initialNumToRender = useInitialNumToRender()
-
const [isPTRing, setIsPTRing] = useState(false)
const {
@@ -104,6 +102,7 @@ export function PostQuotes({uri}: {uri: string}) {
)
}
@@ -119,7 +118,6 @@ export function PostQuotes({uri}: {uri: string}) {
onRefresh={onRefresh}
onEndReached={onEndReached}
onEndReachedThreshold={4}
- ListHeaderComponent={}
ListFooterComponent={
)
}
diff --git a/src/view/com/post-thread/PostRepostedBy.tsx b/src/view/com/post-thread/PostRepostedBy.tsx
index 0d1e86aec7..aeba9a34dd 100644
--- a/src/view/com/post-thread/PostRepostedBy.tsx
+++ b/src/view/com/post-thread/PostRepostedBy.tsx
@@ -1,7 +1,5 @@
import React, {useCallback, useMemo, useState} from 'react'
import {AppBskyActorDefs as ActorDefs} from '@atproto/api'
-import {msg} from '@lingui/macro'
-import {useLingui} from '@lingui/react'
import {cleanError} from '#/lib/strings/errors'
import {logger} from '#/logger'
@@ -10,11 +8,7 @@ import {useResolveUriQuery} from '#/state/queries/resolve-uri'
import {useInitialNumToRender} from 'lib/hooks/useInitialNumToRender'
import {ProfileCardWithFollowBtn} from '#/view/com/profile/ProfileCard'
import {List} from '#/view/com/util/List'
-import {
- ListFooter,
- ListHeaderDesktop,
- ListMaybePlaceholder,
-} from '#/components/Lists'
+import {ListFooter, ListMaybePlaceholder} from '#/components/Lists'
function renderItem({item}: {item: ActorDefs.ProfileViewBasic}) {
return
@@ -25,7 +19,6 @@ function keyExtractor(item: ActorDefs.ProfileViewBasic) {
}
export function PostRepostedBy({uri}: {uri: string}) {
- const {_} = useLingui()
const initialNumToRender = useInitialNumToRender()
const [isPTRing, setIsPTRing] = useState(false)
@@ -78,6 +71,7 @@ export function PostRepostedBy({uri}: {uri: string}) {
)
}
@@ -93,7 +87,6 @@ export function PostRepostedBy({uri}: {uri: string}) {
onRefresh={onRefresh}
onEndReached={onEndReached}
onEndReachedThreshold={4}
- ListHeaderComponent={}
ListFooterComponent={
)
}
diff --git a/src/view/com/post-thread/PostThreadItem.tsx b/src/view/com/post-thread/PostThreadItem.tsx
index a3cfebbabd..3b5ddb1dca 100644
--- a/src/view/com/post-thread/PostThreadItem.tsx
+++ b/src/view/com/post-thread/PostThreadItem.tsx
@@ -181,7 +181,7 @@ let PostThreadItemLoaded = ({
threadgateRecord?: AppBskyFeedThreadgate.Record
}): React.ReactNode => {
const pal = usePalette('default')
- const {_} = useLingui()
+ const {_, i18n} = useLingui()
const langPrefs = useLanguagePrefs()
const {openComposer} = useComposerControls()
const [limitLines, setLimitLines] = React.useState(
@@ -388,7 +388,7 @@ let PostThreadItemLoaded = ({
type="lg"
style={pal.textLight}>
- {formatCount(post.repostCount)}
+ {formatCount(i18n, post.repostCount)}
{' '}
- {formatCount(post.quoteCount)}
+ {formatCount(i18n, post.quoteCount)}
{' '}
- {formatCount(post.likeCount)}
+ {formatCount(i18n, post.likeCount)}
{' '}
@@ -705,7 +705,7 @@ function ExpandedPostDetails({
translatorUrl: string
}) {
const pal = usePalette('default')
- const {_} = useLingui()
+ const {_, i18n} = useLingui()
const openLink = useOpenLink()
const isRootPost = !('reply' in post.record)
@@ -723,7 +723,9 @@ function ExpandedPostDetails({
s.mt2,
s.mb10,
]}>
- {niceDate(post.indexedAt)}
+
+ {niceDate(i18n, post.indexedAt)}
+
{isRootPost && (
)}
diff --git a/src/view/com/posts/Feed.tsx b/src/view/com/posts/Feed.tsx
index f0709a3ea1..bad6ccfea6 100644
--- a/src/view/com/posts/Feed.tsx
+++ b/src/view/com/posts/Feed.tsx
@@ -101,7 +101,7 @@ const feedInterstitialType = 'interstitialFeeds'
const followInterstitialType = 'interstitialFollows'
const progressGuideInterstitialType = 'interstitialProgressGuide'
const interstials: Record<
- 'following' | 'discover',
+ 'following' | 'discover' | 'profile',
(FeedItem & {
type:
| 'interstitialFeeds'
@@ -128,6 +128,16 @@ const interstials: Record<
slot: 20,
},
],
+ profile: [
+ {
+ type: followInterstitialType,
+ params: {
+ variant: 'default',
+ },
+ key: followInterstitialType,
+ slot: 5,
+ },
+ ],
}
export function getFeedPostSlice(feedItem: FeedItem): FeedPostSlice | null {
@@ -193,9 +203,7 @@ let Feed = ({
const [isPTRing, setIsPTRing] = React.useState(false)
const checkForNewRef = React.useRef<(() => void) | null>(null)
const lastFetchRef = React.useRef(Date.now())
- const [feedType, feedUri] = feed.split('|')
- const feedIsDiscover = feedUri === DISCOVER_FEED_URI
- const feedIsFollowing = feedType === 'following'
+ const [feedType, feedUri, feedTab] = feed.split('|')
const gate = useGate()
const opts = React.useMemo(
@@ -339,14 +347,21 @@ let Feed = ({
}
if (hasSession) {
- const feedType = feedIsFollowing
- ? 'following'
- : feedIsDiscover
- ? 'discover'
- : undefined
+ let feedKind: 'following' | 'discover' | 'profile' | undefined
+ if (feedType === 'following') {
+ feedKind = 'following'
+ } else if (feedUri === DISCOVER_FEED_URI) {
+ feedKind = 'discover'
+ } else if (
+ feedType === 'author' &&
+ (feedTab === 'posts_and_author_threads' ||
+ feedTab === 'posts_with_replies')
+ ) {
+ feedKind = 'profile'
+ }
- if (feedType) {
- for (const interstitial of interstials[feedType]) {
+ if (feedKind) {
+ for (const interstitial of interstials[feedKind]) {
const shouldShow =
(interstitial.type === feedInterstitialType &&
gate('suggested_feeds_interstitial')) ||
@@ -377,9 +392,9 @@ let Feed = ({
isEmpty,
lastFetchedAt,
data,
+ feedType,
feedUri,
- feedIsDiscover,
- feedIsFollowing,
+ feedTab,
gate,
hasSession,
])
@@ -470,7 +485,7 @@ let Feed = ({
} else if (item.type === feedInterstitialType) {
return
} else if (item.type === followInterstitialType) {
- return
+ return
} else if (item.type === progressGuideInterstitialType) {
return
} else if (item.type === 'slice') {
diff --git a/src/view/com/profile/ProfileFollowers.tsx b/src/view/com/profile/ProfileFollowers.tsx
index 94ca33e6e1..8318f13de8 100644
--- a/src/view/com/profile/ProfileFollowers.tsx
+++ b/src/view/com/profile/ProfileFollowers.tsx
@@ -8,17 +8,26 @@ import {logger} from '#/logger'
import {useProfileFollowersQuery} from '#/state/queries/profile-followers'
import {useResolveDidQuery} from '#/state/queries/resolve-uri'
import {useInitialNumToRender} from 'lib/hooks/useInitialNumToRender'
+import {isWeb} from 'platform/detection'
import {useSession} from 'state/session'
-import {
- ListFooter,
- ListHeaderDesktop,
- ListMaybePlaceholder,
-} from '#/components/Lists'
+import {ListFooter, ListMaybePlaceholder} from '#/components/Lists'
import {List} from '../util/List'
import {ProfileCardWithFollowBtn} from './ProfileCard'
-function renderItem({item}: {item: ActorDefs.ProfileViewBasic}) {
- return
+function renderItem({
+ item,
+ index,
+}: {
+ item: ActorDefs.ProfileViewBasic
+ index: number
+}) {
+ return (
+
+ )
}
function keyExtractor(item: ActorDefs.ProfileViewBasic) {
@@ -88,6 +97,7 @@ export function ProfileFollowers({name}: {name: string}) {
}
errorMessage={cleanError(resolveError || error)}
onRetry={isError ? refetch : undefined}
+ sideBorders={false}
/>
)
}
@@ -101,7 +111,6 @@ export function ProfileFollowers({name}: {name: string}) {
onRefresh={onRefresh}
onEndReached={onEndReached}
onEndReachedThreshold={4}
- ListHeaderComponent={}
ListFooterComponent={
)
}
diff --git a/src/view/com/profile/ProfileFollows.tsx b/src/view/com/profile/ProfileFollows.tsx
index 9b447c955a..de4346afbc 100644
--- a/src/view/com/profile/ProfileFollows.tsx
+++ b/src/view/com/profile/ProfileFollows.tsx
@@ -8,17 +8,26 @@ import {logger} from '#/logger'
import {useProfileFollowsQuery} from '#/state/queries/profile-follows'
import {useResolveDidQuery} from '#/state/queries/resolve-uri'
import {useInitialNumToRender} from 'lib/hooks/useInitialNumToRender'
+import {isWeb} from 'platform/detection'
import {useSession} from 'state/session'
-import {
- ListFooter,
- ListHeaderDesktop,
- ListMaybePlaceholder,
-} from '#/components/Lists'
+import {ListFooter, ListMaybePlaceholder} from '#/components/Lists'
import {List} from '../util/List'
import {ProfileCardWithFollowBtn} from './ProfileCard'
-function renderItem({item}: {item: ActorDefs.ProfileViewBasic}) {
- return
+function renderItem({
+ item,
+ index,
+}: {
+ item: ActorDefs.ProfileViewBasic
+ index: number
+}) {
+ return (
+
+ )
}
function keyExtractor(item: ActorDefs.ProfileViewBasic) {
@@ -88,6 +97,7 @@ export function ProfileFollows({name}: {name: string}) {
}
errorMessage={cleanError(resolveError || error)}
onRetry={isError ? refetch : undefined}
+ sideBorders={false}
/>
)
}
@@ -101,7 +111,6 @@ export function ProfileFollows({name}: {name: string}) {
onRefresh={onRefresh}
onEndReached={onEndReached}
onEndReachedThreshold={4}
- ListHeaderComponent={}
ListFooterComponent={
)
}
diff --git a/src/view/com/util/PostMeta.tsx b/src/view/com/util/PostMeta.tsx
index b1567c2c69..3bd350bf32 100644
--- a/src/view/com/util/PostMeta.tsx
+++ b/src/view/com/util/PostMeta.tsx
@@ -1,6 +1,7 @@
import React, {memo, useCallback} from 'react'
import {StyleProp, StyleSheet, TextStyle, View, ViewStyle} from 'react-native'
import {AppBskyActorDefs, ModerationDecision, ModerationUI} from '@atproto/api'
+import {useLingui} from '@lingui/react'
import {useQueryClient} from '@tanstack/react-query'
import {precacheProfile} from '#/state/queries/profile'
@@ -35,6 +36,8 @@ interface PostMetaOpts {
}
let PostMeta = (opts: PostMetaOpts): React.ReactNode => {
+ const {i18n} = useLingui()
+
const pal = usePalette('default')
const displayName = opts.author.displayName || opts.author.handle
const handle = opts.author.handle
@@ -101,8 +104,8 @@ let PostMeta = (opts: PostMetaOpts): React.ReactNode => {
type="md"
style={pal.textLight}
text={timeElapsed}
- accessibilityLabel={niceDate(opts.timestamp)}
- title={niceDate(opts.timestamp)}
+ accessibilityLabel={niceDate(i18n, opts.timestamp)}
+ title={niceDate(i18n, opts.timestamp)}
accessibilityHint=""
href={opts.postHref}
onBeforePress={onBeforePressPost}
diff --git a/src/view/com/util/TimeElapsed.tsx b/src/view/com/util/TimeElapsed.tsx
index a495851826..70fed222f2 100644
--- a/src/view/com/util/TimeElapsed.tsx
+++ b/src/view/com/util/TimeElapsed.tsx
@@ -1,4 +1,6 @@
import React from 'react'
+import {I18n} from '@lingui/core'
+import {useLingui} from '@lingui/react'
import {useGetTimeAgo} from '#/lib/hooks/useTimeAgo'
import {useTickEveryMinute} from '#/state/shell'
@@ -10,19 +12,21 @@ export function TimeElapsed({
}: {
timestamp: string
children: ({timeElapsed}: {timeElapsed: string}) => JSX.Element
- timeToString?: (timeElapsed: string) => string
+ timeToString?: (i18n: I18n, timeElapsed: string) => string
}) {
+ const {i18n} = useLingui()
const ago = useGetTimeAgo()
- const format = timeToString ?? ago
const tick = useTickEveryMinute()
const [timeElapsed, setTimeAgo] = React.useState(() =>
- format(timestamp, tick),
+ timeToString ? timeToString(i18n, timestamp) : ago(timestamp, tick),
)
const [prevTick, setPrevTick] = React.useState(tick)
if (prevTick !== tick) {
setPrevTick(tick)
- setTimeAgo(format(timestamp, tick))
+ setTimeAgo(
+ timeToString ? timeToString(i18n, timestamp) : ago(timestamp, tick),
+ )
}
return children({timeElapsed})
diff --git a/src/view/com/util/Views.web.tsx b/src/view/com/util/Views.web.tsx
index ed18c981a6..c4549dbc7f 100644
--- a/src/view/com/util/Views.web.tsx
+++ b/src/view/com/util/Views.web.tsx
@@ -47,7 +47,7 @@ export const CenteredView = React.forwardRef(function CenteredView(
if (!isMobile) {
style = addStyle(style, styles.container)
}
- if (sideBorders) {
+ if (sideBorders && !isMobile) {
style = addStyle(style, {
borderLeftWidth: StyleSheet.hairlineWidth,
borderRightWidth: StyleSheet.hairlineWidth,
diff --git a/src/view/com/util/forms/DateInput.tsx b/src/view/com/util/forms/DateInput.tsx
index 0104562aa5..bfbb2ff55e 100644
--- a/src/view/com/util/forms/DateInput.tsx
+++ b/src/view/com/util/forms/DateInput.tsx
@@ -1,19 +1,18 @@
-import React, {useState, useCallback} from 'react'
+import React, {useCallback, useState} from 'react'
import {StyleProp, StyleSheet, TextStyle, View, ViewStyle} from 'react-native'
+import DatePicker from 'react-native-date-picker'
import {
FontAwesomeIcon,
FontAwesomeIconStyle,
} from '@fortawesome/react-native-fontawesome'
-import {isIOS, isAndroid} from 'platform/detection'
-import {Button, ButtonType} from './Button'
-import {Text} from '../text/Text'
+import {useLingui} from '@lingui/react'
+
+import {usePalette} from 'lib/hooks/usePalette'
import {TypographyVariant} from 'lib/ThemeContext'
import {useTheme} from 'lib/ThemeContext'
-import {usePalette} from 'lib/hooks/usePalette'
-import {getLocales} from 'expo-localization'
-import DatePicker from 'react-native-date-picker'
-
-const LOCALE = getLocales()[0]
+import {isAndroid, isIOS} from 'platform/detection'
+import {Text} from '../text/Text'
+import {Button, ButtonType} from './Button'
interface Props {
testID?: string
@@ -30,16 +29,11 @@ interface Props {
}
export function DateInput(props: Props) {
+ const {i18n} = useLingui()
const [show, setShow] = useState(false)
const theme = useTheme()
const pal = usePalette('default')
- const formatter = React.useMemo(() => {
- return new Intl.DateTimeFormat(LOCALE.languageTag, {
- timeZone: props.handleAsUTC ? 'UTC' : undefined,
- })
- }, [props.handleAsUTC])
-
const onChangeInternal = useCallback(
(date: Date) => {
setShow(false)
@@ -74,7 +68,9 @@ export function DateInput(props: Props) {
- {formatter.format(props.value)}
+ {i18n.date(props.value, {
+ timeZone: props.handleAsUTC ? 'UTC' : undefined,
+ })}
diff --git a/src/view/com/util/numeric/format.ts b/src/view/com/util/numeric/format.ts
index 71d8d73e04..cca9fc7e73 100644
--- a/src/view/com/util/numeric/format.ts
+++ b/src/view/com/util/numeric/format.ts
@@ -1,19 +1,12 @@
-export const formatCount = (num: number) =>
- Intl.NumberFormat('en-US', {
+import type {I18n} from '@lingui/core'
+
+export const formatCount = (i18n: I18n, num: number) => {
+ return i18n.number(num, {
notation: 'compact',
maximumFractionDigits: 1,
// `1,953` shouldn't be rounded up to 2k, it should be truncated.
// @ts-expect-error: `roundingMode` doesn't seem to be in the typings yet
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat/NumberFormat#roundingmode
roundingMode: 'trunc',
- }).format(num)
-
-export function formatCountShortOnly(num: number): string {
- if (num >= 1000000) {
- return (num / 1000000).toFixed(1) + 'M'
- }
- if (num >= 1000) {
- return (num / 1000).toFixed(1) + 'K'
- }
- return String(num)
+ })
}
diff --git a/src/view/com/util/post-ctrls/PostCtrls.tsx b/src/view/com/util/post-ctrls/PostCtrls.tsx
index a0cef8692d..6a58a5624a 100644
--- a/src/view/com/util/post-ctrls/PostCtrls.tsx
+++ b/src/view/com/util/post-ctrls/PostCtrls.tsx
@@ -23,7 +23,6 @@ import {makeProfileLink} from '#/lib/routes/links'
import {shareUrl} from '#/lib/sharing'
import {useGate} from '#/lib/statsig/statsig'
import {toShareUrl} from '#/lib/strings/url-helpers'
-import {s} from '#/lib/styles'
import {Shadow} from '#/state/cache/types'
import {useFeedFeedbackContext} from '#/state/feed-feedback'
import {
@@ -36,14 +35,12 @@ import {
ProgressGuideAction,
useProgressGuideControls,
} from '#/state/shell/progress-guide'
+import {CountWheel} from 'lib/custom-animations/CountWheel'
+import {AnimatedLikeIcon} from 'lib/custom-animations/LikeIcon'
import {atoms as a, useTheme} from '#/alf'
import {useDialogControl} from '#/components/Dialog'
import {ArrowOutOfBox_Stroke2_Corner0_Rounded as ArrowOutOfBox} from '#/components/icons/ArrowOutOfBox'
import {Bubble_Stroke2_Corner2_Rounded as Bubble} from '#/components/icons/Bubble'
-import {
- Heart2_Filled_Stroke2_Corner0_Rounded as HeartIconFilled,
- Heart2_Stroke2_Corner0_Rounded as HeartIconOutline,
-} from '#/components/icons/Heart2'
import * as Prompt from '#/components/Prompt'
import {PostDropdownBtn} from '../forms/PostDropdownBtn'
import {formatCount} from '../numeric/format'
@@ -75,7 +72,7 @@ let PostCtrls = ({
threadgateRecord?: AppBskyFeedThreadgate.Record
}): React.ReactNode => {
const t = useTheme()
- const {_} = useLingui()
+ const {_, i18n} = useLingui()
const {openComposer} = useComposerControls()
const {currentAccount} = useSession()
const [queueLike, queueUnlike] = usePostLikeMutationQueue(post, logContext)
@@ -89,6 +86,11 @@ let PostCtrls = ({
const {captureAction} = useProgressGuideControls()
const playHaptic = useHaptics()
const gate = useGate()
+ const isBlocked = Boolean(
+ post.author.viewer?.blocking ||
+ post.author.viewer?.blockedBy ||
+ post.author.viewer?.blockingByList,
+ )
const shouldShowLoggedOutWarning = React.useMemo(() => {
return (
@@ -104,9 +106,21 @@ let PostCtrls = ({
[t],
) as StyleProp
+ const likeValue = post.viewer?.like ? 1 : 0
+ const nextExpectedLikeValue = React.useRef(likeValue)
+
const onPressToggleLike = React.useCallback(async () => {
+ if (isBlocked) {
+ Toast.show(
+ _(msg`Cannot interact with a blocked user`),
+ 'exclamation-circle',
+ )
+ return
+ }
+
try {
if (!post.viewer?.like) {
+ nextExpectedLikeValue.current = 1
playHaptic()
sendInteraction({
item: post.uri,
@@ -116,6 +130,7 @@ let PostCtrls = ({
captureAction(ProgressGuideAction.Like)
await queueLike()
} else {
+ nextExpectedLikeValue.current = 0
await queueUnlike()
}
} catch (e: any) {
@@ -124,6 +139,7 @@ let PostCtrls = ({
}
}
}, [
+ _,
playHaptic,
post.uri,
post.viewer?.like,
@@ -132,9 +148,18 @@ let PostCtrls = ({
sendInteraction,
captureAction,
feedContext,
+ isBlocked,
])
const onRepost = useCallback(async () => {
+ if (isBlocked) {
+ Toast.show(
+ _(msg`Cannot interact with a blocked user`),
+ 'exclamation-circle',
+ )
+ return
+ }
+
try {
if (!post.viewer?.repost) {
sendInteraction({
@@ -152,15 +177,25 @@ let PostCtrls = ({
}
}
}, [
+ _,
post.uri,
post.viewer?.repost,
queueRepost,
queueUnrepost,
sendInteraction,
feedContext,
+ isBlocked,
])
const onQuote = useCallback(() => {
+ if (isBlocked) {
+ Toast.show(
+ _(msg`Cannot interact with a blocked user`),
+ 'exclamation-circle',
+ )
+ return
+ }
+
sendInteraction({
item: post.uri,
event: 'app.bsky.feed.defs#interactionQuote',
@@ -178,6 +213,7 @@ let PostCtrls = ({
onPost: onPostReply,
})
}, [
+ _,
sendInteraction,
post.uri,
post.cid,
@@ -188,6 +224,7 @@ let PostCtrls = ({
openComposer,
record.text,
onPostReply,
+ isBlocked,
])
const onShare = useCallback(() => {
@@ -207,8 +244,8 @@ let PostCtrls = ({
a.gap_xs,
a.rounded_full,
a.flex_row,
- a.align_center,
a.justify_center,
+ a.align_center,
{padding: 5},
(pressed || hovered) && t.atoms.bg_contrast_25,
],
@@ -247,7 +284,7 @@ let PostCtrls = ({
big ? a.text_md : {fontSize: 15},
a.user_select_none,
]}>
- {formatCount(post.replyCount)}
+ {formatCount(i18n, post.replyCount)}
) : undefined}
@@ -280,29 +317,12 @@ let PostCtrls = ({
}
accessibilityHint=""
hitSlop={POST_CTRL_HITSLOP}>
- {post.viewer?.like ? (
-
- ) : (
-
- )}
- {typeof post.likeCount !== 'undefined' && post.likeCount > 0 ? (
-
- {formatCount(post.likeCount)}
-
- ) : undefined}
+
+
{big && (
diff --git a/src/view/com/util/post-ctrls/RepostButton.tsx b/src/view/com/util/post-ctrls/RepostButton.tsx
index 5994b7ef61..d924adbe43 100644
--- a/src/view/com/util/post-ctrls/RepostButton.tsx
+++ b/src/view/com/util/post-ctrls/RepostButton.tsx
@@ -32,7 +32,7 @@ let RepostButton = ({
embeddingDisabled,
}: Props): React.ReactNode => {
const t = useTheme()
- const {_} = useLingui()
+ const {_, i18n} = useLingui()
const requireAuth = useRequireAuth()
const dialogControl = Dialog.useDialogControl()
const playHaptic = useHaptics()
@@ -79,7 +79,7 @@ let RepostButton = ({
big ? a.text_md : {fontSize: 15},
isReposted && a.font_bold,
]}>
- {formatCount(repostCount)}
+ {formatCount(i18n, repostCount)}
) : undefined}
diff --git a/src/view/com/util/post-ctrls/RepostButton.web.tsx b/src/view/com/util/post-ctrls/RepostButton.web.tsx
index 9a8776b9c9..111b41dd7c 100644
--- a/src/view/com/util/post-ctrls/RepostButton.web.tsx
+++ b/src/view/com/util/post-ctrls/RepostButton.web.tsx
@@ -128,6 +128,7 @@ const RepostInner = ({
repostCount?: number
big?: boolean
}) => {
+ const {i18n} = useLingui()
return (
@@ -140,7 +141,7 @@ const RepostInner = ({
isReposted && [a.font_bold],
a.user_select_none,
]}>
- {formatCount(repostCount)}
+ {formatCount(i18n, repostCount)}
) : undefined}
diff --git a/src/view/com/util/post-embeds/VideoEmbed.tsx b/src/view/com/util/post-embeds/VideoEmbed.tsx
index 378952f56b..55ac188248 100644
--- a/src/view/com/util/post-embeds/VideoEmbed.tsx
+++ b/src/view/com/util/post-embeds/VideoEmbed.tsx
@@ -31,7 +31,7 @@ export function VideoEmbed({embed}: {embed: AppBskyEmbedVideo.View}) {
)
const gate = useGate()
- if (!gate('videos')) {
+ if (!gate('video_view_on_posts')) {
return null
}
@@ -50,7 +50,7 @@ export function VideoEmbed({embed}: {embed: AppBskyEmbedVideo.View}) {
a.rounded_sm,
a.overflow_hidden,
{aspectRatio},
- {backgroundColor: t.palette.black},
+ {backgroundColor: 'black'},
a.my_xs,
]}>
@@ -78,9 +78,7 @@ export function VideoEmbed({embed}: {embed: AppBskyEmbedVideo.View}) {
setActiveSource(embed.playlist)
}}
label={_(msg`Play video`)}
- variant="ghost"
- color="secondary"
- size="large">
+ color="secondary">
>
diff --git a/src/view/com/util/post-embeds/VideoEmbed.web.tsx b/src/view/com/util/post-embeds/VideoEmbed.web.tsx
index 409f2c7bab..0001a7af5a 100644
--- a/src/view/com/util/post-embeds/VideoEmbed.web.tsx
+++ b/src/view/com/util/post-embeds/VideoEmbed.web.tsx
@@ -9,13 +9,12 @@ import {
HLSUnsupportedError,
VideoEmbedInnerWeb,
} from '#/view/com/util/post-embeds/VideoEmbedInner/VideoEmbedInnerWeb'
-import {atoms as a, useTheme} from '#/alf'
+import {atoms as a} from '#/alf'
import {ErrorBoundary} from '../ErrorBoundary'
import {useActiveVideoWeb} from './ActiveVideoWebContext'
import * as VideoFallback from './VideoEmbedInner/VideoFallback'
export function VideoEmbed({embed}: {embed: AppBskyEmbedVideo.View}) {
- const t = useTheme()
const ref = useRef(null)
const gate = useGate()
const {active, setActive, sendPosition, currentActiveView} =
@@ -47,7 +46,7 @@ export function VideoEmbed({embed}: {embed: AppBskyEmbedVideo.View}) {
[key],
)
- if (!gate('videos')) {
+ if (!gate('video_view_on_posts')) {
return null
}
@@ -64,7 +63,7 @@ export function VideoEmbed({embed}: {embed: AppBskyEmbedVideo.View}) {
style={[
a.w_full,
{aspectRatio},
- {backgroundColor: t.palette.black},
+ {backgroundColor: 'black'},
a.relative,
a.rounded_sm,
a.my_xs,
diff --git a/src/view/com/util/post-embeds/VideoEmbedInner/TimeIndicator.tsx b/src/view/com/util/post-embeds/VideoEmbedInner/TimeIndicator.tsx
index 4d07ee78dd..6636883f12 100644
--- a/src/view/com/util/post-embeds/VideoEmbedInner/TimeIndicator.tsx
+++ b/src/view/com/util/post-embeds/VideoEmbedInner/TimeIndicator.tsx
@@ -29,9 +29,9 @@ export function TimeIndicator({time}: {time: number}) {
paddingHorizontal: 6,
paddingVertical: 3,
position: 'absolute',
- left: 5,
- bottom: 5,
- minHeight: 20,
+ left: 6,
+ bottom: 6,
+ minHeight: 21,
justifyContent: 'center',
},
]}>
diff --git a/src/view/com/util/post-embeds/VideoEmbedInner/VideoEmbedInnerNative.tsx b/src/view/com/util/post-embeds/VideoEmbedInner/VideoEmbedInnerNative.tsx
index f5ee139e61..59f9d9f970 100644
--- a/src/view/com/util/post-embeds/VideoEmbedInner/VideoEmbedInnerNative.tsx
+++ b/src/view/com/util/post-embeds/VideoEmbedInner/VideoEmbedInnerNative.tsx
@@ -167,17 +167,20 @@ function VideoControls({
/>
+ style={[
+ a.absolute,
+ a.rounded_full,
+ a.justify_center,
+ {
+ backgroundColor: 'rgba(0, 0, 0, 0.5)',
+ paddingHorizontal: 4,
+ paddingVertical: 4,
+ bottom: 6,
+ right: 6,
+ minHeight: 21,
+ minWidth: 21,
+ },
+ ]}>
{isMuted ? (
-
+
) : (
-
+
)}
diff --git a/src/view/com/util/post-embeds/VideoEmbedInner/VideoWebControls.tsx b/src/view/com/util/post-embeds/VideoEmbedInner/VideoWebControls.tsx
index c97f5e935b..2ff2f65160 100644
--- a/src/view/com/util/post-embeds/VideoEmbedInner/VideoWebControls.tsx
+++ b/src/view/com/util/post-embeds/VideoEmbedInner/VideoWebControls.tsx
@@ -253,7 +253,7 @@ export function Controls({
style={a.flex_1}
onPress={onPressEmptySpace}
/>
- {active && !showControls && !focused && (
+ {active && !showControls && !focused && duration > 0 && (
)}
{
- isSeekingRef.current = false
- onSeekEnd()
- setScrubberActive(false)
- },
- {signal},
- )
-
return () => {
document.body.classList.remove('force-no-clicks')
- abortController.abort()
}
}
}, [scrubberActive, onSeekEnd])
@@ -548,7 +535,8 @@ function Scrubber({
}}
onPointerDown={onPointerDown}
onPointerMove={onPointerMove}
- onPointerUp={onPointerUp}>
+ onPointerUp={onPointerUp}
+ onPointerCancel={onPointerUp}>
- {currentTime > 0 && duration > 0 && (
+ {duration > 0 && (
{
@@ -232,9 +230,6 @@ function AppPassword({
control.open()
}, [control])
- const primaryLocale =
- contentLanguages.length > 0 ? contentLanguages[0] : 'en-US'
-
return (
Created{' '}
- {Intl.DateTimeFormat(primaryLocale, {
+ {i18n.date(createdAt, {
year: 'numeric',
month: 'numeric',
day: 'numeric',
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
- }).format(new Date(createdAt))}
+ })}
{privileged && (
diff --git a/src/view/screens/ProfileFollowers.tsx b/src/view/screens/ProfileFollowers.tsx
index 6f8ecc2e8b..68447bd771 100644
--- a/src/view/screens/ProfileFollowers.tsx
+++ b/src/view/screens/ProfileFollowers.tsx
@@ -1,12 +1,16 @@
import React from 'react'
-import {View} from 'react-native'
-import {useFocusEffect} from '@react-navigation/native'
-import {NativeStackScreenProps, CommonNavigatorParams} from 'lib/routes/types'
-import {ViewHeader} from '../com/util/ViewHeader'
-import {ProfileFollowers as ProfileFollowersComponent} from '../com/profile/ProfileFollowers'
-import {useSetMinimalShellMode} from '#/state/shell'
-import {useLingui} from '@lingui/react'
import {msg} from '@lingui/macro'
+import {useLingui} from '@lingui/react'
+import {useFocusEffect} from '@react-navigation/native'
+
+import {useSetMinimalShellMode} from '#/state/shell'
+import {CommonNavigatorParams, NativeStackScreenProps} from 'lib/routes/types'
+import {isWeb} from 'platform/detection'
+import {CenteredView} from 'view/com/util/Views'
+import {atoms as a} from '#/alf'
+import {ListHeaderDesktop} from '#/components/Lists'
+import {ProfileFollowers as ProfileFollowersComponent} from '../com/profile/ProfileFollowers'
+import {ViewHeader} from '../com/util/ViewHeader'
type Props = NativeStackScreenProps
export const ProfileFollowersScreen = ({route}: Props) => {
@@ -21,9 +25,10 @@ export const ProfileFollowersScreen = ({route}: Props) => {
)
return (
-
-
+
+
+
-
+
)
}
diff --git a/src/view/screens/ProfileFollows.tsx b/src/view/screens/ProfileFollows.tsx
index bdab201535..7cc10ffd1c 100644
--- a/src/view/screens/ProfileFollows.tsx
+++ b/src/view/screens/ProfileFollows.tsx
@@ -1,12 +1,16 @@
import React from 'react'
-import {View} from 'react-native'
-import {useFocusEffect} from '@react-navigation/native'
-import {NativeStackScreenProps, CommonNavigatorParams} from 'lib/routes/types'
-import {ViewHeader} from '../com/util/ViewHeader'
-import {ProfileFollows as ProfileFollowsComponent} from '../com/profile/ProfileFollows'
-import {useSetMinimalShellMode} from '#/state/shell'
-import {useLingui} from '@lingui/react'
import {msg} from '@lingui/macro'
+import {useLingui} from '@lingui/react'
+import {useFocusEffect} from '@react-navigation/native'
+
+import {useSetMinimalShellMode} from '#/state/shell'
+import {CommonNavigatorParams, NativeStackScreenProps} from 'lib/routes/types'
+import {isWeb} from 'platform/detection'
+import {CenteredView} from 'view/com/util/Views'
+import {atoms as a} from '#/alf'
+import {ListHeaderDesktop} from '#/components/Lists'
+import {ProfileFollows as ProfileFollowsComponent} from '../com/profile/ProfileFollows'
+import {ViewHeader} from '../com/util/ViewHeader'
type Props = NativeStackScreenProps
export const ProfileFollowsScreen = ({route}: Props) => {
@@ -21,9 +25,10 @@ export const ProfileFollowsScreen = ({route}: Props) => {
)
return (
-
-
+
+
+
-
+
)
}
diff --git a/src/view/shell/Drawer.tsx b/src/view/shell/Drawer.tsx
index 0e852edd1a..facead2c1e 100644
--- a/src/view/shell/Drawer.tsx
+++ b/src/view/shell/Drawer.tsx
@@ -30,7 +30,7 @@ import {colors, s} from 'lib/styles'
import {useTheme} from 'lib/ThemeContext'
import {isWeb} from 'platform/detection'
import {NavSignupCard} from '#/view/shell/NavSignupCard'
-import {formatCountShortOnly} from 'view/com/util/numeric/format'
+import {formatCount} from 'view/com/util/numeric/format'
import {Text} from 'view/com/util/text/Text'
import {UserAvatar} from 'view/com/util/UserAvatar'
import {atoms as a} from '#/alf'
@@ -68,7 +68,7 @@ let DrawerProfileCard = ({
account: SessionAccount
onPressProfile: () => void
}): React.ReactNode => {
- const {_} = useLingui()
+ const {_, i18n} = useLingui()
const pal = usePalette('default')
const {data: profile} = useProfileQuery({did: account.did})
@@ -108,7 +108,7 @@ let DrawerProfileCard = ({
- {formatCountShortOnly(profile?.followersCount ?? 0)}
+ {formatCount(i18n, profile?.followersCount ?? 0)}
{' '}
- {formatCountShortOnly(profile?.followsCount ?? 0)}
+ {formatCount(i18n, profile?.followsCount ?? 0)}
{' '}