+2
-1
.eslintrc.js
+2
-1
.eslintrc.js
···
37
37
'Toast.Action',
38
38
'AgeAssuranceAdmonition',
39
39
'Span',
40
+
'StackedButton',
40
41
],
41
42
impliedTextProps: [],
42
43
suggestedTextWrappers: {
···
88
89
'no-unused-vars': 'off',
89
90
'@typescript-eslint/no-unused-vars': [
90
91
'error',
91
-
{argsIgnorePattern: '^_', varsIgnorePattern: '^_'},
92
+
{argsIgnorePattern: '^_', varsIgnorePattern: '^_.+'},
92
93
],
93
94
'@typescript-eslint/consistent-type-imports': [
94
95
'warn',
+2
README.md
+2
README.md
···
70
70
71
71
See [./LICENSE](./LICENSE) for the full license.
72
72
73
+
Bluesky Social PBC has committed to a software patent non-aggression pledge. For details see [the original announcement](https://bsky.social/about/blog/10-01-2025-patent-pledge).
74
+
73
75
## P.S.
74
76
75
77
We โค๏ธ you and all of the ways you support us. Thank you for making Bluesky a great place!
+8
-12
jest/test-utils.tsx
+8
-12
jest/test-utils.tsx
···
1
-
import React from 'react'
2
-
import {render} from '@testing-library/react-native'
3
1
import {GestureHandlerRootView} from 'react-native-gesture-handler'
4
-
import {RootSiblingParent} from 'react-native-root-siblings'
5
2
import {SafeAreaProvider} from 'react-native-safe-area-context'
6
-
import {RootStoreProvider, RootStoreModel} from '../src/state'
3
+
import {render} from '@testing-library/react-native'
4
+
7
5
import {ThemeProvider} from '../src/lib/ThemeContext'
6
+
import {type RootStoreModel, RootStoreProvider} from '../src/state'
8
7
9
8
const customRender = (ui: any, rootStore: RootStoreModel) =>
10
9
render(
11
-
// eslint-disable-next-line react-native/no-inline-styles
12
10
<GestureHandlerRootView style={{flex: 1}}>
13
-
<RootSiblingParent>
14
-
<RootStoreProvider value={rootStore}>
15
-
<ThemeProvider theme="light">
16
-
<SafeAreaProvider>{ui}</SafeAreaProvider>
17
-
</ThemeProvider>
18
-
</RootStoreProvider>
19
-
</RootSiblingParent>
11
+
<RootStoreProvider value={rootStore}>
12
+
<ThemeProvider theme="light">
13
+
<SafeAreaProvider>{ui}</SafeAreaProvider>
14
+
</ThemeProvider>
15
+
</RootStoreProvider>
20
16
</GestureHandlerRootView>,
21
17
)
22
18
+2
-3
package.json
+2
-3
package.json
···
197
197
"react-native-progress": "bluesky-social/react-native-progress",
198
198
"react-native-qrcode-styled": "^0.3.3",
199
199
"react-native-reanimated": "^3.19.1",
200
-
"react-native-root-siblings": "^5.0.1",
201
200
"react-native-safe-area-context": "~5.6.0",
202
201
"react-native-screens": "~4.16.0",
203
202
"react-native-svg": "15.12.1",
···
247
246
"babel-jest": "^29.7.0",
248
247
"babel-plugin-macros": "^3.1.0",
249
248
"babel-plugin-module-resolver": "^5.0.2",
250
-
"babel-plugin-react-compiler": "^19.1.0-rc.1",
249
+
"babel-plugin-react-compiler": "^19.1.0-rc.3",
251
250
"babel-preset-expo": "~54.0.0",
252
251
"eslint": "^8.19.0",
253
252
"eslint-plugin-bsky-internal": "link:./eslint",
···
255
254
"eslint-plugin-import": "^2.31.0",
256
255
"eslint-plugin-lingui": "^0.2.0",
257
256
"eslint-plugin-react": "^7.33.2",
258
-
"eslint-plugin-react-compiler": "^19.1.0-rc.1",
257
+
"eslint-plugin-react-compiler": "^19.1.0-rc.2",
259
258
"eslint-plugin-react-native-a11y": "^3.3.0",
260
259
"eslint-plugin-simple-import-sort": "^12.0.0",
261
260
"file-loader": "6.2.0",
+61
-60
src/App.native.tsx
+61
-60
src/App.native.tsx
···
4
4
5
5
import React, {useEffect, useState} from 'react'
6
6
import {GestureHandlerRootView} from 'react-native-gesture-handler'
7
-
import {RootSiblingParent} from 'react-native-root-siblings'
8
7
import {
9
8
initialWindowMetrics,
10
9
SafeAreaProvider,
···
84
83
}
85
84
if (isAndroid) {
86
85
// iOS is handled by the config plugin -sfn
87
-
ScreenOrientation.lockAsync(ScreenOrientation.OrientationLock.PORTRAIT_UP)
86
+
ScreenOrientation.lockAsync(
87
+
ScreenOrientation.OrientationLock.PORTRAIT_UP,
88
+
).catch(error =>
89
+
logger.debug('Could not lock orientation', {safeMessage: error}),
90
+
)
88
91
}
89
92
90
93
/**
···
133
136
<ThemeProvider theme={theme}>
134
137
<ContextMenuProvider>
135
138
<Splash isReady={isReady && hasCheckedReferrer}>
136
-
<RootSiblingParent>
137
-
<VideoVolumeProvider>
138
-
<React.Fragment
139
-
// Resets the entire tree below when it changes:
140
-
key={currentAccount?.did}>
141
-
<QueryProvider currentDid={currentAccount?.did}>
142
-
<PolicyUpdateOverlayProvider>
143
-
<StatsigProvider>
144
-
<AgeAssuranceProvider>
145
-
<ComposerProvider>
146
-
<MessagesProvider>
147
-
{/* LabelDefsProvider MUST come before ModerationOptsProvider */}
148
-
<LabelDefsProvider>
149
-
<ModerationOptsProvider>
150
-
<LoggedOutViewProvider>
151
-
<SelectedFeedProvider>
152
-
<HiddenRepliesProvider>
153
-
<HomeBadgeProvider>
154
-
<UnreadNotifsProvider>
155
-
<BackgroundNotificationPreferencesProvider>
156
-
<MutedThreadsProvider>
157
-
<ProgressGuideProvider>
158
-
<ServiceAccountManager>
159
-
<EmailVerificationProvider>
160
-
<HideBottomBarBorderProvider>
161
-
<GestureHandlerRootView
162
-
style={s.h100pct}>
163
-
<GlobalGestureEventsProvider>
164
-
<IntentDialogProvider>
165
-
<TestCtrls />
166
-
<Shell />
167
-
<NuxDialogs />
168
-
<ToastOutlet />
169
-
</IntentDialogProvider>
170
-
</GlobalGestureEventsProvider>
171
-
</GestureHandlerRootView>
172
-
</HideBottomBarBorderProvider>
173
-
</EmailVerificationProvider>
174
-
</ServiceAccountManager>
175
-
</ProgressGuideProvider>
176
-
</MutedThreadsProvider>
177
-
</BackgroundNotificationPreferencesProvider>
178
-
</UnreadNotifsProvider>
179
-
</HomeBadgeProvider>
180
-
</HiddenRepliesProvider>
181
-
</SelectedFeedProvider>
182
-
</LoggedOutViewProvider>
183
-
</ModerationOptsProvider>
184
-
</LabelDefsProvider>
185
-
</MessagesProvider>
186
-
</ComposerProvider>
187
-
</AgeAssuranceProvider>
188
-
</StatsigProvider>
189
-
</PolicyUpdateOverlayProvider>
190
-
</QueryProvider>
191
-
</React.Fragment>
192
-
</VideoVolumeProvider>
193
-
</RootSiblingParent>
139
+
<VideoVolumeProvider>
140
+
<React.Fragment
141
+
// Resets the entire tree below when it changes:
142
+
key={currentAccount?.did}>
143
+
<QueryProvider currentDid={currentAccount?.did}>
144
+
<PolicyUpdateOverlayProvider>
145
+
<StatsigProvider>
146
+
<AgeAssuranceProvider>
147
+
<ComposerProvider>
148
+
<MessagesProvider>
149
+
{/* LabelDefsProvider MUST come before ModerationOptsProvider */}
150
+
<LabelDefsProvider>
151
+
<ModerationOptsProvider>
152
+
<LoggedOutViewProvider>
153
+
<SelectedFeedProvider>
154
+
<HiddenRepliesProvider>
155
+
<HomeBadgeProvider>
156
+
<UnreadNotifsProvider>
157
+
<BackgroundNotificationPreferencesProvider>
158
+
<MutedThreadsProvider>
159
+
<ProgressGuideProvider>
160
+
<ServiceAccountManager>
161
+
<EmailVerificationProvider>
162
+
<HideBottomBarBorderProvider>
163
+
<GestureHandlerRootView
164
+
style={s.h100pct}>
165
+
<GlobalGestureEventsProvider>
166
+
<IntentDialogProvider>
167
+
<TestCtrls />
168
+
<Shell />
169
+
<NuxDialogs />
170
+
<ToastOutlet />
171
+
</IntentDialogProvider>
172
+
</GlobalGestureEventsProvider>
173
+
</GestureHandlerRootView>
174
+
</HideBottomBarBorderProvider>
175
+
</EmailVerificationProvider>
176
+
</ServiceAccountManager>
177
+
</ProgressGuideProvider>
178
+
</MutedThreadsProvider>
179
+
</BackgroundNotificationPreferencesProvider>
180
+
</UnreadNotifsProvider>
181
+
</HomeBadgeProvider>
182
+
</HiddenRepliesProvider>
183
+
</SelectedFeedProvider>
184
+
</LoggedOutViewProvider>
185
+
</ModerationOptsProvider>
186
+
</LabelDefsProvider>
187
+
</MessagesProvider>
188
+
</ComposerProvider>
189
+
</AgeAssuranceProvider>
190
+
</StatsigProvider>
191
+
</PolicyUpdateOverlayProvider>
192
+
</QueryProvider>
193
+
</React.Fragment>
194
+
</VideoVolumeProvider>
194
195
</Splash>
195
196
</ContextMenuProvider>
196
197
</ThemeProvider>
+54
-57
src/App.web.tsx
+54
-57
src/App.web.tsx
···
3
3
import './style.css'
4
4
5
5
import React, {useEffect, useState} from 'react'
6
-
import {RootSiblingParent} from 'react-native-root-siblings'
7
6
import {SafeAreaProvider} from 'react-native-safe-area-context'
8
7
import {msg} from '@lingui/macro'
9
8
import {useLingui} from '@lingui/react'
···
111
110
<Alf theme={theme}>
112
111
<ThemeProvider theme={theme}>
113
112
<ContextMenuProvider>
114
-
<RootSiblingParent>
115
-
<VideoVolumeProvider>
116
-
<ActiveVideoProvider>
117
-
<React.Fragment
118
-
// Resets the entire tree below when it changes:
119
-
key={currentAccount?.did}>
120
-
<QueryProvider currentDid={currentAccount?.did}>
121
-
<PolicyUpdateOverlayProvider>
122
-
<StatsigProvider>
123
-
<AgeAssuranceProvider>
124
-
<ComposerProvider>
125
-
<MessagesProvider>
126
-
{/* LabelDefsProvider MUST come before ModerationOptsProvider */}
127
-
<LabelDefsProvider>
128
-
<ModerationOptsProvider>
129
-
<LoggedOutViewProvider>
130
-
<SelectedFeedProvider>
131
-
<HiddenRepliesProvider>
132
-
<HomeBadgeProvider>
133
-
<UnreadNotifsProvider>
134
-
<BackgroundNotificationPreferencesProvider>
135
-
<MutedThreadsProvider>
136
-
<SafeAreaProvider>
137
-
<ProgressGuideProvider>
138
-
<ServiceConfigProvider>
139
-
<EmailVerificationProvider>
140
-
<HideBottomBarBorderProvider>
141
-
<IntentDialogProvider>
142
-
<Shell />
143
-
<NuxDialogs />
144
-
<ToastOutlet />
145
-
</IntentDialogProvider>
146
-
</HideBottomBarBorderProvider>
147
-
</EmailVerificationProvider>
148
-
</ServiceConfigProvider>
149
-
</ProgressGuideProvider>
150
-
</SafeAreaProvider>
151
-
</MutedThreadsProvider>
152
-
</BackgroundNotificationPreferencesProvider>
153
-
</UnreadNotifsProvider>
154
-
</HomeBadgeProvider>
155
-
</HiddenRepliesProvider>
156
-
</SelectedFeedProvider>
157
-
</LoggedOutViewProvider>
158
-
</ModerationOptsProvider>
159
-
</LabelDefsProvider>
160
-
</MessagesProvider>
161
-
</ComposerProvider>
162
-
</AgeAssuranceProvider>
163
-
</StatsigProvider>
164
-
</PolicyUpdateOverlayProvider>
165
-
</QueryProvider>
166
-
</React.Fragment>
167
-
</ActiveVideoProvider>
168
-
</VideoVolumeProvider>
169
-
</RootSiblingParent>
113
+
<VideoVolumeProvider>
114
+
<ActiveVideoProvider>
115
+
<React.Fragment
116
+
// Resets the entire tree below when it changes:
117
+
key={currentAccount?.did}>
118
+
<QueryProvider currentDid={currentAccount?.did}>
119
+
<PolicyUpdateOverlayProvider>
120
+
<StatsigProvider>
121
+
<AgeAssuranceProvider>
122
+
<ComposerProvider>
123
+
<MessagesProvider>
124
+
{/* LabelDefsProvider MUST come before ModerationOptsProvider */}
125
+
<LabelDefsProvider>
126
+
<ModerationOptsProvider>
127
+
<LoggedOutViewProvider>
128
+
<SelectedFeedProvider>
129
+
<HiddenRepliesProvider>
130
+
<HomeBadgeProvider>
131
+
<UnreadNotifsProvider>
132
+
<BackgroundNotificationPreferencesProvider>
133
+
<MutedThreadsProvider>
134
+
<SafeAreaProvider>
135
+
<ProgressGuideProvider>
136
+
<ServiceConfigProvider>
137
+
<EmailVerificationProvider>
138
+
<HideBottomBarBorderProvider>
139
+
<IntentDialogProvider>
140
+
<Shell />
141
+
<NuxDialogs />
142
+
<ToastOutlet />
143
+
</IntentDialogProvider>
144
+
</HideBottomBarBorderProvider>
145
+
</EmailVerificationProvider>
146
+
</ServiceConfigProvider>
147
+
</ProgressGuideProvider>
148
+
</SafeAreaProvider>
149
+
</MutedThreadsProvider>
150
+
</BackgroundNotificationPreferencesProvider>
151
+
</UnreadNotifsProvider>
152
+
</HomeBadgeProvider>
153
+
</HiddenRepliesProvider>
154
+
</SelectedFeedProvider>
155
+
</LoggedOutViewProvider>
156
+
</ModerationOptsProvider>
157
+
</LabelDefsProvider>
158
+
</MessagesProvider>
159
+
</ComposerProvider>
160
+
</AgeAssuranceProvider>
161
+
</StatsigProvider>
162
+
</PolicyUpdateOverlayProvider>
163
+
</QueryProvider>
164
+
</React.Fragment>
165
+
</ActiveVideoProvider>
166
+
</VideoVolumeProvider>
170
167
</ContextMenuProvider>
171
168
</ThemeProvider>
172
169
</Alf>
+10
-4
src/alf/util/systemUI.ts
+10
-4
src/alf/util/systemUI.ts
···
1
1
import * as SystemUI from 'expo-system-ui'
2
2
import {type Theme} from '@bsky.app/alf'
3
3
4
+
import {logger} from '#/logger'
4
5
import {isAndroid} from '#/platform/detection'
5
6
6
7
export function setSystemUITheme(themeType: 'theme' | 'lightbox', t: Theme) {
7
8
if (isAndroid) {
8
-
if (themeType === 'theme') {
9
-
SystemUI.setBackgroundColorAsync(t.atoms.bg.backgroundColor)
10
-
} else {
11
-
SystemUI.setBackgroundColorAsync('black')
9
+
try {
10
+
if (themeType === 'theme') {
11
+
SystemUI.setBackgroundColorAsync(t.atoms.bg.backgroundColor)
12
+
} else {
13
+
SystemUI.setBackgroundColorAsync('black')
14
+
}
15
+
} catch (error) {
16
+
// Can reject with 'The current activity is no longer available' - no big deal
17
+
logger.debug('Could not set system UI theme', {safeMessage: error})
12
18
}
13
19
}
14
20
}
+44
-24
src/components/Admonition.tsx
+44
-24
src/components/Admonition.tsx
···
3
3
4
4
import {atoms as a, useBreakpoints, useTheme} from '#/alf'
5
5
import {Button as BaseButton, type ButtonProps} from '#/components/Button'
6
-
import {CircleInfo_Stroke2_Corner0_Rounded as ErrorIcon} from '#/components/icons/CircleInfo'
7
-
import {Eye_Stroke2_Corner0_Rounded as InfoIcon} from '#/components/icons/Eye'
8
-
import {Leaf_Stroke2_Corner0_Rounded as TipIcon} from '#/components/icons/Leaf'
6
+
import {CircleInfo_Stroke2_Corner0_Rounded as CircleInfoIcon} from '#/components/icons/CircleInfo'
7
+
import {CircleX_Stroke2_Corner0_Rounded as CircleXIcon} from '#/components/icons/CircleX'
9
8
import {Warning_Stroke2_Corner0_Rounded as WarningIcon} from '#/components/icons/Warning'
10
9
import {Text as BaseText, type TextProps} from '#/components/Typography'
11
10
12
11
export const colors = {
13
-
warning: {
14
-
light: '#DFBC00',
15
-
dark: '#BFAF1F',
16
-
},
12
+
warning: '#FFC404',
17
13
}
18
14
19
15
type Context = {
···
29
25
const t = useTheme()
30
26
const {type} = useContext(Context)
31
27
const Icon = {
32
-
info: InfoIcon,
33
-
tip: TipIcon,
28
+
info: CircleInfoIcon,
29
+
tip: CircleInfoIcon,
34
30
warning: WarningIcon,
35
-
error: ErrorIcon,
31
+
error: CircleXIcon,
36
32
}[type]
37
33
const fill = {
38
34
info: t.atoms.text_contrast_medium.color,
39
35
tip: t.palette.primary_500,
40
-
warning: colors.warning.light,
36
+
warning: colors.warning,
41
37
error: t.palette.negative_500,
42
38
}[type]
43
39
return <Icon fill={fill} size="md" />
44
40
}
45
41
42
+
export function Content({
43
+
children,
44
+
style,
45
+
...rest
46
+
}: {
47
+
children: React.ReactNode
48
+
style?: StyleProp<ViewStyle>
49
+
}) {
50
+
return (
51
+
<View
52
+
style={[a.gap_sm, a.flex_1, {minHeight: 20}, a.justify_center, style]}
53
+
{...rest}>
54
+
{children}
55
+
</View>
56
+
)
57
+
}
58
+
46
59
export function Text({
47
60
children,
48
61
style,
49
62
...rest
50
63
}: Pick<TextProps, 'children' | 'style'>) {
51
64
return (
52
-
<BaseText
53
-
{...rest}
54
-
style={[a.flex_1, a.text_sm, a.leading_snug, a.pr_md, style]}>
65
+
<BaseText {...rest} style={[a.text_sm, a.leading_snug, a.pr_md, style]}>
55
66
{children}
56
67
</BaseText>
57
68
)
···
60
71
export function Button({
61
72
children,
62
73
...props
63
-
}: Omit<ButtonProps, 'size' | 'variant' | 'color'>) {
74
+
}: Omit<ButtonProps, 'size' | 'variant'>) {
64
75
return (
65
-
<BaseButton size="tiny" variant="outline" color="secondary" {...props}>
76
+
<BaseButton size="tiny" {...props}>
66
77
{children}
67
78
</BaseButton>
68
79
)
69
80
}
70
81
71
-
export function Row({children}: {children: React.ReactNode}) {
82
+
export function Row({
83
+
children,
84
+
style,
85
+
}: {
86
+
children: React.ReactNode
87
+
style?: StyleProp<ViewStyle>
88
+
}) {
72
89
return (
73
-
<View style={[a.flex_1, a.flex_row, a.align_center, a.gap_sm]}>
90
+
<View style={[a.flex_1, a.flex_row, a.align_start, a.gap_sm, style]}>
74
91
{children}
75
92
</View>
76
93
)
···
88
105
const t = useTheme()
89
106
const {gtMobile} = useBreakpoints()
90
107
const borderColor = {
91
-
info: t.atoms.border_contrast_low.borderColor,
92
-
tip: t.atoms.border_contrast_low.borderColor,
93
-
warning: t.atoms.border_contrast_low.borderColor,
94
-
error: t.atoms.border_contrast_low.borderColor,
108
+
info: t.atoms.border_contrast_high.borderColor,
109
+
tip: t.palette.primary_500,
110
+
warning: colors.warning,
111
+
error: t.palette.negative_500,
95
112
}[type]
96
113
return (
97
114
<Context.Provider value={{type}}>
98
115
<View
99
116
style={[
100
117
gtMobile ? a.p_md : a.p_sm,
118
+
a.p_md,
101
119
a.rounded_sm,
102
120
a.border,
103
-
t.atoms.bg_contrast_25,
121
+
t.atoms.bg,
104
122
{borderColor},
105
123
style,
106
124
]}>
···
123
141
<Outer type={type} style={style}>
124
142
<Row>
125
143
<Icon />
126
-
<Text>{children}</Text>
144
+
<Content>
145
+
<Text>{children}</Text>
146
+
</Content>
127
147
</Row>
128
148
</Outer>
129
149
)
+53
-41
src/components/Button.tsx
+53
-41
src/components/Button.tsx
···
274
274
} else if (color === 'primary_subtle') {
275
275
if (!disabled) {
276
276
baseStyles.push({
277
-
backgroundColor: select(t.name, {
278
-
light: t.palette.primary_50,
279
-
dim: t.palette.primary_100,
280
-
dark: t.palette.primary_100,
281
-
}),
277
+
backgroundColor: t.palette.primary_50,
282
278
})
283
279
hoverStyles.push({
284
-
backgroundColor: select(t.name, {
285
-
light: t.palette.primary_100,
286
-
dim: t.palette.primary_200,
287
-
dark: t.palette.primary_200,
288
-
}),
280
+
backgroundColor: t.palette.primary_100,
289
281
})
290
282
} else {
291
283
baseStyles.push({
···
295
287
} else if (color === 'negative_subtle') {
296
288
if (!disabled) {
297
289
baseStyles.push({
298
-
backgroundColor: select(t.name, {
299
-
light: t.palette.negative_50,
300
-
dim: t.palette.negative_100,
301
-
dark: t.palette.negative_100,
302
-
}),
290
+
backgroundColor: t.palette.negative_50,
303
291
})
304
292
hoverStyles.push({
305
-
backgroundColor: select(t.name, {
306
-
light: t.palette.negative_100,
307
-
dim: t.palette.negative_200,
308
-
dark: t.palette.negative_200,
309
-
}),
293
+
backgroundColor: t.palette.negative_100,
310
294
})
311
295
} else {
312
296
baseStyles.push({
···
618
602
} else if (color === 'primary_subtle') {
619
603
if (!disabled) {
620
604
baseStyles.push({
621
-
color: select(t.name, {
622
-
light: t.palette.primary_600,
623
-
dim: t.palette.primary_800,
624
-
dark: t.palette.primary_800,
625
-
}),
605
+
color: t.palette.primary_600,
626
606
})
627
607
} else {
628
608
baseStyles.push({
629
-
color: select(t.name, {
630
-
light: t.palette.primary_200,
631
-
dim: t.palette.primary_200,
632
-
dark: t.palette.primary_200,
633
-
}),
609
+
color: t.palette.primary_200,
634
610
})
635
611
}
636
612
} else if (color === 'negative_subtle') {
637
613
if (!disabled) {
638
614
baseStyles.push({
639
-
color: select(t.name, {
640
-
light: t.palette.negative_600,
641
-
dim: t.palette.negative_800,
642
-
dark: t.palette.negative_800,
643
-
}),
615
+
color: t.palette.negative_600,
644
616
})
645
617
} else {
646
618
baseStyles.push({
647
-
color: select(t.name, {
648
-
light: t.palette.negative_200,
649
-
dim: t.palette.negative_200,
650
-
dark: t.palette.negative_200,
651
-
}),
619
+
color: t.palette.negative_200,
652
620
})
653
621
}
654
622
}
···
755
723
} else if (size === 'small') {
756
724
baseStyles.push(a.text_sm, a.leading_snug, a.font_medium)
757
725
} else if (size === 'tiny') {
758
-
baseStyles.push(a.text_xs, a.leading_snug, a.font_medium)
726
+
baseStyles.push(a.text_xs, a.leading_snug, a.font_semi_bold)
759
727
}
760
728
761
729
return StyleSheet.flatten(baseStyles)
···
869
837
</View>
870
838
)
871
839
}
840
+
841
+
export type StackedButtonProps = Omit<
842
+
ButtonProps,
843
+
keyof VariantProps | 'children'
844
+
> &
845
+
Pick<VariantProps, 'color'> & {
846
+
children: React.ReactNode
847
+
icon: React.ComponentType<SVGIconProps>
848
+
}
849
+
850
+
export function StackedButton({children, ...props}: StackedButtonProps) {
851
+
return (
852
+
<Button
853
+
{...props}
854
+
size="tiny"
855
+
style={[
856
+
a.flex_col,
857
+
{
858
+
height: 72,
859
+
paddingHorizontal: 16,
860
+
borderRadius: 20,
861
+
gap: 4,
862
+
},
863
+
props.style,
864
+
]}>
865
+
<StackedButtonInnerText icon={props.icon}>
866
+
{children}
867
+
</StackedButtonInnerText>
868
+
</Button>
869
+
)
870
+
}
871
+
872
+
function StackedButtonInnerText({
873
+
children,
874
+
icon: Icon,
875
+
}: Pick<StackedButtonProps, 'icon' | 'children'>) {
876
+
const textStyles = useSharedButtonTextStyles()
877
+
return (
878
+
<>
879
+
<Icon width={24} fill={textStyles.color} />
880
+
<ButtonText>{children}</ButtonText>
881
+
</>
882
+
)
883
+
}
+2
-2
src/components/Link.tsx
+2
-2
src/components/Link.tsx
···
165
165
if (isNative && screen !== 'NotFound') {
166
166
const state = navigation.getState()
167
167
// if screen is not in the current navigator, it means it's
168
-
// most likely a tab screen
169
-
if (!state.routeNames.includes(screen)) {
168
+
// most likely a tab screen. note: state can be undefined
169
+
if (!state?.routeNames.includes(screen)) {
170
170
const parent = navigation.getParent()
171
171
if (
172
172
parent &&
-1
src/components/dialogs/StarterPackDialog.tsx
-1
src/components/dialogs/StarterPackDialog.tsx
+3
-1
src/components/moderation/LabelsOnMeDialog.tsx
+3
-1
src/components/moderation/LabelsOnMeDialog.tsx
···
32
32
33
33
export function LabelsOnMeDialog(props: LabelsOnMeDialogProps) {
34
34
return (
35
-
<Dialog.Outer control={props.control}>
35
+
<Dialog.Outer
36
+
control={props.control}
37
+
nativeOptions={{preventExpansion: true}}>
36
38
<Dialog.Handle />
37
39
<LabelsOnMeDialogInner {...props} />
38
40
</Dialog.Outer>
+3
-1
src/components/moderation/ModerationDetailsDialog.tsx
+3
-1
src/components/moderation/ModerationDetailsDialog.tsx
···
24
24
25
25
export function ModerationDetailsDialog(props: ModerationDetailsDialogProps) {
26
26
return (
27
-
<Dialog.Outer control={props.control}>
27
+
<Dialog.Outer
28
+
control={props.control}
29
+
nativeOptions={{preventExpansion: true}}>
28
30
<Dialog.Handle />
29
31
<ModerationDetailsDialogInner {...props} />
30
32
</Dialog.Outer>
+2
-1
src/components/moderation/ProfileHeaderAlerts.tsx
+2
-1
src/components/moderation/ProfileHeaderAlerts.tsx
···
6
6
7
7
export function ProfileHeaderAlerts({
8
8
moderation,
9
+
style,
9
10
}: {
10
11
moderation: ModerationDecision
11
12
style?: StyleProp<ViewStyle>
···
16
17
}
17
18
18
19
return (
19
-
<Pills.Row size="lg">
20
+
<Pills.Row size="lg" style={style}>
20
21
{modui.alerts.filter(unique).map(cause => (
21
22
<Pills.Label
22
23
size="lg"
+6
-3
src/components/moderation/ReportDialog/index.tsx
+6
-3
src/components/moderation/ReportDialog/index.tsx
···
219
219
<Admonition.Outer type="error">
220
220
<Admonition.Row>
221
221
<Admonition.Icon />
222
-
<Admonition.Text>
223
-
<Trans>Something went wrong, please try again</Trans>
224
-
</Admonition.Text>
222
+
<Admonition.Content>
223
+
<Admonition.Text>
224
+
<Trans>Something went wrong, please try again</Trans>
225
+
</Admonition.Text>
226
+
</Admonition.Content>
225
227
<Admonition.Button
228
+
color="negative_subtle"
226
229
label={_(msg`Retry loading report options`)}
227
230
onPress={() => refetchLabelers()}>
228
231
<ButtonText>
+1
-1
src/lib/hooks/useIntentHandler.ts
+1
-1
src/lib/hooks/useIntentHandler.ts
···
51
51
}
52
52
53
53
const urlp = new URL(url)
54
-
const [_, intent, intentType] = urlp.pathname.split('/')
54
+
const [__, intent, intentType] = urlp.pathname.split('/')
55
55
56
56
// On native, our links look like bluesky://intent/SomeIntent, so we have to check the hostname for the
57
57
// intent check. On web, we have to check the first part of the path since we have an actual hostname
+9
-4
src/lib/hooks/useOTAUpdates.ts
+9
-4
src/lib/hooks/useOTAUpdates.ts
···
10
10
useUpdates,
11
11
} from 'expo-updates'
12
12
13
+
import {isNetworkError} from '#/lib/strings/errors'
13
14
import {logger} from '#/logger'
14
15
import {isIOS} from '#/platform/detection'
15
16
import {IS_TESTFLIGHT} from '#/env'
···
145
146
} else {
146
147
logger.debug('No update available.')
147
148
}
148
-
} catch (e) {
149
-
logger.error('OTA Update Error', {error: `${e}`})
149
+
} catch (err) {
150
+
if (!isNetworkError(err)) {
151
+
logger.error('OTA Update Error', {safeMessage: err})
152
+
}
150
153
}
151
154
}, 10e3)
152
155
}, [])
···
154
157
const onIsTestFlight = React.useCallback(async () => {
155
158
try {
156
159
await updateTestflight()
157
-
} catch (e: any) {
158
-
logger.error('Internal OTA Update Error', {error: `${e}`})
160
+
} catch (err: any) {
161
+
if (!isNetworkError(err)) {
162
+
logger.error('Internal OTA Update Error', {safeMessage: err})
163
+
}
159
164
}
160
165
}, [])
161
166
+11
-11
src/lib/strings/embed-player.ts
+11
-11
src/lib/strings/embed-player.ts
···
105
105
urlp.hostname === 'm.youtube.com' ||
106
106
urlp.hostname === 'music.youtube.com'
107
107
) {
108
-
const [_, page, shortOrLiveVideoId] = urlp.pathname.split('/')
108
+
const [__, page, shortOrLiveVideoId] = urlp.pathname.split('/')
109
109
110
110
const isShorts = page === 'shorts'
111
111
const isLive = page === 'live'
···
137
137
window.location.hostname
138
138
: 'localhost'
139
139
140
-
const [_, channelOrVideo, clipOrId, id] = urlp.pathname.split('/')
140
+
const [__, channelOrVideo, clipOrId, id] = urlp.pathname.split('/')
141
141
142
142
if (channelOrVideo === 'videos') {
143
143
return {
···
162
162
163
163
// spotify
164
164
if (urlp.hostname === 'open.spotify.com') {
165
-
const [_, typeOrLocale, idOrType, id] = urlp.pathname.split('/')
165
+
const [__, typeOrLocale, idOrType, id] = urlp.pathname.split('/')
166
166
167
167
if (idOrType) {
168
168
if (typeOrLocale === 'playlist' || idOrType === 'playlist') {
···
210
210
urlp.hostname === 'soundcloud.com' ||
211
211
urlp.hostname === 'www.soundcloud.com'
212
212
) {
213
-
const [_, user, trackOrSets, set] = urlp.pathname.split('/')
213
+
const [__, user, trackOrSets, set] = urlp.pathname.split('/')
214
214
215
215
if (user && trackOrSets) {
216
216
if (trackOrSets === 'sets' && set) {
···
270
270
}
271
271
272
272
if (urlp.hostname === 'vimeo.com' || urlp.hostname === 'www.vimeo.com') {
273
-
const [_, videoId] = urlp.pathname.split('/')
273
+
const [__, videoId] = urlp.pathname.split('/')
274
274
if (videoId) {
275
275
return {
276
276
type: 'vimeo_video',
···
281
281
}
282
282
283
283
if (urlp.hostname === 'giphy.com' || urlp.hostname === 'www.giphy.com') {
284
-
const [_, gifs, nameAndId] = urlp.pathname.split('/')
284
+
const [__, gifs, nameAndId] = urlp.pathname.split('/')
285
285
286
286
/*
287
287
* nameAndId is a string that consists of the name (dash separated) and the id of the gif (the last part of the name)
···
309
309
// These can include (presumably) a tracking id in the path name, so we have to check for that as well
310
310
if (giphyRegex.test(urlp.hostname)) {
311
311
// We can link directly to the gif, if its a proper link
312
-
const [_, media, trackingOrId, idOrFilename, filename] =
312
+
const [__, media, trackingOrId, idOrFilename, filename] =
313
313
urlp.pathname.split('/')
314
314
315
315
if (media === 'media') {
···
338
338
// Finally, we should see if it is a link to i.giphy.com. These links don't necessarily end in .gif but can also
339
339
// be .webp
340
340
if (urlp.hostname === 'i.giphy.com' || urlp.hostname === 'www.i.giphy.com') {
341
-
const [_, mediaOrFilename, filename] = urlp.pathname.split('/')
341
+
const [__, mediaOrFilename, filename] = urlp.pathname.split('/')
342
342
343
343
if (mediaOrFilename === 'media' && filename) {
344
344
const gifId = filename.split('.')[0]
···
389
389
const path_components = urlp.pathname.slice(1, i + 1).split('/')
390
390
if (path_components.length === 4) {
391
391
// discard username - it's not relevant
392
-
const [photos, _, albums, id] = path_components
392
+
const [photos, __, albums, id] = path_components
393
393
if (photos === 'photos' && albums === 'albums') {
394
394
// this at least has the shape of a valid photo-album URL!
395
395
return {
···
417
417
// link shortened flickr path
418
418
if (urlp.hostname === 'flic.kr') {
419
419
const b58alph = '123456789abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ'
420
-
let [_, type, idBase58Enc] = urlp.pathname.split('/')
420
+
let [__, type, idBase58Enc] = urlp.pathname.split('/')
421
421
let id = 0n
422
422
for (const char of idBase58Enc) {
423
423
const nextIdx = b58alph.indexOf(char)
···
528
528
return {success: false}
529
529
}
530
530
531
-
let [_, id, filename] = urlp.pathname.split('/')
531
+
let [__, id, filename] = urlp.pathname.split('/')
532
532
533
533
if (!id || !filename) {
534
534
return {success: false}
-17
src/lib/strings/helpers.ts
-17
src/lib/strings/helpers.ts
···
62
62
}, [splitter, maxCount, text])
63
63
}
64
64
65
-
// https://stackoverflow.com/a/52171480
66
-
export function toHashCode(str: string, seed = 0): number {
67
-
let h1 = 0xdeadbeef ^ seed,
68
-
h2 = 0x41c6ce57 ^ seed
69
-
for (let i = 0, ch; i < str.length; i++) {
70
-
ch = str.charCodeAt(i)
71
-
h1 = Math.imul(h1 ^ ch, 2654435761)
72
-
h2 = Math.imul(h2 ^ ch, 1597334677)
73
-
}
74
-
h1 = Math.imul(h1 ^ (h1 >>> 16), 2246822507)
75
-
h1 ^= Math.imul(h2 ^ (h2 >>> 13), 3266489909)
76
-
h2 = Math.imul(h2 ^ (h2 >>> 16), 2246822507)
77
-
h2 ^= Math.imul(h1 ^ (h1 >>> 13), 3266489909)
78
-
79
-
return 4294967296 * (2097151 & h2) + (h1 >>> 0)
80
-
}
81
-
82
65
export function countLines(str: string | undefined): number {
83
66
if (!str) return 0
84
67
return str.match(/\n/g)?.length ?? 0
+1
-1
src/lib/strings/starter-pack.ts
+1
-1
src/lib/strings/starter-pack.ts
+225
-215
src/locale/locales/en/messages.po
+225
-215
src/locale/locales/en/messages.po
···
124
124
msgid "{0, plural, other {# people have}} used this starter pack!"
125
125
msgstr ""
126
126
127
-
#: src/components/dialogs/StarterPackDialog.tsx:357
127
+
#: src/components/dialogs/StarterPackDialog.tsx:356
128
128
msgid "{0, plural, other {+# more}}"
129
129
msgstr ""
130
130
···
521
521
msgid "7 days"
522
522
msgstr ""
523
523
524
-
#: src/screens/Onboarding/StepFinished.tsx:341
524
+
#: src/screens/Onboarding/StepFinished.tsx:340
525
525
msgid "A collection of popular feeds you can find on Bluesky, including News, Booksky, Game Dev, Blacksky, and Fountain Pens"
526
526
msgstr ""
527
527
···
568
568
msgid "Accept Request"
569
569
msgstr ""
570
570
571
+
#: src/view/com/composer/select-language/SuggestedLanguage.tsx:179
572
+
msgid "Accept this language suggestion"
573
+
msgstr ""
574
+
571
575
#: src/screens/Settings/AccessibilitySettings.tsx:44
572
576
#: src/screens/Settings/Settings.tsx:220
573
577
#: src/screens/Settings/Settings.tsx:223
···
604
608
msgid "Account muted"
605
609
msgstr ""
606
610
607
-
#: src/components/moderation/ModerationDetailsDialog.tsx:103
611
+
#: src/components/moderation/ModerationDetailsDialog.tsx:105
608
612
#: src/lib/moderation/useModerationCauseDescription.ts:98
609
613
msgid "Account Muted"
610
614
msgstr ""
611
615
612
-
#: src/components/moderation/ModerationDetailsDialog.tsx:89
616
+
#: src/components/moderation/ModerationDetailsDialog.tsx:91
613
617
msgid "Account Muted by List"
614
618
msgstr ""
615
619
···
654
658
655
659
#: src/components/dialogs/lists/ListAddRemoveUsersDialog.tsx:169
656
660
#: src/components/dialogs/MutedWords.tsx:333
657
-
#: src/components/dialogs/StarterPackDialog.tsx:371
658
-
#: src/components/dialogs/StarterPackDialog.tsx:377
661
+
#: src/components/dialogs/StarterPackDialog.tsx:370
662
+
#: src/components/dialogs/StarterPackDialog.tsx:376
659
663
#: src/view/com/modals/UserAddRemoveLists.tsx:235
660
664
msgid "Add"
661
665
msgstr ""
···
708
712
msgid "Add another account"
709
713
msgstr ""
710
714
711
-
#: src/view/com/composer/Composer.tsx:811
715
+
#: src/view/com/composer/Composer.tsx:853
712
716
msgid "Add another post"
713
717
msgstr ""
714
718
715
-
#: src/view/com/composer/Composer.tsx:1444
719
+
#: src/view/com/composer/Composer.tsx:1490
716
720
msgid "Add another post to thread"
717
721
msgstr ""
718
722
···
735
739
msgid "Add media to post"
736
740
msgstr ""
737
741
738
-
#: src/components/moderation/ReportDialog/index.tsx:403
739
-
#: src/components/moderation/ReportDialog/index.tsx:407
742
+
#: src/components/moderation/ReportDialog/index.tsx:406
743
+
#: src/components/moderation/ReportDialog/index.tsx:410
740
744
msgid "Add more details (optional)"
741
745
msgstr ""
742
746
···
790
794
msgid "Add to saved posts"
791
795
msgstr ""
792
796
793
-
#: src/components/dialogs/StarterPackDialog.tsx:176
797
+
#: src/components/dialogs/StarterPackDialog.tsx:175
794
798
#: src/view/com/profile/ProfileMenu.tsx:308
795
799
#: src/view/com/profile/ProfileMenu.tsx:311
796
800
msgid "Add to starter packs"
···
805
809
msgid "Added to list"
806
810
msgstr ""
807
811
808
-
#: src/components/dialogs/StarterPackDialog.tsx:258
812
+
#: src/components/dialogs/StarterPackDialog.tsx:257
809
813
msgid "Added to starter pack"
810
814
msgstr ""
811
815
···
813
817
msgid "Additional details (limit 1000 characters)"
814
818
msgstr ""
815
819
816
-
#: src/components/moderation/ReportDialog/index.tsx:421
820
+
#: src/components/moderation/ReportDialog/index.tsx:424
817
821
msgid "Additional details (limit 300 characters)"
818
822
msgstr ""
819
823
···
879
883
#: src/screens/Search/components/SearchLanguageDropdown.tsx:64
880
884
#: src/screens/Search/components/SearchLanguageDropdown.tsx:99
881
885
#: src/screens/Search/components/SearchLanguageDropdown.tsx:101
882
-
#: src/view/com/composer/select-language/PostLanguageSelectDialog.tsx:216
886
+
#: src/view/com/composer/select-language/PostLanguageSelectDialog.tsx:238
883
887
msgid "All languages"
884
888
msgstr ""
885
889
···
903
907
msgstr ""
904
908
905
909
#: src/screens/Settings/ActivityPrivacySettings.tsx:52
906
-
#: src/screens/Settings/PrivacyAndSecuritySettings.tsx:92
910
+
#: src/screens/Settings/PrivacyAndSecuritySettings.tsx:91
907
911
msgid "Allow others to be notified of your posts"
908
912
msgstr ""
909
913
···
967
971
msgstr ""
968
972
969
973
#: src/components/dialogs/GifSelect.tsx:253
970
-
#: src/view/com/composer/select-language/PostLanguageSelectDialog.tsx:298
974
+
#: src/view/com/composer/select-language/PostLanguageSelectDialog.tsx:320
971
975
msgid "An error has occurred"
972
976
msgstr ""
973
977
···
1012
1016
msgid "An error occurred while uploading the video."
1013
1017
msgstr ""
1014
1018
1015
-
#: src/screens/Onboarding/StepFinished.tsx:359
1019
+
#: src/screens/Onboarding/StepFinished.tsx:358
1016
1020
msgid "An illustration of several Bluesky posts alongside repost, like, and comment icons"
1017
1021
msgstr ""
1018
1022
···
1051
1055
msgid "an unknown error occurred"
1052
1056
msgstr ""
1053
1057
1054
-
#: src/components/moderation/ModerationDetailsDialog.tsx:134
1058
+
#: src/components/moderation/ModerationDetailsDialog.tsx:136
1055
1059
#: src/lib/moderation/useModerationCauseDescription.ts:144
1056
1060
msgid "an unknown labeler"
1057
1061
msgstr ""
···
1089
1093
1090
1094
#: src/screens/Settings/ActivityPrivacySettings.tsx:111
1091
1095
#: src/screens/Settings/ActivityPrivacySettings.tsx:116
1092
-
#: src/screens/Settings/PrivacyAndSecuritySettings.tsx:163
1096
+
#: src/screens/Settings/PrivacyAndSecuritySettings.tsx:162
1093
1097
msgid "Anyone who follows me"
1094
1098
msgstr ""
1095
1099
···
1125
1129
msgid "App password names must be at least 4 characters long"
1126
1130
msgstr ""
1127
1131
1128
-
#: src/screens/Settings/PrivacyAndSecuritySettings.tsx:72
1129
-
#: src/screens/Settings/PrivacyAndSecuritySettings.tsx:75
1132
+
#: src/screens/Settings/PrivacyAndSecuritySettings.tsx:71
1133
+
#: src/screens/Settings/PrivacyAndSecuritySettings.tsx:74
1130
1134
msgid "App passwords"
1131
1135
msgstr ""
1132
1136
···
1135
1139
msgid "App Passwords"
1136
1140
msgstr ""
1137
1141
1138
-
#: src/components/moderation/LabelsOnMeDialog.tsx:150
1139
-
#: src/components/moderation/LabelsOnMeDialog.tsx:153
1142
+
#: src/components/moderation/LabelsOnMeDialog.tsx:152
1143
+
#: src/components/moderation/LabelsOnMeDialog.tsx:155
1140
1144
msgid "Appeal"
1141
1145
msgstr ""
1142
1146
1143
-
#: src/components/moderation/LabelsOnMeDialog.tsx:268
1147
+
#: src/components/moderation/LabelsOnMeDialog.tsx:270
1144
1148
msgid "Appeal \"{0}\" label"
1145
1149
msgstr ""
1146
1150
1147
-
#: src/components/moderation/LabelsOnMeDialog.tsx:258
1151
+
#: src/components/moderation/LabelsOnMeDialog.tsx:260
1148
1152
#: src/screens/Messages/components/ChatDisabled.tsx:103
1149
1153
msgctxt "toast"
1150
1154
msgid "Appeal submitted"
···
1221
1225
msgid "Are you sure you want to remove this from your feeds?"
1222
1226
msgstr ""
1223
1227
1224
-
#: src/view/com/composer/Composer.tsx:760
1228
+
#: src/view/com/composer/Composer.tsx:802
1225
1229
msgid "Are you sure you'd like to discard this draft?"
1226
1230
msgstr ""
1227
1231
1228
-
#: src/view/com/composer/Composer.tsx:950
1232
+
#: src/view/com/composer/Composer.tsx:992
1229
1233
msgid "Are you sure you'd like to discard this post?"
1230
1234
msgstr ""
1231
1235
···
1233
1237
msgid "Are you sure?"
1234
1238
msgstr ""
1235
1239
1236
-
#: src/view/com/composer/select-language/SuggestedLanguage.tsx:89
1240
+
#: src/view/com/composer/select-language/SuggestedLanguage.tsx:99
1237
1241
msgid "Are you writing in <0>{suggestedLanguageName}</0>?"
1238
1242
msgstr ""
1239
1243
···
1274
1278
msgid "Available"
1275
1279
msgstr ""
1276
1280
1277
-
#: src/components/moderation/LabelsOnMeDialog.tsx:315
1278
-
#: src/components/moderation/LabelsOnMeDialog.tsx:316
1281
+
#: src/components/moderation/LabelsOnMeDialog.tsx:317
1282
+
#: src/components/moderation/LabelsOnMeDialog.tsx:318
1279
1283
#: src/screens/Login/ChooseAccountForm.tsx:90
1280
1284
#: src/screens/Login/ChooseAccountForm.tsx:95
1281
1285
#: src/screens/Login/ForgotPasswordForm.tsx:123
···
1307
1311
msgid "Before creating a post or replying, you must first verify your email."
1308
1312
msgstr ""
1309
1313
1310
-
#: src/components/dialogs/StarterPackDialog.tsx:71
1314
+
#: src/components/dialogs/StarterPackDialog.tsx:70
1311
1315
#: src/components/StarterPack/ProfileStarterPacks.tsx:231
1312
1316
#: src/components/StarterPack/ProfileStarterPacks.tsx:241
1313
1317
msgid "Before creating a starter pack, you must first verify your email."
···
1558
1562
msgstr ""
1559
1563
1560
1564
#: src/components/LabelingServiceCard/index.tsx:62
1561
-
#: src/components/moderation/ReportDialog/index.tsx:683
1565
+
#: src/components/moderation/ReportDialog/index.tsx:686
1562
1566
#: src/screens/Search/components/StarterPackCard.tsx:106
1563
1567
#: src/screens/Search/Explore.tsx:930
1564
1568
msgid "By {0}"
···
1626
1630
#: src/screens/Settings/Settings.tsx:289
1627
1631
#: src/screens/Takendown.tsx:108
1628
1632
#: src/screens/Takendown.tsx:111
1629
-
#: src/view/com/composer/Composer.tsx:1005
1630
-
#: src/view/com/composer/Composer.tsx:1016
1633
+
#: src/view/com/composer/Composer.tsx:1047
1634
+
#: src/view/com/composer/Composer.tsx:1058
1631
1635
#: src/view/com/composer/photos/EditImageDialog.web.tsx:43
1632
1636
#: src/view/com/composer/photos/EditImageDialog.web.tsx:52
1633
1637
#: src/view/shell/desktop/LeftNav.tsx:213
···
1695
1699
msgid "Change Handle"
1696
1700
msgstr ""
1697
1701
1698
-
#: src/components/moderation/ReportDialog/index.tsx:325
1702
+
#: src/components/moderation/ReportDialog/index.tsx:328
1699
1703
msgid "Change moderation service"
1700
1704
msgstr ""
1701
1705
···
1708
1712
msgid "Change password dialog"
1709
1713
msgstr ""
1710
1714
1711
-
#: src/view/com/composer/select-language/SuggestedLanguage.tsx:100
1712
-
msgid "Change post language to {suggestedLanguageName}"
1713
-
msgstr ""
1714
-
1715
-
#: src/components/moderation/ReportDialog/index.tsx:244
1715
+
#: src/components/moderation/ReportDialog/index.tsx:247
1716
1716
msgid "Change report reason"
1717
1717
msgstr ""
1718
1718
···
1823
1823
msgid "Choose People"
1824
1824
msgstr ""
1825
1825
1826
-
#: src/view/com/composer/select-language/PostLanguageSelectDialog.tsx:162
1826
+
#: src/view/com/composer/select-language/PostLanguageSelectDialog.tsx:184
1827
1827
msgid "Choose Post Languages"
1828
1828
msgstr ""
1829
1829
1830
-
#: src/screens/Onboarding/StepFinished.tsx:575
1830
+
#: src/screens/Onboarding/StepFinished.tsx:573
1831
1831
msgid "Choose the algorithms that power your custom feeds."
1832
1832
msgstr ""
1833
1833
···
1920
1920
#: src/components/dialogs/nuxs/InitialVerificationAnnouncement.tsx:178
1921
1921
#: src/components/dialogs/nuxs/InitialVerificationAnnouncement.tsx:187
1922
1922
#: src/components/dialogs/SearchablePeopleList.tsx:295
1923
-
#: src/components/dialogs/StarterPackDialog.tsx:179
1923
+
#: src/components/dialogs/StarterPackDialog.tsx:178
1924
1924
#: src/components/dms/EmojiPopup.android.tsx:58
1925
1925
#: src/components/dms/ReportDialog.tsx:387
1926
1926
#: src/components/dms/ReportDialog.tsx:396
···
1938
1938
#: src/components/WhoCanReply.tsx:209
1939
1939
#: src/screens/Settings/components/ChangePasswordDialog.tsx:286
1940
1940
#: src/screens/Settings/components/ChangePasswordDialog.tsx:291
1941
-
#: src/view/com/composer/select-language/PostLanguageSelectDialog.tsx:313
1941
+
#: src/view/com/composer/select-language/PostLanguageSelectDialog.tsx:335
1942
1942
#: src/view/com/feeds/MissingFeed.tsx:210
1943
1943
#: src/view/com/feeds/MissingFeed.tsx:217
1944
1944
msgid "Close"
···
1962
1962
#: src/components/dialogs/GifSelect.tsx:263
1963
1963
#: src/components/verification/VerificationsDialog.tsx:136
1964
1964
#: src/components/verification/VerifierDialog.tsx:142
1965
-
#: src/view/com/composer/select-language/PostLanguageSelectDialog.tsx:182
1966
-
#: src/view/com/composer/select-language/PostLanguageSelectDialog.tsx:276
1967
-
#: src/view/com/composer/select-language/PostLanguageSelectDialog.tsx:308
1965
+
#: src/view/com/composer/select-language/PostLanguageSelectDialog.tsx:204
1966
+
#: src/view/com/composer/select-language/PostLanguageSelectDialog.tsx:298
1967
+
#: src/view/com/composer/select-language/PostLanguageSelectDialog.tsx:330
1968
1968
msgid "Close dialog"
1969
1969
msgstr ""
1970
1970
···
2007
2007
msgid "Closes password update alert"
2008
2008
msgstr ""
2009
2009
2010
-
#: src/view/com/composer/Composer.tsx:1013
2010
+
#: src/view/com/composer/Composer.tsx:1055
2011
2011
msgid "Closes post composer and discards post draft"
2012
2012
msgstr ""
2013
2013
···
2053
2053
msgid "Community Guidelines"
2054
2054
msgstr ""
2055
2055
2056
-
#: src/screens/Onboarding/StepFinished.tsx:473
2057
-
#: src/screens/Onboarding/StepFinished.tsx:588
2056
+
#: src/screens/Onboarding/StepFinished.tsx:472
2057
+
#: src/screens/Onboarding/StepFinished.tsx:586
2058
2058
msgid "Complete onboarding and start using your account"
2059
2059
msgstr ""
2060
2060
···
2066
2066
msgid "Compose new post"
2067
2067
msgstr ""
2068
2068
2069
-
#: src/view/com/composer/Composer.tsx:914
2069
+
#: src/view/com/composer/Composer.tsx:956
2070
2070
msgid "Compose posts up to {0, plural, other {# characters}} in length"
2071
2071
msgstr ""
2072
2072
···
2074
2074
msgid "Compose reply"
2075
2075
msgstr ""
2076
2076
2077
-
#: src/view/com/composer/Composer.tsx:1834
2077
+
#: src/view/com/composer/Composer.tsx:1883
2078
2078
msgid "Compressing video..."
2079
2079
msgstr ""
2080
2080
···
2186
2186
msgid "Content Languages"
2187
2187
msgstr ""
2188
2188
2189
-
#: src/components/moderation/ModerationDetailsDialog.tsx:82
2189
+
#: src/components/moderation/ModerationDetailsDialog.tsx:84
2190
2190
#: src/lib/moderation/useModerationCauseDescription.ts:82
2191
2191
msgid "Content Not Available"
2192
2192
msgstr ""
2193
2193
2194
-
#: src/components/moderation/ModerationDetailsDialog.tsx:50
2194
+
#: src/components/moderation/ModerationDetailsDialog.tsx:52
2195
2195
#: src/components/moderation/ScreenHider.tsx:99
2196
2196
#: src/lib/moderation/useGlobalLabelStrings.ts:22
2197
2197
#: src/lib/moderation/useModerationCauseDescription.ts:45
···
2411
2411
2412
2412
#. Text on button to create a new starter pack
2413
2413
#. Text on button to create a new starter pack
2414
-
#: src/components/dialogs/StarterPackDialog.tsx:112
2415
-
#: src/components/dialogs/StarterPackDialog.tsx:201
2414
+
#: src/components/dialogs/StarterPackDialog.tsx:111
2415
+
#: src/components/dialogs/StarterPackDialog.tsx:200
2416
2416
#: src/components/StarterPack/ProfileStarterPacks.tsx:296
2417
2417
msgid "Create"
2418
2418
msgstr ""
···
2478
2478
msgid "Create new account"
2479
2479
msgstr ""
2480
2480
2481
-
#: src/components/moderation/ReportDialog/index.tsx:585
2481
+
#: src/components/moderation/ReportDialog/index.tsx:588
2482
2482
#: src/components/ReportDialog/SelectReportOptionView.tsx:102
2483
2483
msgid "Create report for {0}"
2484
2484
msgstr ""
2485
2485
2486
-
#: src/components/dialogs/StarterPackDialog.tsx:107
2487
-
#: src/components/dialogs/StarterPackDialog.tsx:196
2486
+
#: src/components/dialogs/StarterPackDialog.tsx:106
2487
+
#: src/components/dialogs/StarterPackDialog.tsx:195
2488
2488
msgid "Create starter pack"
2489
2489
msgstr ""
2490
2490
···
2639
2639
2640
2640
#: src/components/PostControls/PostMenu/PostMenuItems.tsx:685
2641
2641
#: src/components/PostControls/PostMenu/PostMenuItems.tsx:687
2642
-
#: src/view/com/composer/Composer.tsx:924
2642
+
#: src/view/com/composer/Composer.tsx:966
2643
2643
msgid "Delete post"
2644
2644
msgstr ""
2645
2645
···
2752
2752
2753
2753
#: src/components/dialogs/lists/CreateOrEditListDialog.tsx:92
2754
2754
#: src/screens/Profile/Header/EditProfileDialog.tsx:82
2755
-
#: src/view/com/composer/Composer.tsx:762
2756
-
#: src/view/com/composer/Composer.tsx:957
2755
+
#: src/view/com/composer/Composer.tsx:804
2756
+
#: src/view/com/composer/Composer.tsx:999
2757
2757
msgid "Discard"
2758
2758
msgstr ""
2759
2759
···
2762
2762
msgid "Discard changes?"
2763
2763
msgstr ""
2764
2764
2765
-
#: src/view/com/composer/Composer.tsx:759
2765
+
#: src/view/com/composer/Composer.tsx:801
2766
2766
msgid "Discard draft?"
2767
2767
msgstr ""
2768
2768
2769
-
#: src/view/com/composer/Composer.tsx:949
2769
+
#: src/view/com/composer/Composer.tsx:991
2770
2770
msgid "Discard post?"
2771
2771
msgstr ""
2772
2772
···
2789
2789
msgid "Dismiss"
2790
2790
msgstr ""
2791
2791
2792
-
#: src/view/com/composer/Composer.tsx:1758
2792
+
#: src/view/com/composer/Composer.tsx:1807
2793
2793
msgid "Dismiss error"
2794
2794
msgstr ""
2795
2795
···
2815
2815
msgid "Display name"
2816
2816
msgstr ""
2817
2817
2818
-
#: src/screens/Onboarding/StepFinished.tsx:347
2818
+
#: src/screens/Onboarding/StepFinished.tsx:346
2819
2819
msgid "Ditch the trolls and clickbait. Find real people and conversations that matter to you."
2820
2820
msgstr ""
2821
2821
···
2868
2868
#: src/view/com/auth/server-input/index.tsx:233
2869
2869
#: src/view/com/composer/labels/LabelsBtn.tsx:223
2870
2870
#: src/view/com/composer/labels/LabelsBtn.tsx:230
2871
-
#: src/view/com/composer/select-language/PostLanguageSelectDialog.tsx:281
2871
+
#: src/view/com/composer/select-language/PostLanguageSelectDialog.tsx:303
2872
2872
#: src/view/com/composer/videos/SubtitleDialog.tsx:168
2873
2873
#: src/view/com/composer/videos/SubtitleDialog.tsx:178
2874
2874
msgid "Done"
···
3055
3055
msgid "Email 2FA disabled"
3056
3056
msgstr ""
3057
3057
3058
-
#: src/screens/Settings/PrivacyAndSecuritySettings.tsx:63
3058
+
#: src/screens/Settings/PrivacyAndSecuritySettings.tsx:62
3059
3059
msgid "Email 2FA enabled"
3060
3060
msgstr ""
3061
3061
···
3126
3126
msgstr ""
3127
3127
3128
3128
#: src/screens/Settings/NotificationSettings/ActivityNotificationSettings.tsx:139
3129
+
#: src/view/screens/Storybook/Admonitions.tsx:75
3129
3130
msgid "Enable notifications for an account by visiting their profile and pressing the <0>bell icon</0> <1/>."
3130
3131
msgstr ""
3131
3132
···
3222
3223
msgid "Entertainment"
3223
3224
msgstr ""
3224
3225
3225
-
#: src/view/com/composer/Composer.tsx:1843
3226
+
#: src/view/com/composer/Composer.tsx:1892
3226
3227
#: src/view/com/util/error/ErrorScreen.tsx:42
3227
3228
msgid "Error"
3228
3229
msgstr ""
···
3231
3232
msgid "Error loading post"
3232
3233
msgstr ""
3233
3234
3234
-
#: src/screens/Settings/PrivacyAndSecuritySettings.tsx:154
3235
+
#: src/screens/Settings/PrivacyAndSecuritySettings.tsx:153
3235
3236
msgid "Error loading preference"
3236
3237
msgstr ""
3237
3238
···
3345
3346
msgid "Expires {0}"
3346
3347
msgstr ""
3347
3348
3348
-
#: src/components/moderation/LabelsOnMeDialog.tsx:199
3349
-
#: src/components/moderation/ModerationDetailsDialog.tsx:208
3349
+
#: src/components/moderation/LabelsOnMeDialog.tsx:201
3350
+
#: src/components/moderation/ModerationDetailsDialog.tsx:210
3350
3351
msgid "Expires in {0}"
3351
3352
msgstr ""
3352
3353
···
3414
3415
msgid "Failed to add emoji reaction"
3415
3416
msgstr ""
3416
3417
3417
-
#: src/components/dialogs/StarterPackDialog.tsx:270
3418
+
#: src/components/dialogs/StarterPackDialog.tsx:269
3418
3419
msgid "Failed to add to starter pack"
3419
3420
msgstr ""
3420
3421
···
3520
3521
msgid "Failed to remove emoji reaction"
3521
3522
msgstr ""
3522
3523
3523
-
#: src/components/dialogs/StarterPackDialog.tsx:289
3524
+
#: src/components/dialogs/StarterPackDialog.tsx:288
3524
3525
msgid "Failed to remove from starter pack"
3525
3526
msgstr ""
3526
3527
···
3554
3555
msgid "Failed to send email, please try again."
3555
3556
msgstr ""
3556
3557
3557
-
#: src/components/moderation/LabelsOnMeDialog.tsx:254
3558
+
#: src/components/moderation/LabelsOnMeDialog.tsx:256
3558
3559
#: src/screens/Messages/components/ChatDisabled.tsx:99
3559
3560
msgid "Failed to submit appeal, please try again."
3560
3561
msgstr ""
···
3697
3698
msgid "Filter who you receive notifications from"
3698
3699
msgstr ""
3699
3700
3700
-
#: src/screens/Onboarding/StepFinished.tsx:479
3701
-
#: src/screens/Onboarding/StepFinished.tsx:591
3701
+
#: src/screens/Onboarding/StepFinished.tsx:478
3702
+
#: src/screens/Onboarding/StepFinished.tsx:589
3702
3703
msgid "Finalizing"
3703
3704
msgstr ""
3704
3705
···
3718
3719
msgid "Find people to follow"
3719
3720
msgstr ""
3720
3721
3721
-
#: src/screens/Search/Shell.tsx:476
3722
+
#: src/screens/Search/Shell.tsx:475
3722
3723
msgid "Find posts, users, and feeds on Bluesky"
3723
3724
msgstr ""
3724
3725
3725
-
#: src/screens/Onboarding/StepFinished.tsx:345
3726
+
#: src/screens/Onboarding/StepFinished.tsx:344
3726
3727
msgid "Find your people"
3727
3728
msgstr ""
3728
3729
···
3749
3750
msgid "Flat White"
3750
3751
msgstr ""
3751
3752
3752
-
#: src/screens/Onboarding/StepFinished.tsx:571
3753
+
#: src/screens/Onboarding/StepFinished.tsx:569
3753
3754
msgid "Flexible"
3754
3755
msgstr ""
3755
3756
···
3923
3924
msgid "Forever"
3924
3925
msgstr ""
3925
3926
3926
-
#: src/screens/Onboarding/StepFinished.tsx:354
3927
+
#: src/screens/Onboarding/StepFinished.tsx:353
3927
3928
msgid "Forget the noise"
3928
3929
msgstr ""
3929
3930
···
3940
3941
msgid "Forgot?"
3941
3942
msgstr ""
3942
3943
3943
-
#: src/screens/Onboarding/StepFinished.tsx:336
3944
+
#: src/screens/Onboarding/StepFinished.tsx:335
3944
3945
msgid "Free your feed"
3945
3946
msgstr ""
3946
3947
···
4418
4419
msgstr ""
4419
4420
4420
4421
#: src/screens/Settings/NotificationSettings/ActivityNotificationSettings.tsx:157
4422
+
#: src/view/screens/Storybook/Admonitions.tsx:89
4421
4423
msgid "If you want to restrict who can receive notifications for your account's activity, you can change this in <0>Settings โ Privacy and Security</0>."
4422
4424
msgstr ""
4423
4425
···
4596
4598
msgid "It's just you right now! Add more people to your starter pack by searching above."
4597
4599
msgstr ""
4598
4600
4599
-
#: src/view/com/composer/Composer.tsx:1777
4601
+
#: src/view/com/composer/Composer.tsx:1826
4600
4602
msgid "Job ID: {0}"
4601
4603
msgstr ""
4602
4604
···
4660
4662
msgid "Labels are annotations on users and content. They can be used to hide, warn, and categorize the network."
4661
4663
msgstr ""
4662
4664
4663
-
#: src/components/moderation/LabelsOnMeDialog.tsx:72
4665
+
#: src/components/moderation/LabelsOnMeDialog.tsx:74
4664
4666
msgid "Labels on your account"
4665
4667
msgstr ""
4666
4668
4667
-
#: src/components/moderation/LabelsOnMeDialog.tsx:74
4669
+
#: src/components/moderation/LabelsOnMeDialog.tsx:76
4668
4670
msgid "Labels on your content"
4669
4671
msgstr ""
4670
4672
···
4746
4748
msgid "Learn more about verification on Bluesky"
4747
4749
msgstr ""
4748
4750
4749
-
#: src/screens/Settings/PrivacyAndSecuritySettings.tsx:128
4750
-
#: src/screens/Settings/PrivacyAndSecuritySettings.tsx:131
4751
+
#: src/screens/Settings/PrivacyAndSecuritySettings.tsx:127
4752
+
#: src/screens/Settings/PrivacyAndSecuritySettings.tsx:130
4751
4753
msgid "Learn more about what is public on Bluesky."
4752
4754
msgstr ""
4753
4755
···
4799
4801
msgid "Let's get your password reset!"
4800
4802
msgstr ""
4801
4803
4802
-
#: src/screens/Onboarding/StepFinished.tsx:481
4803
-
#: src/screens/Onboarding/StepFinished.tsx:591
4804
+
#: src/screens/Onboarding/StepFinished.tsx:480
4805
+
#: src/screens/Onboarding/StepFinished.tsx:589
4804
4806
msgid "Let's go!"
4805
4807
msgstr ""
4806
4808
···
4852
4854
4853
4855
#: src/screens/Post/PostLikedBy.tsx:41
4854
4856
#: src/screens/Profile/ProfileLabelerLikedBy.tsx:32
4855
-
#: src/view/screens/ProfileFeedLikedBy.tsx:34
4857
+
#: src/view/screens/ProfileFeedLikedBy.tsx:32
4856
4858
msgid "Liked By"
4857
4859
msgstr ""
4858
4860
···
5031
5033
msgid "Log"
5032
5034
msgstr ""
5033
5035
5034
-
#: src/screens/Settings/PrivacyAndSecuritySettings.tsx:107
5036
+
#: src/screens/Settings/PrivacyAndSecuritySettings.tsx:106
5035
5037
msgid "Logged-out visibility"
5036
5038
msgstr ""
5037
5039
···
5205
5207
msgid "Moderation"
5206
5208
msgstr ""
5207
5209
5208
-
#: src/components/moderation/ModerationDetailsDialog.tsx:138
5210
+
#: src/components/moderation/ModerationDetailsDialog.tsx:140
5209
5211
msgid "Moderation details"
5210
5212
msgstr ""
5211
5213
···
5254
5256
msgid "Moderation tools"
5255
5257
msgstr ""
5256
5258
5257
-
#: src/components/moderation/ModerationDetailsDialog.tsx:52
5259
+
#: src/components/moderation/ModerationDetailsDialog.tsx:54
5258
5260
#: src/lib/moderation/useModerationCauseDescription.ts:47
5259
5261
msgid "Moderator has chosen to set a general warning on the content."
5260
5262
msgstr ""
···
5264
5266
msgid "More feeds"
5265
5267
msgstr ""
5266
5268
5267
-
#: src/view/com/composer/select-language/PostLanguageSelect.tsx:70
5268
-
#: src/view/com/composer/select-language/PostLanguageSelect.tsx:73
5269
+
#: src/view/com/composer/select-language/PostLanguageSelect.tsx:87
5270
+
#: src/view/com/composer/select-language/PostLanguageSelect.tsx:90
5269
5271
msgid "More languages..."
5270
5272
msgstr ""
5271
5273
···
5370
5372
msgstr ""
5371
5373
5372
5374
#: src/Navigation.tsx:187
5373
-
#: src/view/screens/ModerationMutedAccounts.tsx:118
5375
+
#: src/view/screens/ModerationMutedAccounts.tsx:116
5374
5376
msgid "Muted Accounts"
5375
5377
msgstr ""
5376
5378
5377
-
#: src/view/screens/ModerationMutedAccounts.tsx:204
5379
+
#: src/view/screens/ModerationMutedAccounts.tsx:202
5378
5380
msgid "Muted accounts have their posts removed from your feed and from your notifications. Mutes are completely private."
5379
5381
msgstr ""
5380
5382
···
5437
5439
msgid "Navigates to your profile"
5438
5440
msgstr ""
5439
5441
5440
-
#: src/components/moderation/ReportDialog/index.tsx:271
5441
-
#: src/components/moderation/ReportDialog/index.tsx:288
5442
+
#: src/components/moderation/ReportDialog/index.tsx:274
5443
+
#: src/components/moderation/ReportDialog/index.tsx:291
5442
5444
msgid "Need to report a copyright violation, legal request, or regulatory compliance issue?"
5443
5445
msgstr ""
5444
5446
···
5446
5448
msgid "Need to report a copyright violation?"
5447
5449
msgstr ""
5448
5450
5449
-
#: src/screens/Onboarding/StepFinished.tsx:559
5451
+
#: src/screens/Onboarding/StepFinished.tsx:557
5450
5452
msgid "Never lose access to your followers or data."
5451
5453
msgstr ""
5452
5454
···
5546
5548
msgid "New posts from {firstAuthorName} and {additionalAuthorsCount, plural, one {{formattedAuthorsCount} other} other {{formattedAuthorsCount} others}}"
5547
5549
msgstr ""
5548
5550
5549
-
#: src/components/dialogs/StarterPackDialog.tsx:193
5551
+
#: src/components/dialogs/StarterPackDialog.tsx:192
5550
5552
msgid "New starter pack"
5551
5553
msgstr ""
5552
5554
···
5573
5575
#: src/screens/Login/LoginForm.tsx:350
5574
5576
#: src/screens/Login/SetNewPasswordForm.tsx:182
5575
5577
#: src/screens/Login/SetNewPasswordForm.tsx:188
5576
-
#: src/screens/Onboarding/StepFinished.tsx:474
5577
-
#: src/screens/Onboarding/StepFinished.tsx:483
5578
+
#: src/screens/Onboarding/StepFinished.tsx:473
5579
+
#: src/screens/Onboarding/StepFinished.tsx:482
5578
5580
#: src/screens/Settings/components/AddAppPasswordDialog.tsx:157
5579
5581
#: src/screens/Settings/components/AddAppPasswordDialog.tsx:165
5580
5582
#: src/screens/Signup/BackNextButtons.tsx:67
···
5594
5596
msgid "Next image"
5595
5597
msgstr ""
5596
5598
5597
-
#: src/screens/Onboarding/StepFinished.tsx:356
5599
+
#: src/screens/Onboarding/StepFinished.tsx:355
5598
5600
msgid "No ads, no invasive tracking, no engagement traps. Bluesky respects your time and attention."
5599
5601
msgstr ""
5600
5602
···
5637
5639
msgid "No messages yet"
5638
5640
msgstr ""
5639
5641
5640
-
#: src/screens/Onboarding/StepFinished.tsx:338
5642
+
#: src/screens/Onboarding/StepFinished.tsx:337
5641
5643
msgid "No more doomscrolling junk-filled algorithms. Find feeds that work for you, not against you."
5642
5644
msgstr ""
5643
5645
···
5649
5651
#: src/screens/Messages/Settings.tsx:109
5650
5652
#: src/screens/Settings/ActivityPrivacySettings.tsx:129
5651
5653
#: src/screens/Settings/ActivityPrivacySettings.tsx:134
5652
-
#: src/screens/Settings/PrivacyAndSecuritySettings.tsx:160
5654
+
#: src/screens/Settings/PrivacyAndSecuritySettings.tsx:159
5653
5655
msgid "No one"
5654
5656
msgstr ""
5655
5657
···
5760
5762
msgid "Note about sharing"
5761
5763
msgstr ""
5762
5764
5763
-
#: src/screens/Settings/PrivacyAndSecuritySettings.tsx:117
5765
+
#: src/screens/Settings/PrivacyAndSecuritySettings.tsx:116
5764
5766
msgid "Note: Bluesky is an open and public network. This setting only limits the visibility of your content on the Bluesky app and website, and other apps may not respect this setting. Your content may still be shown to logged-out users by other apps and websites."
5765
5767
msgstr ""
5766
5768
···
5841
5843
msgstr ""
5842
5844
5843
5845
#: src/components/dialogs/GifSelect.tsx:256
5844
-
#: src/view/com/composer/select-language/PostLanguageSelectDialog.tsx:301
5846
+
#: src/view/com/composer/select-language/PostLanguageSelectDialog.tsx:323
5845
5847
#: src/view/com/util/ErrorBoundary.tsx:57
5846
5848
msgid "Oh no!"
5847
5849
msgstr ""
···
5878
5880
msgid "Onboarding reset"
5879
5881
msgstr ""
5880
5882
5881
-
#: src/view/com/composer/Composer.tsx:359
5883
+
#: src/view/com/composer/Composer.tsx:398
5882
5884
msgid "One or more GIFs is missing alt text."
5883
5885
msgstr ""
5884
5886
5885
-
#: src/view/com/composer/Composer.tsx:356
5887
+
#: src/view/com/composer/Composer.tsx:395
5886
5888
msgid "One or more images is missing alt text."
5887
5889
msgstr ""
5888
5890
···
5894
5896
msgid "One or more of your selected files are too large. Maximum size is 100ย MB."
5895
5897
msgstr ""
5896
5898
5897
-
#: src/view/com/composer/Composer.tsx:366
5899
+
#: src/view/com/composer/Composer.tsx:405
5898
5900
msgid "One or more videos is missing alt text."
5899
5901
msgstr ""
5900
5902
···
5908
5910
5909
5911
#: src/screens/Settings/ActivityPrivacySettings.tsx:120
5910
5912
#: src/screens/Settings/ActivityPrivacySettings.tsx:125
5911
-
#: src/screens/Settings/PrivacyAndSecuritySettings.tsx:158
5913
+
#: src/screens/Settings/PrivacyAndSecuritySettings.tsx:157
5912
5914
msgid "Only followers who I follow"
5913
5915
msgstr ""
5914
5916
···
5933
5935
msgid "Oops!"
5934
5936
msgstr ""
5935
5937
5936
-
#: src/screens/Onboarding/StepFinished.tsx:555
5938
+
#: src/screens/Onboarding/StepFinished.tsx:553
5937
5939
msgid "Open"
5938
5940
msgstr ""
5939
5941
···
5951
5953
msgstr ""
5952
5954
5953
5955
#: src/screens/Messages/components/MessageInput.web.tsx:181
5954
-
#: src/view/com/composer/Composer.tsx:1429
5956
+
#: src/view/com/composer/Composer.tsx:1475
5955
5957
msgid "Open emoji picker"
5956
5958
msgstr ""
5957
5959
···
6051
6053
msgid "Opens device gallery to select up to {MAX_IMAGES, plural, other {# images}}, or a single video or GIF."
6052
6054
msgstr ""
6053
6055
6054
-
#: src/view/com/composer/Composer.tsx:1430
6056
+
#: src/view/com/composer/Composer.tsx:1476
6055
6057
msgid "Opens emoji picker"
6056
6058
msgstr ""
6057
6059
···
6085
6087
msgid "Opens password reset form"
6086
6088
msgstr ""
6087
6089
6088
-
#: src/view/com/composer/select-language/PostLanguageSelect.tsx:103
6090
+
#: src/view/com/composer/select-language/PostLanguageSelect.tsx:129
6089
6091
msgid "Opens post language settings"
6090
6092
msgstr ""
6091
6093
···
6411
6413
msgid "Please enter your username"
6412
6414
msgstr ""
6413
6415
6414
-
#: src/components/moderation/LabelsOnMeDialog.tsx:290
6416
+
#: src/components/moderation/LabelsOnMeDialog.tsx:292
6415
6417
msgid "Please explain why you think this label was incorrectly applied by {0}"
6416
6418
msgstr ""
6417
6419
···
6463
6465
msgid "Porn"
6464
6466
msgstr ""
6465
6467
6466
-
#: src/screens/PostThread/index.tsx:502
6468
+
#: src/screens/PostThread/index.tsx:503
6467
6469
msgctxt "description"
6468
6470
msgid "Post"
6469
6471
msgstr ""
6470
6472
6471
-
#: src/view/com/composer/Composer.tsx:1076
6473
+
#: src/view/com/composer/Composer.tsx:1118
6472
6474
msgctxt "action"
6473
6475
msgid "Post"
6474
6476
msgstr ""
6475
6477
6476
-
#: src/view/com/composer/Composer.tsx:1074
6478
+
#: src/view/com/composer/Composer.tsx:1116
6477
6479
msgctxt "action"
6478
6480
msgid "Post All"
6479
6481
msgstr ""
···
6505
6507
msgid "Post has been deleted"
6506
6508
msgstr ""
6507
6509
6508
-
#: src/components/moderation/ModerationDetailsDialog.tsx:107
6510
+
#: src/components/moderation/ModerationDetailsDialog.tsx:109
6509
6511
#: src/lib/moderation/useModerationCauseDescription.ts:106
6510
6512
msgid "Post Hidden by Muted Word"
6511
6513
msgstr ""
6512
6514
6513
-
#: src/components/moderation/ModerationDetailsDialog.tsx:110
6515
+
#: src/components/moderation/ModerationDetailsDialog.tsx:112
6514
6516
#: src/lib/moderation/useModerationCauseDescription.ts:115
6515
6517
msgid "Post Hidden by You"
6516
6518
msgstr ""
···
6525
6527
msgstr ""
6526
6528
6527
6529
#. Accessibility label for button that opens dialog to choose post language settings
6528
-
#: src/view/com/composer/select-language/PostLanguageSelect.tsx:98
6530
+
#: src/view/com/composer/select-language/PostLanguageSelect.tsx:124
6529
6531
msgid "Post language selection"
6530
6532
msgstr ""
6531
6533
···
6618
6620
#: src/Navigation.tsx:407
6619
6621
#: src/Navigation.tsx:415
6620
6622
#: src/screens/Settings/ActivityPrivacySettings.tsx:40
6621
-
#: src/screens/Settings/PrivacyAndSecuritySettings.tsx:45
6623
+
#: src/screens/Settings/PrivacyAndSecuritySettings.tsx:44
6622
6624
msgid "Privacy and Security"
6623
6625
msgstr ""
6624
6626
6625
6627
#: src/screens/Settings/NotificationSettings/ActivityNotificationSettings.tsx:161
6628
+
#: src/view/screens/Storybook/Admonitions.tsx:93
6626
6629
msgid "Privacy and Security settings"
6627
6630
msgstr ""
6628
6631
···
6637
6640
msgid "Privacy Policy"
6638
6641
msgstr ""
6639
6642
6640
-
#: src/view/com/composer/Composer.tsx:1840
6643
+
#: src/view/com/composer/Composer.tsx:1889
6641
6644
msgid "Processing video..."
6642
6645
msgstr ""
6643
6646
···
6663
6666
msgid "Profile updated"
6664
6667
msgstr ""
6665
6668
6666
-
#: src/screens/Onboarding/StepFinished.tsx:541
6669
+
#: src/screens/Onboarding/StepFinished.tsx:539
6667
6670
msgid "Public"
6668
6671
msgstr ""
6669
6672
···
6676
6679
msgstr ""
6677
6680
6678
6681
#. Accessibility label for button to publish a single post
6679
-
#: src/view/com/composer/Composer.tsx:1056
6682
+
#: src/view/com/composer/Composer.tsx:1098
6680
6683
msgid "Publish post"
6681
6684
msgstr ""
6682
6685
6683
6686
#. Accessibility label for button to publish multiple posts in a thread
6684
-
#: src/view/com/composer/Composer.tsx:1049
6687
+
#: src/view/com/composer/Composer.tsx:1091
6685
6688
msgid "Publish posts"
6686
6689
msgstr ""
6687
6690
6688
6691
#. Accessibility label for button to publish multiple replies in a thread
6689
-
#: src/view/com/composer/Composer.tsx:1034
6692
+
#: src/view/com/composer/Composer.tsx:1076
6690
6693
msgid "Publish replies"
6691
6694
msgstr ""
6692
6695
6693
6696
#. Accessibility label for button to publish a single reply
6694
-
#: src/view/com/composer/Composer.tsx:1041
6697
+
#: src/view/com/composer/Composer.tsx:1083
6695
6698
msgid "Publish reply"
6696
6699
msgstr ""
6697
6700
···
6850
6853
msgid "Recent Searches"
6851
6854
msgstr ""
6852
6855
6853
-
#: src/view/com/composer/select-language/PostLanguageSelectDialog.tsx:210
6856
+
#: src/view/com/composer/select-language/PostLanguageSelectDialog.tsx:232
6854
6857
msgid "Recently used"
6855
6858
msgstr ""
6856
6859
···
6878
6881
6879
6882
#: src/components/dialogs/lists/ListAddRemoveUsersDialog.tsx:171
6880
6883
#: src/components/dialogs/MutedWords.tsx:443
6881
-
#: src/components/dialogs/StarterPackDialog.tsx:371
6882
-
#: src/components/dialogs/StarterPackDialog.tsx:377
6884
+
#: src/components/dialogs/StarterPackDialog.tsx:370
6885
+
#: src/components/dialogs/StarterPackDialog.tsx:376
6883
6886
#: src/components/FeedCard.tsx:343
6884
6887
#: src/components/StarterPack/Wizard/WizardListCard.tsx:104
6885
6888
#: src/components/StarterPack/Wizard/WizardListCard.tsx:111
···
7024
7027
msgid "Removed from saved posts"
7025
7028
msgstr ""
7026
7029
7027
-
#: src/components/dialogs/StarterPackDialog.tsx:277
7030
+
#: src/components/dialogs/StarterPackDialog.tsx:276
7028
7031
msgid "Removed from starter pack"
7029
7032
msgstr ""
7030
7033
···
7081
7084
msgid "Replies to this post are disabled."
7082
7085
msgstr ""
7083
7086
7084
-
#: src/view/com/composer/Composer.tsx:1072
7087
+
#: src/view/com/composer/Composer.tsx:1114
7085
7088
msgctxt "action"
7086
7089
msgid "Reply"
7087
7090
msgstr ""
···
7091
7094
msgid "Reply ({0, plural, one {# reply} other {# replies}})"
7092
7095
msgstr ""
7093
7096
7094
-
#: src/components/moderation/ModerationDetailsDialog.tsx:116
7097
+
#: src/components/moderation/ModerationDetailsDialog.tsx:118
7095
7098
#: src/lib/moderation/useModerationCauseDescription.ts:125
7096
7099
msgid "Reply Hidden by Thread Author"
7097
7100
msgstr ""
7098
7101
7099
-
#: src/components/moderation/ModerationDetailsDialog.tsx:115
7102
+
#: src/components/moderation/ModerationDetailsDialog.tsx:117
7100
7103
#: src/lib/moderation/useModerationCauseDescription.ts:124
7101
7104
msgid "Reply Hidden by You"
7102
7105
msgstr ""
···
7345
7348
#: src/components/dms/MessageItem.tsx:322
7346
7349
#: src/components/Error.tsx:65
7347
7350
#: src/components/Lists.tsx:110
7348
-
#: src/components/moderation/ReportDialog/index.tsx:229
7351
+
#: src/components/moderation/ReportDialog/index.tsx:232
7349
7352
#: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/VideoFallback.tsx:55
7350
7353
#: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/VideoFallback.tsx:58
7351
7354
#: src/components/StarterPack/ProfileStarterPacks.tsx:342
···
7363
7366
#: src/screens/Signup/BackNextButtons.tsx:53
7364
7367
#: src/view/com/util/error/ErrorMessage.tsx:60
7365
7368
#: src/view/com/util/error/ErrorScreen.tsx:97
7369
+
#: src/view/screens/Storybook/Admonitions.tsx:63
7366
7370
msgid "Retry"
7367
7371
msgstr ""
7368
7372
7369
-
#: src/components/moderation/ReportDialog/index.tsx:226
7373
+
#: src/components/moderation/ReportDialog/index.tsx:229
7374
+
#: src/view/screens/Storybook/Admonitions.tsx:60
7370
7375
msgid "Retry loading report options"
7371
7376
msgstr ""
7372
7377
···
7496
7501
#: src/components/forms/SearchInput.tsx:34
7497
7502
#: src/components/forms/SearchInput.tsx:36
7498
7503
#: src/screens/Search/Shell.tsx:307
7499
-
#: src/screens/Search/Shell.tsx:464
7504
+
#: src/screens/Search/Shell.tsx:463
7500
7505
#: src/view/shell/bottom-bar/BottomBar.tsx:198
7501
7506
msgid "Search"
7502
7507
msgstr ""
···
7556
7561
msgid "Search is currently unavailable when logged out"
7557
7562
msgstr ""
7558
7563
7559
-
#: src/view/com/composer/select-language/PostLanguageSelectDialog.tsx:193
7560
-
#: src/view/com/composer/select-language/PostLanguageSelectDialog.tsx:194
7564
+
#: src/view/com/composer/select-language/PostLanguageSelectDialog.tsx:215
7565
+
#: src/view/com/composer/select-language/PostLanguageSelectDialog.tsx:216
7561
7566
msgid "Search languages"
7562
7567
msgstr ""
7563
7568
···
7639
7644
msgid "Select {0}"
7640
7645
msgstr ""
7641
7646
7642
-
#: src/view/com/composer/select-language/PostLanguageSelect.tsx:58
7647
+
#: src/view/com/composer/select-language/PostLanguageSelect.tsx:72
7643
7648
msgid "Select {langName}"
7644
7649
msgstr ""
7645
7650
···
7705
7710
msgstr ""
7706
7711
7707
7712
#: src/screens/Settings/LanguageSettings.tsx:178
7708
-
#: src/view/com/composer/select-language/PostLanguageSelectDialog.tsx:226
7713
+
#: src/view/com/composer/select-language/PostLanguageSelectDialog.tsx:248
7709
7714
msgid "Select languages"
7710
7715
msgstr ""
7711
7716
7712
-
#: src/components/moderation/ReportDialog/index.tsx:310
7717
+
#: src/components/moderation/ReportDialog/index.tsx:313
7713
7718
msgid "Select moderation service"
7714
7719
msgstr ""
7715
7720
···
7717
7722
msgid "Select moderator"
7718
7723
msgstr ""
7719
7724
7720
-
#: src/view/com/composer/select-language/PostLanguageSelect.tsx:45
7725
+
#: src/view/com/composer/select-language/PostLanguageSelect.tsx:57
7721
7726
msgid "Select post language"
7722
7727
msgstr ""
7723
7728
···
7738
7743
msgid "Select the moderation service(s) to report to"
7739
7744
msgstr ""
7740
7745
7741
-
#: src/view/com/composer/select-language/PostLanguageSelectDialog.tsx:172
7746
+
#: src/view/com/composer/select-language/PostLanguageSelectDialog.tsx:194
7742
7747
msgid "Select up to 3 languages used in this post"
7743
7748
msgstr ""
7744
7749
···
7800
7805
msgid "Send message"
7801
7806
msgstr ""
7802
7807
7803
-
#: src/components/PostControls/ShareMenu/RecentChats.tsx:123
7808
+
#: src/components/PostControls/ShareMenu/RecentChats.tsx:122
7804
7809
msgid "Send post to {name}"
7805
7810
msgstr ""
7806
7811
···
7819
7824
msgid "Send report to {0}"
7820
7825
msgstr ""
7821
7826
7822
-
#: src/components/moderation/ReportDialog/index.tsx:649
7827
+
#: src/components/moderation/ReportDialog/index.tsx:652
7823
7828
msgid "Send report to {title}"
7824
7829
msgstr ""
7825
7830
···
7875
7880
msgid "Settings for activity from others"
7876
7881
msgstr ""
7877
7882
7878
-
#: src/screens/Settings/PrivacyAndSecuritySettings.tsx:85
7883
+
#: src/screens/Settings/PrivacyAndSecuritySettings.tsx:84
7879
7884
msgid "Settings for allowing others to be notified of your posts"
7880
7885
msgstr ""
7881
7886
···
8206
8211
msgid "Similar accounts"
8207
8212
msgstr ""
8208
8213
8209
-
#: src/screens/Onboarding/StepFinished.tsx:380
8210
-
#: src/screens/Onboarding/StepFinished.tsx:462
8214
+
#: src/screens/Onboarding/StepFinished.tsx:379
8215
+
#: src/screens/Onboarding/StepFinished.tsx:461
8211
8216
#: src/screens/Onboarding/StepInterests/index.tsx:240
8212
8217
#: src/screens/Onboarding/StepSuggestedAccounts/index.tsx:222
8213
8218
#: src/screens/StarterPack/Wizard/index.tsx:218
8214
8219
msgid "Skip"
8215
8220
msgstr ""
8216
8221
8217
-
#: src/screens/Onboarding/StepFinished.tsx:373
8218
-
#: src/screens/Onboarding/StepFinished.tsx:459
8222
+
#: src/screens/Onboarding/StepFinished.tsx:372
8223
+
#: src/screens/Onboarding/StepFinished.tsx:458
8219
8224
msgid "Skip introduction and start using your account"
8220
8225
msgstr ""
8221
8226
···
8270
8275
msgstr ""
8271
8276
8272
8277
#: src/components/ageAssurance/AgeAssuranceInitDialog.tsx:138
8273
-
#: src/components/moderation/ReportDialog/index.tsx:223
8278
+
#: src/components/moderation/ReportDialog/index.tsx:224
8274
8279
#: src/screens/Deactivated.tsx:94
8275
8280
#: src/screens/Settings/components/DeactivateAccountDialog.tsx:59
8281
+
#: src/view/screens/Storybook/Admonitions.tsx:55
8276
8282
msgid "Something went wrong, please try again"
8277
8283
msgstr ""
8278
8284
···
8298
8304
msgid "Something wrong? Let us know."
8299
8305
msgstr ""
8300
8306
8301
-
#: src/App.native.tsx:125
8302
-
#: src/App.web.tsx:101
8307
+
#: src/App.native.tsx:128
8308
+
#: src/App.web.tsx:100
8303
8309
msgid "Sorry! Your session expired. Please sign in again."
8304
8310
msgstr ""
8305
8311
···
8315
8321
msgid "Sort replies to the same post by:"
8316
8322
msgstr ""
8317
8323
8318
-
#: src/components/moderation/LabelsOnMeDialog.tsx:178
8319
-
#: src/components/moderation/ModerationDetailsDialog.tsx:186
8324
+
#: src/components/moderation/LabelsOnMeDialog.tsx:180
8325
+
#: src/components/moderation/ModerationDetailsDialog.tsx:188
8320
8326
msgid "Source: <0>{sourceName}</0>"
8321
8327
msgstr ""
8322
8328
···
8338
8344
msgid "Sports"
8339
8345
msgstr ""
8340
8346
8341
-
#: src/components/PostControls/ShareMenu/RecentChats.tsx:208
8347
+
#: src/components/PostControls/ShareMenu/RecentChats.tsx:207
8342
8348
msgid "Start a conversation, and it will appear here."
8343
8349
msgstr ""
8344
8350
···
8412
8418
8413
8419
#: src/components/ageAssurance/AgeAssuranceAppealDialog.tsx:117
8414
8420
#: src/components/ageAssurance/AgeAssuranceAppealDialog.tsx:123
8415
-
#: src/components/moderation/LabelsOnMeDialog.tsx:324
8416
-
#: src/components/moderation/LabelsOnMeDialog.tsx:325
8421
+
#: src/components/moderation/LabelsOnMeDialog.tsx:326
8422
+
#: src/components/moderation/LabelsOnMeDialog.tsx:327
8417
8423
#: src/screens/Messages/components/ChatDisabled.tsx:154
8418
8424
#: src/screens/Messages/components/ChatDisabled.tsx:155
8419
8425
msgid "Submit"
···
8427
8433
msgid "Submit Appeal"
8428
8434
msgstr ""
8429
8435
8430
-
#: src/components/moderation/ReportDialog/index.tsx:387
8431
-
#: src/components/moderation/ReportDialog/index.tsx:444
8432
-
#: src/components/moderation/ReportDialog/index.tsx:451
8436
+
#: src/components/moderation/ReportDialog/index.tsx:390
8437
+
#: src/components/moderation/ReportDialog/index.tsx:447
8438
+
#: src/components/moderation/ReportDialog/index.tsx:454
8433
8439
msgid "Submit report"
8434
8440
msgstr ""
8435
8441
···
8630
8636
msgid "Text field"
8631
8637
msgstr ""
8632
8638
8633
-
#: src/components/moderation/LabelsOnMeDialog.tsx:288
8639
+
#: src/components/moderation/LabelsOnMeDialog.tsx:290
8634
8640
#: src/screens/Messages/components/ChatDisabled.tsx:120
8635
8641
msgid "Text input field"
8636
8642
msgstr ""
···
8687
8693
msgid "The app will be restarted"
8688
8694
msgstr ""
8689
8695
8690
-
#: src/components/moderation/ModerationDetailsDialog.tsx:119
8696
+
#: src/components/moderation/ModerationDetailsDialog.tsx:121
8691
8697
#: src/lib/moderation/useModerationCauseDescription.ts:128
8692
8698
msgid "The author of this thread has hidden this reply."
8693
8699
msgstr ""
···
8724
8730
msgid "The feed has been replaced with Discover."
8725
8731
msgstr ""
8726
8732
8727
-
#: src/components/moderation/LabelsOnMeDialog.tsx:59
8733
+
#: src/components/moderation/LabelsOnMeDialog.tsx:61
8728
8734
msgid "The following labels were applied to your account."
8729
8735
msgstr ""
8730
8736
8731
-
#: src/components/moderation/LabelsOnMeDialog.tsx:60
8737
+
#: src/components/moderation/LabelsOnMeDialog.tsx:62
8732
8738
msgid "The following labels were applied to your content."
8733
8739
msgstr ""
8734
8740
···
8748
8754
msgid "The open social network."
8749
8755
msgstr ""
8750
8756
8757
+
#: src/view/com/composer/select-language/SuggestedLanguage.tsx:119
8758
+
msgid "The post you're replying to was marked as being written in {suggestedLanguageName} by its author. Would you like to reply in <0>{suggestedLanguageName}</0>?"
8759
+
msgstr ""
8760
+
8751
8761
#: src/view/screens/PrivacyPolicy.tsx:38
8752
8762
msgid "The Privacy Policy has been moved to <0/>"
8753
8763
msgstr ""
···
8878
8888
msgstr ""
8879
8889
8880
8890
#: src/components/dialogs/GifSelect.tsx:258
8881
-
#: src/view/com/composer/select-language/PostLanguageSelectDialog.tsx:303
8891
+
#: src/view/com/composer/select-language/PostLanguageSelectDialog.tsx:325
8882
8892
#: src/view/com/util/ErrorBoundary.tsx:59
8883
8893
msgid "There was an unexpected issue in the application. Please let us know if this happened to you!"
8884
8894
msgstr ""
···
8915
8925
msgid "This action can be undone at any time."
8916
8926
msgstr ""
8917
8927
8918
-
#: src/components/moderation/LabelsOnMeDialog.tsx:271
8928
+
#: src/components/moderation/LabelsOnMeDialog.tsx:273
8919
8929
msgid "This appeal will be sent to <0>{sourceName}</0>."
8920
8930
msgstr ""
8921
8931
···
8943
8953
msgid "This content is hosted by {0}. Do you want to enable external media?"
8944
8954
msgstr ""
8945
8955
8946
-
#: src/components/moderation/ModerationDetailsDialog.tsx:84
8956
+
#: src/components/moderation/ModerationDetailsDialog.tsx:86
8947
8957
#: src/lib/moderation/useModerationCauseDescription.ts:84
8948
8958
msgid "This content is not available because one of the users involved has blocked the other."
8949
8959
msgstr ""
···
9007
9017
msgid "This is not a valid link"
9008
9018
msgstr ""
9009
9019
9010
-
#: src/components/moderation/ModerationDetailsDialog.tsx:168
9020
+
#: src/components/moderation/ModerationDetailsDialog.tsx:170
9011
9021
msgid "This label was applied by the author."
9012
9022
msgstr ""
9013
9023
9014
-
#: src/components/moderation/LabelsOnMeDialog.tsx:165
9024
+
#: src/components/moderation/LabelsOnMeDialog.tsx:167
9015
9025
msgid "This label was applied by you."
9016
9026
msgstr ""
9017
9027
···
9059
9069
msgid "This post will be hidden from feeds and threads. This cannot be undone."
9060
9070
msgstr ""
9061
9071
9062
-
#: src/view/com/composer/Composer.tsx:475
9072
+
#: src/view/com/composer/Composer.tsx:514
9063
9073
msgid "This post's author has disabled quote posts."
9064
9074
msgstr ""
9065
9075
···
9095
9105
msgid "This user has blocked you"
9096
9106
msgstr ""
9097
9107
9098
-
#: src/components/moderation/ModerationDetailsDialog.tsx:79
9108
+
#: src/components/moderation/ModerationDetailsDialog.tsx:81
9099
9109
#: src/lib/moderation/useModerationCauseDescription.ts:75
9100
9110
msgid "This user has blocked you. You cannot view their content."
9101
9111
msgstr ""
···
9104
9114
msgid "This user has requested that their content only be shown to signed-in users."
9105
9115
msgstr ""
9106
9116
9107
-
#: src/components/moderation/ModerationDetailsDialog.tsx:59
9117
+
#: src/components/moderation/ModerationDetailsDialog.tsx:61
9108
9118
msgid "This user is included in the <0>{0}</0> list which you have blocked."
9109
9119
msgstr ""
9110
9120
9111
-
#: src/components/moderation/ModerationDetailsDialog.tsx:91
9121
+
#: src/components/moderation/ModerationDetailsDialog.tsx:93
9112
9122
msgid "This user is included in the <0>{0}</0> list which you have muted."
9113
9123
msgstr ""
9114
9124
···
9240
9250
msgid "TV"
9241
9251
msgstr ""
9242
9252
9243
-
#: src/screens/Settings/PrivacyAndSecuritySettings.tsx:65
9253
+
#: src/screens/Settings/PrivacyAndSecuritySettings.tsx:64
9244
9254
msgid "Two-factor authentication (2FA)"
9245
9255
msgstr ""
9246
9256
···
9365
9375
msgid "Unfortunately, Bluesky is unavailable in Mississippi right now."
9366
9376
msgstr ""
9367
9377
9368
-
#: src/components/moderation/ReportDialog/index.tsx:372
9378
+
#: src/components/moderation/ReportDialog/index.tsx:375
9369
9379
msgid "Unfortunately, none of your subscribed labelers supports this report type."
9370
9380
msgstr ""
9371
9381
···
9487
9497
msgid "Unsubscribed from list"
9488
9498
msgstr ""
9489
9499
9490
-
#: src/view/com/composer/Composer.tsx:852
9500
+
#: src/view/com/composer/Composer.tsx:894
9491
9501
msgid "Unsupported video type: {mimeType}"
9492
9502
msgstr ""
9493
9503
···
9573
9583
msgid "Uploading link thumbnail..."
9574
9584
msgstr ""
9575
9585
9576
-
#: src/view/com/composer/Composer.tsx:1837
9586
+
#: src/view/com/composer/Composer.tsx:1886
9577
9587
msgid "Uploading video..."
9578
9588
msgstr ""
9579
9589
···
9617
9627
msgid "User blocked"
9618
9628
msgstr ""
9619
9629
9620
-
#: src/components/moderation/ModerationDetailsDialog.tsx:71
9630
+
#: src/components/moderation/ModerationDetailsDialog.tsx:73
9621
9631
#: src/lib/moderation/useModerationCauseDescription.ts:63
9622
9632
msgid "User Blocked"
9623
9633
msgstr ""
···
9630
9640
msgid "User blocked by list"
9631
9641
msgstr ""
9632
9642
9633
-
#: src/components/moderation/ModerationDetailsDialog.tsx:57
9643
+
#: src/components/moderation/ModerationDetailsDialog.tsx:59
9634
9644
msgid "User Blocked by List"
9635
9645
msgstr ""
9636
9646
···
9638
9648
msgid "User Blocking You"
9639
9649
msgstr ""
9640
9650
9641
-
#: src/components/moderation/ModerationDetailsDialog.tsx:77
9651
+
#: src/components/moderation/ModerationDetailsDialog.tsx:79
9642
9652
msgid "User Blocks You"
9643
9653
msgstr ""
9644
9654
···
9831
9841
msgid "Video settings"
9832
9842
msgstr ""
9833
9843
9834
-
#: src/view/com/composer/Composer.tsx:1847
9844
+
#: src/view/com/composer/Composer.tsx:1896
9835
9845
msgid "Video uploaded"
9836
9846
msgstr ""
9837
9847
···
9847
9857
msgid "Videos must be less than 3 minutes long."
9848
9858
msgstr ""
9849
9859
9850
-
#: src/view/com/composer/Composer.tsx:546
9860
+
#: src/view/com/composer/Composer.tsx:585
9851
9861
msgctxt "Action to view the post the user just created"
9852
9862
msgid "View"
9853
9863
msgstr ""
9854
9864
9855
-
#: src/screens/Profile/Header/Shell.tsx:229
9865
+
#: src/screens/Profile/Header/Shell.tsx:241
9856
9866
msgid "View {0}'s avatar"
9857
9867
msgstr ""
9858
9868
···
9900
9910
9901
9911
#: src/components/interstitials/TrendingVideos.tsx:198
9902
9912
#: src/components/interstitials/TrendingVideos.tsx:220
9903
-
#: src/screens/Search/modules/ExploreTrendingVideos.tsx:194
9904
-
#: src/screens/Search/modules/ExploreTrendingVideos.tsx:213
9913
+
#: src/screens/Search/modules/ExploreTrendingVideos.tsx:193
9914
+
#: src/screens/Search/modules/ExploreTrendingVideos.tsx:212
9905
9915
msgid "View more"
9906
9916
msgstr ""
9907
9917
···
9909
9919
msgid "View more trending videos"
9910
9920
msgstr ""
9911
9921
9912
-
#: src/view/com/composer/Composer.tsx:541
9922
+
#: src/view/com/composer/Composer.tsx:580
9913
9923
msgid "View post"
9914
9924
msgstr ""
9915
9925
···
10040
10050
msgid "We have sent another verification email to <0>{0}</0>."
10041
10051
msgstr ""
10042
10052
10043
-
#: src/screens/Onboarding/StepFinished.tsx:533
10053
+
#: src/screens/Onboarding/StepFinished.tsx:531
10044
10054
msgid "We hope you have a wonderful time. Remember, Bluesky is:"
10045
10055
msgstr ""
10046
10056
···
10137
10147
msgid "We're sorry, but your search could not be completed. Please try again in a few minutes."
10138
10148
msgstr ""
10139
10149
10140
-
#: src/view/com/composer/Composer.tsx:472
10150
+
#: src/view/com/composer/Composer.tsx:511
10141
10151
msgid "We're sorry! The post you are replying to has been deleted."
10142
10152
msgstr ""
10143
10153
···
10188
10198
10189
10199
#: src/view/com/auth/SplashScreen.tsx:51
10190
10200
#: src/view/com/auth/SplashScreen.web.tsx:103
10191
-
#: src/view/com/composer/Composer.tsx:812
10201
+
#: src/view/com/composer/Composer.tsx:854
10192
10202
msgid "What's up?"
10193
10203
msgstr ""
10194
10204
···
10219
10229
msgstr ""
10220
10230
10221
10231
#: src/components/interstitials/TrendingVideos.tsx:125
10222
-
#: src/screens/Search/modules/ExploreTrendingVideos.tsx:108
10232
+
#: src/screens/Search/modules/ExploreTrendingVideos.tsx:107
10223
10233
msgid "Whoops! Trending videos failed to load."
10224
10234
msgstr ""
10225
10235
···
10266
10276
msgid "Write a message"
10267
10277
msgstr ""
10268
10278
10269
-
#: src/view/com/composer/Composer.tsx:912
10279
+
#: src/view/com/composer/Composer.tsx:954
10270
10280
msgid "Write post"
10271
10281
msgstr ""
10272
10282
10273
10283
#: src/screens/PostThread/components/ThreadComposePrompt.tsx:90
10274
-
#: src/view/com/composer/Composer.tsx:810
10284
+
#: src/view/com/composer/Composer.tsx:852
10275
10285
msgid "Write your reply"
10276
10286
msgstr ""
10277
10287
···
10289
10299
msgid "www.mylivestream.tv"
10290
10300
msgstr ""
10291
10301
10292
-
#: src/view/com/composer/select-language/SuggestedLanguage.tsx:102
10302
+
#: src/view/com/composer/select-language/SuggestedLanguage.tsx:181
10293
10303
msgid "Yes"
10294
10304
msgstr ""
10295
10305
···
10456
10466
msgid "You have blocked this user"
10457
10467
msgstr ""
10458
10468
10459
-
#: src/components/moderation/ModerationDetailsDialog.tsx:73
10469
+
#: src/components/moderation/ModerationDetailsDialog.tsx:75
10460
10470
#: src/lib/moderation/useModerationCauseDescription.ts:57
10461
10471
#: src/lib/moderation/useModerationCauseDescription.ts:65
10462
10472
msgid "You have blocked this user. You cannot view their content."
···
10476
10486
msgid "You have hidden this post"
10477
10487
msgstr ""
10478
10488
10479
-
#: src/components/moderation/ModerationDetailsDialog.tsx:111
10489
+
#: src/components/moderation/ModerationDetailsDialog.tsx:113
10480
10490
msgid "You have hidden this post."
10481
10491
msgstr ""
10482
10492
10483
-
#: src/components/moderation/ModerationDetailsDialog.tsx:104
10493
+
#: src/components/moderation/ModerationDetailsDialog.tsx:106
10484
10494
#: src/lib/moderation/useModerationCauseDescription.ts:99
10485
10495
msgid "You have muted this account."
10486
10496
msgstr ""
···
10502
10512
msgid "You have no lists."
10503
10513
msgstr ""
10504
10514
10505
-
#: src/components/dialogs/StarterPackDialog.tsx:101
10515
+
#: src/components/dialogs/StarterPackDialog.tsx:100
10506
10516
msgid "You have no starter packs."
10507
10517
msgstr ""
10508
10518
···
10510
10520
msgid "You have not blocked any accounts yet. To block an account, go to their profile and select \"Block account\" from the menu on their account."
10511
10521
msgstr ""
10512
10522
10513
-
#: src/view/screens/ModerationMutedAccounts.tsx:179
10523
+
#: src/view/screens/ModerationMutedAccounts.tsx:177
10514
10524
msgid "You have not muted any accounts yet. To mute an account, go to their profile and select \"Mute account\" from the menu on their account."
10515
10525
msgstr ""
10516
10526
···
10534
10544
msgid "You haven't muted any words or tags yet"
10535
10545
msgstr ""
10536
10546
10537
-
#: src/components/moderation/ModerationDetailsDialog.tsx:118
10547
+
#: src/components/moderation/ModerationDetailsDialog.tsx:120
10538
10548
#: src/lib/moderation/useModerationCauseDescription.ts:127
10539
10549
msgid "You hid this reply."
10540
10550
msgstr ""
···
10551
10561
msgid "You joined Bluesky using a starter pack {timeAgoString} ago"
10552
10562
msgstr ""
10553
10563
10554
-
#: src/components/moderation/LabelsOnMeDialog.tsx:79
10564
+
#: src/components/moderation/LabelsOnMeDialog.tsx:81
10555
10565
msgid "You may appeal non-self labels if you feel they were placed in error."
10556
10566
msgstr ""
10557
10567
10558
-
#: src/components/moderation/LabelsOnMeDialog.tsx:84
10568
+
#: src/components/moderation/LabelsOnMeDialog.tsx:86
10559
10569
msgid "You may appeal these labels if you feel they were placed in error."
10560
10570
msgstr ""
10561
10571
···
10685
10695
msgid "You're in line"
10686
10696
msgstr ""
10687
10697
10688
-
#: src/screens/Onboarding/StepFinished.tsx:530
10698
+
#: src/screens/Onboarding/StepFinished.tsx:528
10689
10699
msgid "You're ready to go!"
10690
10700
msgstr ""
10691
10701
···
10694
10704
msgid "You're signed in with an App Password. Please sign in with your main password to continue deactivating your account."
10695
10705
msgstr ""
10696
10706
10697
-
#: src/components/moderation/ModerationDetailsDialog.tsx:108
10707
+
#: src/components/moderation/ModerationDetailsDialog.tsx:110
10698
10708
#: src/lib/moderation/useModerationCauseDescription.ts:108
10699
10709
msgid "You've chosen to hide a word or tag within this post."
10700
10710
msgstr ""
···
10849
10859
msgid "Your password must be at least 8 characters long."
10850
10860
msgstr ""
10851
10861
10852
-
#: src/view/com/composer/Composer.tsx:537
10862
+
#: src/view/com/composer/Composer.tsx:576
10853
10863
msgid "Your post was sent"
10854
10864
msgstr ""
10855
10865
10856
-
#: src/view/com/composer/Composer.tsx:534
10866
+
#: src/view/com/composer/Composer.tsx:573
10857
10867
msgid "Your posts were sent"
10858
10868
msgstr ""
10859
10869
10860
-
#: src/screens/Onboarding/StepFinished.tsx:545
10870
+
#: src/screens/Onboarding/StepFinished.tsx:543
10861
10871
msgid "Your posts, likes, and blocks are public. Mutes are private."
10862
10872
msgstr ""
10863
10873
···
10865
10875
msgid "Your preferred language"
10866
10876
msgstr ""
10867
10877
10868
-
#: src/screens/Onboarding/StepFinished.tsx:422
10878
+
#: src/screens/Onboarding/StepFinished.tsx:421
10869
10879
msgid "Your profile picture"
10870
10880
msgstr ""
10871
10881
10872
-
#: src/screens/Onboarding/StepFinished.tsx:350
10882
+
#: src/screens/Onboarding/StepFinished.tsx:349
10873
10883
msgid "Your profile picture surrounded by concentric circles of other users' profile pictures"
10874
10884
msgstr ""
10875
10885
···
10877
10887
msgid "Your profile, posts, feeds, and lists will no longer be visible to other Bluesky users. You can reactivate your account at any time by logging in."
10878
10888
msgstr ""
10879
10889
10880
-
#: src/view/com/composer/Composer.tsx:536
10890
+
#: src/view/com/composer/Composer.tsx:575
10881
10891
msgid "Your reply was sent"
10882
10892
msgstr ""
10883
10893
10884
-
#: src/components/moderation/ReportDialog/index.tsx:394
10894
+
#: src/components/moderation/ReportDialog/index.tsx:397
10885
10895
msgid "Your report will be sent to <0>{0}</0>."
10886
10896
msgstr ""
10887
10897
+10
-5
src/logger/metrics.ts
+10
-5
src/logger/metrics.ts
···
175
175
'feed:suggestion:press': {
176
176
feedUrl: string
177
177
}
178
-
'discover:showMore': {
178
+
'feed:showMore': {
179
+
feed: string
179
180
feedContext: string
180
181
}
181
-
'discover:showLess': {
182
+
'feed:showLess': {
183
+
feed: string
182
184
feedContext: string
183
185
}
184
-
'discover:clickthrough': {
186
+
'feed:clickthrough': {
187
+
feed: string
185
188
count: number
186
189
}
187
-
'discover:engaged': {
190
+
'feed:engaged': {
191
+
feed: string
188
192
count: number
189
193
}
190
-
'discover:seen': {
194
+
'feed:seen': {
195
+
feed: string
191
196
count: number
192
197
}
193
198
-2
src/screens/Onboarding/StepFinished.tsx
-2
src/screens/Onboarding/StepFinished.tsx
···
69
69
import * as bsky from '#/types/bsky'
70
70
71
71
export function StepFinished() {
72
-
const {_} = useLingui()
73
72
const {state, dispatch} = useContext(Context)
74
73
const onboardDispatch = useOnboardingDispatch()
75
74
const [saving, setSaving] = useState(false)
···
495
494
496
495
function Dot({active}: {active: boolean}) {
497
496
const t = useTheme()
498
-
const {_} = useLingui()
499
497
500
498
return (
501
499
<View
+1
-1
src/screens/PostThread/components/ThreadItemAnchor.tsx
+1
-1
src/screens/PostThread/components/ThreadItemAnchor.tsx
+13
-12
src/screens/PostThread/index.tsx
+13
-12
src/screens/PostThread/index.tsx
···
148
148
*/
149
149
const shouldHandleScroll = useRef(true)
150
150
/**
151
-
* Called any time the content size of the list changes, _just_ before paint.
151
+
* Called any time the content size of the list changes. Could be a fresh
152
+
* render, items being added to the list, or any resize that changes the
153
+
* scrollable size of the content.
152
154
*
153
155
* We want this to fire every time we change params (which will reset
154
156
* `deferParents` via `onLayout` on the anchor post, due to the key change),
···
193
195
* will give us a _positive_ offset, which will scroll the anchor post
194
196
* back _up_ to the top of the screen.
195
197
*/
196
-
list.scrollToOffset({
197
-
offset: anchorOffsetTop - headerHeight,
198
-
})
198
+
const offset = anchorOffsetTop - headerHeight
199
+
list.scrollToOffset({offset})
199
200
200
201
/*
201
-
* After the second pass, `deferParents` will be `false`, and we need
202
-
* to ensure this doesn't run again until scroll handling is requested
203
-
* again via `shouldHandleScroll.current === true` and a params
204
-
* change via `prepareForParamsUpdate`.
202
+
* After we manage to do a positive adjustment, we need to ensure this
203
+
* doesn't run again until scroll handling is requested again via
204
+
* `shouldHandleScroll.current === true` and a params change via
205
+
* `prepareForParamsUpdate`.
205
206
*
206
207
* The `isRoot` here is needed because if we're looking at the anchor
207
208
* post, this handler will not fire after `deferParents` is set to
208
209
* `false`, since there are no parents to render above it. In this case,
209
-
* we want to make sure `shouldHandleScroll` is set to `false` so that
210
-
* subsequent size changes unrelated to a params change (like pagination)
211
-
* do not affect scroll.
210
+
* we want to make sure `shouldHandleScroll` is set to `false` right away
211
+
* so that subsequent size changes unrelated to a params change (like
212
+
* pagination) do not affect scroll.
212
213
*/
213
-
if (!deferParents || isRoot) shouldHandleScroll.current = false
214
+
if (offset > 0 || isRoot) shouldHandleScroll.current = false
214
215
}
215
216
})
216
217
+23
-11
src/screens/Profile/Header/Shell.tsx
+23
-11
src/screens/Profile/Header/Shell.tsx
···
209
209
210
210
{children}
211
211
212
-
{!isPlaceholderProfile && (
213
-
<View
214
-
style={[a.px_lg, a.pt_xs, a.pb_sm]}
215
-
pointerEvents={isIOS ? 'auto' : 'box-none'}>
216
-
{isMe ? (
217
-
<LabelsOnMe type="account" labels={profile.labels} />
218
-
) : (
219
-
<ProfileHeaderAlerts moderation={moderation} />
220
-
)}
221
-
</View>
222
-
)}
212
+
{!isPlaceholderProfile &&
213
+
(isMe ? (
214
+
<LabelsOnMe
215
+
type="account"
216
+
labels={profile.labels}
217
+
style={[
218
+
a.px_lg,
219
+
a.pt_xs,
220
+
a.pb_sm,
221
+
isIOS ? a.pointer_events_auto : {pointerEvents: 'box-none'},
222
+
]}
223
+
/>
224
+
) : (
225
+
<ProfileHeaderAlerts
226
+
moderation={moderation}
227
+
style={[
228
+
a.px_lg,
229
+
a.pt_xs,
230
+
a.pb_sm,
231
+
isIOS ? a.pointer_events_auto : {pointerEvents: 'box-none'},
232
+
]}
233
+
/>
234
+
))}
223
235
224
236
<GrowableAvatar style={[a.absolute, {top: 104, left: 10}]}>
225
237
<TouchableWithoutFeedback
-1
src/screens/Search/Shell.tsx
-1
src/screens/Search/Shell.tsx
-1
src/screens/Search/modules/ExploreTrendingVideos.tsx
-1
src/screens/Search/modules/ExploreTrendingVideos.tsx
+1
-1
src/screens/Settings/AppPasswords.tsx
+1
-1
src/screens/Settings/AppPasswords.tsx
···
195
195
</View>
196
196
{appPassword.privileged && (
197
197
<View style={[a.flex_row, a.gap_sm, a.align_center, a.mt_md]}>
198
-
<WarningIcon style={[{color: colors.warning[t.scheme]}]} />
198
+
<WarningIcon style={[{color: colors.warning}]} />
199
199
<Text style={t.atoms.text_contrast_high}>
200
200
<Trans>Allows access to direct messages</Trans>
201
201
</Text>
+2
-2
src/screens/Settings/NotificationSettings/ActivityNotificationSettings.tsx
+2
-2
src/screens/Settings/NotificationSettings/ActivityNotificationSettings.tsx
···
134
134
<Admonition.Outer type="tip">
135
135
<Admonition.Row>
136
136
<Admonition.Icon />
137
-
<View style={[a.flex_1, a.gap_sm]}>
137
+
<Admonition.Content>
138
138
<Admonition.Text>
139
139
<Trans>
140
140
Enable notifications for an account by visiting their
···
166
166
.
167
167
</Trans>
168
168
</Admonition.Text>
169
-
</View>
169
+
</Admonition.Content>
170
170
</Admonition.Row>
171
171
</Admonition.Outer>
172
172
) : (
+2
-3
src/screens/Settings/PrivacyAndSecuritySettings.tsx
+2
-3
src/screens/Settings/PrivacyAndSecuritySettings.tsx
···
1
-
import {View} from 'react-native'
2
1
import {type AppBskyNotificationDeclaration} from '@atproto/api'
3
2
import {msg, Trans} from '@lingui/macro'
4
3
import {useLingui} from '@lingui/react'
···
112
111
<Admonition.Outer type="tip" style={[a.flex_1]}>
113
112
<Admonition.Row>
114
113
<Admonition.Icon />
115
-
<View style={[a.flex_1, a.gap_sm]}>
114
+
<Admonition.Content>
116
115
<Admonition.Text>
117
116
<Trans>
118
117
Note: Bluesky is an open and public network. This setting
···
131
130
<Trans>Learn more about what is public on Bluesky.</Trans>
132
131
</InlineLinkText>
133
132
</Admonition.Text>
134
-
</View>
133
+
</Admonition.Content>
135
134
</Admonition.Row>
136
135
</Admonition.Outer>
137
136
</SettingsList.Item>
+41
-15
src/state/feed-feedback.tsx
+41
-15
src/state/feed-feedback.tsx
···
12
12
13
13
import {PROD_FEEDS, STAGING_FEEDS} from '#/lib/constants'
14
14
import {isNetworkError} from '#/lib/hooks/useCleanError'
15
-
import {logEvent} from '#/lib/statsig/statsig'
16
15
import {Logger} from '#/logger'
17
16
import {
18
17
type FeedSourceFeedInfo,
···
28
27
29
28
export const FEEDBACK_FEEDS = [...PROD_FEEDS, ...STAGING_FEEDS]
30
29
31
-
export const DIRECT_FEEDBACK_INTERACTIONS = new Set<
30
+
export const THIRD_PARTY_ALLOWED_INTERACTIONS = new Set<
32
31
AppBskyFeedDefs.Interaction['event']
33
-
>(['app.bsky.feed.defs#requestLess', 'app.bsky.feed.defs#requestMore'])
32
+
>([
33
+
// These are explicit actions and are therefore fine to send.
34
+
'app.bsky.feed.defs#requestLess',
35
+
'app.bsky.feed.defs#requestMore',
36
+
// These can be inferred from the firehose and are therefore fine to send.
37
+
'app.bsky.feed.defs#interactionLike',
38
+
'app.bsky.feed.defs#interactionQuote',
39
+
'app.bsky.feed.defs#interactionReply',
40
+
'app.bsky.feed.defs#interactionRepost',
41
+
// This can be inferred from pagination requests for everything except the very last page
42
+
// so it is fine to send. It is crucial for third party algorithmic feeds to receive these.
43
+
'app.bsky.feed.defs#interactionSeen',
44
+
])
34
45
35
46
const logger = Logger.create(Logger.Context.FeedFeedback)
36
47
···
78
89
const aggregatedStats = useRef<AggregatedStats | null>(null)
79
90
const throttledFlushAggregatedStats = useMemo(
80
91
() =>
81
-
throttle(() => flushToStatsig(aggregatedStats.current), 45e3, {
82
-
leading: true, // The outer call is already throttled somewhat.
83
-
trailing: true,
84
-
}),
85
-
[],
92
+
throttle(
93
+
() =>
94
+
flushToStatsig(
95
+
aggregatedStats.current,
96
+
feed?.feedDescriptor ?? 'unknown',
97
+
),
98
+
45e3,
99
+
{
100
+
leading: true, // The outer call is already throttled somewhat.
101
+
trailing: true,
102
+
},
103
+
),
104
+
[feed?.feedDescriptor],
86
105
)
87
106
88
107
const sendToFeedNoDelay = useCallback(() => {
···
123
142
sendOrAggregateInteractionsForStats(
124
143
aggregatedStats.current,
125
144
interactionsToSend,
145
+
feed?.feedDescriptor ?? 'unknown',
126
146
)
127
147
throttledFlushAggregatedStats()
128
148
logger.debug('flushed')
···
228
248
return false
229
249
}
230
250
const isDiscover = isDiscoverFeed(feed.feedDescriptor)
231
-
return isDiscover ? true : DIRECT_FEEDBACK_INTERACTIONS.has(interaction)
251
+
return isDiscover ? true : THIRD_PARTY_ALLOWED_INTERACTIONS.has(interaction)
232
252
}
233
253
234
254
function toString(interaction: AppBskyFeedDefs.Interaction): string {
···
259
279
function sendOrAggregateInteractionsForStats(
260
280
stats: AggregatedStats,
261
281
interactions: AppBskyFeedDefs.Interaction[],
282
+
feed: string,
262
283
) {
263
284
for (let interaction of interactions) {
264
285
switch (interaction.event) {
265
286
// Pressing "Show more" / "Show less" is relatively uncommon so we won't aggregate them.
266
287
// This lets us send the feed context together with them.
267
288
case 'app.bsky.feed.defs#requestLess': {
268
-
logEvent('discover:showLess', {
289
+
logger.metric('feed:showLess', {
290
+
feed,
269
291
feedContext: interaction.feedContext ?? '',
270
292
})
271
293
break
272
294
}
273
295
case 'app.bsky.feed.defs#requestMore': {
274
-
logEvent('discover:showMore', {
296
+
logger.metric('feed:showMore', {
297
+
feed,
275
298
feedContext: interaction.feedContext ?? '',
276
299
})
277
300
break
···
301
324
}
302
325
}
303
326
304
-
function flushToStatsig(stats: AggregatedStats | null) {
327
+
function flushToStatsig(stats: AggregatedStats | null, feedDescriptor: string) {
305
328
if (stats === null) {
306
329
return
307
330
}
308
331
309
332
if (stats.clickthroughCount > 0) {
310
-
logEvent('discover:clickthrough', {
333
+
logger.metric('feed:clickthrough', {
311
334
count: stats.clickthroughCount,
335
+
feed: feedDescriptor,
312
336
})
313
337
stats.clickthroughCount = 0
314
338
}
315
339
316
340
if (stats.engagedCount > 0) {
317
-
logEvent('discover:engaged', {
341
+
logger.metric('feed:engaged', {
318
342
count: stats.engagedCount,
343
+
feed: feedDescriptor,
319
344
})
320
345
stats.engagedCount = 0
321
346
}
322
347
323
348
if (stats.seenCount > 0) {
324
-
logEvent('discover:seen', {
349
+
logger.metric('feed:seen', {
325
350
count: stats.seenCount,
351
+
feed: feedDescriptor,
326
352
})
327
353
stats.seenCount = 0
328
354
}
+36
-1
src/state/geolocation/useSyncedDeviceGeolocation.ts
+36
-1
src/state/geolocation/useSyncedDeviceGeolocation.ts
···
1
1
import {useEffect, useRef} from 'react'
2
2
import * as Location from 'expo-location'
3
+
import {createPermissionHook} from 'expo-modules-core'
3
4
4
5
import {logger} from '#/state/geolocation/logger'
5
6
import {getDeviceGeolocation} from '#/state/geolocation/util'
6
7
import {device, useStorage} from '#/storage'
7
8
8
9
/**
10
+
* Location.useForegroundPermissions on web just errors if the navigator.permissions API is not available.
11
+
* We need to catch and ignore it, since it's effectively denied.
12
+
* @see https://github.com/expo/expo/blob/72f1562ed9cce5ff6dfe04aa415b71632a3d4b87/packages/expo-location/src/Location.ts#L290-L293
13
+
*/
14
+
const useForegroundPermissions = createPermissionHook({
15
+
getMethod: () =>
16
+
Location.getForegroundPermissionsAsync().catch(error => {
17
+
logger.debug(
18
+
'useForegroundPermission: error getting location permissions',
19
+
{safeMessage: error},
20
+
)
21
+
return {
22
+
status: Location.PermissionStatus.DENIED,
23
+
granted: false,
24
+
canAskAgain: false,
25
+
expires: 0,
26
+
}
27
+
}),
28
+
requestMethod: () =>
29
+
Location.requestForegroundPermissionsAsync().catch(error => {
30
+
logger.debug(
31
+
'useForegroundPermission: error requesting location permissions',
32
+
{safeMessage: error},
33
+
)
34
+
return {
35
+
status: Location.PermissionStatus.DENIED,
36
+
granted: false,
37
+
canAskAgain: false,
38
+
expires: 0,
39
+
}
40
+
}),
41
+
})
42
+
43
+
/**
9
44
* Hook to get and sync the device geolocation from the device GPS and store it
10
45
* using device storage. If permissions are not granted, it will clear any cached
11
46
* storage value.
12
47
*/
13
48
export function useSyncedDeviceGeolocation() {
14
49
const synced = useRef(false)
15
-
const [status] = Location.useForegroundPermissions()
50
+
const [status] = useForegroundPermissions()
16
51
const [deviceGeolocation, setDeviceGeolocation] = useStorage(device, [
17
52
'deviceGeolocation',
18
53
])
+1
-1
src/state/persisted/schema.ts
+1
-1
src/state/persisted/schema.ts
···
71
71
contentLanguages: z.array(z.string()),
72
72
/**
73
73
* The language(s) the user is currently posting in, configured within the
74
-
* composer. Multiple languages are psearate by commas.
74
+
* composer. Multiple languages are separated by commas.
75
75
*
76
76
* BCP-47 2-letter language code without region.
77
77
*/
+4
src/state/preferences/languages.tsx
+4
src/state/preferences/languages.tsx
···
156
156
return postLanguage.split(',').filter(Boolean)
157
157
}
158
158
159
+
export function fromPostLanguages(languages: string[]): string {
160
+
return languages.filter(Boolean).join(',')
161
+
}
162
+
159
163
export function hasPostLanguage(postLanguage: string, code2: string): boolean {
160
164
return toPostLanguages(postLanguage).includes(code2)
161
165
}
+5
-5
src/state/queries/post-feed.ts
+5
-5
src/state/queries/post-feed.ts
···
492
492
}
493
493
}
494
494
} else if (feedDesc.startsWith('author')) {
495
-
const [_, actor, filter] = feedDesc.split('|')
495
+
const [__, actor, filter] = feedDesc.split('|')
496
496
return new AuthorFeedAPI({agent, feedParams: {actor, filter}})
497
497
} else if (feedDesc.startsWith('likes')) {
498
-
const [_, actor] = feedDesc.split('|')
498
+
const [__, actor] = feedDesc.split('|')
499
499
return new LikesFeedAPI({agent, feedParams: {actor}})
500
500
} else if (feedDesc.startsWith('feedgen')) {
501
-
const [_, feed] = feedDesc.split('|')
501
+
const [__, feed] = feedDesc.split('|')
502
502
return new CustomFeedAPI({
503
503
agent,
504
504
feedParams: {feed},
505
505
userInterests,
506
506
})
507
507
} else if (feedDesc.startsWith('list')) {
508
-
const [_, list] = feedDesc.split('|')
508
+
const [__, list] = feedDesc.split('|')
509
509
return new ListFeedAPI({agent, feedParams: {list}})
510
510
} else if (feedDesc.startsWith('posts')) {
511
-
const [_, uriList] = feedDesc.split('|')
511
+
const [__, uriList] = feedDesc.split('|')
512
512
return new PostListFeedAPI({agent, feedParams: {uris: uriList.split(',')}})
513
513
} else if (feedDesc === 'demo') {
514
514
return new DemoFeedAPI({agent})
+1
-1
src/state/queries/trending/useGetSuggestedUsersQuery.ts
+1
-1
src/state/queries/trending/useGetSuggestedUsersQuery.ts
+55
-6
src/view/com/composer/Composer.tsx
+55
-6
src/view/com/composer/Composer.tsx
···
88
88
import {useModalControls} from '#/state/modals'
89
89
import {useRequireAltTextEnabled} from '#/state/preferences'
90
90
import {
91
+
fromPostLanguages,
91
92
toPostLanguages,
92
93
useLanguagePrefs,
93
94
useLanguagePrefsApi,
···
197
198
const [publishingStage, setPublishingStage] = useState('')
198
199
const [error, setError] = useState('')
199
200
201
+
/**
202
+
* A temporary local reference to a language suggestion that the user has
203
+
* accepted. This overrides the global post language preference, but is not
204
+
* stored permanently.
205
+
*/
206
+
const [acceptedLanguageSuggestion, setAcceptedLanguageSuggestion] = useState<
207
+
string | null
208
+
>(null)
209
+
210
+
/**
211
+
* The language(s) of the post being replied to.
212
+
*/
213
+
const [replyToLanguages, setReplyToLanguages] = useState<string[]>(
214
+
replyTo?.langs || [],
215
+
)
216
+
217
+
/**
218
+
* The currently selected languages of the post. Prefer local temporary
219
+
* language suggestion over global lang prefs, if available.
220
+
*/
221
+
const currentLanguages = useMemo(
222
+
() =>
223
+
acceptedLanguageSuggestion
224
+
? [acceptedLanguageSuggestion]
225
+
: toPostLanguages(langPrefs.postLanguage),
226
+
[acceptedLanguageSuggestion, langPrefs.postLanguage],
227
+
)
228
+
229
+
/**
230
+
* When the user selects a language from the composer language selector,
231
+
* clear any temporary language suggestions they may have selected
232
+
* previously, and any we might try to suggest to them.
233
+
*/
234
+
const onSelectLanguage = () => {
235
+
setAcceptedLanguageSuggestion(null)
236
+
setReplyToLanguages([])
237
+
}
238
+
200
239
const [composerState, composerDispatch] = useReducer(
201
240
composerReducer,
202
241
{
···
414
453
thread,
415
454
replyTo: replyTo?.uri,
416
455
onStateChange: setPublishingStage,
417
-
langs: toPostLanguages(langPrefs.postLanguage),
456
+
langs: currentLanguages,
418
457
})
419
458
).uris[0]
420
459
···
490
529
isPartOfThread: thread.posts.length > 1,
491
530
hasLink: !!post.embed.link,
492
531
hasQuote: !!post.embed.quote,
493
-
langs: langPrefs.postLanguage,
532
+
langs: fromPostLanguages(currentLanguages),
494
533
logContext: 'Composer',
495
534
})
496
535
index++
···
557
596
thread,
558
597
canPost,
559
598
isPublishing,
560
-
langPrefs.postLanguage,
599
+
currentLanguages,
561
600
onClose,
562
601
onPost,
563
602
onPostSuccess,
···
654
693
<>
655
694
<SuggestedLanguage
656
695
text={activePost.richtext.text}
657
-
// NOTE(@elijaharita): currently just choosing the first language if any exists
658
-
replyToLanguage={replyTo?.langs?.[0]}
696
+
replyToLanguages={replyToLanguages}
697
+
currentLanguages={currentLanguages}
698
+
onAcceptSuggestedLanguage={setAcceptedLanguageSuggestion}
659
699
/>
660
700
<ComposerPills
661
701
isReply={!!replyTo}
···
678
718
type: 'add_post',
679
719
})
680
720
}}
721
+
currentLanguages={currentLanguages}
722
+
onSelectLanguage={onSelectLanguage}
681
723
/>
682
724
</>
683
725
)
···
1289
1331
onEmojiButtonPress,
1290
1332
onSelectVideo,
1291
1333
onAddPost,
1334
+
currentLanguages,
1335
+
onSelectLanguage,
1292
1336
}: {
1293
1337
post: PostDraft
1294
1338
dispatch: (action: PostAction) => void
···
1297
1341
onError: (error: string) => void
1298
1342
onSelectVideo: (postId: string, asset: ImagePickerAsset) => void
1299
1343
onAddPost: () => void
1344
+
currentLanguages: string[]
1345
+
onSelectLanguage?: (language: string) => void
1300
1346
}) {
1301
1347
const t = useTheme()
1302
1348
const {_} = useLingui()
···
1450
1496
<PlusIcon size="lg" />
1451
1497
</Button>
1452
1498
)}
1453
-
<PostLanguageSelect />
1499
+
<PostLanguageSelect
1500
+
currentLanguages={currentLanguages}
1501
+
onSelectLanguage={onSelectLanguage}
1502
+
/>
1454
1503
<CharProgress
1455
1504
count={post.shortenedGraphemeLength}
1456
1505
style={{width: 65}}
-1
src/view/com/composer/photos/EditImageDialog.web.tsx
-1
src/view/com/composer/photos/EditImageDialog.web.tsx
+35
-9
src/view/com/composer/select-language/PostLanguageSelect.tsx
+35
-9
src/view/com/composer/select-language/PostLanguageSelect.tsx
···
17
17
import {Text} from '#/components/Typography'
18
18
import {PostLanguageSelectDialog} from './PostLanguageSelectDialog'
19
19
20
-
export function PostLanguageSelect() {
20
+
export function PostLanguageSelect({
21
+
currentLanguages: currentLanguagesProp,
22
+
onSelectLanguage,
23
+
}: {
24
+
currentLanguages?: string[]
25
+
onSelectLanguage?: (language: string) => void
26
+
}) {
21
27
const {_} = useLingui()
22
28
const langPrefs = useLanguagePrefs()
23
29
const setLangPrefs = useLanguagePrefsApi()
···
26
32
const dedupedHistory = Array.from(
27
33
new Set([...langPrefs.postLanguageHistory, langPrefs.postLanguage]),
28
34
)
35
+
36
+
const currentLanguages =
37
+
currentLanguagesProp ?? toPostLanguages(langPrefs.postLanguage)
29
38
30
39
if (
31
40
dedupedHistory.length === 1 &&
···
34
43
return (
35
44
<>
36
45
<LanguageBtn onPress={languageDialogControl.open} />
37
-
<PostLanguageSelectDialog control={languageDialogControl} />
46
+
<PostLanguageSelectDialog
47
+
control={languageDialogControl}
48
+
currentLanguages={currentLanguages}
49
+
/>
38
50
</>
39
51
)
40
52
}
···
43
55
<>
44
56
<Menu.Root>
45
57
<Menu.Trigger label={_(msg`Select post language`)}>
46
-
{({props}) => <LanguageBtn {...props} />}
58
+
{({props}) => (
59
+
<LanguageBtn currentLanguages={currentLanguages} {...props} />
60
+
)}
47
61
</Menu.Trigger>
48
62
<Menu.Outer>
49
63
<Menu.Group>
···
56
70
<Menu.Item
57
71
key={historyItem}
58
72
label={_(msg`Select ${langName}`)}
59
-
onPress={() => setLangPrefs.setPostLanguage(historyItem)}>
73
+
onPress={() => {
74
+
setLangPrefs.setPostLanguage(historyItem)
75
+
onSelectLanguage?.(historyItem)
76
+
}}>
60
77
<Menu.ItemText>{langName}</Menu.ItemText>
61
78
<Menu.ItemRadio
62
-
selected={historyItem === langPrefs.postLanguage}
79
+
selected={currentLanguages.includes(historyItem)}
63
80
/>
64
81
</Menu.Item>
65
82
)
···
77
94
</Menu.Outer>
78
95
</Menu.Root>
79
96
80
-
<PostLanguageSelectDialog control={languageDialogControl} />
97
+
<PostLanguageSelectDialog
98
+
control={languageDialogControl}
99
+
currentLanguages={currentLanguages}
100
+
onSelectLanguage={onSelectLanguage}
101
+
/>
81
102
</>
82
103
)
83
104
}
84
105
85
-
function LanguageBtn(props: Omit<ButtonProps, 'label' | 'children'>) {
106
+
function LanguageBtn(
107
+
props: Omit<ButtonProps, 'label' | 'children'> & {
108
+
currentLanguages?: string[]
109
+
},
110
+
) {
86
111
const {_} = useLingui()
87
112
const langPrefs = useLanguagePrefs()
88
113
const t = useTheme()
89
114
90
115
const postLanguagesPref = toPostLanguages(langPrefs.postLanguage)
116
+
const currentLanguages = props.currentLanguages ?? postLanguagesPref
91
117
92
118
return (
93
119
<Button
···
106
132
{({pressed, hovered}) => {
107
133
const color =
108
134
pressed || hovered ? t.palette.primary_300 : t.palette.primary_500
109
-
if (postLanguagesPref.length > 0) {
135
+
if (currentLanguages.length > 0) {
110
136
return (
111
137
<Text
112
138
style={[
···
117
143
{maxWidth: 100},
118
144
]}
119
145
numberOfLines={1}>
120
-
{postLanguagesPref
146
+
{currentLanguages
121
147
.map(lang => codeToLanguageName(lang, langPrefs.appLanguage))
122
148
.join(', ')}
123
149
</Text>
+25
-3
src/view/com/composer/select-language/PostLanguageSelectDialog.tsx
+25
-3
src/view/com/composer/select-language/PostLanguageSelectDialog.tsx
···
8
8
import {type Language, LANGUAGES, LANGUAGES_MAP_CODE2} from '#/locale/languages'
9
9
import {isNative, isWeb} from '#/platform/detection'
10
10
import {
11
+
toPostLanguages,
11
12
useLanguagePrefs,
12
13
useLanguagePrefsApi,
13
14
} from '#/state/preferences/languages'
···
23
24
24
25
export function PostLanguageSelectDialog({
25
26
control,
27
+
/**
28
+
* Optionally can be passed to show different values than what is saved in
29
+
* langPrefs.
30
+
*/
31
+
currentLanguages,
32
+
onSelectLanguage,
26
33
}: {
27
34
control: Dialog.DialogControlProps
35
+
currentLanguages?: string[]
36
+
onSelectLanguage?: (language: string) => void
28
37
}) {
29
38
const {height} = useWindowDimensions()
30
39
const insets = useSafeAreaInsets()
···
40
49
nativeOptions={{minHeight: height - insets.top}}>
41
50
<Dialog.Handle />
42
51
<ErrorBoundary renderError={renderErrorBoundary}>
43
-
<DialogInner />
52
+
<DialogInner
53
+
currentLanguages={currentLanguages}
54
+
onSelectLanguage={onSelectLanguage}
55
+
/>
44
56
</ErrorBoundary>
45
57
</Dialog.Outer>
46
58
)
47
59
}
48
60
49
-
export function DialogInner() {
61
+
export function DialogInner({
62
+
currentLanguages,
63
+
onSelectLanguage,
64
+
}: {
65
+
currentLanguages?: string[]
66
+
onSelectLanguage?: (language: string) => void
67
+
}) {
50
68
const control = Dialog.useDialogContext()
51
69
const [headerHeight, setHeaderHeight] = useState(0)
52
70
···
63
81
}, [])
64
82
65
83
const langPrefs = useLanguagePrefs()
84
+
const postLanguagesPref =
85
+
currentLanguages ?? toPostLanguages(langPrefs.postLanguage)
86
+
66
87
const [checkedLanguagesCode2, setCheckedLanguagesCode2] = useState<string[]>(
67
-
langPrefs.postLanguage.split(',') || [langPrefs.primaryLanguage],
88
+
postLanguagesPref || [langPrefs.primaryLanguage],
68
89
)
69
90
const [search, setSearch] = useState('')
70
91
···
79
100
langsString = langPrefs.primaryLanguage
80
101
}
81
102
setLangPrefs.setPostLanguage(langsString)
103
+
onSelectLanguage?.(langsString)
82
104
})
83
105
}
84
106
+121
-44
src/view/com/composer/select-language/SuggestedLanguage.tsx
+121
-44
src/view/com/composer/select-language/SuggestedLanguage.tsx
···
1
1
import {useEffect, useState} from 'react'
2
-
import {View} from 'react-native'
2
+
import {Text as RNText, View} from 'react-native'
3
3
import {parseLanguage} from '@atproto/api'
4
4
import {msg, Trans} from '@lingui/macro'
5
5
import {useLingui} from '@lingui/react'
6
6
import lande from 'lande'
7
7
8
8
import {code3ToCode2Strict, codeToLanguageName} from '#/locale/helpers'
9
-
import {
10
-
toPostLanguages,
11
-
useLanguagePrefs,
12
-
useLanguagePrefsApi,
13
-
} from '#/state/preferences/languages'
9
+
import {useLanguagePrefs} from '#/state/preferences/languages'
14
10
import {atoms as a, useTheme} from '#/alf'
15
11
import {Button, ButtonText} from '#/components/Button'
16
12
import {Earth_Stroke2_Corner2_Rounded as EarthIcon} from '#/components/icons/Globe'
···
22
18
23
19
export function SuggestedLanguage({
24
20
text,
25
-
replyToLanguage: replyToLanguageProp,
21
+
replyToLanguages: replyToLanguagesProp,
22
+
currentLanguages,
23
+
onAcceptSuggestedLanguage,
26
24
}: {
27
25
text: string
28
-
replyToLanguage?: string
26
+
/**
27
+
* All languages associated with the post being replied to.
28
+
*/
29
+
replyToLanguages: string[]
30
+
/**
31
+
* All languages currently selected for the post being composed.
32
+
*/
33
+
currentLanguages: string[]
34
+
/**
35
+
* Called when the user accepts a suggested language. We only pass a single
36
+
* language here. If the post being replied to has multiple languages, we
37
+
* only suggest the first one.
38
+
*/
39
+
onAcceptSuggestedLanguage: (language: string | null) => void
29
40
}) {
30
-
const replyToLanguage = cleanUpLanguage(replyToLanguageProp)
41
+
const langPrefs = useLanguagePrefs()
42
+
const replyToLanguages = replyToLanguagesProp
43
+
.map(lang => cleanUpLanguage(lang))
44
+
.filter(Boolean) as string[]
45
+
const [hasInteracted, setHasInteracted] = useState(false)
31
46
const [suggestedLanguage, setSuggestedLanguage] = useState<
32
47
string | undefined
33
-
>(text.length === 0 ? replyToLanguage : undefined)
34
-
const langPrefs = useLanguagePrefs()
35
-
const setLangPrefs = useLanguagePrefsApi()
36
-
const t = useTheme()
37
-
const {_} = useLingui()
48
+
>(undefined)
38
49
39
50
useEffect(() => {
40
-
// For replies, suggest the language of the post being replied to if no text
41
-
// has been typed yet
42
-
if (replyToLanguage && text.length === 0) {
43
-
setSuggestedLanguage(replyToLanguage)
44
-
return
51
+
if (text.length > 0 && !hasInteracted) {
52
+
setHasInteracted(true)
45
53
}
54
+
}, [text, hasInteracted])
46
55
56
+
useEffect(() => {
47
57
const textTrimmed = text.trim()
48
58
49
59
// Don't run the language model on small posts, the results are likely
···
58
68
})
59
69
60
70
return () => cancelIdle(idle)
61
-
}, [text, replyToLanguage])
71
+
}, [text])
72
+
73
+
/*
74
+
* We've detected a language, and the user hasn't already selected it.
75
+
*/
76
+
const hasLanguageSuggestion =
77
+
suggestedLanguage && !currentLanguages.includes(suggestedLanguage)
78
+
/*
79
+
* We have not detected a different language, and the user is not already
80
+
* using or has not already selected one of the languages of the post they
81
+
* are replying to.
82
+
*/
83
+
const hasSuggestedReplyLanguage =
84
+
!hasInteracted &&
85
+
!suggestedLanguage &&
86
+
replyToLanguages.length &&
87
+
!replyToLanguages.some(l => currentLanguages.includes(l))
62
88
63
-
if (
64
-
suggestedLanguage &&
65
-
!toPostLanguages(langPrefs.postLanguage).includes(suggestedLanguage)
66
-
) {
89
+
if (hasLanguageSuggestion) {
67
90
const suggestedLanguageName = codeToLanguageName(
68
91
suggestedLanguage,
69
92
langPrefs.appLanguage,
70
93
)
71
94
72
95
return (
96
+
<LanguageSuggestionButton
97
+
label={
98
+
<RNText>
99
+
<Trans>
100
+
Are you writing in{' '}
101
+
<Text style={[a.font_bold]}>{suggestedLanguageName}</Text>?
102
+
</Trans>
103
+
</RNText>
104
+
}
105
+
value={suggestedLanguage}
106
+
onAccept={onAcceptSuggestedLanguage}
107
+
/>
108
+
)
109
+
} else if (hasSuggestedReplyLanguage) {
110
+
const suggestedLanguageName = codeToLanguageName(
111
+
replyToLanguages[0],
112
+
langPrefs.appLanguage,
113
+
)
114
+
115
+
return (
116
+
<LanguageSuggestionButton
117
+
label={
118
+
<RNText>
119
+
<Trans>
120
+
The post you're replying to was marked as being written in{' '}
121
+
{suggestedLanguageName} by its author. Would you like to reply in{' '}
122
+
<Text style={[a.font_bold]}>{suggestedLanguageName}</Text>?
123
+
</Trans>
124
+
</RNText>
125
+
}
126
+
value={replyToLanguages[0]}
127
+
onAccept={onAcceptSuggestedLanguage}
128
+
/>
129
+
)
130
+
} else {
131
+
return null
132
+
}
133
+
}
134
+
135
+
function LanguageSuggestionButton({
136
+
label,
137
+
value,
138
+
onAccept,
139
+
}: {
140
+
label: React.ReactNode
141
+
value: string
142
+
onAccept: (language: string | null) => void
143
+
}) {
144
+
const t = useTheme()
145
+
const {_} = useLingui()
146
+
147
+
return (
148
+
<View style={[a.px_lg, a.py_sm]}>
73
149
<View
74
150
style={[
75
-
t.atoms.border_contrast_low,
76
-
a.gap_sm,
151
+
a.gap_md,
77
152
a.border,
78
153
a.flex_row,
79
154
a.align_center,
80
155
a.rounded_sm,
81
-
a.px_lg,
82
-
a.py_md,
83
-
a.mx_md,
84
-
a.my_sm,
156
+
a.p_md,
157
+
a.pl_lg,
85
158
t.atoms.bg,
159
+
t.atoms.border_contrast_low,
86
160
]}>
87
161
<EarthIcon />
88
-
<Text style={[a.flex_1]}>
89
-
<Trans>
90
-
Are you writing in{' '}
91
-
<Text style={[a.font_semi_bold]}>{suggestedLanguageName}</Text>?
92
-
</Trans>
93
-
</Text>
162
+
<View style={[a.flex_1]}>
163
+
<Text
164
+
style={[
165
+
a.flex_1,
166
+
a.leading_snug,
167
+
{
168
+
maxWidth: 400,
169
+
},
170
+
]}>
171
+
{label}
172
+
</Text>
173
+
</View>
94
174
95
175
<Button
96
-
color="secondary"
97
176
size="small"
98
-
variant="solid"
99
-
onPress={() => setLangPrefs.setPostLanguage(suggestedLanguage)}
100
-
label={_(msg`Change post language to ${suggestedLanguageName}`)}>
177
+
color="secondary"
178
+
onPress={() => onAccept(value)}
179
+
label={_(msg`Accept this language suggestion`)}>
101
180
<ButtonText>
102
181
<Trans>Yes</Trans>
103
182
</ButtonText>
104
183
</Button>
105
184
</View>
106
-
)
107
-
} else {
108
-
return null
109
-
}
185
+
</View>
186
+
)
110
187
}
111
188
112
189
/**
+1
-1
src/view/com/composer/text-input/web/TagDecorator.ts
+1
-1
src/view/com/composer/text-input/web/TagDecorator.ts
+1
-1
src/view/com/posts/PostFeedErrorMessage.tsx
+1
-1
src/view/com/posts/PostFeedErrorMessage.tsx
···
126
126
})[knownError],
127
127
[_l, knownError],
128
128
)
129
-
const [_, uri] = feedDesc.split('|')
129
+
const [__, uri] = feedDesc.split('|')
130
130
const [ownerDid] = safeParseFeedgenUri(uri)
131
131
const removePromptControl = Prompt.usePromptControl()
132
132
const {mutateAsync: removeFeed} = useRemoveFeedMutation()
-2
src/view/screens/ModerationMutedAccounts.tsx
-2
src/view/screens/ModerationMutedAccounts.tsx
···
2
2
import {type StyleProp, View, type ViewStyle} from 'react-native'
3
3
import {type AppBskyActorDefs as ActorDefs} from '@atproto/api'
4
4
import {Trans} from '@lingui/macro'
5
-
import {useLingui} from '@lingui/react'
6
5
import {useFocusEffect} from '@react-navigation/native'
7
6
import {type NativeStackScreenProps} from '@react-navigation/native-stack'
8
7
···
27
26
export function ModerationMutedAccounts({}: Props) {
28
27
const t = useTheme()
29
28
const moderationOpts = useModerationOpts()
30
-
const {_} = useLingui()
31
29
const setMinimalShellMode = useSetMinimalShellMode()
32
30
33
31
const [isPTRing, setIsPTRing] = useState(false)
-2
src/view/screens/ProfileFeedLikedBy.tsx
-2
src/view/screens/ProfileFeedLikedBy.tsx
···
1
1
import {useCallback} from 'react'
2
2
import {Trans} from '@lingui/macro'
3
-
import {useLingui} from '@lingui/react'
4
3
import {useFocusEffect} from '@react-navigation/native'
5
4
6
5
import {
···
17
16
const setMinimalShellMode = useSetMinimalShellMode()
18
17
const {name, rkey} = route.params
19
18
const uri = makeRecordUri(name, 'app.bsky.feed.generator', rkey)
20
-
const {_} = useLingui()
21
19
22
20
useFocusEffect(
23
21
useCallback(() => {
+74
-3
src/view/screens/Storybook/Admonitions.tsx
+74
-3
src/view/screens/Storybook/Admonitions.tsx
···
1
-
import {View} from 'react-native'
1
+
import {Text as RNText, View} from 'react-native'
2
+
import {msg, Trans} from '@lingui/macro'
3
+
import {useLingui} from '@lingui/react'
2
4
3
-
import {atoms as a} from '#/alf'
4
-
import {Admonition} from '#/components/Admonition'
5
+
import {atoms as a, useTheme} from '#/alf'
6
+
import {
7
+
Admonition,
8
+
Button as AdmonitionButton,
9
+
Content as AdmonitionContent,
10
+
Icon as AdmonitionIcon,
11
+
Outer as AdmonitionOuter,
12
+
Row as AdmonitionRow,
13
+
Text as AdmonitionText,
14
+
} from '#/components/Admonition'
15
+
import {ButtonIcon, ButtonText} from '#/components/Button'
16
+
import {ArrowRotateCounterClockwise_Stroke2_Corner0_Rounded as Retry} from '#/components/icons/ArrowRotateCounterClockwise'
17
+
import {BellRinging_Filled_Corner0_Rounded as BellRingingFilledIcon} from '#/components/icons/BellRinging'
5
18
import {InlineLinkText} from '#/components/Link'
6
19
import {H1} from '#/components/Typography'
7
20
8
21
export function Admonitions() {
22
+
const {_} = useLingui()
23
+
const t = useTheme()
24
+
9
25
return (
10
26
<View style={[a.gap_md]}>
11
27
<H1>Admonitions</H1>
···
30
46
<Admonition type="error">
31
47
The quick brown fox jumps over the lazy dog.
32
48
</Admonition>
49
+
50
+
<AdmonitionOuter type="error">
51
+
<AdmonitionRow>
52
+
<AdmonitionIcon />
53
+
<AdmonitionContent>
54
+
<AdmonitionText>
55
+
<Trans>Something went wrong, please try again</Trans>
56
+
</AdmonitionText>
57
+
</AdmonitionContent>
58
+
<AdmonitionButton
59
+
color="negative_subtle"
60
+
label={_(msg`Retry loading report options`)}
61
+
onPress={() => {}}>
62
+
<ButtonText>
63
+
<Trans>Retry</Trans>
64
+
</ButtonText>
65
+
<ButtonIcon icon={Retry} />
66
+
</AdmonitionButton>
67
+
</AdmonitionRow>
68
+
</AdmonitionOuter>
69
+
70
+
<AdmonitionOuter type="tip">
71
+
<AdmonitionRow>
72
+
<AdmonitionIcon />
73
+
<AdmonitionContent>
74
+
<AdmonitionText>
75
+
<Trans>
76
+
Enable notifications for an account by visiting their profile
77
+
and pressing the{' '}
78
+
<RNText style={[a.font_bold, t.atoms.text_contrast_high]}>
79
+
bell icon
80
+
</RNText>{' '}
81
+
<BellRingingFilledIcon
82
+
size="xs"
83
+
style={t.atoms.text_contrast_high}
84
+
/>
85
+
.
86
+
</Trans>
87
+
</AdmonitionText>
88
+
<AdmonitionText>
89
+
<Trans>
90
+
If you want to restrict who can receive notifications for your
91
+
account's activity, you can change this in{' '}
92
+
<InlineLinkText
93
+
label={_(msg`Privacy and Security settings`)}
94
+
to={{screen: 'ActivityPrivacySettings'}}
95
+
style={[a.font_bold]}>
96
+
Settings → Privacy and Security
97
+
</InlineLinkText>
98
+
.
99
+
</Trans>
100
+
</AdmonitionText>
101
+
</AdmonitionContent>
102
+
</AdmonitionRow>
103
+
</AdmonitionOuter>
33
104
</View>
34
105
)
35
106
}
+25
src/view/screens/Storybook/Buttons.tsx
+25
src/view/screens/Storybook/Buttons.tsx
···
8
8
ButtonIcon,
9
9
type ButtonSize,
10
10
ButtonText,
11
+
StackedButton,
11
12
} from '#/components/Button'
12
13
import {ChevronLeft_Stroke2_Corner0_Rounded as ChevronLeft} from '#/components/icons/Chevron'
13
14
import {Globe_Stroke2_Corner0_Rounded as Globe} from '#/components/icons/Globe'
···
17
18
return (
18
19
<View style={[a.gap_md]}>
19
20
<Text style={[a.font_bold, a.text_5xl]}>Buttons</Text>
21
+
22
+
<View style={[a.flex_row, a.gap_md, a.align_start, {maxWidth: 350}]}>
23
+
<StackedButton
24
+
label="stacked"
25
+
icon={Globe}
26
+
color="secondary"
27
+
style={[a.flex_1]}>
28
+
Bop it
29
+
</StackedButton>
30
+
<StackedButton
31
+
label="stacked"
32
+
icon={Globe}
33
+
color="negative_subtle"
34
+
style={[a.flex_1]}>
35
+
Twist it
36
+
</StackedButton>
37
+
<StackedButton
38
+
label="stacked"
39
+
icon={Globe}
40
+
color="primary"
41
+
style={[a.flex_1]}>
42
+
Pull it
43
+
</StackedButton>
44
+
</View>
20
45
21
46
{[
22
47
'primary',
+5
-17
yarn.lock
+5
-17
yarn.lock
···
8646
8646
dependencies:
8647
8647
"@babel/helper-define-polyfill-provider" "^0.6.3"
8648
8648
8649
-
babel-plugin-react-compiler@^19.1.0-rc.1:
8650
-
version "19.1.0-rc.1"
8651
-
resolved "https://registry.yarnpkg.com/babel-plugin-react-compiler/-/babel-plugin-react-compiler-19.1.0-rc.1.tgz#99d131be61017e40abbaedd98321069bf8b7e54a"
8652
-
integrity sha512-M4fpG+Hfq5gWzsJeeMErdRokzg0fdJ8IAk+JDhfB/WLT+U3WwJWR8edphypJrk447/JEvYu6DBFwsTn10bMW4Q==
8653
-
dependencies:
8654
-
"@babel/types" "^7.26.0"
8655
-
8656
-
babel-plugin-react-compiler@^19.1.0-rc.2:
8649
+
babel-plugin-react-compiler@^19.1.0-rc.2, babel-plugin-react-compiler@^19.1.0-rc.3:
8657
8650
version "19.1.0-rc.3"
8658
8651
resolved "https://registry.yarnpkg.com/babel-plugin-react-compiler/-/babel-plugin-react-compiler-19.1.0-rc.3.tgz#45e5a282a2460b3701971e5eb8310a90a7919022"
8659
8652
integrity sha512-mjRn69WuTz4adL0bXGx8Rsyk1086zFJeKmes6aK0xPuK3aaXmDJdLHqwKKMrpm6KAI1MCoUK72d2VeqQbu8YIA==
···
10867
10860
dependencies:
10868
10861
"@typescript-eslint/utils" "^5.61.0"
10869
10862
10870
-
eslint-plugin-react-compiler@^19.1.0-rc.1:
10871
-
version "19.1.0-rc.1"
10872
-
resolved "https://registry.yarnpkg.com/eslint-plugin-react-compiler/-/eslint-plugin-react-compiler-19.1.0-rc.1.tgz#e974ba9541c9a4464d77723e0505b5742bc22e56"
10873
-
integrity sha512-3umw5eqZXapBl7aQGmvcjheKhUbsElb9jTETxRZg371e1LG4EPs/zCHt2JzP+wNcdaZWzjU/R730zPUJblY2zw==
10863
+
eslint-plugin-react-compiler@^19.1.0-rc.2:
10864
+
version "19.1.0-rc.2"
10865
+
resolved "https://registry.yarnpkg.com/eslint-plugin-react-compiler/-/eslint-plugin-react-compiler-19.1.0-rc.2.tgz#83343e7422e00fa61e729af8e8468f0ddec37925"
10866
+
integrity sha512-oKalwDGcD+RX9mf3NEO4zOoUMeLvjSvcbbEOpquzmzqEEM2MQdp7/FY/Hx9NzmUwFzH1W9SKTz5fihfMldpEYw==
10874
10867
dependencies:
10875
10868
"@babel/core" "^7.24.4"
10876
10869
"@babel/parser" "^7.24.4"
···
17113
17106
convert-source-map "^2.0.0"
17114
17107
invariant "^2.2.4"
17115
17108
react-native-is-edge-to-edge "1.1.7"
17116
-
17117
-
react-native-root-siblings@^5.0.1:
17118
-
version "5.0.1"
17119
-
resolved "https://registry.yarnpkg.com/react-native-root-siblings/-/react-native-root-siblings-5.0.1.tgz#97e050e5155228f65810fb1c466ff8e769c5272c"
17120
-
integrity sha512-Ay3k/fBj6ReUkWX5WNS+oEAcgPLEGOK8n7K/L7D85mf3xvd8rm/b4spsv26E4HlFzluVx5HKbxEt9cl0wQ1u3g==
17121
17109
17122
17110
react-native-safe-area-context@~5.6.0:
17123
17111
version "5.6.1"