Merge remote-tracking branch 'origin/main' into next/base

* origin/main: (39 commits)
  Revert "fix quote & feed padding not being pressable (#7694)" (#7733)
  Nightly source-language update
  Add dev mode for easy copying of at:// URIs and DIDs (#7723)
  Improved search language select (#7591)
  Improved search page (#7590)
  Animate drawer menu on mobile web (#7711)
  Special treatment for recommended starter packs (#7706)
  invert new postonboarding gate (#7695)
  Animate dropdown menus (#7704)
  Better animations for dialogs, animate web composer (#7703)
  fix quote & feed padding not being pressable (#7694)
  ungate trending (#7696)
  run prettier (#7727)
  Nightly source-language update
  Add deploy key to nightly commit (#7722)
  [APP-1031] Add new followerRule to threadgate settings (#7681)
  Fix bug in GH action nightly-update-source-languages.yaml
  More i18n process updates (#7720)
  Run intl:extract on english to see if crowdin picks up on the changes
  Refine accessibilityHint (#7554)
  ...
This commit is contained in:
Eric Bailey
2025-02-13 11:19:59 -06:00
97 changed files with 14126 additions and 4505 deletions
@@ -0,0 +1,38 @@
name: Nightly Update Source Languages
on:
schedule:
- cron: '0 2 * * *' # run at 2 AM UTC
workflow_dispatch:
jobs:
extract-languages:
name: Nightly Update Source Languages
runs-on: ubuntu-latest
permissions:
# Give the default GITHUB_TOKEN write permission to commit and push the
# added or changed files to the repository.
contents: write
steps:
- name: Check out Git repository
uses: actions/checkout@v3
with:
ssh-key: ${{secrets.GH_ACTION_DEPLOY_KEY}}
- name: Install node
uses: actions/setup-node@v4
with:
node-version-file: .nvmrc
- name: Yarn install
uses: Wandalen/wretry.action@master
with:
command: yarn --frozen-lockfile
attempt_limit: 3
attempt_delay: 2000
- name: Extract language strings
run: yarn intl:extract
- name: Create commit
uses: stefanzweifel/git-auto-commit-action@v5
with:
commit_message: Nightly source-language update
file_pattern: ./src/locale/locales/en/messages.po
+42
View File
@@ -0,0 +1,42 @@
import {IsValidHandle, validateServiceHandle} from '#/lib/strings/handles'
describe('handle validation', () => {
const valid = [
['ali', 'bsky.social'],
['alice', 'bsky.social'],
['a-lice', 'bsky.social'],
['a-----lice', 'bsky.social'],
['123', 'bsky.social'],
['123456789012345678', 'bsky.social'],
['alice', 'custom-pds.com'],
['alice', 'my-custom-pds-with-long-name.social'],
['123456789012345678', 'my-custom-pds-with-long-name.social'],
]
it.each(valid)(`should be valid: %s.%s`, (handle, service) => {
const result = validateServiceHandle(handle, service)
expect(result.overall).toEqual(true)
})
const invalid = [
['al', 'bsky.social', 'frontLength'],
['-alice', 'bsky.social', 'hyphenStartOrEnd'],
['alice-', 'bsky.social', 'hyphenStartOrEnd'],
['%%%', 'bsky.social', 'handleChars'],
['1234567890123456789', 'bsky.social', 'frontLength'],
[
'1234567890123456789',
'my-custom-pds-with-long-name.social',
'frontLength',
],
['al', 'my-custom-pds-with-long-name.social', 'frontLength'],
['a'.repeat(300), 'toolong.com', 'totalLength'],
] satisfies [string, string, keyof IsValidHandle][]
it.each(invalid)(
`should be invalid: %s.%s due to %s`,
(handle, service, expectedError) => {
const result = validateServiceHandle(handle, service)
expect(result.overall).toEqual(false)
expect(result[expectedError]).toEqual(false)
},
)
})
+1
View File
@@ -86,6 +86,7 @@ module.exports = function (config) {
'gl',
'hi',
'hu',
'ia',
'id',
'it',
'ja',
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><path fill="#000" fill-rule="evenodd" d="M11.293 4.293a1 1 0 0 1 1.414 0l4 4a1 1 0 0 1-1.414 1.414L12 6.414 8.707 9.707a1 1 0 0 1-1.414-1.414l4-4Zm-4 10a1 1 0 0 1 1.414 0L12 17.586l3.293-3.293a1 1 0 0 1 1.414 1.414l-4 4a1 1 0 0 1-1.414 0l-4-4a1 1 0 0 1 0-1.414Z" clip-rule="evenodd"/></svg>

After

Width:  |  Height:  |  Size: 363 B

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><path fill="#000" fill-rule="evenodd" d="M7 2a1 1 0 0 1 1 1v2a1 1 0 0 1-2 0V3a1 1 0 0 1 1-1Zm4 0a1 1 0 0 1 1 1v2a1 1 0 1 1-2 0V3a1 1 0 0 1 1-1Zm4 0a1 1 0 0 1 1 1v2a1 1 0 1 1-2 0V3a1 1 0 0 1 1-1ZM4 9a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2h.5a3.5 3.5 0 1 1 0 7H18v4a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V9Zm12 0H6v11h10V9Zm2 5h.5a1.5 1.5 0 0 0 0-3H18v3Z" clip-rule="evenodd"/></svg>

Before

Width:  |  Height:  |  Size: 437 B

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><path fill="#000" fill-rule="evenodd" d="M6.5 4.999Zm0 0a.1.1 0 0 1-.016.024.2.2 0 0 1-.152.043q.086.014.168.032V7.5a1 1 0 0 1-.33.742c-.543.49-.917 1.176-1.23 2.036a1 1 0 0 1-.94.657H3V14h1a1 1 0 0 1 .866.5c.436.754.879 1.195 1.637 1.636A1 1 0 0 1 7 17v2h2a2 2 0 0 1 2-2h2a2 2 0 0 1 2 2h2v-2.537a1 1 0 0 1 .332-.744A4.98 4.98 0 0 0 19 11.999 5 5 0 0 0 14 7H9.474a1 1 0 0 1-.893-.55v-.001l-.001-.002-.005-.008L9.474 6l-.9.438h.001v.002l.002.001.001.003.002.003v.003a2.6 2.6 0 0 0-.443-.538c-.319-.298-.839-.648-1.637-.814V5Zm2.082 1.452ZM9.5 4.447c.211.196.379.387.508.553H14a7 7 0 0 1 6.72 8.96 1.003 1.003 0 0 0 1.146-1.46 1 1 0 1 1 1.731-1 3 3 0 0 1-3.714 4.286A7 7 0 0 1 19 16.888V19a2 2 0 0 1-2 2h-2a2 2 0 0 1-2-2h-2a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2v-1.45A5.8 5.8 0 0 1 3.448 16H3a2 2 0 0 1-2-2v-3.065a2 2 0 0 1 2-2h.324c.286-.649.657-1.291 1.176-1.852V5c0-1.047.89-2.12 2.161-1.907 1.33.223 2.248.805 2.839 1.354ZM8.25 12a1.25 1.25 0 1 0 0-2.5 1.25 1.25 0 0 0 0 2.5Z" clip-rule="evenodd"/></svg>

Before

Width:  |  Height:  |  Size: 1.0 KiB

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"><path fill="#000" fill-rule="evenodd" d="M12 4c0-1.012.894-2.187 2.237-1.846a5.002 5.002 0 0 1 3.218 7.119 4 4 0 0 1 2.345 4.976A3.501 3.501 0 0 1 18.5 21h-13a3.5 3.5 0 0 1-1.3-6.75 4 4 0 0 1 2.052-4.848A4 4 0 0 1 10 4h2.001Zm-4.187 7.009A2 2 0 0 0 6 13c0 .366.098.706.271 1H12a1 1 0 1 1 0 2H5.5a1.5 1.5 0 0 0 0 3h13a1.5 1.5 0 1 0 0-3H17a1 1 0 1 1 0-2h.729A1.96 1.96 0 0 0 18 13a2 2 0 0 0-2-2h-2a1 1 0 1 1 0-2h1.236a3.002 3.002 0 0 0-1.243-4.832A2 2 0 0 1 12 6h-2a2 2 0 0 0-1.728 3.009H9a1 1 0 0 1 0 2H7.813Z" clip-rule="evenodd"/></svg>

Before

Width:  |  Height:  |  Size: 610 B

+20
View File
@@ -0,0 +1,20 @@
{
"project_id": "664276",
"base_path": ".",
"preserve_hierarchy": true,
"files": [
{
"source": "/src/locale/locales/en/messages.po",
"translation": "/src/locale/locales/%two_letters_code%/messages.po",
"languages_mapping": {
"two_letters_code": {
"pt-BR": "pt-BR",
"en-GB": "en-GB",
"zh-CN": "zh-CN",
"zh-TW": "zh-TW",
"zh-HK": "zh-HK"
}
}
}
]
}
+57 -7
View File
@@ -3,9 +3,64 @@
We want the official Bluesky app to be supported in as many languages as possible. If you want to help us translate the app, please open a PR or issue on the [Bluesky app repo on GitHub](https://github.com/bluesky-social/social-app)
## Tools
We are using Lingui to manage translations. You can find the documentation [here](https://lingui.dev/).
- We use Lingui to implement translations. You can find the documentation [here](https://lingui.dev/).
- We use Crowdin to manage translations.
- Bluesky Crowdin: https://crowdin.com/project/bluesky-social
- Introduction to Crowdin: https://support.crowdin.com/for-translators/
## Translators
Much of the app is translated by community contributions. (We <3 our translators!) If you want to participate in the translation of the app, read this section.
### Using Crowdin
[Crowdin](https://crowdin.com/project/bluesky-social) is our primary tool for managing translations. There are two roles:
- **Proof-readers**. Can create new translations and approve submitted translations.
- **Translators**. Can create new translations.
All translations must be approved by proof-readers before they are accepted into the app.
### Using other platforms
You may contribute PRs separately from Crowdin, however we strongly recommend using Crowdin to avoid conflicts.
### Code of conduct on Crowdin
Please treat everyone with respect. Proof-readers are given final say on translations. Translators who frequently come into conflict with other translators, or who contribute noticably incorrect translations, will have their membership to the Crowdin project revoked.
### Adding a new language
Create a new [Crowdin discussion](https://crowdin.com/project/bluesky-social/discussions) or [GitHub issue](https://github.com/bluesky-social/social-app/issues) requesting the new language be added to the project.
Please only request a new language when you are certain you will be able to contribute a substantive portion of translations for the language.
## Maintainers
Install the [Crowdin CLI](https://crowdin.github.io/crowdin-cli/). You will need to [configure your API token](https://crowdin.github.io/crowdin-cli/configuration) to access the project.
### English source-file sync with Crowdin
Every night, a GitHub action will run `yarn intl:extract` to update the english `messages.po` file. This will be automatically synced with Crowdin. Crowdin should notify all subscribed users of new translations.
### Release process
1. Pull main and create a branch.
1. Run `yarn intl:pull` to fetch all translation updates from Crowdin.
1. Create a PR, ensure the translations all look correct, and merge.
1. If needed:
1. Merge all approved translation PRs (contributions from outside crowdin).
1. Run `yarn intl:push` to sync Crowdin with the state of the repo.
### Testing the translations in Crowdin
You can run `yarn intl:pull` to pull the currently-approved translations from Crowdin.
## Developers
### Adding new strings
When adding a new string, do it as follows:
```jsx
// Before
@@ -61,6 +116,7 @@ So the workflow is as follows:
6. Enjoy translated app!
### Common pitfalls
These pitfalls are memoization pitfalls that will cause the components to not re-render when the locale is changed -- causing stale translations to be shown.
```jsx
@@ -111,9 +167,3 @@ export function Welcome() {
return <div>{welcome}</div>;
}
```
### Credits
Please check each individual `messages.po` file for the credits of the translators. We are very grateful for their help!
If you would like to translate the Bluesky app into your language, please open a PR or issue on this repo.
+1
View File
@@ -17,6 +17,7 @@ module.exports = {
'gl',
'hi',
'hu',
'ia',
'id',
'it',
'ja',
+9
View File
@@ -70,6 +70,7 @@ import {MessagesScreen} from '#/screens/Messages/ChatList'
import {MessagesConversationScreen} from '#/screens/Messages/Conversation'
import {MessagesSettingsScreen} from '#/screens/Messages/Settings'
import {ModerationScreen} from '#/screens/Moderation'
import {Screen as ModerationInteractionSettings} from '#/screens/ModerationInteractionSettings'
import {PostLikedByScreen} from '#/screens/Post/PostLikedBy'
import {PostQuotesScreen} from '#/screens/Post/PostQuotes'
import {PostRepostedByScreen} from '#/screens/Post/PostRepostedBy'
@@ -155,6 +156,14 @@ function commonScreens(Stack: typeof HomeTab, unreadCountLabel?: string) {
getComponent={() => ModerationBlockedAccounts}
options={{title: title(msg`Blocked Accounts`), requireAuth: true}}
/>
<Stack.Screen
name="ModerationInteractionSettings"
getComponent={() => ModerationInteractionSettings}
options={{
title: title(msg`Post Interaction Settings`),
requireAuth: true,
}}
/>
<Stack.Screen
name="Settings"
getComponent={() => SettingsScreen}
+35 -2
View File
@@ -50,6 +50,12 @@ export const atoms = {
overflow_hidden: {
overflow: 'hidden',
},
/**
* @platform web
*/
overflow_auto: web({
overflow: 'auto',
}),
/*
* Width
@@ -68,7 +74,6 @@ export const atoms = {
* Used for the outermost components on screens, to ensure that they can fill
* the screen and extend beyond.
*/
// @ts-ignore - web only minHeight string
util_screen_outer: [
web({
minHeight: '100vh',
@@ -76,7 +81,7 @@ export const atoms = {
native({
height: '100%',
}),
] as ViewStyle,
] as StyleProp<ViewStyle>,
/*
* Theme-independent bg colors
@@ -965,6 +970,34 @@ export const atoms = {
transitionDelay: '50ms',
}),
/*
* Animaations
*/
fade_in: web({
animation: 'fadeIn ease-out 0.15s',
}),
fade_out: web({
animation: 'fadeOut ease-out 0.15s',
}),
zoom_in: web({
animation: 'zoomIn ease-out 0.1s',
}),
zoom_out: web({
animation: 'zoomOut ease-out 0.1s',
}),
slide_in_left: web({
// exponential easing function
animation: 'slideInLeft cubic-bezier(0.16, 1, 0.3, 1) 0.5s',
}),
slide_out_left: web({
animation: 'slideOutLeft ease-in 0.15s',
animationFillMode: 'forwards',
}),
// special composite animation for dialogs
zoom_fade_in: web({
animation: 'zoomIn ease-out 0.1s, fadeIn ease-out 0.1s',
}),
/**
* {@link Layout.SCROLLBAR_OFFSET}
*/
+10 -14
View File
@@ -15,6 +15,7 @@ import {FocusScope} from '@radix-ui/react-focus-scope'
import {RemoveScrollBar} from 'react-remove-scroll-bar'
import {logger} from '#/logger'
import {useA11y} from '#/state/a11y'
import {useDialogStateControlContext} from '#/state/dialogs'
import {atoms as a, flatten, useBreakpoints, useTheme, web} from '#/alf'
import {Button, ButtonIcon} from '#/components/Button'
@@ -152,6 +153,7 @@ export function Inner({
const t = useTheme()
const {close} = React.useContext(Context)
const {gtMobile} = useBreakpoints()
const {reduceMotionEnabled} = useA11y()
useFocusGuards()
return (
<FocusScope loop asChild trapped>
@@ -161,7 +163,7 @@ export function Inner({
aria-label={label}
aria-labelledby={accessibilityLabelledBy}
aria-describedby={accessibilityDescribedBy}
// @ts-ignore web only -prf
// @ts-expect-error web only -prf
onClick={stopPropagation}
onStartShouldSetResponder={_ => true}
onTouchEnd={stopPropagation}
@@ -177,10 +179,9 @@ export function Inner({
shadowColor: t.palette.black,
shadowOpacity: t.name === 'light' ? 0.1 : 0.4,
shadowRadius: 30,
// @ts-ignore web only
animation: 'fadeIn ease-out 0.1s',
},
flatten(style),
!reduceMotionEnabled && a.zoom_fade_in,
style,
])}>
<DismissableLayer
onInteractOutside={preventDefault}
@@ -216,7 +217,7 @@ export const InnerFlatList = React.forwardRef<
style={[
a.overflow_hidden,
a.px_0,
// @ts-ignore web only -sfn
// @ts-expect-error web only -sfn
{maxHeight: 'calc(-36px + 100vh)'},
webInnerStyle,
]}
@@ -262,20 +263,15 @@ export function Handle() {
function Backdrop() {
const t = useTheme()
const {reduceMotionEnabled} = useA11y()
return (
<View
style={{
opacity: 0.8,
}}>
<View style={{opacity: 0.8}}>
<View
style={[
a.fixed,
a.inset_0,
{
backgroundColor: t.palette.black,
// @ts-ignore web only
animation: 'fadeIn ease-out 0.15s',
},
{backgroundColor: t.palette.black},
!reduceMotionEnabled && a.fade_in,
]}
/>
</View>
+2 -1
View File
@@ -1,5 +1,5 @@
import {createContext, useCallback, useContext} from 'react'
import {GestureResponderEvent, View} from 'react-native'
import {GestureResponderEvent, Keyboard, View} from 'react-native'
import {msg} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {useNavigation} from '@react-navigation/native'
@@ -140,6 +140,7 @@ export function MenuButton() {
const {gtMobile} = useBreakpoints()
const onPress = useCallback(() => {
Keyboard.dismiss()
setDrawerOpen(true)
}, [setDrawerOpen])
+12 -4
View File
@@ -4,6 +4,7 @@ import {msg} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import * as DropdownMenu from '@radix-ui/react-dropdown-menu'
import {useA11y} from '#/state/a11y'
import {atoms as a, flatten, useTheme, web} from '#/alf'
import * as Dialog from '#/components/Dialog'
import {useInteractionState} from '#/components/hooks/useInteractionState'
@@ -177,10 +178,16 @@ export function Outer({
style?: StyleProp<ViewStyle>
}>) {
const t = useTheme()
const {reduceMotionEnabled} = useA11y()
return (
<DropdownMenu.Portal>
<DropdownMenu.Content sideOffset={5} loop aria-label="Test">
<DropdownMenu.Content
sideOffset={5}
collisionPadding={{left: 5, right: 5, bottom: 5}}
loop
aria-label="Test"
className="dropdown-menu-transform-origin">
<View
style={[
a.rounded_sm,
@@ -189,6 +196,8 @@ export function Outer({
t.name === 'light' ? t.atoms.bg : t.atoms.bg_contrast_25,
t.atoms.shadow_md,
t.atoms.border_contrast_low,
a.overflow_auto,
!reduceMotionEnabled && a.zoom_fade_in,
style,
]}>
{children}
@@ -373,9 +382,8 @@ export function Divider() {
style={flatten([
a.my_xs,
t.atoms.bg_contrast_100,
{
height: 1,
},
a.flex_shrink_0,
{height: 1},
])}
/>
)
@@ -762,7 +762,7 @@ function SearchInput({
autoComplete="off"
autoCapitalize="none"
accessibilityLabel={_(msg`Search profiles`)}
accessibilityHint={_(msg`Search profiles`)}
accessibilityHint={_(msg`Searches for profiles`)}
/>
</View>
)
@@ -12,7 +12,7 @@ import {precacheResolvedUri} from '#/state/queries/resolve-uri'
import {precacheStarterPack} from '#/state/queries/starter-packs'
import {useSession} from '#/state/session'
import {atoms as a, useTheme} from '#/alf'
import {StarterPack} from '#/components/icons/StarterPack'
import {StarterPack as StarterPackIcon} from '#/components/icons/StarterPack'
import {Link as BaseLink, LinkProps as BaseLinkProps} from '#/components/Link'
import {Text} from '#/components/Typography'
import * as bsky from '#/types/bsky'
@@ -70,7 +70,7 @@ export function Card({
return (
<View style={[a.w_full, a.gap_md]}>
<View style={[a.flex_row, a.gap_sm, a.w_full]}>
{!noIcon ? <StarterPack width={40} gradient="sky" /> : null}
{!noIcon ? <StarterPackIcon width={40} gradient="sky" /> : null}
<View style={[a.flex_1]}>
<Text
emoji
+50 -40
View File
@@ -12,6 +12,7 @@ import {PressableScale} from '#/lib/custom-animations/PressableScale'
// import {UserAvatar} from '#/view/com/util/UserAvatar'
import type {TrendingTopic} from '#/state/queries/trending/useTrendingTopics'
import {atoms as a, native, useTheme, ViewStyleProp} from '#/alf'
import {StarterPack as StarterPackIcon} from '#/components/icons/StarterPack'
import {Link as InternalLink, LinkProps} from '#/components/Link'
import {Text} from '#/components/Typography'
@@ -24,9 +25,8 @@ export function TrendingTopic({
const topic = useTopic(raw)
const isSmall = size === 'small'
// const hasAvi = topic.type === 'feed' || topic.type === 'profile'
// const aviSize = isSmall ? 16 : 20
// const iconSize = isSmall ? 16 : 20
const hasIcon = topic.type === 'starter-pack' && !isSmall
const iconSize = 20
return (
<View
@@ -45,45 +45,47 @@ export function TrendingTopic({
},
]
: [a.py_sm, a.px_md],
hasIcon && {gap: 6},
style,
/*
{
padding: 6,
gap: hasAvi ? 4 : 2,
},
a.pr_md,
*/
]}>
{hasIcon && topic.type === 'starter-pack' && (
<StarterPackIcon
gradient="sky"
width={iconSize}
style={{marginLeft: -3, marginVertical: -1}}
/>
)}
{/*
<View
style={[
a.align_center,
a.justify_center,
a.rounded_full,
a.overflow_hidden,
{
width: aviSize,
height: aviSize,
},
]}>
{topic.type === 'tag' ? (
<Hashtag width={iconSize} />
) : topic.type === 'topic' ? (
<Quote width={iconSize - 2} />
) : topic.type === 'feed' ? (
<UserAvatar
type="user"
size={aviSize}
avatar=""
/>
) : (
<UserAvatar
type="user"
size={aviSize}
avatar=""
/>
)}
</View>
<View
style={[
a.align_center,
a.justify_center,
a.rounded_full,
a.overflow_hidden,
{
width: iconSize,
height: iconSize,
},
]}>
{topic.type === 'tag' ? (
<Hashtag width={iconSize} />
) : topic.type === 'topic' ? (
<Quote width={iconSize - 2} />
) : topic.type === 'feed' ? (
<UserAvatar
type="user"
size={aviSize}
avatar=""
/>
) : (
<UserAvatar
type="user"
size={aviSize}
avatar=""
/>
)}
</View>
*/}
<Text
@@ -151,7 +153,7 @@ export function TrendingTopicLink({
type ParsedTrendingTopic =
| {
type: 'topic' | 'tag' | 'unknown'
type: 'topic' | 'tag' | 'starter-pack' | 'unknown'
label: string
displayName: string
url: string
@@ -187,6 +189,14 @@ export function useTopic(raw: TrendingTopic): ParsedTrendingTopic {
uri: undefined,
url: link,
}
} else if (link.startsWith('/starter-pack')) {
return {
type: 'starter-pack',
label: _(msg`Browse starter pack ${displayName}`),
displayName,
uri: undefined,
url: link,
}
}
/*
+1 -1
View File
@@ -118,7 +118,7 @@ export function VideoPostCard({
return (
<Link
accessibilityHint={_(msg`Tap to view video in immersive mode.`)}
accessibilityHint={_(msg`Views video in immersive mode`)}
label={_(msg`Video from ${author.handle}: ${text}`)}
to={{
screen: 'VideoFeed',
+19 -1
View File
@@ -213,7 +213,12 @@ function Rules({
a.flex_wrap,
t.atoms.text_contrast_medium,
]}>
{settings[0].type === 'everybody' ? (
{settings.length === 0 ? (
<Trans>
This post has an unknown type of threadgate on it. Your app may be
out of date.
</Trans>
) : settings[0].type === 'everybody' ? (
<Trans>Everybody can reply to this post.</Trans>
) : settings[0].type === 'nobody' ? (
<Trans>Replies to this post are disabled.</Trans>
@@ -257,6 +262,19 @@ function Rule({
if (rule.type === 'mention') {
return <Trans>mentioned users</Trans>
}
if (rule.type === 'followers') {
return (
<Trans>
users following{' '}
<InlineLinkText
label={`@${post.author.handle}`}
to={makeProfileLink(post.author)}
style={[a.text_sm, a.leading_snug]}>
@{post.author.handle}
</InlineLinkText>
</Trans>
)
}
if (rule.type === 'following') {
return (
<Trans>
@@ -40,6 +40,7 @@ import {Loader} from '#/components/Loader'
import {Text} from '#/components/Typography'
export type PostInteractionSettingsFormProps = {
canSave?: boolean
onSave: () => void
isSaving?: boolean
@@ -58,20 +59,53 @@ export function PostInteractionSettingsControlledDialog({
}: PostInteractionSettingsFormProps & {
control: Dialog.DialogControlProps
}) {
const t = useTheme()
const {_} = useLingui()
return (
<Dialog.Outer control={control}>
<Dialog.Handle />
<Dialog.ScrollableInner
label={_(msg`Edit post interaction settings`)}
style={[{maxWidth: 500}, a.w_full]}>
<PostInteractionSettingsForm {...rest} />
<View style={[a.gap_md]}>
<Header />
<PostInteractionSettingsForm {...rest} />
<Text
style={[
a.pt_sm,
a.text_sm,
a.leading_snug,
t.atoms.text_contrast_medium,
]}>
<Trans>
You can set default interaction settings in{' '}
<Text style={[a.font_bold, t.atoms.text_contrast_medium]}>
Settings &rarr; Moderation &rarr; Interaction settings.
</Text>
</Trans>
</Text>
</View>
<Dialog.Close />
</Dialog.ScrollableInner>
</Dialog.Outer>
)
}
export function Header() {
return (
<View style={[a.gap_md, a.pb_sm]}>
<Text style={[a.text_2xl, a.font_bold]}>
<Trans>Post interaction settings</Trans>
</Text>
<Text style={[a.text_md, a.pb_xs]}>
<Trans>Customize who can interact with this post.</Trans>
</Text>
<Divider />
</View>
)
}
export type PostInteractionSettingsDialogProps = {
control: Dialog.DialogControlProps
/**
@@ -203,26 +237,31 @@ export function PostInteractionSettingsDialogControlledInner(
<Dialog.ScrollableInner
label={_(msg`Edit post interaction settings`)}
style={[{maxWidth: 500}, a.w_full]}>
{isLoading ? (
<View style={[a.flex_1, a.py_4xl, a.align_center, a.justify_center]}>
<Loader size="xl" />
</View>
) : (
<PostInteractionSettingsForm
replySettingsDisabled={!isThreadgateOwnedByViewer}
isSaving={isSaving}
onSave={onSave}
postgate={postgateValue}
onChangePostgate={setEditedPostgate}
threadgateAllowUISettings={allowUIValue}
onChangeThreadgateAllowUISettings={setEditedAllowUISettings}
/>
)}
<View style={[a.gap_md]}>
<Header />
{isLoading ? (
<View style={[a.flex_1, a.py_4xl, a.align_center, a.justify_center]}>
<Loader size="xl" />
</View>
) : (
<PostInteractionSettingsForm
replySettingsDisabled={!isThreadgateOwnedByViewer}
isSaving={isSaving}
onSave={onSave}
postgate={postgateValue}
onChangePostgate={setEditedPostgate}
threadgateAllowUISettings={allowUIValue}
onChangeThreadgateAllowUISettings={setEditedAllowUISettings}
/>
)}
</View>
</Dialog.ScrollableInner>
)
}
export function PostInteractionSettingsForm({
canSave = true,
onSave,
isSaving,
postgate,
@@ -283,17 +322,7 @@ export function PostInteractionSettingsForm({
return (
<View>
<View style={[a.flex_1, a.gap_md]}>
<Text style={[a.text_2xl, a.font_bold]}>
<Trans>Post interaction settings</Trans>
</Text>
<View style={[a.gap_lg]}>
<Text style={[a.text_md]}>
<Trans>Customize who can interact with this post.</Trans>
</Text>
<Divider />
<View style={[a.gap_sm]}>
<Text style={[a.font_bold, a.text_lg]}>
<Trans>Quote settings</Trans>
@@ -400,7 +429,7 @@ export function PostInteractionSettingsForm({
disabled={replySettingsDisabled}
/>
<Selectable
label={_(msg`Followed users`)}
label={_(msg`Users you follow`)}
isSelected={
!!threadgateAllowUISettings.find(
v => v.type === 'following',
@@ -409,6 +438,16 @@ export function PostInteractionSettingsForm({
onPress={() => onPressAudience({type: 'following'})}
disabled={replySettingsDisabled}
/>
<Selectable
label={_(msg`Your followers`)}
isSelected={
!!threadgateAllowUISettings.find(
v => v.type === 'followers',
)
}
onPress={() => onPressAudience({type: 'followers'})}
disabled={replySettingsDisabled}
/>
{lists && lists.length > 0
? lists.map(list => (
<Selectable
@@ -435,6 +474,7 @@ export function PostInteractionSettingsForm({
</View>
<Button
disabled={!canSave || isSaving}
label={_(msg`Save`)}
onPress={onSave}
color="primary"
@@ -506,7 +506,7 @@ function SearchInput({
autoCapitalize="none"
autoFocus
accessibilityLabel={_(msg`Search profiles`)}
accessibilityHint={_(msg`Search profiles`)}
accessibilityHint={_(msg`Searches for profiles`)}
/>
</View>
)
+2 -2
View File
@@ -47,7 +47,7 @@ export function HostingProvider({
</Text>
<Button
label={toNiceDomain(serviceUrl)}
accessibilityHint={_(msg`Press to change hosting provider`)}
accessibilityHint={_(msg`Changes hosting provider`)}
onPress={onPressSelectService}
variant="ghost"
color="secondary"
@@ -63,7 +63,7 @@ export function HostingProvider({
<Button
testID="selectServiceButton"
label={toNiceDomain(serviceUrl)}
accessibilityHint={_(msg`Press to change hosting provider`)}
accessibilityHint={_(msg`Changes hosting provider`)}
variant="solid"
color="secondary"
style={[
+1 -1
View File
@@ -226,6 +226,7 @@ export function createInput(Component: typeof TextInput) {
<>
<Component
accessibilityHint={undefined}
hitSlop={HITSLOP_20}
{...rest}
accessibilityLabel={label}
ref={refs}
@@ -242,7 +243,6 @@ export function createInput(Component: typeof TextInput) {
placeholder={placeholder || label}
placeholderTextColor={t.palette.contrast_500}
keyboardAppearance={t.name === 'light' ? 'light' : 'dark'}
hitSlop={HITSLOP_20}
style={flattened}
/>
+1
View File
@@ -65,6 +65,7 @@ const locales: Record<AppLanguage, Locale | undefined> = {
gl,
hi,
hu,
ia: undefined,
id,
it,
ja,
+4
View File
@@ -15,3 +15,7 @@ export const ChevronTop_Stroke2_Corner0_Rounded = createSinglePathSVG({
export const ChevronBottom_Stroke2_Corner0_Rounded = createSinglePathSVG({
path: 'M3.293 8.293a1 1 0 0 1 1.414 0L12 15.586l7.293-7.293a1 1 0 1 1 1.414 1.414l-8 8a1 1 0 0 1-1.414 0l-8-8a1 1 0 0 1 0-1.414Z',
})
export const ChevronTopBottom_Stroke2_Corner0_Rounded = createSinglePathSVG({
path: 'M11.293 4.293a1 1 0 0 1 1.414 0l4 4a1 1 0 0 1-1.414 1.414L12 6.414 8.707 9.707a1 1 0 0 1-1.414-1.414l4-4Zm-4 10a1 1 0 0 1 1.414 0L12 17.586l3.293-3.293a1 1 0 0 1 1.414 1.414l-4 4a1 1 0 0 1-1.414 0l-4-4a1 1 0 0 1 0-1.414Z',
})
-5
View File
@@ -1,5 +0,0 @@
import {createSinglePathSVG} from './TEMPLATE'
export const Coffee_Stroke2_Corner0_Rounded = createSinglePathSVG({
path: 'M7 2a1 1 0 0 1 1 1v2a1 1 0 0 1-2 0V3a1 1 0 0 1 1-1Zm4 0a1 1 0 0 1 1 1v2a1 1 0 1 1-2 0V3a1 1 0 0 1 1-1Zm4 0a1 1 0 0 1 1 1v2a1 1 0 1 1-2 0V3a1 1 0 0 1 1-1ZM4 9a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2h.5a3.5 3.5 0 1 1 0 7H18v4a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V9Zm12 0H6v11h10V9Zm2 5h.5a1.5 1.5 0 0 0 0-3H18v3Z',
})
-5
View File
@@ -1,5 +0,0 @@
import {createSinglePathSVG} from './TEMPLATE'
export const PiggyBank_Stroke2_Corner0_Rounded = createSinglePathSVG({
path: 'M6.5 4.999Zm0 0a.054.054 0 0 1-.016.024.196.196 0 0 1-.152.043c.057.01.113.02.168.032V7.5a1 1 0 0 1-.33.742c-.543.49-.917 1.176-1.23 2.036a1 1 0 0 1-.94.657H3V14h1a1 1 0 0 1 .866.5c.436.754.879 1.195 1.637 1.636A1 1 0 0 1 7 17v2h2a2 2 0 0 1 2-2h2a2 2 0 0 1 2 2h2v-2.537a1 1 0 0 1 .332-.744A4.978 4.978 0 0 0 19 11.999 5 5 0 0 0 14 7H9.474a1 1 0 0 1-.893-.55v-.001l-.001-.002-.005-.008L9.474 6l-.9.438h.001v.002l.002.001.001.003.002.003v.003a2.62 2.62 0 0 0-.443-.538c-.319-.298-.839-.648-1.637-.814V5Zm2.082 1.452ZM9.5 4.447c.211.196.379.387.508.553H14a7 7 0 0 1 6.72 8.96 1.003 1.003 0 0 0 1.146-1.46 1 1 0 1 1 1.731-1 3 3 0 0 1-3.714 4.286A7.074 7.074 0 0 1 19 16.888V19a2 2 0 0 1-2 2h-2a2 2 0 0 1-2-2h-2a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2v-1.45A5.783 5.783 0 0 1 3.448 16H3a2 2 0 0 1-2-2v-3.065a2 2 0 0 1 2-2h.324c.286-.649.657-1.291 1.176-1.852V5c0-1.047.89-2.12 2.161-1.907 1.33.223 2.248.805 2.839 1.354ZM8.25 12a1.25 1.25 0 1 0 0-2.5 1.25 1.25 0 0 0 0 2.5Z',
})
-5
View File
@@ -1,5 +0,0 @@
import {createSinglePathSVG} from './TEMPLATE'
export const Poop_Stroke2_Corner0_Rounded = createSinglePathSVG({
path: 'M12 4c0-1.012.894-2.187 2.237-1.846a5.002 5.002 0 0 1 3.218 7.119 4.001 4.001 0 0 1 2.345 4.976A3.501 3.501 0 0 1 18.5 21h-13a3.5 3.5 0 0 1-1.3-6.75 4 4 0 0 1 2.052-4.848A4 4 0 0 1 10 4h2.001Zm-4.187 7.009A2 2 0 0 0 6 13c0 .366.098.706.271 1H12a1 1 0 1 1 0 2H5.5a1.5 1.5 0 0 0 0 3h13a1.5 1.5 0 1 0 0-3H17a1 1 0 1 1 0-2h.729c.173-.294.271-.634.271-1a2 2 0 0 0-2-2h-2a1 1 0 1 1 0-2h1.236a3.002 3.002 0 0 0-1.243-4.832A2 2 0 0 1 12 6h-2a2 2 0 0 0-1.728 3.009H9a1 1 0 0 1 0 2H7.813Z',
})
+3 -3
View File
@@ -146,10 +146,10 @@ function ContentHiderActive({
label={desc.name}
accessibilityHint={
modui.noOverride
? _(msg`Learn more about the moderation applied to this content.`)
? _(msg`Learn more about the moderation applied to this content`)
: override
? _(msg`Hide the content`)
: _(msg`Show the content`)
? _(msg`Hides the content`)
: _(msg`Shows the content`)
}>
{state => (
<View
+1 -1
View File
@@ -76,7 +76,7 @@ export function PostHider({
}}
accessibilityRole="button"
accessibilityHint={
override ? _(msg`Hide the content`) : _(msg`Show the content`)
override ? _(msg`Hides the content`) : _(msg`Shows the content`)
}
accessibilityLabel=""
style={[
+1
View File
@@ -12,6 +12,7 @@ export type CommonNavigatorParams = {
ModerationModlists: undefined
ModerationMutedAccounts: undefined
ModerationBlockedAccounts: undefined
ModerationInteractionSettings: undefined
Settings: undefined
Profile: {name: string; hideBackButton?: boolean}
ProfileFollowers: {name: string}
+16
View File
@@ -25,3 +25,19 @@ export async function shareUrl(url: string) {
Toast.show(t`Copied to clipboard`, 'clipboard-check')
}
}
/**
* This function shares a text using the native Share API if available, or copies it to the clipboard
* and displays a toast message if not (mostly on web)
*
* @param {string} text - A string representing the text that needs to be shared or copied to the
* clipboard.
*/
export async function shareText(text: string) {
if (isAndroid || isIOS) {
await Share.share({message: text})
} else {
await setStringAsync(text)
Toast.show(t`Copied to clipboard`, 'clipboard-check')
}
}
+1 -2
View File
@@ -2,9 +2,8 @@ export type Gate =
// Keep this alphabetic please.
| 'debug_show_feedcontext'
| 'debug_subscriptions'
| 'new_postonboarding'
| 'old_postonboarding'
| 'onboarding_add_video_feed'
| 'remove_show_latest_button'
| 'test_gate_1'
| 'test_gate_2'
| 'trending_topics_beta'
+3 -4
View File
@@ -42,10 +42,9 @@ export interface IsValidHandle {
}
// More checks from https://github.com/bluesky-social/atproto/blob/main/packages/pds/src/handle/index.ts#L72
export function validateHandle(
export function validateServiceHandle(
str: string,
userDomain: string,
isServiceHandle?: boolean,
): IsValidHandle {
const fullHandle = createFullHandle(str, userDomain)
@@ -53,8 +52,8 @@ export function validateHandle(
handleChars:
!str || (VALIDATE_REGEX.test(fullHandle) && !str.includes('.')),
hyphenStartOrEnd: !str.startsWith('-') && !str.endsWith('-'),
frontLength: str.length >= 3,
totalLength: fullHandle.length <= (isServiceHandle ? 30 : 253),
frontLength: str.length >= 3 && str.length <= 18,
totalLength: fullHandle.length <= 253,
}
return {
+2
View File
@@ -185,6 +185,8 @@ export function sanitizeAppLanguageSetting(appLanguage: string): AppLanguage {
return AppLanguage.hi
case 'hu':
return AppLanguage.hu
case 'ia':
return AppLanguage.ia
case 'id':
return AppLanguage.id
case 'it':
+9
View File
@@ -27,6 +27,7 @@ import {messages as messagesGa} from '#/locale/locales/ga/messages'
import {messages as messagesGl} from '#/locale/locales/gl/messages'
import {messages as messagesHi} from '#/locale/locales/hi/messages'
import {messages as messagesHu} from '#/locale/locales/hu/messages'
import {messages as messagesIa} from '#/locale/locales/ia/messages'
import {messages as messagesId} from '#/locale/locales/id/messages'
import {messages as messagesIt} from '#/locale/locales/it/messages'
import {messages as messagesJa} from '#/locale/locales/ja/messages'
@@ -173,6 +174,14 @@ export async function dynamicActivate(locale: AppLanguage) {
])
break
}
case AppLanguage.id: {
i18n.loadAndActivate({locale, messages: messagesIa})
await Promise.all([
import('@formatjs/intl-pluralrules/locale-data/ia'),
import('@formatjs/intl-numberformat/locale-data/ia'),
])
break
}
case AppLanguage.id: {
i18n.loadAndActivate({locale, messages: messagesId})
await Promise.all([
+4
View File
@@ -72,6 +72,10 @@ export async function dynamicActivate(locale: AppLanguage) {
mod = await import(`./locales/hu/messages`)
break
}
case AppLanguage.ia: {
mod = await import(`./locales/ia/messages`)
break
}
case AppLanguage.id: {
mod = await import(`./locales/id/messages`)
break
+13 -19
View File
@@ -21,6 +21,7 @@ export enum AppLanguage {
gl = 'gl',
hi = 'hi',
hu = 'hu',
ia = 'ia',
id = 'id',
it = 'it',
ja = 'ja',
@@ -64,6 +65,7 @@ export const APP_LANGUAGES: AppLanguageConfig[] = [
{code2: AppLanguage.gl, name: 'Galego Galician'},
{code2: AppLanguage.hi, name: 'हिंदी Hindi'},
{code2: AppLanguage.hu, name: 'magyar Hungarian'},
{code2: AppLanguage.ia, name: 'Interlingua'},
{code2: AppLanguage.id, name: 'Bahasa Indonesia Indonesian'},
{code2: AppLanguage.it, name: 'Italiano Italian'},
{code2: AppLanguage.ja, name: '日本語 Japanese'},
@@ -177,11 +179,7 @@ export const LANGUAGES: Language[] = [
{code3: 'cho', code2: '', name: 'Choctaw'},
{code3: 'chp', code2: '', name: 'Chipewyan; Dene Suline'},
{code3: 'chr', code2: '', name: 'Cherokee'},
{
code3: 'chu',
code2: 'cu',
name: 'Church Slavic; Old Slavonic; Church Slavonic; Old Bulgarian; Old Church Slavonic',
},
{code3: 'chu', code2: 'cu', name: 'Church Slavic'},
{code3: 'chv', code2: 'cv', name: 'Chuvash'},
{code3: 'chy', code2: '', name: 'Cheyenne'},
{code3: 'cmc', code2: '', name: 'Chamic languages'},
@@ -299,13 +297,9 @@ export const LANGUAGES: Language[] = [
{code3: 'iii', code2: 'ii', name: 'Sichuan Yi; Nuosu'},
{code3: 'ijo', code2: '', name: 'Ijo languages'},
{code3: 'iku', code2: 'iu', name: 'Inuktitut'},
{code3: 'ile', code2: 'ie', name: 'Interlingue; Occidental'},
{code3: 'ile', code2: 'ie', name: 'Interlingue'},
{code3: 'ilo', code2: '', name: 'Iloko'},
{
code3: 'ina',
code2: 'ia',
name: 'Interlingua (International Auxiliary Language Association)',
},
{code3: 'ina', code2: 'ia', name: 'Interlingua'},
{code3: 'inc', code2: '', name: 'Indic languages'},
{code3: 'ind', code2: 'id', name: 'Indonesian'},
{code3: 'ine', code2: '', name: 'Indo-European languages'},
@@ -323,7 +317,7 @@ export const LANGUAGES: Language[] = [
{code3: 'kaa', code2: '', name: 'Kara-Kalpak'},
{code3: 'kab', code2: '', name: 'Kabyle'},
{code3: 'kac', code2: '', name: 'Kachin; Jingpho'},
{code3: 'kal', code2: 'kl', name: 'Kalaallisut; Greenlandic'},
{code3: 'kal', code2: 'kl', name: 'Kalaallisut'},
{code3: 'kam', code2: '', name: 'Kamba'},
{code3: 'kan', code2: 'kn', name: 'Kannada'},
{code3: 'kar', code2: '', name: 'Karen languages'},
@@ -362,7 +356,7 @@ export const LANGUAGES: Language[] = [
{code3: 'lat', code2: 'la', name: 'Latin'},
{code3: 'lav', code2: 'lv', name: 'Latvian'},
{code3: 'lez', code2: '', name: 'Lezghian'},
{code3: 'lim', code2: 'li', name: 'Limburgan; Limburger; Limburgish'},
{code3: 'lim', code2: 'li', name: 'Limburgish'},
{code3: 'lin', code2: 'ln', name: 'Lingala'},
{code3: 'lit', code2: 'lt', name: 'Lithuanian'},
{code3: 'lol', code2: '', name: 'Mongo'},
@@ -423,9 +417,9 @@ export const LANGUAGES: Language[] = [
{code3: 'nai', code2: '', name: 'North American Indian languages'},
{code3: 'nap', code2: '', name: 'Neapolitan'},
{code3: 'nau', code2: 'na', name: 'Nauru'},
{code3: 'nav', code2: 'nv', name: 'Navajo; Navaho'},
{code3: 'nbl', code2: 'nr', name: 'Ndebele, South; South Ndebele'},
{code3: 'nde', code2: 'nd', name: 'Ndebele, North; North Ndebele'},
{code3: 'nav', code2: 'nv', name: 'Navajo'},
{code3: 'nbl', code2: 'nr', name: 'South Ndebele'},
{code3: 'nde', code2: 'nd', name: 'North Ndebele'},
{code3: 'ndo', code2: 'ng', name: 'Ndonga'},
{
code3: 'nds',
@@ -438,8 +432,8 @@ export const LANGUAGES: Language[] = [
{code3: 'nic', code2: '', name: 'Niger-Kordofanian languages'},
{code3: 'niu', code2: '', name: 'Niuean'},
{code3: 'nld', code2: 'nl', name: 'Dutch; Flemish'},
{code3: 'nno', code2: 'nn', name: 'Norwegian Nynorsk; Nynorsk, Norwegian'},
{code3: 'nob', code2: 'nb', name: 'Bokmål, Norwegian; Norwegian Bokmål'},
{code3: 'nno', code2: 'nn', name: 'Norwegian Nynorsk'},
{code3: 'nob', code2: 'nb', name: 'Norwegian Bokmål'},
{code3: 'nog', code2: '', name: 'Nogai'},
{code3: 'non', code2: '', name: 'Norse, Old'},
{code3: 'nor', code2: 'no', name: 'Norwegian'},
@@ -461,7 +455,7 @@ export const LANGUAGES: Language[] = [
{code3: 'ori', code2: 'or', name: 'Oriya'},
{code3: 'orm', code2: 'om', name: 'Oromo'},
{code3: 'osa', code2: '', name: 'Osage'},
{code3: 'oss', code2: 'os', name: 'Ossetian; Ossetic'},
{code3: 'oss', code2: 'os', name: 'Ossetic'},
{code3: 'ota', code2: '', name: 'Turkish, Ottoman (1500-1928)'},
{code3: 'oto', code2: '', name: 'Otomian languages'},
{code3: 'paa', code2: '', name: 'Papuan languages'},
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+94 -94
View File
@@ -15,7 +15,7 @@ msgstr ""
#: src/components/ProgressGuide/FollowDialog.tsx:567
msgid "(active)"
msgstr ""
msgstr "(सक्रिय)"
#: src/screens/Messages/components/ChatListItem.tsx:130
msgid "(contains embedded content)"
@@ -37,11 +37,11 @@ msgstr "{0, plural, other {# दिन}}"
#: src/screens/Profile/ProfileFollowers.tsx:40
msgid "{0, plural, one {# follower} other {# followers}}"
msgstr ""
msgstr "{0, plural, one {# फ़ॉलोअर} other {# फ़ॉलोअर}}"
#: src/screens/Profile/ProfileFollows.tsx:40
msgid "{0, plural, one {# following} other {# following}}"
msgstr ""
msgstr "{0, plural, one {# फ़ॉलोइंग} other {# फ़ॉलोइंग}}"
#: src/lib/hooks/useTimeAgo.ts:146
msgid "{0, plural, one {# hour} other {# hours}}"
@@ -65,15 +65,15 @@ msgstr "{0, plural, one {# घंटा} other {# घंटे}}"
#: src/components/moderation/LabelsOnMe.tsx:53
msgid "{0, plural, one {# label has} other {# labels have}} been placed on this account"
msgstr ""
msgstr "इस खाते पर {0, plural, one {# लेबल लगाया गया है।} other {# लेबल लगाए गए हैं।}}"
#: src/components/moderation/LabelsOnMe.tsx:62
msgid "{0, plural, one {# label has} other {# labels have}} been placed on this content"
msgstr ""
msgstr "इस सामाग्री पर {0, plural, one {# लेबल लगाया गया है।} other {# लेबल लगाए गए हैं।}}"
#: src/screens/Post/PostLikedBy.tsx:41
msgid "{0, plural, one {# like} other {# likes}}"
msgstr ""
msgstr "{0, plural, one {# पसंद} other {# पसंद}}"
#: src/lib/hooks/useTimeAgo.ts:136
msgid "{0, plural, one {# minute} other {# minutes}}"
@@ -85,7 +85,7 @@ msgstr "{0, plural, one {# महीना} other {# महीने}}"
#: src/screens/Post/PostQuotes.tsx:41
msgid "{0, plural, one {# quote} other {# quotes}}"
msgstr ""
msgstr "{0, plural, one {# क्वोट} other {# क्वोट}}"
#: src/screens/Post/PostRepostedBy.tsx:41
msgid "{0, plural, one {# repost} other {# reposts}}"
@@ -335,7 +335,7 @@ msgstr "{handle} को संदेश भेजा नहीं जा सक
#: src/view/shell/bottom-bar/BottomBarWeb.tsx:260
msgid "{notificationCount} unread items"
msgstr ""
msgstr "{notificationCount} अपठित चीज़ें"
#: src/view/shell/Drawer.tsx:448
msgid "{numUnreadNotifications} unread"
@@ -621,7 +621,7 @@ msgstr "नया पोस्ट जोड़ें"
#: src/view/screens/ProfileList.tsx:903
#: src/view/screens/ProfileList.tsx:921
msgid "Add people"
msgstr ""
msgstr "लोगों को जोड़ें"
#: src/screens/Home/NoFeedsPinned.tsx:99
msgid "Add recommended feeds"
@@ -703,7 +703,7 @@ msgstr "विकसित"
#: src/view/screens/Notifications.tsx:86
msgid "All"
msgstr ""
msgstr "सभी"
#: src/screens/StarterPack/StarterPackScreen.tsx:381
msgid "All accounts have been followed!"
@@ -725,7 +725,7 @@ msgstr "इनसे नए संदेश आने की अनुमति
#: src/components/dialogs/PostInteractionSettingsDialog.tsx:314
msgid "Allow quote posts"
msgstr ""
msgstr "क्वोट पोस्ट की अनुमति दें"
#: src/components/dialogs/PostInteractionSettingsDialog.tsx:359
msgid "Allow replies from:"
@@ -888,7 +888,7 @@ msgstr "कोई भी संपर्क कर सकता है"
#: src/screens/Settings/AppIconSettings/SettingsListItem.tsx:18
#: src/screens/Settings/AppIconSettings/SettingsListItem.tsx:23
msgid "App Icon"
msgstr ""
msgstr "ऐप आइकॉन"
#: src/screens/Settings/LanguageSettings.tsx:80
msgid "App Language"
@@ -1113,7 +1113,7 @@ msgstr "अन्य उपयोगकर्ताओं को संदेश
#: src/screens/Search/components/ExploreTrendingTopics.tsx:74
msgid "BETA"
msgstr ""
msgstr "बीटा"
#: src/components/dialogs/BirthDateSettings.tsx:106
#: src/screens/Settings/AccountSettings.tsx:109
@@ -1252,11 +1252,11 @@ msgstr "Bluesky लॉग आउट उपयोगकर्ताओं को
#: src/screens/Settings/AppIconSettings/index.tsx:99
msgid "Bluesky+"
msgstr ""
msgstr "Bluesky+"
#: src/screens/Settings/AppIconSettings/index.tsx:102
msgid "Bluesky+ icons"
msgstr ""
msgstr "Bluesky+ आइकॉन"
#: src/lib/moderation/useLabelBehaviorDescription.ts:53
msgid "Blur images"
@@ -1298,15 +1298,15 @@ msgstr "अन्य फ़ीड देखें"
#: src/components/TrendingTopics.tsx:176
msgid "Browse posts about {displayName}"
msgstr ""
msgstr "{displayName} के बारे में पोस्ट देखें"
#: src/components/TrendingTopics.tsx:184
msgid "Browse posts tagged with {displayName}"
msgstr ""
msgstr "{displayName} से टैग किए गए पोस्ट देखें"
#: src/components/TrendingTopics.tsx:222
msgid "Browse topic {displayName}"
msgstr ""
msgstr "{displayName} विषय देखें"
#: src/view/screens/Settings/index.tsx:893
#~ msgid "Build version {0} {1}"
@@ -1330,7 +1330,7 @@ msgstr "{0} के द्वारा"
#: src/screens/Profile/components/ProfileFeedHeader.tsx:447
msgid "By <0>{0}</0>"
msgstr ""
msgstr "<0>{0}</0> के द्वार"
#: src/screens/Signup/StepInfo/Policies.tsx:80
#~ msgid "By creating an account you agree to the {els}."
@@ -1463,12 +1463,12 @@ msgstr "बदलें"
#: src/screens/Settings/AppIconSettings/index.tsx:39
msgid "Change app icon"
msgstr ""
msgstr "ऐप आइकॉन बदलें"
#: src/screens/Settings/AppIconSettings/index.tsx:38
#: src/screens/Settings/AppIconSettings/index.tsx:221
msgid "Change app icon to \"{0}\""
msgstr ""
msgstr "ऐप आइकॉन को \"{0}\" में बदलें"
#: src/screens/Settings/AccountSettings.tsx:97
#: src/screens/Settings/AccountSettings.tsx:101
@@ -1600,7 +1600,7 @@ msgstr "इस रंग को अपना अवतार के रूप
#: src/view/screens/Feeds.tsx:728
#: src/view/screens/Search/Explore.tsx:411
msgid "Choose your own timeline! Feeds built by the community help you find content you love."
msgstr ""
msgstr "आपका अपना टाइमलाइन चुनें! समुदाय द्वारा बनाए गए फ़ीड आपको पसंदीदा सामग्री ढूँढने में सहायता करते हैं।"
#: src/screens/Signup/StepInfo/index.tsx:201
msgid "Choose your password"
@@ -1696,7 +1696,7 @@ msgstr "डायलॉग बंद करें"
#: src/view/com/composer/text-input/web/EmojiPicker.web.tsx:137
#: src/view/com/composer/text-input/web/EmojiPicker.web.tsx:173
msgid "Close emoji picker"
msgstr ""
msgstr "इमोजी चयन बंद करें"
#: src/components/dialogs/GifSelect.tsx:169
msgid "Close GIF dialog"
@@ -1859,7 +1859,7 @@ msgstr "सहायता से संपर्क करें"
#: src/screens/Settings/ContentAndMediaSettings.tsx:49
msgid "Content & Media"
msgstr ""
msgstr "सामाग्री और मीडिया"
#: src/screens/Settings/AccessibilitySettings.tsx:109
#: src/screens/Settings/Settings.tsx:175
@@ -2149,7 +2149,7 @@ msgstr "{0} बनाया गया"
#: src/screens/List/ListHiddenScreen.tsx:127
msgid "Creator has been blocked"
msgstr ""
msgstr "रचयिता को अवरुद्ध किया गया"
#: src/screens/Onboarding/index.tsx:26
#: src/screens/Onboarding/state.ts:99
@@ -2227,7 +2227,7 @@ msgstr "डिफ़ॉल्‍ट"
#: src/screens/Settings/AppIconSettings/index.tsx:75
msgid "Default icons"
msgstr ""
msgstr "डिफ़ॉल्‍ट आइकॉन"
#: src/components/dms/MessageMenu.tsx:151
#: src/screens/Settings/AppPasswords.tsx:212
@@ -2713,7 +2713,7 @@ msgstr "शिक्षा"
#: src/screens/List/ListHiddenScreen.tsx:141
msgid "Either the creator of this list has blocked you or you have blocked the creator."
msgstr ""
msgstr "इस सूची के रचयिता ने आपको अवरुद्ध किया है या आपने रचयिता को अवरुद्ध किया है।"
#: src/screens/Settings/AccountSettings.tsx:60
#: src/screens/Signup/StepInfo/index.tsx:170
@@ -2774,7 +2774,7 @@ msgstr "इस पोस्ट को अपने वेबसाइट मे
#: src/view/com/util/post-embeds/VideoEmbedInner/VideoEmbedInnerWeb.tsx:57
msgid "Embedded video player"
msgstr ""
msgstr "एम्बेडेड वीडियो प्लेयर"
#: src/screens/Settings/components/Email2FAToggle.tsx:56
#: src/screens/Settings/components/Email2FAToggle.tsx:60
@@ -2822,7 +2822,7 @@ msgstr "केवल इस सूत्र को सक्षम करें"
#: src/screens/Settings/ContentAndMediaSettings.tsx:122
#: src/screens/Settings/ContentAndMediaSettings.tsx:136
msgid "Enable trending topics"
msgstr ""
msgstr "रुझान सक्षम करें"
#: src/screens/Messages/Settings.tsx:130
#: src/screens/Messages/Settings.tsx:133
@@ -2861,7 +2861,7 @@ msgstr "पुष्टिकरण कोड दर्ज करें"
#: src/view/com/util/post-embeds/VideoEmbedInner/web-controls/VideoControls.tsx:405
msgid "Enter fullscreen"
msgstr ""
msgstr "फ़ुलस्क्रीन में जाएँ"
#: src/view/com/modals/ChangePassword.tsx:154
msgid "Enter the code you received to change your password."
@@ -3166,7 +3166,7 @@ msgstr "{0} द्वारा फ़ीड"
#: src/screens/Profile/components/ProfileFeedHeader.tsx:353
msgid "Feed menu"
msgstr ""
msgstr "फ़ीड मेनू"
#: src/view/screens/Feeds.tsx:709
#~ msgid "Feed offline"
@@ -3217,7 +3217,7 @@ msgstr "फ़ीड अपडेट की गई!"
#: src/screens/Search/components/ExploreRecommendations.tsx:54
msgid "Feeds we think you might like."
msgstr ""
msgstr "फ़ीड जो हमें लगता है आपको पसंद आए।"
#: src/view/com/modals/ChangeHandle.tsx:468
#~ msgid "File Contents"
@@ -3245,7 +3245,7 @@ msgstr "फ़ॉलो करने के लिए खाते खोजे
#: src/components/ProgressGuide/FollowDialog.tsx:88
#: src/components/ProgressGuide/FollowDialog.tsx:417
msgid "Find people to follow"
msgstr ""
msgstr "फ़ॉलो करने के लिए लोग खोजें"
#: src/view/screens/Search/Search.tsx:595
msgid "Find posts and users on Bluesky"
@@ -3305,7 +3305,7 @@ msgstr "{name} को फ़ॉलो करें"
#: src/components/ProgressGuide/List.tsx:52
#: src/state/shell/progress-guide.tsx:229
msgid "Follow 10 accounts"
msgstr ""
msgstr "10 खाते फ़ॉलो करें"
#: src/components/ProgressGuide/List.tsx:69
msgid "Follow 7 accounts"
@@ -3732,13 +3732,13 @@ msgstr "यह पोस्ट छिपाएँ?"
#: src/screens/Search/components/ExploreTrendingTopics.tsx:83
#: src/view/shell/desktop/SidebarTrendingTopics.tsx:62
msgid "Hide trending topics"
msgstr ""
msgstr "रुझान छिपाएँ"
#: src/components/interstitials/Trending.tsx:99
#: src/screens/Search/components/ExploreTrendingTopics.tsx:135
#: src/view/shell/desktop/SidebarTrendingTopics.tsx:108
msgid "Hide trending topics?"
msgstr ""
msgstr "रुझान छिपाएँ?"
#: src/view/com/notifications/NotificationFeedItem.tsx:592
msgid "Hide user list"
@@ -3806,7 +3806,7 @@ msgstr "होस्टिंग प्रदाता"
#: src/view/com/post-thread/PostThread.tsx:652
#: src/view/com/post-thread/PostThread.tsx:657
msgid "Hot replies first"
msgstr ""
msgstr "गर्मागर्म जवाब पहले"
#: src/view/com/modals/InAppBrowserConsent.tsx:41
msgid "How should we open this link?"
@@ -3859,7 +3859,7 @@ msgstr "यदि आप इस सूची को मिटते हैं,
#: src/screens/Settings/components/ChangeHandleDialog.tsx:250
msgid "If you have your own domain, you can use that as your handle. This lets you self-verify your identity. <0>Learn more here.</0>"
msgstr ""
msgstr "यदि आपका अपना डोमेन है, आप उसे अपने हैंडल के तौर पर उपयोग कर सकते हैं। इसे आप ख़ुद अपने पहचान की पुष्टि कर सकते हैं। <0>यहाँ अधिक जानें।</0>"
#: src/view/com/util/forms/PostDropdownBtnMenuItems.tsx:658
msgid "If you remove this post, you won't be able to recover it."
@@ -4216,11 +4216,11 @@ msgstr "रोशन"
#: src/screens/Profile/components/ProfileFeedHeader.tsx:514
msgid "Like"
msgstr ""
msgstr "पसंद"
#: src/view/com/util/post-ctrls/PostCtrls.tsx:311
msgid "Like ({0, plural, one {# like} other {# likes}})"
msgstr ""
msgstr "पसंद करें ({0, plural, one {# पसंद} other {# पसंद}})"
#: src/components/ProgressGuide/List.tsx:63
msgid "Like 10 posts"
@@ -4233,7 +4233,7 @@ msgstr "डिस्कवर फ़ीड को प्रशिक्षित
#: src/screens/Profile/components/ProfileFeedHeader.tsx:501
msgid "Like feed"
msgstr ""
msgstr "फ़ीड पसंद करें"
#: src/screens/Profile/Header/ProfileHeaderLabeler.tsx:275
msgid "Like this feed"
@@ -4254,14 +4254,14 @@ msgstr "इन्होनें पसंद किया"
#: src/components/FeedCard.tsx:213
#: src/view/com/feeds/FeedSourceCard.tsx:303
msgid "Liked by {0, plural, one {# user} other {# users}}"
msgstr ""
msgstr "{0, plural, one {# उपयोगकर्ता} other {# उपयोगकर्ताओं}} ने पसंद किया"
#: src/components/LabelingServiceCard/index.tsx:96
#: src/screens/Profile/components/ProfileFeedHeader.tsx:489
#: src/screens/Profile/Header/ProfileHeaderLabeler.tsx:295
#: src/screens/Profile/Header/ProfileHeaderLabeler.tsx:309
msgid "Liked by {likeCount, plural, one {# user} other {# users}}"
msgstr ""
msgstr "{likeCount, plural, one {# उपयोगकर्ता} other {# उपयोगकर्ताओं}} ने पसंद किया"
#: src/view/com/notifications/FeedItem.tsx:211
#~ msgid "liked your custom feed"
@@ -4282,7 +4282,7 @@ msgstr "इस पोस्ट पर पसंद"
#: src/view/com/post-thread/PostThread.tsx:626
#: src/view/com/post-thread/PostThread.tsx:631
msgid "Linear"
msgstr ""
msgstr "रेखीय"
#: src/Navigation.tsx:198
msgid "List"
@@ -4303,11 +4303,11 @@ msgstr "{0} द्वारा सूची"
#: src/view/com/profile/ProfileSubpageHeader.tsx:156
msgid "List by <0/>"
msgstr ""
msgstr "<0/> द्वारा सूची"
#: src/view/com/profile/ProfileSubpageHeader.tsx:154
msgid "List by you"
msgstr ""
msgstr "आपके द्वारा सूची"
#: src/view/screens/ProfileList.tsx:455
msgid "List deleted"
@@ -4412,7 +4412,7 @@ msgstr "उस खाते में लॉग इन करें जो स
#: src/view/shell/desktop/RightNav.tsx:121
msgid "Logo by @sawaratsuki.bsky.social"
msgstr ""
msgstr "@sawaratsuki.bsky.social द्वारा लोगो"
#: src/view/shell/desktop/RightNav.tsx:103
#~ msgid "Logo by <0/>"
@@ -4483,7 +4483,7 @@ msgstr "उल्लेखित उपयोगकर्ता"
#: src/view/screens/Notifications.tsx:99
msgid "Mentions"
msgstr ""
msgstr "उल्लेख"
#: src/components/Menu/index.tsx:96
#: src/view/screens/Search/Search.tsx:863
@@ -4837,7 +4837,7 @@ msgstr "नया हैंडल"
#: src/view/screens/Lists.tsx:66
#: src/view/screens/ModerationModlists.tsx:66
msgid "New list"
msgstr ""
msgstr "नई सूची"
#: src/components/dms/NewMessagesPill.tsx:92
msgid "New messages"
@@ -5241,7 +5241,7 @@ msgstr "बातचीत विकल्प खोलें"
#: src/components/Layout/Header/index.tsx:145
msgid "Open drawer menu"
msgstr ""
msgstr "ड्रॉअर मेनू खोलें"
#: src/screens/Messages/components/MessageInput.web.tsx:181
#: src/view/com/composer/Composer.tsx:1228
@@ -5251,7 +5251,7 @@ msgstr "इमोजी चयन खोलें"
#: src/screens/Profile/components/ProfileFeedHeader.tsx:190
msgid "Open feed info screen"
msgstr ""
msgstr "फ़ीड जानकारी स्क्रीन खोलें"
#: src/screens/Profile/components/ProfileFeedHeader.tsx:290
#: src/screens/Profile/components/ProfileFeedHeader.tsx:295
@@ -5582,7 +5582,7 @@ msgstr "वयस्कों के लिए छवि।"
#: src/screens/Profile/components/ProfileFeedHeader.tsx:519
#: src/screens/Profile/components/ProfileFeedHeader.tsx:526
msgid "Pin feed"
msgstr ""
msgstr "फ़ीड पिन करें"
#: src/view/screens/ProfileList.tsx:685
msgid "Pin to home"
@@ -5604,7 +5604,7 @@ msgstr "पिन किया गया"
#: src/screens/Profile/components/ProfileFeedHeader.tsx:162
#: src/screens/Profile/components/ProfileFeedHeader.tsx:174
msgid "Pinned {0} to Home"
msgstr ""
msgstr "होम में {0} पिन किया गया"
#: src/view/screens/SavedFeeds.tsx:123
msgid "Pinned Feeds"
@@ -5912,11 +5912,11 @@ msgstr "सार्वजनिक"
#: src/view/com/lists/MyLists.tsx:77
msgid "Public, sharable lists of users to mute or block in bulk."
msgstr ""
msgstr "उपयोगकर्ताओं की सार्वजनिक, साझा योग्य सूचियाँ जिन्हें एक साथ म्यूट या अवरुद्ध किया जा सकता है।"
#: src/view/com/lists/MyLists.tsx:72
msgid "Public, sharable lists which can be used to drive feeds."
msgstr ""
msgstr "सार्वजनिक, साझा योग्य सूचियाँ जो फ़ीड चला सकती हैं।"
#: src/view/screens/ModerationModlists.tsx:75
#~ msgid "Public, shareable lists of users to mute or block in bulk."
@@ -6024,7 +6024,7 @@ msgstr "हाल के खोज"
#: src/screens/Search/components/ExploreRecommendations.tsx:50
msgid "Recommended"
msgstr ""
msgstr "अनुशंसित"
#: src/view/com/auth/onboarding/RecommendedFeeds.tsx:117
#~ msgid "Recommended Feeds"
@@ -6216,7 +6216,7 @@ msgstr "जवाब दें"
#: src/view/com/util/post-ctrls/PostCtrls.tsx:263
msgid "Reply ({0, plural, one {# reply} other {# replies}})"
msgstr ""
msgstr "जवाब दें ({0, plural, one {# जवाब} other {# जवाब}})"
#: src/view/screens/PreferencesFollowingFeed.tsx:142
#~ msgid "Reply Filters"
@@ -6242,7 +6242,7 @@ msgstr "जवाब सेटिंग थ्रेड का लेखक च
#: src/view/com/post-thread/PostThread.tsx:648
msgid "Reply sorting"
msgstr ""
msgstr "जवाब क्रम"
#: src/view/com/post/Post.tsx:204
#: src/view/com/posts/PostFeedItem.tsx:553
@@ -6365,7 +6365,7 @@ msgstr "रीपोस्ट"
#: src/view/com/util/post-ctrls/RepostButton.tsx:74
msgid "Repost ({0, plural, one {# repost} other {# reposts}})"
msgstr ""
msgstr "रीपोस्ट करें ({0, plural, one {# रीपोस्ट} other {# रीपोस्ट}})"
#: src/screens/StarterPack/StarterPackScreen.tsx:548
#: src/view/com/util/post-ctrls/RepostButton.tsx:148
@@ -6637,15 +6637,15 @@ msgstr "खोजें"
#: src/components/ProgressGuide/FollowDialog.tsx:745
msgid "Search by name or interest"
msgstr ""
msgstr "नाम या दिलचस्पी से खोजें"
#: src/view/screens/Feeds.tsx:441
msgid "Search feeds"
msgstr ""
msgstr "फ़ीड खोजें"
#: src/components/ProgressGuide/FollowDialog.tsx:575
msgid "Search for \"{interestsDisplayName}\"{activeText}"
msgstr ""
msgstr "\"{interestsDisplayName}\"{activeText} खोजें"
#: src/view/shell/desktop/Search.tsx:201
msgid "Search for \"{query}\""
@@ -6716,7 +6716,7 @@ msgstr "यह गाइड देखें"
#: src/view/com/util/post-embeds/VideoEmbedInner/web-controls/Scrubber.tsx:194
msgid "Seek slider. Use the arrow keys to seek forwards and backwards, and space to play/pause"
msgstr ""
msgstr "सीक स्लाइडर। एरो की से आगे और पीछे सीक करें, और स्पेस से चालू/बंद करें"
#: src/view/com/util/Selector.tsx:107
#~ msgid "Select {item}"
@@ -6909,7 +6909,7 @@ msgstr "सर्वर पता"
#: src/screens/Settings/AppIconSettings/index.tsx:178
msgid "Set app icon to {0}"
msgstr ""
msgstr "ऐप आइकॉन को {0} करें"
#: src/screens/Moderation/index.tsx:290
msgid "Set birthdate"
@@ -7030,7 +7030,7 @@ msgstr "QR कोड साझा करें"
#: src/screens/Profile/components/ProfileFeedHeader.tsx:470
msgid "Share this feed"
msgstr ""
msgstr "इस फ़ीड को साझा करें"
#: src/screens/StarterPack/StarterPackScreen.tsx:416
msgid "Share this starter pack"
@@ -7129,11 +7129,11 @@ msgstr "जवाब दिखाएँ"
#: src/view/com/post-thread/PostThread.tsx:622
msgid "Show replies as"
msgstr ""
msgstr "जवाबों को ऐसे दिखाएँ"
#: src/screens/Settings/ThreadPreferences.tsx:151
msgid "Show replies as threaded"
msgstr ""
msgstr "जवाबों को थ्रेड में दिखाएँ"
#: src/screens/Settings/ThreadPreferences.tsx:126
msgid "Show replies by people you follow before all other replies"
@@ -7334,7 +7334,7 @@ msgstr "कोई गड़बड़ हुई!"
#: src/screens/Profile/components/ProfileFeedHeader.tsx:538
msgid "Something wrong? Let us know."
msgstr ""
msgstr "कुछ गड़बड़ है? हमें बताएँ।"
#: src/App.native.tsx:122
#: src/App.web.tsx:97
@@ -7390,12 +7390,12 @@ msgstr "नया बातचीत शुरू करें"
#: src/view/screens/ProfileList.tsx:817
#: src/view/screens/ProfileList.tsx:938
msgid "Start adding people"
msgstr ""
msgstr "लोगों को जोड़ना शुरू करें"
#: src/view/screens/ProfileList.tsx:824
#: src/view/screens/ProfileList.tsx:945
msgid "Start adding people!"
msgstr ""
msgstr "लोगों को जोड़ना शुरू करें!"
#: src/components/dms/dialogs/SearchablePeopleList.tsx:353
msgid "Start chat with {displayName}"
@@ -7413,7 +7413,7 @@ msgstr "{0} द्वारा स्टार्टर पैक"
#: src/view/com/profile/ProfileSubpageHeader.tsx:182
msgid "Starter pack by <0/>"
msgstr ""
msgstr "<0/> द्वारा स्टार्टर पैक"
#: src/components/StarterPack/StarterPackCard.tsx:80
#: src/view/com/profile/ProfileSubpageHeader.tsx:180
@@ -7560,12 +7560,12 @@ msgstr "केवल टैग"
#: src/screens/Settings/AppIconSettings/index.tsx:216
msgid "Tap to change app icon"
msgstr ""
msgstr "ऐप आइकॉन बदलने के लिए टैप करें"
#: src/view/com/composer/text-input/web/EmojiPicker.web.tsx:138
#: src/view/com/composer/text-input/web/EmojiPicker.web.tsx:174
msgid "Tap to close the emoji picker"
msgstr ""
msgstr "इमोजी चयन बंद करने के लिए टैप करें"
#: src/components/ProgressGuide/Toast.tsx:156
msgid "Tap to dismiss"
@@ -7590,7 +7590,7 @@ msgstr "पूरी छवि देखने के लिए दबाएँ"
#: src/state/shell/progress-guide.tsx:233
msgid "Task complete - 10 follows!"
msgstr ""
msgstr "कार्य समाप्त - 10 फ़ॉलो!"
#: src/state/shell/progress-guide.tsx:223
msgid "Task complete - 10 likes!"
@@ -7693,7 +7693,7 @@ msgstr "अनअवरुद्ध करने के बाद खाता
#: src/screens/Settings/AppIconSettings/index.tsx:41
#: src/screens/Settings/AppIconSettings/index.tsx:222
msgid "The app will be restarted"
msgstr ""
msgstr "ऐप को फिर से शुरू किया जाएगा"
#: src/components/moderation/ModerationDetailsDialog.tsx:127
#~ msgid "the author"
@@ -8006,7 +8006,7 @@ msgstr "<0>{0}</0> द्वारा बनाई गई इस सूची
#: src/view/screens/ProfileList.tsx:933
msgid "This list is empty."
msgstr ""
msgstr "यह सूची खाली है।"
#: src/screens/Profile/ErrorState.tsx:40
msgid "This moderation service is unavailable. See below for more details. If this issue persists, contact us."
@@ -8105,7 +8105,7 @@ msgstr "यह आपके पोस्ट को इस क्वोट पो
#: src/view/com/post-thread/PostThread.tsx:606
#: src/view/com/post-thread/PostThread.tsx:609
msgid "Thread options"
msgstr ""
msgstr "थ्रेड विकल्प"
#: src/screens/Settings/ContentAndMediaSettings.tsx:66
#: src/screens/Settings/ContentAndMediaSettings.tsx:69
@@ -8119,7 +8119,7 @@ msgstr "थ्रेड प्राथमिकताएँ"
#: src/view/com/post-thread/PostThread.tsx:636
#: src/view/com/post-thread/PostThread.tsx:641
msgid "Threaded"
msgstr ""
msgstr "थ्रेड"
#: src/screens/Settings/ThreadPreferences.tsx:142
msgid "Threaded mode"
@@ -8135,7 +8135,7 @@ msgstr "थ्रेड प्राथमिकताएँ"
#: src/view/com/util/post-embeds/VideoEmbedInner/TimeIndicator.tsx:33
msgid "Time remaining: {time} seconds"
msgstr ""
msgstr "समय बाक़ी: {time} सेकंड"
#: src/screens/Settings/components/DisableEmail2FADialog.tsx:99
msgid "To disable the email 2FA method, please verify your access to the email address."
@@ -8173,7 +8173,7 @@ msgstr "बहतरीन"
#: src/Navigation.tsx:383
msgid "Topic"
msgstr ""
msgstr "विषय"
#: src/view/com/modals/EditImage.tsx:272
#~ msgid "Transformations"
@@ -8191,7 +8191,7 @@ msgstr "अनुवाद करें"
#: src/screens/Search/components/ExploreTrendingTopics.tsx:69
#: src/view/shell/desktop/SidebarTrendingTopics.tsx:59
msgid "Trending"
msgstr ""
msgstr "रुझान"
#: src/view/com/util/error/ErrorScreen.tsx:103
msgctxt "action"
@@ -8285,7 +8285,7 @@ msgstr "रीपोस्ट पूर्ववत करें"
#: src/view/com/util/post-ctrls/RepostButton.tsx:68
msgid "Undo repost ({0, plural, one {# repost} other {# reposts}})"
msgstr ""
msgstr "रीपोस्ट पूर्ववत करें ({0, plural, one {# रीपोस्ट} other {# रीपोस्ट}})"
#: src/view/com/profile/FollowButton.tsx:60
msgctxt "action"
@@ -8307,11 +8307,11 @@ msgstr "खाता अनफ़ॉलो करें"
#: src/screens/Profile/components/ProfileFeedHeader.tsx:514
msgid "Unlike"
msgstr ""
msgstr "नापसंद करें"
#: src/view/com/util/post-ctrls/PostCtrls.tsx:305
msgid "Unlike ({0, plural, one {# like} other {# likes}})"
msgstr ""
msgstr "नापसंद करें ({0, plural, one {# पसंद} other {# पसंद}})"
#: src/view/screens/ProfileFeed.tsx:576
#~ msgid "Unlike this feed"
@@ -8361,7 +8361,7 @@ msgstr "पिन से हटाएँ"
#: src/screens/Profile/components/ProfileFeedHeader.tsx:519
#: src/screens/Profile/components/ProfileFeedHeader.tsx:526
msgid "Unpin feed"
msgstr ""
msgstr "फ़ीड को पिन से हटाएँ"
#: src/screens/Profile/components/ProfileFeedHeader.tsx:309
#: src/screens/Profile/components/ProfileFeedHeader.tsx:311
@@ -8379,7 +8379,7 @@ msgstr "मॉडरेशन सूची को पिन से हटाए
#: src/screens/Profile/components/ProfileFeedHeader.tsx:164
msgid "Unpinned {0} from Home"
msgstr ""
msgstr "{0} को होम पिन से हटाया गया"
#: src/view/screens/ProfileList.tsx:352
msgid "Unpinned from your feeds"
@@ -8404,11 +8404,11 @@ msgstr "सूची की सदस्यता छोड़ी गई"
#: src/view/com/composer/Composer.tsx:766
msgid "Unsupported video type"
msgstr ""
msgstr "असमर्थित वीडियो प्रकार"
#: src/view/com/composer/videos/SelectVideoBtn.tsx:66
msgid "Unsupported video type: {0}"
msgstr ""
msgstr "असमर्थित वीडियो प्रकार: {0}"
#: src/view/com/composer/videos/SelectVideoBtn.tsx:72
#~ msgid "Unsupported video type: {mimeType}"
@@ -8815,7 +8815,7 @@ msgstr "साइट पर जाएँ"
#: src/view/com/util/post-embeds/VideoEmbedInner/web-controls/VolumeControl.tsx:80
msgid "Volume"
msgstr ""
msgstr "वॉल्यूम"
#: src/components/moderation/LabelPreference.tsx:136
#: src/lib/moderation/useLabelBehaviorDescription.ts:17
@@ -8837,7 +8837,7 @@ msgstr "हमें इस हैशटैग के लिए कोई पर
#: src/screens/Topic.tsx:178
msgid "We couldn't find any results for that topic."
msgstr ""
msgstr "उस विषय से जुड़े हमें कोई परिणाम नहीं मिले।"
#: src/screens/Messages/Conversation.tsx:103
msgid "We couldn't load this conversation"
@@ -8951,7 +8951,7 @@ msgstr "आप अपने स्टार्टर पैक को क्य
#: src/screens/Search/components/ExploreTrendingTopics.tsx:79
msgid "What people are posting about."
msgstr ""
msgstr "किसके बारे में लोग पोस्ट कर रहे हैं।"
#: src/view/com/auth/SplashScreen.tsx:38
#: src/view/com/auth/SplashScreen.web.tsx:99
@@ -9114,7 +9114,7 @@ msgstr "आप लॉग इन जारी रखने के लिए अ
#: src/screens/Search/components/ExploreTrendingTopics.tsx:136
#: src/view/shell/desktop/SidebarTrendingTopics.tsx:109
msgid "You can update this later from your settings."
msgstr ""
msgstr "आप इसे बाद में अपने सेटिंग में बदल सकते हैं।"
#: src/view/com/profile/ProfileFollowers.tsx:94
msgid "You do not have any followers."
@@ -9348,7 +9348,7 @@ msgstr "आपने इस पोस्ट में से एक शब्द
#: src/state/shell/progress-guide.tsx:234
msgid "You've found some people to follow"
msgstr ""
msgstr "आपने फ़ॉलो करने के लिए कुछ लोगों को ढूँढा है"
#: src/view/com/posts/FollowingEndOfFeed.tsx:44
msgid "You've reached the end of your feed! Find some more accounts to follow."
@@ -9396,7 +9396,7 @@ msgstr "आपका चयन सहेजा जाएगा, पर सेट
#: src/screens/Settings/components/ChangeHandleDialog.tsx:496
msgid "Your current handle <0>{0}</0> will automatically remain reserved for you. You can switch back to it at any time from this account."
msgstr ""
msgstr "आपका वर्तमान हैंडल <0>{0}</0> आपके लिए आरक्षित रहेगा। आप इस खाते से कभी भी उस हैंडल पर वापस जा सकते हैं।"
#: src/screens/Login/ForgotPasswordForm.tsx:51
#: src/screens/Signup/state.ts:203
File diff suppressed because it is too large Load Diff
+152 -152
View File
@@ -8,8 +8,8 @@ msgid ""
msgstr ""
"Project-Id-Version: Italian localization\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-01-24 00:43+0000\n"
"PO-Revision-Date: 2025-01-24 17:53+0000\n"
"POT-Creation-Date: 2025-02-04 00:43+0000\n"
"PO-Revision-Date: 2025-02-04 07:28+0000\n"
"Last-Translator: Michele Locati <michele@locati.it>, 2025\n"
"Language-Team: Italian (https://app.transifex.com/mlocati/teams/201833/it/)\n"
"Language: it\n"
@@ -23,7 +23,7 @@ msgstr ""
msgid "(active)"
msgstr "(attivo)"
#: src/screens/Messages/components/ChatListItem.tsx:139
#: src/screens/Messages/components/ChatListItem.tsx:145
msgid "(contains embedded content)"
msgstr "(contiene allegati)"
@@ -374,9 +374,9 @@ msgstr "7 giorni"
#: src/screens/Settings/Settings.tsx:215
#: src/screens/Settings/Settings.tsx:218
msgid "About"
msgstr "Informazioni su"
msgstr "Risorse aggiuntive"
#: src/view/screens/Search/Search.tsx:858
#: src/view/screens/Search/Search.tsx:864
msgid "Access navigation links and settings"
msgstr "Accedi alle impostazioni di navigazione"
@@ -728,10 +728,10 @@ msgstr "Si è verificato un problema nell'aprire la chat"
#: src/components/hooks/useFollowMethods.ts:35
#: src/components/hooks/useFollowMethods.ts:50
#: src/components/ProfileCard.tsx:330
#: src/components/ProfileCard.tsx:351
#: src/view/com/profile/FollowButton.tsx:35
#: src/view/com/profile/FollowButton.tsx:45
#: src/components/ProfileCard.tsx:333
#: src/components/ProfileCard.tsx:354
#: src/view/com/profile/FollowButton.tsx:38
#: src/view/com/profile/FollowButton.tsx:48
msgid "An issue occurred, please try again."
msgstr "Si è verificato un problema, riprova."
@@ -928,7 +928,7 @@ msgstr "Le opzioni di riproduzione automatica sono state spostate nelle <0>impos
msgid "Autoplay videos and GIFs"
msgstr "Riproduci automaticamente video e GIF"
#: src/components/dms/MessagesListHeader.tsx:74
#: src/components/dms/MessagesListHeader.tsx:81
#: src/components/moderation/LabelsOnMeDialog.tsx:290
#: src/components/moderation/LabelsOnMeDialog.tsx:291
#: src/screens/Login/ChooseAccountForm.tsx:90
@@ -962,7 +962,7 @@ msgstr "Prima di creare uno starter pack, devi verificare la tua email."
#: src/components/dms/dialogs/NewChatDialog.tsx:79
#: src/components/dms/MessageProfileButton.tsx:89
#: src/screens/Messages/Conversation.tsx:209
#: src/screens/Messages/Conversation.tsx:204
msgid "Before you may message another user, you must first verify your email."
msgstr "Prima di poter inviare un messaggio a un altro utente, devi verificare la tua email."
@@ -971,7 +971,7 @@ msgstr "Prima di poter inviare un messaggio a un altro utente, devi verificare l
msgid "BETA"
msgstr "BETA"
#: src/components/dialogs/BirthDateSettings.tsx:106
#: src/components/dialogs/BirthDateSettings.tsx:102
#: src/screens/Settings/AccountSettings.tsx:109
msgid "Birthday"
msgstr "Compleanno"
@@ -982,8 +982,8 @@ msgstr "Compleanno"
msgid "Block"
msgstr "Blocca"
#: src/components/dms/ConvoMenu.tsx:191
#: src/components/dms/ConvoMenu.tsx:195
#: src/components/dms/ConvoMenu.tsx:250
#: src/components/dms/ConvoMenu.tsx:253
#: src/view/com/util/forms/PostDropdownBtnMenuItems.tsx:603
#: src/view/com/util/forms/PostDropdownBtnMenuItems.tsx:605
msgid "Block account"
@@ -1070,8 +1070,8 @@ msgstr "Il blocco non impedirà l'applicazione delle etichette al tuo account, m
msgid "Blog"
msgstr "Blog"
#: src/view/com/auth/server-input/index.tsx:92
#: src/view/com/auth/server-input/index.tsx:94
#: src/view/com/auth/server-input/index.tsx:132
#: src/view/com/auth/server-input/index.tsx:133
msgid "Bluesky"
msgstr "Bluesky"
@@ -1079,13 +1079,13 @@ msgstr "Bluesky"
msgid "Bluesky cannot confirm the authenticity of the claimed date."
msgstr "Bluesky non può confermare l'autenticità della data dichiarata."
#: src/view/com/auth/server-input/index.tsx:172
#: src/view/com/auth/server-input/index.tsx:208
msgid "Bluesky is an open network where you can choose your hosting provider. If you're a developer, you can host your own server."
msgstr "Bluesky è una rete aperta dove puoi scegliere il fornitore che ospita i tuoi dati. Se sei uno sviluppatore, puoi utilizzare il tuo server."
#: src/view/com/auth/server-input/index.tsx:109
#: src/view/com/auth/server-input/index.tsx:145
msgid "Bluesky is an open network where you can choose your own provider. If you're new here, we recommend sticking with the default Bluesky Social option."
msgstr "Bluesky è una rete aperta che ti permette di scegliere il fornitore dei tuoi dati. Se sei un nuovo utente, ti consigliamo di iniziare con l'opzione predefinita Bluesky Social."
msgstr "Bluesky è una rete aperta che ti permette di scegliere chi ospita i tuoi dati. Se sei un nuovo utente, ti consigliamo di iniziare con l'opzione predefinita Bluesky Social."
#: src/components/ProgressGuide/List.tsx:53
#: src/components/ProgressGuide/List.tsx:70
@@ -1094,7 +1094,7 @@ msgstr "Bluesky è meglio con gli amici!"
#: src/screens/Takendown.tsx:217
msgid "Bluesky Social Terms of Service"
msgstr "Termini di servizio di Bluesky Social"
msgstr "Termini di servizio di Bluesky"
#: src/components/StarterPack/ProfileStarterPacks.tsx:299
msgid "Bluesky will choose a set of recommended accounts from people in your network."
@@ -1125,23 +1125,23 @@ msgstr "Sfoca le immagini e filtra dai feed"
msgid "Books"
msgstr "Libri"
#: src/components/FeedInterstitials.tsx:349
#: src/components/FeedInterstitials.tsx:370
msgid "Browse more accounts on the Explore page"
msgstr "Scopri altri account dalla Ricerca"
#: src/components/FeedInterstitials.tsx:486
#: src/components/FeedInterstitials.tsx:507
msgid "Browse more feeds on the Explore page"
msgstr "Scopri nuovi feed dalla Ricerca"
#: src/components/FeedInterstitials.tsx:330
#: src/components/FeedInterstitials.tsx:333
#: src/components/FeedInterstitials.tsx:467
#: src/components/FeedInterstitials.tsx:470
#: src/components/FeedInterstitials.tsx:351
#: src/components/FeedInterstitials.tsx:354
#: src/components/FeedInterstitials.tsx:488
#: src/components/FeedInterstitials.tsx:491
msgid "Browse more suggestions"
msgstr "Scopri nuovi suggerimenti"
#: src/components/FeedInterstitials.tsx:357
#: src/components/FeedInterstitials.tsx:495
#: src/components/FeedInterstitials.tsx:378
#: src/components/FeedInterstitials.tsx:516
msgid "Browse more suggestions on the Explore page"
msgstr "Scopri nuovi suggerimenti dalla Ricerca"
@@ -1218,7 +1218,7 @@ msgstr "Fotocamera"
#: src/view/com/modals/VerifyEmail.tsx:255
#: src/view/com/modals/VerifyEmail.tsx:261
#: src/view/com/util/post-ctrls/RepostButton.tsx:213
#: src/view/screens/Search/Search.tsx:887
#: src/view/screens/Search/Search.tsx:893
#: src/view/shell/desktop/LeftNav.tsx:209
msgid "Cancel"
msgstr "Annulla"
@@ -1252,7 +1252,7 @@ msgid "Cancel reactivation and log out"
msgstr "Annulla la riattivazione e disconnettiti"
#: src/view/com/modals/ListAddRemoveUsers.tsx:88
#: src/view/screens/Search/Search.tsx:879
#: src/view/screens/Search/Search.tsx:885
msgid "Cancel search"
msgstr "Annulla la ricerca"
@@ -1331,14 +1331,14 @@ msgstr "Modifica il tuo indirizzo email"
msgid "Chat"
msgstr "Messaggi"
#: src/components/dms/ConvoMenu.tsx:85
#: src/components/dms/ConvoMenu.tsx:179
msgid "Chat muted"
msgstr "Conversazione silenziata"
#: src/components/dms/ConvoMenu.tsx:115
#: src/components/dms/ConvoMenu.tsx:78
#: src/components/dms/MessageMenu.tsx:81
#: src/Navigation.tsx:394
#: src/screens/Messages/ChatList.tsx:254
#: src/screens/Messages/ChatList.tsx:253
msgid "Chat settings"
msgstr "Impostazioni chat"
@@ -1346,7 +1346,7 @@ msgstr "Impostazioni chat"
msgid "Chat Settings"
msgstr "Impostazioni chat"
#: src/components/dms/ConvoMenu.tsx:87
#: src/components/dms/ConvoMenu.tsx:181
msgid "Chat unmuted"
msgstr "Conversazione riattivata"
@@ -1391,12 +1391,12 @@ msgstr "Scegli gli algoritmi che compilano i tuoi feed personalizzati."
msgid "Choose this color as your avatar"
msgstr "Scegli questo colore per il tuo avatar"
#: src/view/com/auth/server-input/index.tsx:86
#: src/view/com/auth/server-input/index.tsx:126
msgid "Choose your account provider"
msgstr "Seleziona il fornitore del tuo account"
msgstr "Scegli il fornitore del tuo account"
#: src/view/screens/Feeds.tsx:728
#: src/view/screens/Search/Explore.tsx:424
#: src/view/screens/Search/Explore.tsx:427
msgid "Choose your own timeline! Feeds built by the community help you find content you love."
msgstr "Personalizza la tua cronologia! I feed creati dalla collettività ti permettono di scoprire i contenuti di tuo interesse."
@@ -1547,7 +1547,7 @@ msgstr "Linee guida della community"
#: src/screens/Onboarding/StepFinished.tsx:300
msgid "Complete onboarding and start using your account"
msgstr "Completa l'incorporazione e inizia a usare il tuo account"
msgstr "Completa la registrazione e inizia a usare il tuo account"
#: src/screens/Signup/index.tsx:144
msgid "Complete the challenge"
@@ -1700,7 +1700,7 @@ msgstr "Continua thread..."
msgid "Continue to next step"
msgstr "Vai al passaggio successivo"
#: src/screens/Messages/components/ChatListItem.tsx:173
#: src/screens/Messages/components/ChatListItem.tsx:179
msgid "Conversation deleted"
msgstr "Conversazione eliminata"
@@ -1807,7 +1807,7 @@ msgstr "Non è stato possibile caricare il feed"
msgid "Could not load list"
msgstr "Non è stato possibile caricare la lista"
#: src/components/dms/ConvoMenu.tsx:91
#: src/components/dms/ConvoMenu.tsx:185
msgid "Could not mute chat"
msgstr "Errore nel silenziare la conversazione"
@@ -1842,7 +1842,7 @@ msgstr "Crea uno starter pack per me"
#: src/view/shell/NavSignupCard.tsx:47
#: src/view/shell/NavSignupCard.tsx:52
msgid "Create account"
msgstr "Crea un account"
msgstr "Crea account"
#: src/screens/Signup/index.tsx:93
msgid "Create Account"
@@ -1883,8 +1883,8 @@ msgstr "L'autore è stato bloccato"
msgid "Culture"
msgstr "Cultura"
#: src/view/com/auth/server-input/index.tsx:100
#: src/view/com/auth/server-input/index.tsx:102
#: src/view/com/auth/server-input/index.tsx:138
#: src/view/com/auth/server-input/index.tsx:139
msgid "Custom"
msgstr "Personalizzato"
@@ -1897,7 +1897,7 @@ msgstr "Personalizza chi può interagire con questo post."
msgid "Dark"
msgstr "Scuro"
#: src/view/screens/Debug.tsx:70
#: src/view/screens/Debug.tsx:69
msgid "Dark mode"
msgstr "Aspetto scuro"
@@ -1919,7 +1919,7 @@ msgstr "Disattiva account"
msgid "Debug Moderation"
msgstr "Eliminare errori nella Moderazione"
#: src/view/screens/Debug.tsx:90
#: src/view/screens/Debug.tsx:89
msgid "Debug panel"
msgstr "Pannello per il debug"
@@ -2018,8 +2018,8 @@ msgstr "Eliminare questo post?"
msgid "Deleted"
msgstr "Eliminato"
#: src/components/dms/MessagesListHeader.tsx:148
#: src/screens/Messages/components/ChatListItem.tsx:107
#: src/components/dms/MessagesListHeader.tsx:154
#: src/screens/Messages/components/ChatListItem.tsx:113
msgid "Deleted Account"
msgstr "Account eliminato"
@@ -2120,7 +2120,7 @@ msgstr "Scoraggia le app dal mostrare il mio account agli utenti disconnessi"
msgid "Discover new custom feeds"
msgstr "Scopri nuovi feed personalizzati"
#: src/view/screens/Search/Explore.tsx:422
#: src/view/screens/Search/Explore.tsx:425
msgid "Discover new feeds"
msgstr "Scopri nuovi feed"
@@ -2189,17 +2189,17 @@ msgstr "Non inizia o termina con un trattino"
msgid "Domain verified!"
msgstr "Dominio verificato!"
#: src/components/dialogs/BirthDateSettings.tsx:118
#: src/components/dialogs/BirthDateSettings.tsx:124
#: src/components/dialogs/BirthDateSettings.tsx:114
#: src/components/dialogs/BirthDateSettings.tsx:120
#: src/components/dms/ReportDialog.tsx:323
#: src/components/forms/DateField/index.tsx:77
#: src/components/forms/DateField/index.tsx:83
#: src/components/forms/DateField/index.tsx:87
#: src/components/forms/DateField/index.tsx:93
#: src/screens/Onboarding/StepProfile/index.tsx:330
#: src/screens/Onboarding/StepProfile/index.tsx:333
#: src/screens/Settings/components/AddAppPasswordDialog.tsx:215
#: src/screens/Settings/components/AddAppPasswordDialog.tsx:222
#: src/view/com/auth/server-input/index.tsx:192
#: src/view/com/auth/server-input/index.tsx:193
#: src/view/com/auth/server-input/index.tsx:228
#: src/view/com/auth/server-input/index.tsx:229
#: src/view/com/composer/labels/LabelsBtn.tsx:224
#: src/view/com/composer/labels/LabelsBtn.tsx:231
#: src/view/com/composer/videos/SubtitleDialog.tsx:169
@@ -2226,7 +2226,7 @@ msgstr "Fatto{extraText}"
msgid "Double tap to close the dialog"
msgstr "Tocca due volte per chiudere la finestra"
#: src/screens/VideoFeed/index.tsx:1037
#: src/screens/VideoFeed/index.tsx:1039
msgid "Double tap to like"
msgstr "Tocca due volte per mettere mi piace"
@@ -2550,7 +2550,7 @@ msgstr "Inserisci il dominio che vuoi usare"
msgid "Enter the email you used to create your account. We'll send you a \"reset code\" so you can set a new password."
msgstr "Inserisci l'email che hai usato per creare il tuo account. Ti invieremo un codice in modo che tu possa scegliere una nuova password."
#: src/components/dialogs/BirthDateSettings.tsx:107
#: src/components/dialogs/BirthDateSettings.tsx:103
msgid "Enter your birth date"
msgstr "Inserisci la tua data di nascita"
@@ -2738,8 +2738,8 @@ msgstr "Non è stato possibile eliminare il post, riprovare"
msgid "Failed to delete starter pack"
msgstr "Impossibile eliminare lo starter pack"
#: src/view/screens/Search/Explore.tsx:460
#: src/view/screens/Search/Explore.tsx:488
#: src/view/screens/Search/Explore.tsx:463
#: src/view/screens/Search/Explore.tsx:491
msgid "Failed to load feeds preferences"
msgstr "Impossibile caricare preferenze feed"
@@ -2751,12 +2751,12 @@ msgstr "Impossibile caricare GIF"
msgid "Failed to load past messages"
msgstr "Impossibile caricare messaggi vecchi"
#: src/view/screens/Search/Explore.tsx:453
#: src/view/screens/Search/Explore.tsx:481
#: src/view/screens/Search/Explore.tsx:456
#: src/view/screens/Search/Explore.tsx:484
msgid "Failed to load suggested feeds"
msgstr "Impossibile caricare feed consigliati"
#: src/view/screens/Search/Explore.tsx:411
#: src/view/screens/Search/Explore.tsx:414
msgid "Failed to load suggested follows"
msgstr "Impossibile caricare follow consigliati"
@@ -2897,16 +2897,16 @@ msgid "Flexible"
msgstr "Flessibile"
#. User is not following this account, click to follow
#: src/components/ProfileCard.tsx:363
#: src/components/ProfileCard.tsx:366
#: src/components/ProfileHoverCard/index.web.tsx:449
#: src/components/ProfileHoverCard/index.web.tsx:460
#: src/screens/Profile/Header/ProfileHeaderStandard.tsx:234
#: src/screens/VideoFeed/index.tsx:819
#: src/screens/VideoFeed/index.tsx:821
#: src/view/com/post-thread/PostThreadFollowBtn.tsx:132
msgid "Follow"
msgstr "Segui"
#: src/view/com/profile/FollowButton.tsx:69
#: src/view/com/profile/FollowButton.tsx:72
msgctxt "action"
msgid "Follow"
msgstr "Segui"
@@ -2916,7 +2916,7 @@ msgstr "Segui"
msgid "Follow {0}"
msgstr "Segui {0}"
#: src/screens/VideoFeed/index.tsx:798
#: src/screens/VideoFeed/index.tsx:800
msgid "Follow {handle}"
msgstr "Segui {handle}"
@@ -2948,14 +2948,14 @@ msgstr "Segui tutti"
msgid "Follow Back"
msgstr "Ricambia follow"
#: src/view/com/profile/FollowButton.tsx:78
#: src/view/com/profile/FollowButton.tsx:81
msgctxt "action"
msgid "Follow Back"
msgstr "Segui anche tu"
#: src/view/screens/Search/Explore.tsx:368
#: src/view/screens/Search/Explore.tsx:370
msgid "Follow more accounts to get connected to your interests and build your network."
msgstr "Segui altri account per connetterti ai tuoi interessi e crea il tuo network personale."
msgstr "Segui altri account per connetterti ai tuoi interessi e creare la tua rete personale."
#: src/components/KnownFollowers.tsx:232
msgid "Followed by <0>{0}</0>"
@@ -2987,23 +2987,23 @@ msgid "Followers you know"
msgstr "Follower che conosci"
#. User is following this account, click to unfollow
#: src/components/ProfileCard.tsx:357
#: src/components/ProfileCard.tsx:360
#: src/components/ProfileHoverCard/index.web.tsx:448
#: src/components/ProfileHoverCard/index.web.tsx:459
#: src/screens/Profile/Header/ProfileHeaderStandard.tsx:230
#: src/screens/VideoFeed/index.tsx:817
#: src/screens/VideoFeed/index.tsx:819
#: src/view/com/post-thread/PostThreadFollowBtn.tsx:135
#: src/view/screens/Feeds.tsx:599
#: src/view/screens/SavedFeeds.tsx:422
msgid "Following"
msgstr "Following"
#: src/components/ProfileCard.tsx:321
#: src/components/ProfileCard.tsx:323
#: src/screens/Profile/Header/ProfileHeaderStandard.tsx:98
msgid "Following {0}"
msgstr "Stai seguendo {0}"
#: src/screens/VideoFeed/index.tsx:797
#: src/screens/VideoFeed/index.tsx:799
msgid "Following {handle}"
msgstr "Stai seguendo {handle}"
@@ -3122,8 +3122,8 @@ msgstr "Evidenti violazioni della legge o dei termini di servizio"
#: src/components/moderation/ScreenHider.tsx:163
#: src/screens/Profile/ProfileFeed/index.tsx:89
#: src/screens/VideoFeed/components/Header.tsx:163
#: src/screens/VideoFeed/index.tsx:1098
#: src/screens/VideoFeed/index.tsx:1102
#: src/screens/VideoFeed/index.tsx:1100
#: src/screens/VideoFeed/index.tsx:1104
#: src/view/com/auth/LoggedOut.tsx:72
#: src/view/screens/NotFound.tsx:57
#: src/view/screens/ProfileList.tsx:1009
@@ -3166,7 +3166,7 @@ msgstr "Vai alla home"
msgid "Go Home"
msgstr "Vai alla home"
#: src/screens/Messages/components/ChatListItem.tsx:274
#: src/screens/Messages/components/ChatListItem.tsx:282
msgid "Go to conversation with {0}"
msgstr "Vai alla conversazione con {0}"
@@ -3175,11 +3175,11 @@ msgstr "Vai alla conversazione con {0}"
msgid "Go to next"
msgstr "Prosegui"
#: src/components/dms/ConvoMenu.tsx:170
#: src/components/dms/ConvoMenu.tsx:230
msgid "Go to profile"
msgstr "Vai al profilo"
#: src/components/dms/ConvoMenu.tsx:167
#: src/components/dms/ConvoMenu.tsx:227
msgid "Go to user's profile"
msgstr "Vai al profilo dell'utente"
@@ -3439,7 +3439,7 @@ msgstr "Se rimuovi questo post, non potrai recuperarlo."
msgid "If you want to change your password, we will send you a code to verify that this is your account."
msgstr "Se vuoi modificare la password, ti invieremo un codice per verificare che questo è il tuo account."
#: src/view/com/auth/server-input/index.tsx:168
#: src/view/com/auth/server-input/index.tsx:204
msgid "If you're a developer, you can host your own server."
msgstr "Se sei uno sviluppatore, puoi utilizzare il tuo server."
@@ -3560,7 +3560,7 @@ msgstr "Inviti, ma personali"
msgid "It looks like you may have entered your email address incorrectly. Are you sure it's right?"
msgstr "Sembra che tu abbia inserito il tuo indirizzo email in modo errato. Sei sicuro che sia corretto?"
#: src/screens/Signup/StepInfo/index.tsx:235
#: src/screens/Signup/StepInfo/index.tsx:236
msgid "It's correct"
msgstr "È corretto"
@@ -3574,7 +3574,7 @@ msgstr "ID processo: {0}"
#: src/view/com/auth/SplashScreen.web.tsx:178
msgid "Jobs"
msgstr "Lavora con noi"
msgstr "Opportunità di lavoro"
#: src/screens/StarterPack/StarterPackLandingScreen.tsx:201
#: src/screens/StarterPack/StarterPackLandingScreen.tsx:207
@@ -3658,7 +3658,7 @@ msgstr "Ulteriori Informazioni"
msgid "Learn more about Bluesky"
msgstr "Scopri di più su Bluesky"
#: src/view/com/auth/server-input/index.tsx:178
#: src/view/com/auth/server-input/index.tsx:214
msgid "Learn more about self hosting your PDS."
msgstr "Maggiori informazioni su come ospitare il tuo server."
@@ -3678,7 +3678,7 @@ msgid "Learn more about what is public on Bluesky."
msgstr "Scopri cosa è pubblico su Bluesky."
#: src/components/moderation/ContentHider.tsx:239
#: src/view/com/auth/server-input/index.tsx:180
#: src/view/com/auth/server-input/index.tsx:216
msgid "Learn more."
msgstr "Saperne di più."
@@ -3686,15 +3686,15 @@ msgstr "Saperne di più."
msgid "Leave"
msgstr "Abbandona"
#: src/components/dms/MessagesListBlockedFooter.tsx:66
#: src/components/dms/MessagesListBlockedFooter.tsx:73
#: src/components/dms/MessagesListBlockedFooter.tsx:80
msgid "Leave chat"
msgstr "Abbandona la chat"
#: src/components/dms/ConvoMenu.tsx:141
#: src/components/dms/ConvoMenu.tsx:144
#: src/components/dms/ConvoMenu.tsx:211
#: src/components/dms/ConvoMenu.tsx:214
#: src/components/dms/ConvoMenu.tsx:206
#: src/components/dms/ConvoMenu.tsx:209
#: src/components/dms/ConvoMenu.tsx:269
#: src/components/dms/ConvoMenu.tsx:272
#: src/components/dms/LeaveConvoPrompt.tsx:40
msgid "Leave conversation"
msgstr "Abbandona conversazione"
@@ -3856,15 +3856,15 @@ msgstr "Liste"
msgid "Lists blocking this user:"
msgstr "Liste che bloccano questo utente:"
#: src/view/screens/Search/Explore.tsx:134
#: src/view/screens/Search/Explore.tsx:135
msgid "Load more"
msgstr "Carica di più"
#: src/view/screens/Search/Explore.tsx:223
#: src/view/screens/Search/Explore.tsx:224
msgid "Load more suggested feeds"
msgstr "Carica più feed consigliati"
#: src/view/screens/Search/Explore.tsx:221
#: src/view/screens/Search/Explore.tsx:222
msgid "Load more suggested follows"
msgstr "Carica più utenti suggeriti"
@@ -3956,8 +3956,8 @@ msgstr "Gestisci i feed salvati"
msgid "Manage your muted words and tags"
msgstr "Gestisci parole e tag silenziati"
#: src/components/dms/ConvoMenu.tsx:154
#: src/components/dms/ConvoMenu.tsx:161
#: src/components/dms/ConvoMenu.tsx:218
#: src/components/dms/ConvoMenu.tsx:221
msgid "Mark as read"
msgstr "Segna come letto"
@@ -3982,7 +3982,7 @@ msgid "Mentions"
msgstr "Menzioni"
#: src/components/Menu/index.tsx:103
#: src/view/screens/Search/Search.tsx:856
#: src/view/screens/Search/Search.tsx:862
msgid "Menu"
msgstr "Menu"
@@ -3991,7 +3991,7 @@ msgid "Message {0}"
msgstr "Messaggio {0}"
#: src/components/dms/MessageMenu.tsx:72
#: src/screens/Messages/components/ChatListItem.tsx:174
#: src/screens/Messages/components/ChatListItem.tsx:180
msgid "Message deleted"
msgstr "Messaggio eliminato"
@@ -4009,8 +4009,8 @@ msgid "Message is too long"
msgstr "Il messaggio è troppo lungo"
#: src/Navigation.tsx:618
#: src/screens/Messages/ChatList.tsx:270
#: src/screens/Messages/ChatList.tsx:294
#: src/screens/Messages/ChatList.tsx:269
#: src/screens/Messages/ChatList.tsx:293
msgid "Messages"
msgstr "Messaggi"
@@ -4133,8 +4133,8 @@ msgstr "Silenzia account"
msgid "Mute accounts"
msgstr "Silenzia account"
#: src/components/dms/ConvoMenu.tsx:175
#: src/components/dms/ConvoMenu.tsx:181
#: src/components/dms/ConvoMenu.tsx:235
#: src/components/dms/ConvoMenu.tsx:241
msgid "Mute conversation"
msgstr "Silenzia conversazione"
@@ -4275,8 +4275,8 @@ msgid "New"
msgstr "Nuova"
#: src/components/dms/dialogs/NewChatDialog.tsx:65
#: src/screens/Messages/ChatList.tsx:277
#: src/screens/Messages/ChatList.tsx:284
#: src/screens/Messages/ChatList.tsx:276
#: src/screens/Messages/ChatList.tsx:283
msgid "New chat"
msgstr "Nuova chat"
@@ -4390,7 +4390,7 @@ msgstr "Nessun feed trovato. Prova a cercarne altri."
msgid "No likes yet"
msgstr "Ancora nessun mi piace"
#: src/components/ProfileCard.tsx:342
#: src/components/ProfileCard.tsx:345
#: src/screens/Profile/Header/ProfileHeaderStandard.tsx:119
msgid "No longer following {0}"
msgstr "Non segui più {0}"
@@ -4399,7 +4399,7 @@ msgstr "Non segui più {0}"
msgid "No longer than {0} characters"
msgstr "Non più di {0} caratteri"
#: src/screens/Messages/components/ChatListItem.tsx:118
#: src/screens/Messages/components/ChatListItem.tsx:124
msgid "No messages yet"
msgstr "Ancora nessun messaggio"
@@ -4599,7 +4599,7 @@ msgstr "su<0><1/><2><3/></2></0>"
#: src/screens/Settings/Settings.tsx:304
msgid "Onboarding reset"
msgstr "Reimpostazione dell'onboarding"
msgstr "Reimposta account"
#: src/view/com/composer/Composer.tsx:338
msgid "One or more GIFs is missing alt text."
@@ -4663,8 +4663,8 @@ msgstr "Apri il creatore di avatar"
msgid "Open change handle dialog"
msgstr "Apri la finestra di dialogo per la modifica del nome utente"
#: src/screens/Messages/components/ChatListItem.tsx:282
#: src/screens/Messages/components/ChatListItem.tsx:283
#: src/screens/Messages/components/ChatListItem.tsx:290
#: src/screens/Messages/components/ChatListItem.tsx:291
msgid "Open conversation options"
msgstr "Apri opzioni conversazione"
@@ -4827,7 +4827,7 @@ msgstr "Altro..."
#: src/components/dms/ReportDialog.tsx:339
msgid "Our moderation team has received your report."
msgstr "Il nostro team di moderazione ha ricevuto la segnalazione."
msgstr "Il team di moderazione ha ricevuto la tua segnalazione."
#: src/screens/Messages/components/ChatDisabled.tsx:28
msgid "Our moderators have reviewed reports and decided to disable your access to chats on Bluesky."
@@ -5326,11 +5326,11 @@ msgstr "Riattacca citazione"
msgid "Reactivate your account"
msgstr "Riattiva account"
#: src/screens/VideoFeed/index.tsx:932
#: src/screens/VideoFeed/index.tsx:934
msgid "Read less"
msgstr "Leggi di meno"
#: src/screens/VideoFeed/index.tsx:932
#: src/screens/VideoFeed/index.tsx:934
msgid "Read more"
msgstr "Leggi di più"
@@ -5351,13 +5351,13 @@ msgstr "Leggi i termini di servizio di Bluesky"
#: src/screens/Takendown.tsx:162
#: src/screens/Takendown.tsx:170
msgid "Reason for appeal"
msgstr "Motivo della richiesta di revisione della sospensione"
msgstr "Motivo del ricorso"
#: src/components/dms/ReportDialog.tsx:210
msgid "Reason:"
msgstr "Motivo:"
#: src/view/screens/Search/Search.tsx:1034
#: src/view/screens/Search/Search.tsx:1040
msgid "Recent Searches"
msgstr "Ricerche recenti"
@@ -5450,11 +5450,11 @@ msgstr "Rimuovi l'immagine"
msgid "Remove mute word from your list"
msgstr "Rimuovi parola silenziata dalla tua lista"
#: src/view/screens/Search/Search.tsx:1082
#: src/view/screens/Search/Search.tsx:1088
msgid "Remove profile"
msgstr "Rimuovi profilo"
#: src/view/screens/Search/Search.tsx:1084
#: src/view/screens/Search/Search.tsx:1090
msgid "Remove profile from search history"
msgstr "Rimuovi profilo dalla cronologia di ricerca"
@@ -5465,7 +5465,7 @@ msgstr "Rimuovi citazione"
#: src/view/com/util/post-ctrls/RepostButton.tsx:155
#: src/view/com/util/post-ctrls/RepostButton.tsx:165
msgid "Remove repost"
msgstr "Rimuovi ripubblicazione"
msgstr "Rimuovi repost"
#: src/view/com/composer/videos/SubtitleDialog.tsx:260
msgid "Remove subtitle file"
@@ -5586,8 +5586,8 @@ msgid "Reply was successfully hidden"
msgstr "Risposta nascosta con successo"
#: src/components/dms/MessageMenu.tsx:132
#: src/components/dms/MessagesListBlockedFooter.tsx:77
#: src/components/dms/MessagesListBlockedFooter.tsx:84
#: src/components/dms/MessagesListBlockedFooter.tsx:91
msgid "Report"
msgstr "Segnala"
@@ -5596,8 +5596,8 @@ msgstr "Segnala"
msgid "Report Account"
msgstr "Segnala account"
#: src/components/dms/ConvoMenu.tsx:200
#: src/components/dms/ConvoMenu.tsx:203
#: src/components/dms/ConvoMenu.tsx:258
#: src/components/dms/ConvoMenu.tsx:261
#: src/components/dms/ReportConversationPrompt.tsx:17
msgid "Report conversation"
msgstr "Segnala conversazione"
@@ -5759,7 +5759,7 @@ msgstr "Reimposta il codice"
#: src/screens/Settings/Settings.tsx:344
#: src/screens/Settings/Settings.tsx:346
msgid "Reset onboarding state"
msgstr "Reimposta lo stato dell'incorporazione"
msgstr "Ripristina lo stato di registrazione"
#: src/screens/Login/ForgotPasswordForm.tsx:80
msgid "Reset password"
@@ -5804,12 +5804,12 @@ msgid "Returns to home page"
msgstr "Ritorna alla home"
#: src/screens/Profile/ProfileFeed/index.tsx:90
#: src/screens/VideoFeed/index.tsx:1099
#: src/screens/VideoFeed/index.tsx:1101
#: src/view/screens/NotFound.tsx:60
msgid "Returns to previous page"
msgstr "Ritorna alla pagina precedente"
#: src/components/dialogs/BirthDateSettings.tsx:124
#: src/components/dialogs/BirthDateSettings.tsx:120
#: src/components/dialogs/PostInteractionSettingsDialog.tsx:438
#: src/components/dialogs/PostInteractionSettingsDialog.tsx:444
#: src/components/StarterPack/QrCodeDialog.tsx:185
@@ -5834,7 +5834,7 @@ msgctxt "action"
msgid "Save"
msgstr "Salva"
#: src/components/dialogs/BirthDateSettings.tsx:118
#: src/components/dialogs/BirthDateSettings.tsx:114
msgid "Save birthday"
msgstr "Salva il compleanno"
@@ -5934,7 +5934,7 @@ msgstr "Cerca \"{interestsDisplayName}\"{activeText}"
msgid "Search for \"{query}\""
msgstr "Cerca \"{query}\""
#: src/view/screens/Search/Search.tsx:980
#: src/view/screens/Search/Search.tsx:986
msgid "Search for \"{searchText}\""
msgstr "Cerca \"{searchText}\""
@@ -6145,7 +6145,7 @@ msgstr "Invia tramite messaggi"
msgid "Sends email with confirmation code for account deletion"
msgstr "Invia un'email con il codice di conferma per l'eliminazione dell'account"
#: src/view/com/auth/server-input/index.tsx:127
#: src/view/com/auth/server-input/index.tsx:163
msgid "Server address"
msgstr "Indirizzo del server"
@@ -6420,7 +6420,7 @@ msgstr "Esci"
#: src/screens/Settings/Settings.tsx:256
#: src/view/shell/desktop/LeftNav.tsx:205
msgid "Sign out?"
msgstr "Uscire?"
msgstr "Vuoi uscire?"
#: src/components/moderation/ScreenHider.tsx:91
#: src/lib/moderation/useGlobalLabelStrings.ts:28
@@ -6437,7 +6437,7 @@ msgstr "Accesso effettuato come @{0}"
msgid "Signup without a starter pack"
msgstr "Iscriviti senza uno starter pack"
#: src/components/FeedInterstitials.tsx:314
#: src/components/FeedInterstitials.tsx:335
msgid "Similar accounts"
msgstr "Account simili"
@@ -6459,7 +6459,7 @@ msgstr "Più piccolo"
msgid "Software Dev"
msgstr "Sviluppo software"
#: src/components/FeedInterstitials.tsx:449
#: src/components/FeedInterstitials.tsx:470
msgid "Some other feeds you might like"
msgstr "Altri feed che potrebbero piacerti"
@@ -6467,7 +6467,7 @@ msgstr "Altri feed che potrebbero piacerti"
msgid "Some people can reply"
msgstr "Risposte limitate"
#: src/screens/Messages/Conversation.tsx:102
#: src/screens/Messages/Conversation.tsx:112
msgid "Something went wrong"
msgstr "Qualcosa è andato storto"
@@ -6632,11 +6632,11 @@ msgstr "Iscriviti alla lista"
msgid "Success!"
msgstr "Successo!"
#: src/view/screens/Search/Explore.tsx:366
#: src/view/screens/Search/Explore.tsx:368
msgid "Suggested accounts"
msgstr "Account suggeriti"
#: src/components/FeedInterstitials.tsx:316
#: src/components/FeedInterstitials.tsx:337
msgid "Suggested for you"
msgstr "Suggerito per te"
@@ -6703,7 +6703,7 @@ msgstr "Tocca per ignorare"
msgid "Tap to enter full screen"
msgstr "Tocca per entrare in modalità a schermo intero"
#: src/screens/VideoFeed/index.tsx:931
#: src/screens/VideoFeed/index.tsx:933
msgid "Tap to expand or collapse post text."
msgstr "Tocca per espandere o comprimere il testo del post."
@@ -6820,7 +6820,7 @@ msgstr "Impossibile trovare starter pack."
msgid "That's all, folks!"
msgstr "Questo è tutto, gente!"
#: src/screens/VideoFeed/index.tsx:1071
#: src/screens/VideoFeed/index.tsx:1073
msgid "That's everything!"
msgstr "È tutto!"
@@ -7065,7 +7065,7 @@ msgstr "Questo contenuto non è disponibile perché uno degli utenti coinvolti h
msgid "This content is not viewable without a Bluesky account."
msgstr "Questo contenuto non è visualizzabile senza un account Bluesky."
#: src/screens/Messages/components/ChatListItem.tsx:276
#: src/screens/Messages/components/ChatListItem.tsx:284
msgid "This conversation is with a deleted or a deactivated account. Press for options."
msgstr "L'utente di questa conversazione ha disattivato o eliminato l'account. Premi per le opzioni."
@@ -7180,7 +7180,7 @@ msgstr "Questo dovrebbe creare un record di dominio in:"
msgid "This user doesn't have any followers."
msgstr "Questo utente non ha follower."
#: src/components/dms/MessagesListBlockedFooter.tsx:60
#: src/components/dms/MessagesListBlockedFooter.tsx:67
msgid "This user has blocked you"
msgstr "Questo utente ti ha bloccato"
@@ -7359,10 +7359,10 @@ msgstr "Impossibile contattare il servizio. Verifica la tua connessione a intern
msgid "Unable to delete"
msgstr "Impossibile eliminare"
#: src/components/dms/MessagesListBlockedFooter.tsx:89
#: src/components/dms/MessagesListBlockedFooter.tsx:96
#: src/components/dms/MessagesListBlockedFooter.tsx:104
#: src/components/dms/MessagesListBlockedFooter.tsx:103
#: src/components/dms/MessagesListBlockedFooter.tsx:111
#: src/components/dms/MessagesListBlockedFooter.tsx:118
#: src/screens/Profile/Header/ProfileHeaderStandard.tsx:197
#: src/screens/Profile/Header/ProfileHeaderStandard.tsx:283
#: src/view/com/profile/ProfileMenu.tsx:341
@@ -7375,8 +7375,8 @@ msgctxt "action"
msgid "Unblock"
msgstr "Sblocca"
#: src/components/dms/ConvoMenu.tsx:191
#: src/components/dms/ConvoMenu.tsx:195
#: src/components/dms/ConvoMenu.tsx:250
#: src/components/dms/ConvoMenu.tsx:253
msgid "Unblock account"
msgstr "Sblocca account"
@@ -7399,7 +7399,7 @@ msgstr "Annulla ripubblicazione"
msgid "Undo repost ({0, plural, one {# repost} other {# reposts}})"
msgstr "Annulla ripubblicazione ({0, plural, one {# ripubblicazione} other {# ripubblicazioni}})"
#: src/view/com/profile/FollowButton.tsx:60
#: src/view/com/profile/FollowButton.tsx:63
msgctxt "action"
msgid "Unfollow"
msgstr "Smetti di seguire"
@@ -7413,7 +7413,7 @@ msgstr "Smetti di seguire {0}"
msgid "Unfollow Account"
msgstr "Smetti di seguire questo account"
#: src/screens/VideoFeed/index.tsx:801
#: src/screens/VideoFeed/index.tsx:803
msgid "Unfollow user"
msgstr "Smetti di seguire"
@@ -7445,7 +7445,7 @@ msgstr "Riattiva {tag}"
msgid "Unmute Account"
msgstr "Riattiva questo account"
#: src/components/dms/ConvoMenu.tsx:179
#: src/components/dms/ConvoMenu.tsx:239
msgid "Unmute conversation"
msgstr "Riattiva conversazione"
@@ -7746,11 +7746,11 @@ msgstr "Video da {0}: {text}"
msgid "Video Games"
msgstr "Videogiochi"
#: src/screens/VideoFeed/index.tsx:1029
#: src/screens/VideoFeed/index.tsx:1031
msgid "Video is paused"
msgstr "Video in pausa"
#: src/screens/VideoFeed/index.tsx:1029
#: src/screens/VideoFeed/index.tsx:1031
msgid "Video is playing"
msgstr "Video in riproduzione"
@@ -7785,12 +7785,12 @@ msgstr "Vedi l'avatar di {0}"
#: src/components/ProfileCard.tsx:110
#: src/screens/Profile/components/ProfileFeedHeader.tsx:450
#: src/screens/VideoFeed/index.tsx:762
#: src/screens/VideoFeed/index.tsx:763
#: src/view/com/notifications/NotificationFeedItem.tsx:409
msgid "View {0}'s profile"
msgstr "Vedi il profilo di {0}"
#: src/components/dms/MessagesListHeader.tsx:167
#: src/components/dms/MessagesListHeader.tsx:173
msgid "View {displayName}'s profile"
msgstr "Vedi il profilo di {displayName}"
@@ -7905,7 +7905,7 @@ msgstr "Non siamo riusciti a trovare alcun risultato per quell'hashtag."
msgid "We couldn't find any results for that topic."
msgstr "Nessun risultato per questo argomento."
#: src/screens/Messages/Conversation.tsx:103
#: src/screens/Messages/Conversation.tsx:113
msgid "We couldn't load this conversation"
msgstr "Non riusciamo a caricare questa conversazione"
@@ -8037,7 +8037,7 @@ msgstr "Ops! Non è stato possibile caricare i video di tendenza."
#: src/screens/Takendown.tsx:173
msgid "Why are you appealing?"
msgstr "Perché richiedi la revisione?"
msgstr "Per quale motivo fai ricorso?"
#: src/components/ReportDialog/SelectReportOptionView.tsx:42
msgid "Why should this content be reviewed?"
@@ -8192,7 +8192,7 @@ msgstr "Non hai salvato nessun feed."
msgid "You have blocked the author or you have been blocked by the author."
msgstr "Hai bloccato l'autore o sei stato bloccato dall'autore."
#: src/components/dms/MessagesListBlockedFooter.tsx:58
#: src/components/dms/MessagesListBlockedFooter.tsx:65
msgid "You have blocked this user"
msgstr "Hai bloccato questo utente"
@@ -8329,15 +8329,15 @@ msgstr "Riceverai le notifiche per questo thread"
msgid "You will receive an email with a \"reset code.\" Enter that code here, then enter your new password."
msgstr "Riceverai un'email con un codice di recupero. Inserisci il codice qui, poi inserisci la tua nuova password."
#: src/screens/Messages/components/ChatListItem.tsx:133
#: src/screens/Messages/components/ChatListItem.tsx:139
msgid "You: {0}"
msgstr "Tu: {0}"
#: src/screens/Messages/components/ChatListItem.tsx:162
#: src/screens/Messages/components/ChatListItem.tsx:168
msgid "You: {defaultEmbeddedContentMessage}"
msgstr "Tu: {defaultEmbeddedContentMessage}"
#: src/screens/Messages/components/ChatListItem.tsx:155
#: src/screens/Messages/components/ChatListItem.tsx:161
msgid "You: {short}"
msgstr "Tu: {short}"
@@ -8399,7 +8399,7 @@ msgstr "Hai raggiunto il limite giornaliero di video caricati (troppi byte)"
msgid "You've reached your daily limit for video uploads (too many videos)"
msgstr "Hai raggiunto il limite giornaliero di video caricati (troppi video)"
#: src/screens/VideoFeed/index.tsx:1080
#: src/screens/VideoFeed/index.tsx:1082
msgid "You've run out of videos to watch. Maybe it's a good time to take a break?"
msgstr "Non ci sono altri video da guardare. Forse è il momento giusto per fare una pausa?"
@@ -8425,11 +8425,11 @@ msgstr "L'archivio del tuo account, che contiene tutti i record di dati pubblici
#: src/screens/Takendown.tsx:214
msgid "Your account was found to be in violation of the <0>Bluesky Social Terms of Service</0>. You have been sent an email outlining the specific violation and suspension period, if applicable. You can appeal this decision if you believe it was made in error."
msgstr "Abbiamo riscontrato che il tuo account viola i <0>termini di servizio di Bluesky Social</0>. Ti abbiamo inviato un'email che descrive la violazione e il periodo di sospensione, se applicabile. Se ritieni che questa decisione sia stata presa per errore, puoi richiedere una revisione della sospensione."
msgstr "Abbiamo riscontrato che il tuo account viola i <0>termini di servizio di Bluesky</0>. Ti abbiamo inviato un'email che descrive la violazione e il periodo di sospensione, se applicabile. Se ritieni che questa decisione sia stata presa per errore, puoi richiedere una revisione della sospensione."
#: src/screens/Takendown.tsx:154
msgid "Your appeal has been submitted. If your appeal succeeds, you will receive an email."
msgstr "La tua richiesta di revisione è stata inviata. Riceverai un'email se la revisione avrà esito positivo."
msgstr "La richiesta di revisione è stata inviata. Se il ricorso ha esito positivo, riceverai un'email."
#: src/screens/Signup/StepInfo/index.tsx:205
msgid "Your birth date"
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+1
View File
@@ -13,6 +13,7 @@ export const router = new Router({
ModerationModlists: '/moderation/modlists',
ModerationMutedAccounts: '/moderation/muted-accounts',
ModerationBlockedAccounts: '/moderation/blocked-accounts',
ModerationInteractionSettings: '/moderation/interaction-settings',
// profiles, threads, lists
Profile: ['/profile/:name', '/profile/:name/rss'],
ProfileFollowers: '/profile/:name/followers',
+1 -1
View File
@@ -232,7 +232,7 @@ export const LoginForm = ({
onSubmitEditing={onPressNext}
blurOnSubmit={false} // HACK: https://github.com/facebook/react-native/issues/21911#issuecomment-558343069 Keyboard blur behavior is now handled in onSubmitEditing
editable={!isProcessing}
accessibilityHint={_(msg`Input your password`)}
accessibilityHint={_(msg`Enter your password`)}
/>
<Button
testID="forgotPasswordButton"
@@ -281,7 +281,7 @@ function ChatListItemReady({
!isDeletedAccount
? _(msg`Go to conversation with ${profile.handle}`)
: _(
msg`This conversation is with a deleted or a deactivated account. Press for options.`,
msg`This conversation is with a deleted or a deactivated account. Press for options`,
)
}
accessibilityActions={
+16
View File
@@ -28,6 +28,7 @@ import * as Toggle from '#/components/forms/Toggle'
import {ChevronRight_Stroke2_Corner0_Rounded as ChevronRight} from '#/components/icons/Chevron'
import {CircleBanSign_Stroke2_Corner0_Rounded as CircleBanSign} from '#/components/icons/CircleBanSign'
import {Props as SVGIconProps} from '#/components/icons/common'
import {EditBig_Stroke2_Corner0_Rounded as EditBig} from '#/components/icons/EditBig'
import {Filter_Stroke2_Corner0_Rounded as Filter} from '#/components/icons/Filter'
import {Group3_Stroke2_Corner0_Rounded as Group} from '#/components/icons/Group'
import {Person_Stroke2_Corner0_Rounded as Person} from '#/components/icons/Person'
@@ -199,6 +200,21 @@ export function ModerationScreenInner({
a.overflow_hidden,
t.atoms.bg_contrast_25,
]}>
<Link
label={_(msg`View your default post interaction settings`)}
testID="interactionSettingsBtn"
to="/moderation/interaction-settings">
{state => (
<SubItem
title={_(msg`Interaction settings`)}
icon={EditBig}
style={[
(state.hovered || state.pressed) && [t.atoms.bg_contrast_50],
]}
/>
)}
</Link>
<Divider />
<Button
testID="mutedWordsBtn"
label={_(msg`Open muted words and tags settings`)}
@@ -0,0 +1,127 @@
import React from 'react'
import {View} from 'react-native'
import {msg, Trans} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import deepEqual from 'lodash.isequal'
import {logger} from '#/logger'
import {usePostInteractionSettingsMutation} from '#/state/queries/post-interaction-settings'
import {createPostgateRecord} from '#/state/queries/postgate/util'
import {
usePreferencesQuery,
UsePreferencesQueryResponse,
} from '#/state/queries/preferences'
import {
threadgateAllowUISettingToAllowRecordValue,
threadgateRecordToAllowUISetting,
} from '#/state/queries/threadgate'
import * as Toast from '#/view/com/util/Toast'
import {atoms as a, useGutters} from '#/alf'
import {Admonition} from '#/components/Admonition'
import {PostInteractionSettingsForm} from '#/components/dialogs/PostInteractionSettingsDialog'
import * as Layout from '#/components/Layout'
import {Loader} from '#/components/Loader'
export function Screen() {
const gutters = useGutters(['base'])
const {data: preferences} = usePreferencesQuery()
return (
<Layout.Screen testID="ModerationInteractionSettingsScreen">
<Layout.Header.Outer>
<Layout.Header.BackButton />
<Layout.Header.Content>
<Layout.Header.TitleText>
<Trans>Post Interaction Settings</Trans>
</Layout.Header.TitleText>
</Layout.Header.Content>
<Layout.Header.Slot />
</Layout.Header.Outer>
<Layout.Content>
<View style={[gutters, a.gap_xl]}>
<Admonition type="tip">
<Trans>
The following settings will be used as your defaults when creating
new posts. You can edit these for a specific post from the
composer.
</Trans>
</Admonition>
{preferences ? (
<Inner preferences={preferences} />
) : (
<View style={[gutters, a.justify_center, a.align_center]}>
<Loader size="xl" />
</View>
)}
</View>
</Layout.Content>
</Layout.Screen>
)
}
function Inner({preferences}: {preferences: UsePreferencesQueryResponse}) {
const {_} = useLingui()
const {mutateAsync: setPostInteractionSettings, isPending} =
usePostInteractionSettingsMutation()
const [error, setError] = React.useState<string | undefined>(undefined)
const allowUI = React.useMemo(() => {
return threadgateRecordToAllowUISetting({
$type: 'app.bsky.feed.threadgate',
post: '',
createdAt: new Date().toString(),
allow: preferences.postInteractionSettings.threadgateAllowRules,
})
}, [preferences.postInteractionSettings.threadgateAllowRules])
const postgate = React.useMemo(() => {
return createPostgateRecord({
post: '',
embeddingRules:
preferences.postInteractionSettings.postgateEmbeddingRules,
})
}, [preferences.postInteractionSettings.postgateEmbeddingRules])
const [maybeEditedAllowUI, setAllowUI] = React.useState(allowUI)
const [maybeEditedPostgate, setEditedPostgate] = React.useState(postgate)
const wasEdited = React.useMemo(() => {
return (
!deepEqual(allowUI, maybeEditedAllowUI) ||
!deepEqual(postgate.embeddingRules, maybeEditedPostgate.embeddingRules)
)
}, [postgate, allowUI, maybeEditedAllowUI, maybeEditedPostgate])
const onSave = React.useCallback(async () => {
setError('')
try {
await setPostInteractionSettings({
threadgateAllowRules:
threadgateAllowUISettingToAllowRecordValue(maybeEditedAllowUI),
postgateEmbeddingRules: maybeEditedPostgate.embeddingRules ?? [],
})
Toast.show(_(msg`Settings saved`))
} catch (e: any) {
logger.error(`Failed to save post interaction settings`, {
context: 'ModerationInteractionSettingsScreen',
safeMessage: e.message,
})
setError(_(msg`Failed to save settings. Please try again.`))
}
}, [_, maybeEditedPostgate, maybeEditedAllowUI, setPostInteractionSettings])
return (
<>
<PostInteractionSettingsForm
canSave={wasEdited}
isSaving={isPending}
onSave={onSave}
postgate={maybeEditedPostgate}
onChangePostgate={setEditedPostgate}
threadgateAllowUISettings={maybeEditedAllowUI}
onChangeThreadgateAllowUISettings={setAllowUI}
/>
{error && <Admonition type="error">{error}</Admonition>}
</>
)
}
+1 -3
View File
@@ -53,9 +53,7 @@ export function Layout({children}: React.PropsWithChildren<{}>) {
aria-role="dialog"
aria-label={dialogLabel}
accessibilityLabel={dialogLabel}
accessibilityHint={_(
msg`The following steps will help customize your Bluesky experience.`,
)}
accessibilityHint={_(msg`Customizes your Bluesky experience`)}
style={[
// @ts-ignore web only -prf
isWeb ? a.fixed : a.absolute,
+1 -1
View File
@@ -205,7 +205,7 @@ export function StepFinished() {
setActiveStarterPack(undefined)
setHasCheckedForStarterPack(true)
startProgressGuide(
gate('new_postonboarding') ? 'follow-10' : 'like-10-and-follow-7',
gate('old_postonboarding') ? 'like-10-and-follow-7' : 'follow-10',
)
dispatch({type: 'finish'})
onboardDispatch({type: 'finish'})
@@ -3,7 +3,6 @@ import {Apple_Stroke2_Corner0_Rounded as Apple} from '#/components/icons/Apple'
import {At_Stroke2_Corner0_Rounded as At} from '#/components/icons/At'
import {Atom_Stroke2_Corner0_Rounded as Atom} from '#/components/icons/Atom'
import {Celebrate_Stroke2_Corner0_Rounded as Celebrate} from '#/components/icons/Celebrate'
import {Coffee_Stroke2_Corner0_Rounded as Coffee} from '#/components/icons/Coffee'
import {
EmojiArc_Stroke2_Corner0_Rounded as EmojiArc,
EmojiHeartEyes_Stroke2_Corner0_Rounded as EmojiHeartEyes,
@@ -13,8 +12,6 @@ import {GameController_Stroke2_Corner0_Rounded as GameController} from '#/compon
import {Lab_Stroke2_Corner0_Rounded as Lab} from '#/components/icons/Lab'
import {Leaf_Stroke2_Corner0_Rounded as Leaf} from '#/components/icons/Leaf'
import {MusicNote_Stroke2_Corner0_Rounded as MusicNote} from '#/components/icons/MusicNote'
import {PiggyBank_Stroke2_Corner0_Rounded as PiggyBank} from '#/components/icons/PiggyBank'
import {Poop_Stroke2_Corner0_Rounded as Poop} from '#/components/icons/Poop'
import {Rose_Stroke2_Corner0_Rounded as Rose} from '#/components/icons/Rose'
import {Shaka_Stroke2_Corner0_Rounded as Shaka} from '#/components/icons/Shaka'
import {UFO_Stroke2_Corner0_Rounded as UFO} from '#/components/icons/UFO'
@@ -33,7 +30,6 @@ export const emojiNames = [
'apple',
'atom',
'celebrate',
'coffee',
'gameController',
'leaf',
'musicNote',
@@ -43,8 +39,6 @@ export const emojiNames = [
'zap',
'explosion',
'lab',
'piggyBank',
'poop',
] as const
export type EmojiName = (typeof emojiNames)[number]
@@ -81,10 +75,6 @@ export const emojiItems: Record<EmojiName, Emoji> = {
name: 'celebrate',
component: Celebrate,
},
coffee: {
name: 'coffee',
component: Coffee,
},
gameController: {
name: 'gameController',
component: GameController,
@@ -121,14 +111,6 @@ export const emojiItems: Record<EmojiName, Emoji> = {
name: 'lab',
component: Lab,
},
piggyBank: {
name: 'piggyBank',
component: PiggyBank,
},
poop: {
name: 'poop',
component: Poop,
},
}
export const avatarColors = [
+1 -1
View File
@@ -60,7 +60,7 @@ export function ErrorState({error}: {error: string}) {
color="secondary"
variant="solid"
label={_(msg`Go Back`)}
accessibilityHint="Return to previous page"
accessibilityHint="Returns to previous page"
onPress={onPressBack}>
<ButtonText>
<Trans>Go Back</Trans>
@@ -1,4 +1,5 @@
import {View} from 'react-native'
import {AppBskyUnspeccedDefs} from '@atproto/api'
import {Trans} from '@lingui/macro'
import {logEvent} from '#/lib/statsig/statsig'
@@ -27,6 +28,7 @@ function Inner() {
const gutters = useGutters([0, 'compact'])
const {data: trending, error, isLoading} = useTrendingTopics()
const noRecs = !isLoading && !error && !trending?.suggested?.length
const allFeeds = trending?.suggested && isAllFeeds(trending.suggested)
return error || noRecs ? null : (
<>
@@ -50,9 +52,17 @@ function Inner() {
<Trans>Recommended</Trans>
</Text>
</View>
<Text style={[t.atoms.text_contrast_high, a.leading_snug]}>
<Trans>Feeds we think you might like.</Trans>
</Text>
{!allFeeds ? (
<Text style={[t.atoms.text_contrast_high, a.leading_snug]}>
<Trans>
Content from across the network we think you might like.
</Trans>
</Text>
) : (
<Text style={[t.atoms.text_contrast_high, a.leading_snug]}>
<Trans>Feeds we think you might like.</Trans>
</Text>
)}
</View>
</View>
@@ -98,3 +108,10 @@ function Inner() {
</>
)
}
function isAllFeeds(topics: AppBskyUnspeccedDefs.TrendingTopic[]) {
return topics.every(topic => {
const segments = topic.link.split('/').slice(1)
return segments[0] === 'profile' && segments[2] === 'feed'
})
}
+12 -1
View File
@@ -7,6 +7,7 @@ import {NativeStackScreenProps} from '@react-navigation/native-stack'
import {appVersion, BUNDLE_DATE, bundleInfo} from '#/lib/app-info'
import {STATUS_PAGE_URL} from '#/lib/constants'
import {CommonNavigatorParams} from '#/lib/routes/types'
import {useDevModeEnabled} from '#/state/preferences/dev-mode'
import * as Toast from '#/view/com/util/Toast'
import * as SettingsList from '#/screens/Settings/components/SettingsList'
import {CodeLines_Stroke2_Corner2_Rounded as CodeLinesIcon} from '#/components/icons/CodeLines'
@@ -18,6 +19,7 @@ import * as Layout from '#/components/Layout'
type Props = NativeStackScreenProps<CommonNavigatorParams, 'AboutSettings'>
export function AboutSettingsScreen({}: Props) {
const {_} = useLingui()
const [devModeEnabled, setDevModeEnabled] = useDevModeEnabled()
return (
<Layout.Screen>
@@ -65,7 +67,16 @@ export function AboutSettingsScreen({}: Props) {
</SettingsList.LinkItem>
<SettingsList.PressableItem
label={_(msg`Version ${appVersion}`)}
accessibilityHint={_(msg`Copy build version to clipboard`)}
accessibilityHint={_(msg`Copies build version to clipboard`)}
onLongPress={() => {
const newDevModeEnabled = !devModeEnabled
setDevModeEnabled(newDevModeEnabled)
Toast.show(
newDevModeEnabled
? _(msg`Developer mode enabled`)
: _(msg`Developer mode disabled`),
)
}}
onPress={() => {
setStringAsync(
`Build version: ${appVersion}; Bundle info: ${bundleInfo}; Bundle date: ${BUNDLE_DATE}; Platform: ${Platform.OS}; Platform version: ${Platform.Version}`,
+1 -1
View File
@@ -124,7 +124,7 @@ export function AccountSettingsScreen({}: Props) {
</SettingsList.PressableItem>
<SettingsList.PressableItem
label={_(msg`Handle`)}
accessibilityHint={_(msg`Open change handle dialog`)}
accessibilityHint={_(msg`Opens change handle dialog`)}
onPress={() => changeHandleControl.open()}>
<SettingsList.ItemIcon icon={AtIcon} />
<SettingsList.ItemText>
@@ -213,7 +213,7 @@ function AppIcon({icon, size = 50}: {icon: AppIconSet; size: number}) {
return (
<PressableScale
accessibilityLabel={icon.name}
accessibilityHint={_(msg`Tap to change app icon`)}
accessibilityHint={_(msg`Changes app icon`)}
targetScale={0.95}
onPress={() => {
if (isAndroid) {
+3 -3
View File
@@ -101,7 +101,7 @@ export function SettingsScreen({}: Props) {
<SettingsList.PressableItem
label={_(msg`Switch account`)}
accessibilityHint={_(
msg`Show other accounts you can switch to`,
msg`Shows other accounts you can switch to`,
)}
onPress={() => {
if (!reducedMotion) {
@@ -205,7 +205,7 @@ export function SettingsScreen({}: Props) {
<SettingsList.PressableItem
onPress={() => Linking.openURL(HELP_DESK_URL)}
label={_(msg`Help`)}
accessibilityHint={_(msg`Open helpdesk in browser`)}>
accessibilityHint={_(msg`Opens helpdesk in browser`)}>
<SettingsList.ItemIcon icon={CircleQuestionIcon} />
<SettingsList.ItemText>
<Trans>Help</Trans>
@@ -421,7 +421,7 @@ function AccountRow({
<View style={[{width: 28}]} />
)}
<SettingsList.ItemText>
<Trans>{sanitizeHandle(account.handle, '@')}</Trans>
{sanitizeHandle(account.handle, '@')}
</SettingsList.ItemText>
{pendingDid === account.did && <SettingsList.ItemIcon icon={Loader} />}
</SettingsList.PressableItem>
@@ -17,8 +17,8 @@ import {useMutation, useQueryClient} from '@tanstack/react-query'
import {HITSLOP_10} from '#/lib/constants'
import {cleanError} from '#/lib/strings/errors'
import {createFullHandle, validateServiceHandle} from '#/lib/strings/handles'
import {sanitizeHandle} from '#/lib/strings/handles'
import {createFullHandle, validateHandle} from '#/lib/strings/handles'
import {useFetchDid, useUpdateHandleMutation} from '#/state/queries/handle'
import {RQKEY as RQKEY_PROFILE} from '#/state/queries/profile'
import {useServiceQuery} from '#/state/queries/service'
@@ -172,7 +172,7 @@ function ProvidedHandlePage({
const host = serviceInfo.availableUserDomains[0]
const validation = useMemo(
() => validateHandle(subdomain, host, true),
() => validateServiceHandle(subdomain, host),
[subdomain, host],
)
@@ -537,7 +537,7 @@ function OwnHandlePage({goToServiceHandle}: {goToServiceHandle: () => void}) {
<Button
label={_(msg`Use default provider`)}
accessibilityHint={_(msg`Go back to previous page`)}
accessibilityHint={_(msg`Returns to previous page`)}
onPress={goToServiceHandle}
variant="outline"
color="secondary"
+3 -3
View File
@@ -7,7 +7,7 @@ import {logEvent} from '#/lib/statsig/statsig'
import {
createFullHandle,
maxServiceHandleLength,
validateHandle,
validateServiceHandle,
} from '#/lib/strings/handles'
import {useAgent} from '#/state/session'
import {ScreenTransition} from '#/screens/Login/ScreenTransition'
@@ -37,7 +37,7 @@ export function StepHandle() {
value: handle,
})
const newValidCheck = validateHandle(handle, state.userDomain)
const newValidCheck = validateServiceHandle(handle, state.userDomain)
if (!newValidCheck.overall) {
return
}
@@ -97,7 +97,7 @@ export function StepHandle() {
})
}, [dispatch, state.activeStep])
const validCheck = validateHandle(draftValue, state.userDomain, true)
const validCheck = validateServiceHandle(draftValue, state.userDomain)
return (
<ScreenTransition>
<View style={[a.gap_lg]}>
+1 -1
View File
@@ -285,7 +285,7 @@ function WizardInner({
<Layout.Header.Outer>
<Layout.Header.BackButton
label={_(msg`Back`)}
accessibilityHint={_(msg`Go back to the previous step`)}
accessibilityHint={_(msg`Returns to the previous step`)}
onPress={evt => {
if (state.currentStep !== 'Details') {
evt.preventDefault()
+4 -2
View File
@@ -806,7 +806,9 @@ function Overlay({
: _(msg`Follow ${handle}`)
}
accessibilityHint={
profile.viewer?.following ? _(msg`Unfollow user`) : ''
profile.viewer?.following
? _(msg`Unfollows the user`)
: ''
}
size="small"
variant="solid"
@@ -936,7 +938,7 @@ function ExpandableRichTextView({
/>
{constrained && !screenReaderEnabled && (
<Pressable
accessibilityHint={_(msg`Tap to expand or collapse post text.`)}
accessibilityHint={_(msg`Expands or collapses post text`)}
accessibilityLabel={expanded ? _(msg`Read less`) : _(msg`Read more`)}
hitSlop={HITSLOP_20}
onPress={() => setExpanded(prev => !prev)}
+9
View File
@@ -0,0 +1,9 @@
import {device, useStorage} from '#/storage'
export function useDevModeEnabled() {
const [devModeEnabled = false, setDevModeEnabled] = useStorage(device, [
'devMode',
])
return [devModeEnabled, setDevModeEnabled] as const
}
@@ -0,0 +1,20 @@
import {AppBskyActorDefs} from '@atproto/api'
import {useMutation, useQueryClient} from '@tanstack/react-query'
import {preferencesQueryKey} from '#/state/queries/preferences'
import {useAgent} from '#/state/session'
export function usePostInteractionSettingsMutation() {
const qc = useQueryClient()
const agent = useAgent()
return useMutation({
async mutationFn(props: AppBskyActorDefs.PostInteractionSettingsPref) {
await agent.setPostInteractionSettings(props)
},
async onSuccess() {
await qc.invalidateQueries({
queryKey: preferencesQueryKey,
})
},
})
}
+4
View File
@@ -39,4 +39,8 @@ export const DEFAULT_LOGGED_OUT_PREFERENCES: UsePreferencesQueryResponse = {
activeProgressGuide: undefined,
nuxs: [],
},
postInteractionSettings: {
threadgateAllowRules: undefined,
postgateEmbeddingRules: [],
},
}
+9 -1
View File
@@ -11,6 +11,7 @@ import {
Un$Typed,
} from '@atproto/api'
import {
keepPreviousData,
QueryClient,
useMutation,
useQuery,
@@ -84,7 +85,13 @@ export function useProfileQuery({
})
}
export function useProfilesQuery({handles}: {handles: string[]}) {
export function useProfilesQuery({
handles,
maintainData,
}: {
handles: string[]
maintainData?: boolean
}) {
const agent = useAgent()
return useQuery({
staleTime: STALE.MINUTES.FIVE,
@@ -93,6 +100,7 @@ export function useProfilesQuery({handles}: {handles: string[]}) {
const res = await agent.getProfiles({actors: handles})
return res.data
},
placeholderData: maintainData ? keepPreviousData : undefined,
})
}
+1
View File
@@ -4,3 +4,4 @@ export type ThreadgateAllowUISetting =
| {type: 'mention'}
| {type: 'following'}
| {type: 'list'; list: string}
| {type: 'followers'}
+4
View File
@@ -46,6 +46,8 @@ export function threadgateRecordToAllowUISetting(
setting = {type: 'following'}
} else if (AppBskyFeedThreadgate.isListRule(allow)) {
setting = {type: 'list', list: allow.list}
} else if (AppBskyFeedThreadgate.isFollowerRule(allow)) {
setting = {type: 'followers'}
}
return setting
})
@@ -76,6 +78,8 @@ export function threadgateAllowUISettingToAllowRecordValue(
allow.push({$type: 'app.bsky.feed.threadgate#mentionRule'})
} else if (rule.type === 'following') {
allow.push({$type: 'app.bsky.feed.threadgate#followingRule'})
} else if (rule.type === 'followers') {
allow.push({$type: 'app.bsky.feed.threadgate#followerRule'})
} else if (rule.type === 'list') {
allow.push({
$type: 'app.bsky.feed.threadgate#listRule',
+3 -16
View File
@@ -1,6 +1,5 @@
import React from 'react'
import {useGate} from '#/lib/statsig/statsig'
import {useLanguagePrefs} from '#/state/preferences/languages'
import {useServiceConfigQuery} from '#/state/queries/service-config'
import {device} from '#/storage'
@@ -14,7 +13,6 @@ const Context = React.createContext<Context>({
})
export function Provider({children}: React.PropsWithChildren<{}>) {
const gate = useGate()
const langPrefs = useLanguagePrefs()
const {data: config, isLoading: isInitialLoad} = useServiceConfigQuery()
const ctx = React.useMemo<Context>(() => {
@@ -45,23 +43,12 @@ export function Provider({children}: React.PropsWithChildren<{}>) {
* the server, we can exit early.
*/
const enabled = Boolean(config?.topicsEnabled)
if (!enabled) {
// cache for next reload
device.set(['trendingBetaEnabled'], enabled)
return {enabled: false}
}
/*
* Service is enabled, but also check statsig in case we're rolling back.
*/
const gateEnabled = gate('trending_topics_beta')
const _enabled = enabled && gateEnabled
// update cache
device.set(['trendingBetaEnabled'], _enabled)
device.set(['trendingBetaEnabled'], enabled)
return {enabled: _enabled}
}, [isInitialLoad, config, gate, langPrefs.contentLanguages])
return {enabled}
}, [isInitialLoad, config, langPrefs.contentLanguages])
return <Context.Provider value={ctx}>{children}</Context.Provider>
}
+69 -3
View File
@@ -1,5 +1,5 @@
import {useCallback, useEffect, useState} from 'react'
import {MMKV} from 'react-native-mmkv'
import {Did} from '@atproto/api'
import {Account, Device} from '#/storage/schema'
@@ -65,6 +65,72 @@ export class Storage<Scopes extends unknown[], Schema> {
removeMany<Key extends keyof Schema>(scopes: [...Scopes], keys: Key[]) {
keys.forEach(key => this.remove([...scopes, key]))
}
/**
* Fires a callback when the storage associated with a given key changes
*
* @returns Listener - call `remove()` to stop listening
*/
addOnValueChangedListener<Key extends keyof Schema>(
scopes: [...Scopes, Key],
callback: () => void,
) {
return this.store.addOnValueChangedListener(key => {
if (key === scopes.join(this.sep)) {
callback()
}
})
}
}
type StorageSchema<T extends Storage<any, any>> = T extends Storage<
any,
infer U
>
? U
: never
type StorageScopes<T extends Storage<any, any>> = T extends Storage<
infer S,
any
>
? S
: never
/**
* Hook to use a storage instance. Acts like a useState hook, but persists the
* value in storage.
*/
export function useStorage<
Store extends Storage<any, any>,
Key extends keyof StorageSchema<Store>,
>(
storage: Store,
scopes: [...StorageScopes<Store>, Key],
): [
StorageSchema<Store>[Key] | undefined,
(data: StorageSchema<Store>[Key]) => void,
] {
type Schema = StorageSchema<Store>
const [value, setValue] = useState<Schema[Key] | undefined>(() =>
storage.get(scopes),
)
useEffect(() => {
const sub = storage.addOnValueChangedListener(scopes, () => {
setValue(storage.get(scopes))
})
return () => sub.remove()
}, [storage, scopes])
const setter = useCallback(
(data: Schema[Key]) => {
setValue(data)
storage.set(scopes, data)
},
[storage, scopes],
)
return [value, setter] as const
}
/**
@@ -77,10 +143,10 @@ export const device = new Storage<[], Device>({id: 'bsky_device'})
/**
* Account data that's specific to the account on this device
*/
export const account = new Storage<[Did], Account>({id: 'bsky_account'})
export const account = new Storage<[string], Account>({id: 'bsky_account'})
if (__DEV__ && typeof window !== 'undefined') {
// @ts-ignore
// @ts-expect-error - dev global
window.bsky_storage = {
device,
account,
+2
View File
@@ -9,8 +9,10 @@ export type Device = {
countryCode: string | undefined
}
trendingBetaEnabled: boolean
devMode: boolean
}
export type Account = {
searchTermHistory?: string[]
searchAccountHistory?: string[]
}
+33
View File
@@ -187,6 +187,7 @@ input:focus {
animation: rotate 500ms linear infinite;
}
/* animations for atoms */
@keyframes fadeIn {
from {
opacity: 0;
@@ -205,6 +206,38 @@ input:focus {
}
}
@keyframes zoomIn {
from {
transform: scale(0.95);
}
to {
transform: scale(1);
}
}
@keyframes slideInLeft {
from {
transform: translateX(-100%);
}
to {
transform: translateX(0);
}
}
@keyframes slideOutLeft {
from {
transform: translateX(0);
}
to {
transform: translateX(-100%);
}
}
/* animating radix dropdowns requires knowing the data attributes */
.dropdown-menu-transform-origin > * {
transform-origin: var(--radix-dropdown-menu-content-transform-origin);
}
.force-no-clicks > *,
.force-no-clicks * {
pointer-events: none !important;
+10 -2
View File
@@ -83,6 +83,7 @@ import {
useLanguagePrefs,
useLanguagePrefsApi,
} from '#/state/preferences/languages'
import {usePreferencesQuery} from '#/state/queries/preferences'
import {useProfileQuery} from '#/state/queries/profile'
import {Gif} from '#/state/queries/tenor'
import {useAgent, useSession} from '#/state/session'
@@ -169,6 +170,7 @@ export const ComposePost = ({
const discardPromptControl = Prompt.usePromptControl()
const {closeAllDialogs} = useDialogStateControlContext()
const {closeAllModals} = useModalControls()
const {data: preferences} = usePreferencesQuery()
const [isKeyboardVisible] = useIsKeyboardVisible({iosUseWillEvents: true})
const [isPublishing, setIsPublishing] = useState(false)
@@ -177,7 +179,13 @@ export const ComposePost = ({
const [composerState, composerDispatch] = useReducer(
composerReducer,
{initImageUris, initQuoteUri: initQuote?.uri, initText, initMention},
{
initImageUris,
initQuoteUri: initQuote?.uri,
initText,
initMention,
initInteractionSettings: preferences?.postInteractionSettings,
},
createComposerState,
)
@@ -1233,7 +1241,7 @@ function ComposerFooter({
onPress={onEmojiButtonPress}
style={a.p_sm}
label={_(msg`Open emoji picker`)}
accessibilityHint={_(msg`Open emoji picker`)}
accessibilityHint={_(msg`Opens emoji picker`)}
variant="ghost"
shape="round"
color="primary">
+1 -1
View File
@@ -70,7 +70,7 @@ export function ComposerReplyTo({replyTo}: {replyTo: ComposerOptsPostRef}) {
msg`Expand or collapse the full post you are replying to`,
)}
accessibilityHint={_(
msg`Expand or collapse the full post you are replying to`,
msg`Expands or collapses the full post you are replying to`,
)}>
<PreviewableUserAvatar
size={50}
+21 -4
View File
@@ -1,5 +1,10 @@
import {ImagePickerAsset} from 'expo-image-picker'
import {AppBskyFeedPostgate, AppBskyRichtextFacet, RichText} from '@atproto/api'
import {
AppBskyFeedPostgate,
AppBskyRichtextFacet,
BskyPreferences,
RichText,
} from '@atproto/api'
import {nanoid} from 'nanoid/non-secure'
import {SelfLabel} from '#/lib/moderation'
@@ -13,7 +18,7 @@ import {
import {ComposerImage, createInitialImages} from '#/state/gallery'
import {createPostgateRecord} from '#/state/queries/postgate/util'
import {Gif} from '#/state/queries/tenor'
import {threadgateViewToAllowUISetting} from '#/state/queries/threadgate'
import {threadgateRecordToAllowUISetting} from '#/state/queries/threadgate'
import {ThreadgateAllowUISetting} from '#/state/queries/threadgate'
import {ComposerOpts} from '#/state/shell/composer'
import {
@@ -477,11 +482,15 @@ export function createComposerState({
initMention,
initImageUris,
initQuoteUri,
initInteractionSettings,
}: {
initText: string | undefined
initMention: string | undefined
initImageUris: ComposerOpts['imageUris']
initQuoteUri: string | undefined
initInteractionSettings:
| BskyPreferences['postInteractionSettings']
| undefined
}): ComposerState {
let media: ImagesMedia | undefined
if (initImageUris?.length) {
@@ -591,8 +600,16 @@ export function createComposerState({
},
},
],
postgate: createPostgateRecord({post: ''}),
threadgate: threadgateViewToAllowUISetting(undefined),
postgate: createPostgateRecord({
post: '',
embeddingRules: initInteractionSettings?.postgateEmbeddingRules || [],
}),
threadgate: threadgateRecordToAllowUISetting({
$type: 'app.bsky.feed.threadgate',
post: '',
createdAt: new Date().toString(),
allow: initInteractionSettings?.threadgateAllowRules,
}),
},
}
}
@@ -135,7 +135,7 @@ export function EmojiPicker({state, close, pinToTop}: IProps) {
<Pressable
accessible
accessibilityLabel={_(msg`Close emoji picker`)}
accessibilityHint={_(msg`Tap to close the emoji picker`)}
accessibilityHint={_(msg`Closes the emoji picker`)}
onPress={close}
style={[a.fixed, a.inset_0]}
/>
@@ -171,7 +171,7 @@ export function EmojiPicker({state, close, pinToTop}: IProps) {
<Pressable
accessible
accessibilityLabel={_(msg`Close emoji picker`)}
accessibilityHint={_(msg`Tap to close the emoji picker`)}
accessibilityHint={_(msg`Closes the emoji picker`)}
onPress={close}
style={[a.fixed, a.inset_0]}
/>
+1
View File
@@ -120,6 +120,7 @@ export function ModalsContainer() {
}
handleIndicatorStyle={{backgroundColor: pal.text.color}}
handleStyle={[styles.handle, pal.view]}
backgroundStyle={pal.view}
onChange={onBottomSheetChange}>
{element}
</BottomSheet>
+7 -2
View File
@@ -242,6 +242,11 @@ let PostThreadItemLoaded = ({
return makeProfileLink(post.author, 'post', urip.rkey, 'quotes')
}, [post.uri, post.author])
const quotesTitle = _(msg`Quotes of this post`)
const onlyFollowersCanReply = !!threadgateRecord?.allow?.find(
rule => rule.$type === 'app.bsky.feed.threadgate#followerRule',
)
const showFollowButton =
currentAccount?.did !== post.author.did && !onlyFollowersCanReply
const translatorUrl = getTranslatorLink(
record?.text || '',
@@ -344,7 +349,7 @@ let PostThreadItemLoaded = ({
</Text>
</Link>
</View>
{currentAccount?.did !== post.author.did && (
{showFollowButton && (
<View>
<PostThreadFollowBtn did={post.author.did} />
</View>
@@ -806,7 +811,7 @@ function BackdatedPostIndicator({post}: {post: AppBskyFeedDefs.PostView}) {
<Button
label={_(msg`Archived post`)}
accessibilityHint={_(
msg`Show information about when this post was created`,
msg`Shows information about when this post was created`,
)}
onPress={e => {
e.preventDefault()
+36 -1
View File
@@ -6,11 +6,12 @@ import {useQueryClient} from '@tanstack/react-query'
import {HITSLOP_20} from '#/lib/constants'
import {makeProfileLink} from '#/lib/routes/links'
import {shareUrl} from '#/lib/sharing'
import {shareText, shareUrl} from '#/lib/sharing'
import {toShareUrl} from '#/lib/strings/url-helpers'
import {logger} from '#/logger'
import {Shadow} from '#/state/cache/types'
import {useModalControls} from '#/state/modals'
import {useDevModeEnabled} from '#/state/preferences/dev-mode'
import {
RQKEY as profileQueryKey,
useProfileBlockMutationQueue,
@@ -52,6 +53,7 @@ let ProfileMenu = ({
const isBlocked = profile.viewer?.blocking || profile.viewer?.blockedBy
const isFollowingBlockedAccount = isFollowing && isBlocked
const isLabelerAndNotBlocked = !!profile.associated?.labeler && !isBlocked
const [devModeEnabled] = useDevModeEnabled()
const [queueMute, queueUnmute] = useProfileMuteMutationQueue(profile)
const [queueBlock, queueUnblock] = useProfileBlockMutationQueue(profile)
@@ -167,6 +169,14 @@ let ProfileMenu = ({
reportDialogControl.open()
}, [reportDialogControl])
const onPressShareATUri = React.useCallback(() => {
shareText(`at://${profile.did}`)
}, [profile.did])
const onPressShareDID = React.useCallback(() => {
shareText(profile.did)
}, [profile.did])
return (
<EventStopper onKeyDown={false}>
<Menu.Root>
@@ -308,6 +318,31 @@ let ProfileMenu = ({
</Menu.Group>
</>
)}
{devModeEnabled ? (
<>
<Menu.Divider />
<Menu.Group>
<Menu.Item
testID="profileHeaderDropdownShareATURIBtn"
label={_(msg`Copy at:// URI`)}
onPress={onPressShareATUri}>
<Menu.ItemText>
<Trans>Copy at:// URI</Trans>
</Menu.ItemText>
<Menu.ItemIcon icon={Share} />
</Menu.Item>
<Menu.Item
testID="profileHeaderDropdownShareDIDBtn"
label={_(msg`Copy DID`)}
onPress={onPressShareDID}>
<Menu.ItemText>
<Trans>Copy DID</Trans>
</Menu.ItemText>
<Menu.ItemIcon icon={Share} />
</Menu.Item>
</Menu.Group>
</>
) : null}
</Menu.Outer>
</Menu.Root>
+37 -31
View File
@@ -14,6 +14,7 @@ import {useLingui} from '@lingui/react'
import {usePalette} from '#/lib/hooks/usePalette'
import {useTheme} from '#/lib/ThemeContext'
import * as Layout from '#/components/Layout'
import {Text} from '../text/Text'
export function ErrorMessage({
@@ -31,39 +32,44 @@ export function ErrorMessage({
const pal = usePalette('error')
const {_} = useLingui()
return (
<View testID="errorMessageView" style={[styles.outer, pal.view, style]}>
<View
style={[styles.errorIcon, {backgroundColor: theme.palette.error.icon}]}>
<FontAwesomeIcon
icon="exclamation"
style={pal.text as FontAwesomeIconStyle}
size={16}
/>
</View>
<Text
type="sm-medium"
style={[styles.message, pal.text]}
numberOfLines={numberOfLines}>
{message}
</Text>
{onPressTryAgain && (
<TouchableOpacity
testID="errorMessageTryAgainButton"
style={styles.btn}
onPress={onPressTryAgain}
accessibilityRole="button"
accessibilityLabel={_(msg`Retry`)}
accessibilityHint={_(
msg`Retries the last action, which errored out`,
)}>
<Layout.Center>
<View testID="errorMessageView" style={[styles.outer, pal.view, style]}>
<View
style={[
styles.errorIcon,
{backgroundColor: theme.palette.error.icon},
]}>
<FontAwesomeIcon
icon="arrows-rotate"
style={{color: theme.palette.error.icon}}
size={18}
icon="exclamation"
style={pal.text as FontAwesomeIconStyle}
size={16}
/>
</TouchableOpacity>
)}
</View>
</View>
<Text
type="sm-medium"
style={[styles.message, pal.text]}
numberOfLines={numberOfLines}>
{message}
</Text>
{onPressTryAgain && (
<TouchableOpacity
testID="errorMessageTryAgainButton"
style={styles.btn}
onPress={onPressTryAgain}
accessibilityRole="button"
accessibilityLabel={_(msg`Retry`)}
accessibilityHint={_(
msg`Retries the last action, which errored out`,
)}>
<FontAwesomeIcon
icon="arrows-rotate"
style={{color: theme.palette.error.icon}}
size={18}
/>
</TouchableOpacity>
)}
</View>
</Layout.Center>
)
}
+3 -1
View File
@@ -293,7 +293,9 @@ const DropdownItems = ({
onPress={() => onPressItem(index)}
accessibilityRole="button"
accessibilityLabel={item.label}
accessibilityHint={_(msg`Option ${index + 1} of ${numItems}`)}>
accessibilityHint={_(
msg`Selects option ${index + 1} of ${numItems}`,
)}>
{item.icon && (
<FontAwesomeIcon
style={styles.icon}
@@ -21,7 +21,7 @@ import {useOpenLink} from '#/lib/hooks/useOpenLink'
import {getCurrentRoute} from '#/lib/routes/helpers'
import {makeProfileLink} from '#/lib/routes/links'
import {CommonNavigatorParams, NavigationProp} from '#/lib/routes/types'
import {shareUrl} from '#/lib/sharing'
import {shareText, shareUrl} from '#/lib/sharing'
import {logEvent} from '#/lib/statsig/statsig'
import {richTextToString} from '#/lib/strings/rich-text-helpers'
import {toShareUrl} from '#/lib/strings/url-helpers'
@@ -33,6 +33,7 @@ import {useProfileShadow} from '#/state/cache/profile-shadow'
import {useFeedFeedbackContext} from '#/state/feed-feedback'
import {useLanguagePrefs} from '#/state/preferences'
import {useHiddenPosts, useHiddenPostsApi} from '#/state/preferences'
import {useDevModeEnabled} from '#/state/preferences/dev-mode'
import {usePinnedPostMutation} from '#/state/queries/pinned-post'
import {
usePostDeleteMutation,
@@ -122,6 +123,7 @@ let PostDropdownMenuItems = ({
const hideReplyConfirmControl = useDialogControl()
const {mutateAsync: toggleReplyVisibility} =
useToggleReplyVisibilityMutation()
const [devModeEnabled] = useDevModeEnabled()
const postUri = post.uri
const postCid = post.cid
@@ -366,6 +368,14 @@ let PostDropdownMenuItems = ({
}
}, [_, queueBlock])
const onShareATURI = useCallback(() => {
shareText(postUri)
}, [postUri])
const onShareAuthorDID = useCallback(() => {
shareText(postAuthor.did)
}, [postAuthor.did])
return (
<>
<Menu.Outer>
@@ -647,6 +657,28 @@ let PostDropdownMenuItems = ({
</>
)}
</Menu.Group>
{devModeEnabled ? (
<>
<Menu.Divider />
<Menu.Group>
<Menu.Item
testID="postAtUriShareBtn"
label={_(msg`Copy post at:// URI`)}
onPress={onShareATURI}>
<Menu.ItemText>{_(msg`Copy post at:// URI`)}</Menu.ItemText>
<Menu.ItemIcon icon={Share} position="right" />
</Menu.Item>
<Menu.Item
testID="postAuthorDIDShareBtn"
label={_(msg`Copy author DID`)}
onPress={onShareAuthorDID}>
<Menu.ItemText>{_(msg`Copy author DID`)}</Menu.ItemText>
<Menu.ItemIcon icon={Share} position="right" />
</Menu.Item>
</Menu.Group>
</>
) : null}
</>
)}
</Menu.Outer>
+2 -2
View File
@@ -196,7 +196,7 @@ export function AutoSizedImage({
onPressIn={onPressIn}
// alt here is what screen readers actually use
accessibilityLabel={image.alt}
accessibilityHint={_(msg`Tap to view full image`)}
accessibilityHint={_(msg`Views full image`)}
style={[
a.w_full,
a.rounded_md,
@@ -218,7 +218,7 @@ export function AutoSizedImage({
onPressIn={onPressIn}
// alt here is what screen readers actually use
accessibilityLabel={image.alt}
accessibilityHint={_(msg`Tap to view full image`)}
accessibilityHint={_(msg`Views full image`)}
style={[a.h_full]}>
{contents}
</Pressable>
+12 -2
View File
@@ -26,6 +26,7 @@ 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 {useProfileShadow} from '#/state/cache/profile-shadow'
import {Shadow} from '#/state/cache/types'
import {useFeedFeedbackContext} from '#/state/feed-feedback'
import {
@@ -94,6 +95,15 @@ let PostCtrls = ({
post.author.viewer?.blockingByList,
)
const shadowedAuthor = useProfileShadow(post.author)
const followersCanReply = !!threadgateRecord?.allow?.find(
rule => rule.$type === 'app.bsky.feed.threadgate#followerRule',
)
const canOverrideReplyDisabled =
followersCanReply &&
shadowedAuthor.viewer?.following?.startsWith('at://did')
const replyDisabled = post.viewer?.replyDisabled && !canOverrideReplyDisabled
const shouldShowLoggedOutWarning = React.useMemo(() => {
return (
post.author.did !== currentAccount?.did &&
@@ -247,13 +257,13 @@ let PostCtrls = ({
<View
style={[
big ? a.align_center : [a.flex_1, a.align_start, {marginLeft: -6}],
post.viewer?.replyDisabled ? {opacity: 0.5} : undefined,
replyDisabled ? {opacity: 0.5} : undefined,
]}>
<Pressable
testID="replyBtn"
style={btnStyle}
onPress={() => {
if (!post.viewer?.replyDisabled) {
if (!replyDisabled) {
playHaptic('Light')
requireAuth(() => onPressReply())
}
@@ -56,7 +56,7 @@ function PlaceholderOverlay({
<Pressable
accessibilityRole="button"
accessibilityLabel={_(msg`Play Video`)}
accessibilityHint={_(msg`Play Video`)}
accessibilityHint={_(msg`Plays the video`)}
onPress={onPress}
style={[styles.overlayContainer]}>
{!isPlayerActive ? (
+1 -1
View File
@@ -39,7 +39,7 @@ function PlaybackControls({
return (
<Pressable
accessibilityRole="button"
accessibilityHint={_(msg`Play or pause the GIF`)}
accessibilityHint={_(msg`Plays or pauses the GIF`)}
accessibilityLabel={isPlaying ? _(msg`Pause`) : _(msg`Play`)}
style={[
a.absolute,
@@ -132,13 +132,13 @@ function VideoControls({
onPress={enterFullscreen}
style={a.flex_1}
accessibilityLabel={_(msg`Video`)}
accessibilityHint={_(msg`Tap to enter full screen`)}
accessibilityHint={_(msg`Enters full screen`)}
accessibilityRole="button"
/>
<ControlButton
onPress={togglePlayback}
label={isPlaying ? _(msg`Pause`) : _(msg`Play`)}
accessibilityHint={_(msg`Tap to play or pause`)}
accessibilityHint={_(msg`Plays or pauses the video`)}
style={{left: 6}}>
{isPlaying ? (
<PauseIcon width={13} fill={t.palette.white} />
@@ -155,7 +155,7 @@ function VideoControls({
? _(msg({message: `Unmute`, context: 'video'}))
: _(msg({message: `Mute`, context: 'video'}))
}
accessibilityHint={_(msg`Tap to toggle sound`)}
accessibilityHint={_(msg`Toggles the sound`)}
style={{right: 6}}>
{muted ? (
<MuteIcon width={13} fill={t.palette.white} />
+1 -1
View File
@@ -1007,7 +1007,7 @@ function ErrorScreen({error}: {error: string}) {
<Button
type="default"
accessibilityLabel={_(msg`Go back`)}
accessibilityHint={_(msg`Return to previous page`)}
accessibilityHint={_(msg`Returns to previous page`)}
onPress={onPressBack}
style={{flexShrink: 1}}>
<Text type="button" style={pal.text}>
+237 -285
View File
@@ -1,4 +1,4 @@
import React from 'react'
import React, {useCallback, useLayoutEffect, useMemo} from 'react'
import {
ActivityIndicator,
Image,
@@ -10,7 +10,6 @@ import {
View,
} from 'react-native'
import {ScrollView as RNGHScrollView} from 'react-native-gesture-handler'
import RNPickerSelect from 'react-native-picker-select'
import {AppBskyActorDefs, AppBskyFeedDefs, moderateProfile} from '@atproto/api'
import {
FontAwesomeIcon,
@@ -18,11 +17,10 @@ import {
} from '@fortawesome/react-native-fontawesome'
import {msg, Trans} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import AsyncStorage from '@react-native-async-storage/async-storage'
import {useFocusEffect, useNavigation} from '@react-navigation/native'
import {APP_LANGUAGES, LANGUAGES} from '#/lib/../locale/languages'
import {createHitslop} from '#/lib/constants'
import {createHitslop, HITSLOP_20} from '#/lib/constants'
import {HITSLOP_10} from '#/lib/constants'
import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback'
import {usePalette} from '#/lib/hooks/usePalette'
@@ -37,7 +35,6 @@ import {
import {sanitizeDisplayName} from '#/lib/strings/display-names'
import {augmentSearchQuery} from '#/lib/strings/helpers'
import {languageName} from '#/locale/helpers'
import {logger} from '#/logger'
import {isNative, isWeb} from '#/platform/detection'
import {listenSoftReset} from '#/state/events'
import {useLanguagePrefs} from '#/state/preferences/languages'
@@ -45,9 +42,9 @@ import {useModerationOpts} from '#/state/preferences/moderation-opts'
import {useActorAutocompleteQuery} from '#/state/queries/actor-autocomplete'
import {useActorSearch} from '#/state/queries/actor-search'
import {usePopularFeedsSearch} from '#/state/queries/feed'
import {useProfilesQuery} from '#/state/queries/profile'
import {useSearchPostsQuery} from '#/state/queries/search-posts'
import {useSession} from '#/state/session'
import {useSetDrawerOpen} from '#/state/shell'
import {useSetMinimalShellMode} from '#/state/shell'
import {Pager} from '#/view/com/pager/Pager'
import {TabBar} from '#/view/com/pager/TabBar'
@@ -61,17 +58,24 @@ import {SearchLinkCard, SearchProfileCard} from '#/view/shell/desktop/Search'
import {makeSearchQuery, parseSearchQuery} from '#/screens/Search/utils'
import {
atoms as a,
native,
platform,
tokens,
useBreakpoints,
useTheme as useThemeNew,
useTheme,
web,
} from '#/alf'
import {Button, ButtonIcon, ButtonText} from '#/components/Button'
import * as FeedCard from '#/components/FeedCard'
import {SearchInput} from '#/components/forms/SearchInput'
import {ChevronBottom_Stroke2_Corner0_Rounded as ChevronDown} from '#/components/icons/Chevron'
import {Menu_Stroke2_Corner0_Rounded as Menu} from '#/components/icons/Menu'
import {
ChevronBottom_Stroke2_Corner0_Rounded as ChevronDownIcon,
ChevronTopBottom_Stroke2_Corner0_Rounded as ChevronUpDownIcon,
} from '#/components/icons/Chevron'
import {Earth_Stroke2_Corner0_Rounded as EarthIcon} from '#/components/icons/Globe'
import * as Layout from '#/components/Layout'
import * as Menu from '#/components/Menu'
import {account, useStorage} from '#/storage'
function Loader() {
return (
@@ -278,7 +282,7 @@ let SearchScreenFeedsResults = ({
query: string
active: boolean
}): React.ReactNode => {
const t = useThemeNew()
const t = useTheme()
const {_} = useLingui()
const {data: results, isFetched} = usePopularFeedsSearch({
@@ -323,103 +327,95 @@ function SearchLanguageDropdown({
value: string
onChange(value: string): void
}) {
const t = useThemeNew()
const {_} = useLingui()
const {appLanguage, contentLanguages} = useLanguagePrefs()
const items = React.useMemo(() => {
return [
{
label: _(msg`Any language`),
inputLabel: _(msg`Any language`),
value: '',
key: '*',
},
].concat(
LANGUAGES.filter(
(lang, index, self) =>
Boolean(lang.code2) && // reduce to the code2 varieties
index === self.findIndex(t => t.code2 === lang.code2), // remove dupes (which will happen)
)
.map(l => ({
label: languageName(l, appLanguage),
inputLabel: languageName(l, appLanguage),
value: l.code2,
key: l.code2 + l.code3,
}))
.sort((a, b) => {
// prioritize user's languages
const aIsUser = contentLanguages.includes(a.value)
const bIsUser = contentLanguages.includes(b.value)
if (aIsUser && !bIsUser) return -1
if (bIsUser && !aIsUser) return 1
// prioritize "common" langs in the network
const aIsCommon = !!APP_LANGUAGES.find(al => al.code2 === a.value)
const bIsCommon = !!APP_LANGUAGES.find(al => al.code2 === b.value)
if (aIsCommon && !bIsCommon) return -1
if (bIsCommon && !aIsCommon) return 1
// fall back to alphabetical
return a.label.localeCompare(b.label)
}),
const languages = useMemo(() => {
return LANGUAGES.filter(
(lang, index, self) =>
Boolean(lang.code2) && // reduce to the code2 varieties
index === self.findIndex(t => t.code2 === lang.code2), // remove dupes (which will happen)
)
}, [_, appLanguage, contentLanguages])
.map(l => ({
label: languageName(l, appLanguage),
value: l.code2,
key: l.code2 + l.code3,
}))
.sort((a, b) => {
// prioritize user's languages
const aIsUser = contentLanguages.includes(a.value)
const bIsUser = contentLanguages.includes(b.value)
if (aIsUser && !bIsUser) return -1
if (bIsUser && !aIsUser) return 1
// prioritize "common" langs in the network
const aIsCommon = !!APP_LANGUAGES.find(al => al.code2 === a.value)
const bIsCommon = !!APP_LANGUAGES.find(al => al.code2 === b.value)
if (aIsCommon && !bIsCommon) return -1
if (bIsCommon && !aIsCommon) return 1
// fall back to alphabetical
return a.label.localeCompare(b.label)
})
}, [appLanguage, contentLanguages])
const style = {
backgroundColor: t.atoms.bg_contrast_25.backgroundColor,
color: t.atoms.text.color,
fontSize: a.text_xs.fontSize,
fontFamily: 'inherit',
fontWeight: a.font_bold.fontWeight,
paddingHorizontal: 14,
paddingRight: 32,
paddingVertical: 8,
borderRadius: a.rounded_full.borderRadius,
borderWidth: a.border.borderWidth,
borderColor: t.atoms.border_contrast_low.borderColor,
}
const currentLanguageLabel =
languages.find(lang => lang.value === value)?.label ?? _(msg`All languages`)
return (
<RNPickerSelect
darkTheme={t.scheme === 'dark'}
placeholder={{}}
value={value}
onValueChange={onChange}
items={items}
Icon={() => (
<ChevronDown fill={t.atoms.text_contrast_low.color} size="sm" />
)}
useNativeAndroidPickerStyle={false}
style={{
iconContainer: {
pointerEvents: 'none',
right: a.px_sm.paddingRight,
top: 0,
bottom: 0,
display: 'flex',
justifyContent: 'center',
},
inputAndroid: {
...style,
paddingVertical: 2,
},
inputIOS: {
...style,
},
inputWeb: web({
...style,
cursor: 'pointer',
// @ts-ignore web only
'-moz-appearance': 'none',
'-webkit-appearance': 'none',
appearance: 'none',
outline: 0,
borderWidth: 0,
overflow: 'hidden',
whiteSpace: 'nowrap',
textOverflow: 'ellipsis',
}),
}}
/>
<Menu.Root>
<Menu.Trigger
label={_(
msg`Filter search by language (currently: ${currentLanguageLabel})`,
)}>
{({props}) => (
<Button
{...props}
label={props.accessibilityLabel}
size="small"
color={platform({native: 'primary', default: 'secondary'})}
variant={platform({native: 'ghost', default: 'solid'})}
style={native([
a.py_sm,
a.px_sm,
{marginRight: tokens.space.sm * -1},
])}>
<ButtonIcon icon={EarthIcon} />
<ButtonText>{currentLanguageLabel}</ButtonText>
<ButtonIcon
icon={platform({
native: ChevronUpDownIcon,
default: ChevronDownIcon,
})}
/>
</Button>
)}
</Menu.Trigger>
<Menu.Outer
// HACKFIX: Currently there is no height limit for Radix dropdowns,
// so if it's too tall it just goes off screen. TODO: fix internally -sfn
style={web({maxHeight: '70vh'})}>
<Menu.LabelText>
<Trans>Filter search by language</Trans>
</Menu.LabelText>
<Menu.Item label={_(msg`All languages`)} onPress={() => onChange('')}>
<Menu.ItemText>
<Trans>All languages</Trans>
</Menu.ItemText>
<Menu.ItemRadio selected={value === ''} />
</Menu.Item>
<Menu.Divider />
<Menu.Group>
{languages.map(lang => (
<Menu.Item
key={lang.key}
label={lang.label}
onPress={() => onChange(lang.value)}>
<Menu.ItemText>{lang.label}</Menu.ItemText>
<Menu.ItemRadio selected={value === lang.value} />
</Menu.Item>
))}
</Menu.Group>
</Menu.Outer>
</Menu.Root>
)
}
@@ -534,12 +530,7 @@ let SearchScreenInner = ({
<Pager
onPageSelected={onPageSelected}
renderTabBar={props => (
<Layout.Center
style={[
a.z_10,
web([a.sticky]),
{top: isWeb ? headerHeight : undefined},
]}>
<Layout.Center style={[a.z_10, web([a.sticky, {top: headerHeight}])]}>
<TabBar items={sections.map(section => section.title)} {...props} />
</Layout.Center>
)}
@@ -597,13 +588,13 @@ SearchScreenInner = React.memo(SearchScreenInner)
export function SearchScreen(
props: NativeStackScreenProps<SearchTabNavigatorParams, 'Search'>,
) {
const t = useThemeNew()
const t = useTheme()
const {gtMobile} = useBreakpoints()
const navigation = useNavigation<NavigationProp>()
const textInput = React.useRef<TextInput>(null)
const {_} = useLingui()
const setDrawerOpen = useSetDrawerOpen()
const setMinimalShellMode = useSetMinimalShellMode()
const {currentAccount} = useSession()
// Query terms
const queryParam = props.route?.params?.q ?? ''
@@ -612,20 +603,72 @@ export function SearchScreen(
useActorAutocompleteQuery(searchText, true)
const [showAutocomplete, setShowAutocomplete] = React.useState(false)
const [searchHistory, setSearchHistory] = React.useState<string[]>([])
const [selectedProfiles, setSelectedProfiles] = React.useState<
AppBskyActorDefs.ProfileViewBasic[]
>([])
const [termHistory = [], setTermHistory] = useStorage(account, [
currentAccount?.did ?? 'pwi',
'searchTermHistory',
] as const)
const [accountHistory = [], setAccountHistory] = useStorage(account, [
currentAccount?.did ?? 'pwi',
'searchAccountHistory',
])
const {data: accountHistoryProfiles} = useProfilesQuery({
handles: accountHistory,
maintainData: true,
})
const updateSearchHistory = useCallback(
async (item: string) => {
if (!item) return
const newSearchHistory = [
item,
...termHistory.filter(search => search !== item),
].slice(0, 6)
setTermHistory(newSearchHistory)
},
[termHistory, setTermHistory],
)
const updateProfileHistory = useCallback(
async (item: AppBskyActorDefs.ProfileViewBasic) => {
const newAccountHistory = [
item.did,
...accountHistory.filter(p => p !== item.did),
].slice(0, 5)
setAccountHistory(newAccountHistory)
},
[accountHistory, setAccountHistory],
)
const deleteSearchHistoryItem = useCallback(
async (item: string) => {
setTermHistory(termHistory.filter(search => search !== item))
},
[termHistory, setTermHistory],
)
const deleteProfileHistoryItem = useCallback(
async (item: AppBskyActorDefs.ProfileViewBasic) => {
setAccountHistory(accountHistory.filter(p => p !== item.did))
},
[accountHistory, setAccountHistory],
)
const {params, query, queryWithParams} = useQueryManager({
initialQuery: queryParam,
})
const showFilters = Boolean(queryWithParams && !showAutocomplete)
/*
* Arbitrary sizing, so guess and check, used for sticky header alignment and
* sizing.
*/
const headerHeight = 60 + (showFilters ? 40 : 0)
// web only - measure header height for sticky positioning
const [headerHeight, setHeaderHeight] = React.useState(0)
const headerRef = React.useRef(null)
useLayoutEffect(() => {
if (isWeb) {
if (!headerRef.current) return
const measurement = (headerRef.current as Element).getBoundingClientRect()
setHeaderHeight(measurement.height)
}
}, [])
useFocusEffect(
useNonReactiveCallback(() => {
@@ -635,30 +678,6 @@ export function SearchScreen(
}),
)
React.useEffect(() => {
const loadSearchHistory = async () => {
try {
const history = await AsyncStorage.getItem('searchHistory')
if (history !== null) {
setSearchHistory(JSON.parse(history))
}
const profiles = await AsyncStorage.getItem('selectedProfiles')
if (profiles !== null) {
setSelectedProfiles(JSON.parse(profiles))
}
} catch (e: any) {
logger.error('Failed to load search history', {message: e})
}
}
loadSearchHistory()
}, [])
const onPressMenu = React.useCallback(() => {
textInput.current?.blur()
setDrawerOpen(true)
}, [setDrawerOpen])
const onPressClearQuery = React.useCallback(() => {
scrollToTopWeb()
setSearchText('')
@@ -670,57 +689,6 @@ export function SearchScreen(
setSearchText(text)
}, [])
const updateSearchHistory = React.useCallback(
async (newQuery: string) => {
newQuery = newQuery.trim()
if (newQuery) {
let newHistory = [
newQuery,
...searchHistory.filter(q => q !== newQuery),
]
if (newHistory.length > 5) {
newHistory = newHistory.slice(0, 5)
}
setSearchHistory(newHistory)
try {
await AsyncStorage.setItem(
'searchHistory',
JSON.stringify(newHistory),
)
} catch (e: any) {
logger.error('Failed to save search history', {message: e})
}
}
},
[searchHistory, setSearchHistory],
)
const updateSelectedProfiles = React.useCallback(
async (profile: AppBskyActorDefs.ProfileViewBasic) => {
let newProfiles = [
profile,
...selectedProfiles.filter(p => p.did !== profile.did),
]
if (newProfiles.length > 5) {
newProfiles = newProfiles.slice(0, 5)
}
setSelectedProfiles(newProfiles)
try {
await AsyncStorage.setItem(
'selectedProfiles',
JSON.stringify(newProfiles),
)
} catch (e: any) {
logger.error('Failed to save selected profiles', {message: e})
}
},
[selectedProfiles, setSelectedProfiles],
)
const navigateToItem = React.useCallback(
(item: string) => {
scrollToTopWeb()
@@ -774,10 +742,10 @@ export function SearchScreen(
(profile: AppBskyActorDefs.ProfileViewBasic) => {
// Slight delay to avoid updating during push nav animation.
setTimeout(() => {
updateSelectedProfiles(profile)
updateProfileHistory(profile)
}, 400)
},
[updateSelectedProfiles],
[updateProfileHistory],
)
const onSoftReset = React.useCallback(() => {
@@ -798,36 +766,6 @@ export function SearchScreen(
}, [onSoftReset, setMinimalShellMode]),
)
const handleRemoveHistoryItem = React.useCallback(
(itemToRemove: string) => {
const updatedHistory = searchHistory.filter(item => item !== itemToRemove)
setSearchHistory(updatedHistory)
AsyncStorage.setItem(
'searchHistory',
JSON.stringify(updatedHistory),
).catch(e => {
logger.error('Failed to update search history', {message: e})
})
},
[searchHistory],
)
const handleRemoveProfile = React.useCallback(
(profileToRemove: AppBskyActorDefs.ProfileViewBasic) => {
const updatedProfiles = selectedProfiles.filter(
profile => profile.did !== profileToRemove.did,
)
setSelectedProfiles(updatedProfiles)
AsyncStorage.setItem(
'selectedProfiles',
JSON.stringify(updatedProfiles),
).catch(e => {
logger.error('Failed to update selected profiles', {message: e})
})
},
[selectedProfiles],
)
const onSearchInputFocus = React.useCallback(() => {
if (isWeb) {
// Prevent a jump on iPad by ensuring that
@@ -843,75 +781,89 @@ export function SearchScreen(
return (
<Layout.Screen testID="searchScreen">
<View
ref={headerRef}
onLayout={evt => {
if (isWeb) setHeaderHeight(evt.nativeEvent.layout.height)
}}
style={[
a.relative,
a.z_10,
web({
height: headerHeight,
position: 'sticky',
top: 0,
zIndex: 1,
}),
]}>
<Layout.Center>
<View style={[a.p_md, a.pb_sm, a.gap_sm, t.atoms.bg]}>
<View style={[a.flex_row, a.gap_sm]}>
{!gtMobile && !showAutocomplete && (
<Button
testID="viewHeaderBackOrMenuBtn"
onPress={onPressMenu}
hitSlop={HITSLOP_10}
label={_(msg`Menu`)}
accessibilityHint={_(
msg`Access navigation links and settings`,
)}
size="large"
variant="solid"
color="secondary"
shape="square">
<ButtonIcon icon={Menu} size="lg" />
</Button>
)}
<View style={[a.flex_1]}>
<SearchInput
ref={textInput}
value={searchText}
onFocus={onSearchInputFocus}
onChangeText={onChangeText}
onClearText={onPressClearQuery}
onSubmitEditing={onSubmit}
/>
</View>
{showAutocomplete && (
<Button
label={_(msg`Cancel search`)}
size="large"
variant="ghost"
color="secondary"
style={[a.px_sm]}
onPress={onPressCancelSearch}
hitSlop={HITSLOP_10}>
<ButtonText>
<Trans>Cancel</Trans>
</ButtonText>
</Button>
)}
<Layout.Center style={t.atoms.bg}>
{!gtMobile && (
<View
// HACK: shift up search input. we can't remove the top padding
// on the search input because it messes up the layout animation
// if we add it only when the header is hidden
style={{marginBottom: tokens.space.xs * -1}}>
<Layout.Header.Outer noBottomBorder>
<Layout.Header.MenuButton />
<Layout.Header.Content align="left">
<Layout.Header.TitleText>
<Trans>Search</Trans>
</Layout.Header.TitleText>
</Layout.Header.Content>
{showFilters ? (
<SearchLanguageDropdown
value={params.lang}
onChange={params.setLang}
/>
) : (
<Layout.Header.Slot />
)}
</Layout.Header.Outer>
</View>
)}
<View style={[a.px_md, a.pt_sm, a.pb_sm, a.overflow_hidden]}>
<View style={[a.gap_sm]}>
<View style={[a.w_full, a.flex_row, a.align_stretch, a.gap_xs]}>
<View style={[a.flex_1]}>
<SearchInput
ref={textInput}
value={searchText}
onFocus={onSearchInputFocus}
onChangeText={onChangeText}
onClearText={onPressClearQuery}
onSubmitEditing={onSubmit}
placeholder={_(msg`Search for posts, users, or feeds`)}
hitSlop={{...HITSLOP_20, top: 0}}
/>
</View>
{showAutocomplete && (
<Button
label={_(msg`Cancel search`)}
size="large"
variant="ghost"
color="secondary"
style={[a.px_sm]}
onPress={onPressCancelSearch}
hitSlop={HITSLOP_10}>
<ButtonText>
<Trans>Cancel</Trans>
</ButtonText>
</Button>
)}
</View>
{showFilters && (
<View
style={[
a.flex_row,
a.align_center,
a.justify_between,
a.gap_sm,
]}>
<View style={[{width: 140}]}>
{showFilters && gtMobile && (
<View
style={[
a.flex_row,
a.align_center,
a.justify_between,
a.gap_sm,
]}>
<SearchLanguageDropdown
value={params.lang}
onChange={params.setLang}
/>
</View>
</View>
)}
)}
</View>
</View>
</Layout.Center>
</View>
@@ -932,12 +884,12 @@ export function SearchScreen(
/>
) : (
<SearchHistory
searchHistory={searchHistory}
selectedProfiles={selectedProfiles}
searchHistory={termHistory}
selectedProfiles={accountHistoryProfiles?.profiles || []}
onItemClick={handleHistoryItemClick}
onProfileClick={handleProfileClick}
onRemoveItemClick={handleRemoveHistoryItem}
onRemoveProfileClick={handleRemoveProfile}
onRemoveItemClick={deleteSearchHistoryItem}
onRemoveProfileClick={deleteProfileHistoryItem}
/>
)}
</View>
@@ -1087,7 +1039,7 @@ function SearchHistory({
accessibilityRole="button"
accessibilityLabel={_(msg`Remove profile`)}
accessibilityHint={_(
msg`Remove profile from search history`,
msg`Removes profile from search history`,
)}
onPress={() => onRemoveProfileClick(profile)}
hitSlop={createHitslop(6)}
+19 -14
View File
@@ -5,6 +5,7 @@ import {useFocusGuards} from '@radix-ui/react-focus-guards'
import {FocusScope} from '@radix-ui/react-focus-scope'
import {RemoveScrollBar} from 'react-remove-scroll-bar'
import {useA11y} from '#/state/a11y'
import {useModals} from '#/state/modals'
import {ComposerOpts, useComposerState} from '#/state/shell/composer'
import {
@@ -12,7 +13,7 @@ import {
EmojiPickerPosition,
EmojiPickerState,
} from '#/view/com/composer/text-input/web/EmojiPicker.web'
import {useBreakpoints, useTheme} from '#/alf'
import {atoms as a, flatten, useBreakpoints, useTheme} from '#/alf'
import {ComposePost, useComposerCancelRef} from '../com/composer/Composer'
const BOTTOM_BAR_HEIGHT = 61
@@ -41,6 +42,7 @@ function Inner({state}: {state: ComposerOpts}) {
const {isModalActive} = useModals()
const t = useTheme()
const {gtMobile} = useBreakpoints()
const {reduceMotionEnabled} = useA11y()
const [pickerState, setPickerState] = React.useState<EmojiPickerState>({
isOpen: false,
pos: {top: 0, left: 0, right: 0, bottom: 0, nextFocusRef: null},
@@ -71,17 +73,15 @@ function Inner({state}: {state: ComposerOpts}) {
<DismissableLayer
role="dialog"
aria-modal
style={{
position: 'fixed',
top: 0,
left: 0,
width: '100%',
height: '100%',
backgroundColor: '#000c',
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
}}
style={flatten([
{position: 'fixed'},
a.inset_0,
{backgroundColor: '#000c'},
a.flex,
a.flex_col,
a.align_center,
!reduceMotionEnabled && a.fade_in,
])}
onFocusOutside={evt => evt.preventDefault()}
onInteractOutside={evt => evt.preventDefault()}
onDismiss={() => {
@@ -96,6 +96,11 @@ function Inner({state}: {state: ComposerOpts}) {
!gtMobile && styles.containerMobile,
t.atoms.bg,
t.atoms.border_contrast_medium,
!reduceMotionEnabled && [
a.zoom_fade_in,
{animationDelay: 0.1},
{animationFillMode: 'backwards'},
],
]}>
<ComposePost
cancelRef={ref}
@@ -123,14 +128,14 @@ const styles = StyleSheet.create({
borderRadius: 8,
marginBottom: 0,
borderWidth: 1,
// @ts-ignore web only
// @ts-expect-error web only
maxHeight: 'calc(100% - (40px * 2))',
overflow: 'hidden',
},
containerMobile: {
borderRadius: 0,
marginBottom: BOTTOM_BAR_HEIGHT,
// @ts-ignore web only
// @ts-expect-error web only
maxHeight: `calc(100% - ${BOTTOM_BAR_HEIGHT}px)`,
},
})
+5 -2
View File
@@ -33,7 +33,7 @@ import {LoadingPlaceholder} from '#/view/com/util/LoadingPlaceholder'
import {PressableWithHover} from '#/view/com/util/PressableWithHover'
import {UserAvatar} from '#/view/com/util/UserAvatar'
import {NavSignupCard} from '#/view/shell/NavSignupCard'
import {atoms as a, tokens, useBreakpoints, useTheme} from '#/alf'
import {atoms as a, tokens, useBreakpoints, useTheme, web} from '#/alf'
import {Button, ButtonIcon, ButtonText} from '#/components/Button'
import {DialogControlProps} from '#/components/Dialog'
import {ArrowBoxLeft_Stroke2_Corner0_Rounded as LeaveIcon} from '#/components/icons/ArrowBoxLeft'
@@ -235,7 +235,10 @@ function SwitchMenuItems({
closeEverything()
}
return (
<Menu.Outer>
<Menu.Outer
// HACKFIX: Currently there is no height limit for Radix dropdowns,
// so if it's too tall it just goes off screen. TODO: fix internally -sfn
style={web({maxHeight: '70vh'})}>
{accounts && accounts.length > 0 && (
<>
<Menu.Group>
+33 -12
View File
@@ -1,4 +1,4 @@
import React, {useEffect} from 'react'
import {useEffect, useLayoutEffect, useState} from 'react'
import {StyleSheet, TouchableWithoutFeedback, View} from 'react-native'
import {msg} from '@lingui/macro'
import {useLingui} from '@lingui/react'
@@ -33,6 +33,20 @@ function ShellInner() {
const closeAllActiveElements = useCloseAllActiveElements()
const {_} = useLingui()
const showDrawer = !isDesktop && isDrawerOpen
const [showDrawerDelayedExit, setShowDrawerDelayedExit] = useState(showDrawer)
useLayoutEffect(() => {
if (showDrawer !== showDrawerDelayedExit) {
if (showDrawer) {
setShowDrawerDelayedExit(true)
} else {
const timeout = setTimeout(() => {
setShowDrawerDelayedExit(false)
}, 160)
return () => clearTimeout(timeout)
}
}
}, [showDrawer, showDrawerDelayedExit])
useComposerKeyboardShortcut()
useIntentHandler()
@@ -56,7 +70,7 @@ function ShellInner() {
<Lightbox />
<PortalOutlet />
{showDrawer && (
{showDrawerDelayedExit && (
<>
<RemoveScrollBar />
<TouchableWithoutFeedback
@@ -66,20 +80,27 @@ function ShellInner() {
setDrawerOpen(false)
}
}}
accessibilityLabel={_(msg`Close navigation footer`)}
accessibilityHint={_(msg`Closes bottom navigation bar`)}>
accessibilityLabel={_(msg`Close drawer menu`)}
accessibilityHint="">
<View
style={[
styles.drawerMask,
{
backgroundColor: select(t.name, {
light: 'rgba(0, 57, 117, 0.1)',
dark: 'rgba(1, 82, 168, 0.1)',
dim: 'rgba(10, 13, 16, 0.8)',
}),
backgroundColor: showDrawer
? select(t.name, {
light: 'rgba(0, 57, 117, 0.1)',
dark: 'rgba(1, 82, 168, 0.1)',
dim: 'rgba(10, 13, 16, 0.8)',
})
: 'transparent',
},
a.transition_color,
]}>
<View style={styles.drawerContainer}>
<View
style={[
styles.drawerContainer,
showDrawer ? a.slide_in_left : a.slide_out_left,
]}>
<DrawerContent />
</View>
</View>
@@ -109,7 +130,6 @@ const styles = StyleSheet.create({
backgroundColor: colors.black, // TODO
},
drawerMask: {
// @ts-ignore web only
position: 'fixed',
width: '100%',
height: '100%',
@@ -118,10 +138,11 @@ const styles = StyleSheet.create({
},
drawerContainer: {
display: 'flex',
// @ts-ignore web only
position: 'fixed',
top: 0,
left: 0,
height: '100%',
width: 330,
maxWidth: '80%',
},
})
+1 -1
View File
@@ -3,7 +3,7 @@
"compilerOptions": {
"jsx": "react-jsx",
"module": "esnext",
"types": ["node"],
"types": ["node", "jest"],
"paths": {
"#/*": ["./src/*"],
"lib/*": ["./src/lib/*"],
+1 -1
View File
@@ -277,7 +277,7 @@
multiformats "^9.9.0"
zod "^3.23.8"
"@atproto/lexicon@^0.4.7":
"@atproto/lexicon@^0.4.4", "@atproto/lexicon@^0.4.7":
version "0.4.7"
resolved "https://registry.yarnpkg.com/@atproto/lexicon/-/lexicon-0.4.7.tgz#f5d31615c21bcfd3e655f1e4f11a40a62fea9f86"
integrity sha512-/x6h3tAiDNzSi4eXtC8ke65B7UzsagtlGRHmUD95698x5lBRpDnpizj0fZWTZVYed5qnOmz/ZEue+v3wDmO61g==