Internationalize more strings (#2440)

Co-authored-by: Ansh <anshnanda10@gmail.com>
This commit is contained in:
Stanislas Signoud 2024-01-09 23:37:15 +01:00 committed by GitHub
parent aeeacd10d3
commit 008893b911
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
108 changed files with 925 additions and 558 deletions

View file

@ -39,6 +39,8 @@ import {
import {Provider as UnreadNotifsProvider} from 'state/queries/notifications/unread' import {Provider as UnreadNotifsProvider} from 'state/queries/notifications/unread'
import * as persisted from '#/state/persisted' import * as persisted from '#/state/persisted'
import {Splash} from '#/Splash' import {Splash} from '#/Splash'
import {msg} from '@lingui/macro'
import {useLingui} from '@lingui/react'
SplashScreen.preventAutoHideAsync() SplashScreen.preventAutoHideAsync()
@ -46,17 +48,18 @@ function InnerApp() {
const colorMode = useColorMode() const colorMode = useColorMode()
const {isInitialLoad, currentAccount} = useSession() const {isInitialLoad, currentAccount} = useSession()
const {resumeSession} = useSessionApi() const {resumeSession} = useSessionApi()
const {_} = useLingui()
// init // init
useEffect(() => { useEffect(() => {
notifications.init(queryClient) notifications.init(queryClient)
listenSessionDropped(() => { listenSessionDropped(() => {
Toast.show('Sorry! Your session expired. Please log in again.') Toast.show(_(msg`Sorry! Your session expired. Please log in again.`))
}) })
const account = persisted.get('session').currentAccount const account = persisted.get('session').currentAccount
resumeSession(account) resumeSession(account)
}, [resumeSession]) }, [resumeSession, _])
return ( return (
<SafeAreaProvider initialMetrics={initialWindowMetrics}> <SafeAreaProvider initialMetrics={initialWindowMetrics}>

View file

@ -76,6 +76,8 @@ import {PreferencesHomeFeed} from 'view/screens/PreferencesHomeFeed'
import {PreferencesThreads} from 'view/screens/PreferencesThreads' import {PreferencesThreads} from 'view/screens/PreferencesThreads'
import {PreferencesExternalEmbeds} from '#/view/screens/PreferencesExternalEmbeds' import {PreferencesExternalEmbeds} from '#/view/screens/PreferencesExternalEmbeds'
import {createNativeStackNavigatorWithAuth} from './view/shell/createNativeStackNavigatorWithAuth' import {createNativeStackNavigatorWithAuth} from './view/shell/createNativeStackNavigatorWithAuth'
import {msg} from '@lingui/macro'
import {i18n, MessageDescriptor} from '@lingui/core'
const navigationRef = createNavigationContainerRef<AllNavigatorParams>() const navigationRef = createNavigationContainerRef<AllNavigatorParams>()
@ -93,55 +95,56 @@ const Tab = createBottomTabNavigator<BottomTabNavigatorParams>()
* These "common screens" are reused across stacks. * These "common screens" are reused across stacks.
*/ */
function commonScreens(Stack: typeof HomeTab, unreadCountLabel?: string) { function commonScreens(Stack: typeof HomeTab, unreadCountLabel?: string) {
const title = (page: string) => bskyTitle(page, unreadCountLabel) const title = (page: MessageDescriptor) =>
bskyTitle(i18n._(page), unreadCountLabel)
return ( return (
<> <>
<Stack.Screen <Stack.Screen
name="NotFound" name="NotFound"
getComponent={() => NotFoundScreen} getComponent={() => NotFoundScreen}
options={{title: title('Not Found')}} options={{title: title(msg`Not Found`)}}
/> />
<Stack.Screen <Stack.Screen
name="Lists" name="Lists"
component={ListsScreen} component={ListsScreen}
options={{title: title('Lists'), requireAuth: true}} options={{title: title(msg`Lists`), requireAuth: true}}
/> />
<Stack.Screen <Stack.Screen
name="Moderation" name="Moderation"
getComponent={() => ModerationScreen} getComponent={() => ModerationScreen}
options={{title: title('Moderation'), requireAuth: true}} options={{title: title(msg`Moderation`), requireAuth: true}}
/> />
<Stack.Screen <Stack.Screen
name="ModerationModlists" name="ModerationModlists"
getComponent={() => ModerationModlistsScreen} getComponent={() => ModerationModlistsScreen}
options={{title: title('Moderation Lists'), requireAuth: true}} options={{title: title(msg`Moderation Lists`), requireAuth: true}}
/> />
<Stack.Screen <Stack.Screen
name="ModerationMutedAccounts" name="ModerationMutedAccounts"
getComponent={() => ModerationMutedAccounts} getComponent={() => ModerationMutedAccounts}
options={{title: title('Muted Accounts'), requireAuth: true}} options={{title: title(msg`Muted Accounts`), requireAuth: true}}
/> />
<Stack.Screen <Stack.Screen
name="ModerationBlockedAccounts" name="ModerationBlockedAccounts"
getComponent={() => ModerationBlockedAccounts} getComponent={() => ModerationBlockedAccounts}
options={{title: title('Blocked Accounts'), requireAuth: true}} options={{title: title(msg`Blocked Accounts`), requireAuth: true}}
/> />
<Stack.Screen <Stack.Screen
name="Settings" name="Settings"
getComponent={() => SettingsScreen} getComponent={() => SettingsScreen}
options={{title: title('Settings'), requireAuth: true}} options={{title: title(msg`Settings`), requireAuth: true}}
/> />
<Stack.Screen <Stack.Screen
name="LanguageSettings" name="LanguageSettings"
getComponent={() => LanguageSettingsScreen} getComponent={() => LanguageSettingsScreen}
options={{title: title('Language Settings'), requireAuth: true}} options={{title: title(msg`Language Settings`), requireAuth: true}}
/> />
<Stack.Screen <Stack.Screen
name="Profile" name="Profile"
getComponent={() => ProfileScreen} getComponent={() => ProfileScreen}
options={({route}) => ({ options={({route}) => ({
title: title(`@${route.params.name}`), title: title(msg`@${route.params.name}`),
animation: 'none', animation: 'none',
})} })}
/> />
@ -149,106 +152,112 @@ function commonScreens(Stack: typeof HomeTab, unreadCountLabel?: string) {
name="ProfileFollowers" name="ProfileFollowers"
getComponent={() => ProfileFollowersScreen} getComponent={() => ProfileFollowersScreen}
options={({route}) => ({ options={({route}) => ({
title: title(`People following @${route.params.name}`), title: title(msg`People following @${route.params.name}`),
})} })}
/> />
<Stack.Screen <Stack.Screen
name="ProfileFollows" name="ProfileFollows"
getComponent={() => ProfileFollowsScreen} getComponent={() => ProfileFollowsScreen}
options={({route}) => ({ options={({route}) => ({
title: title(`People followed by @${route.params.name}`), title: title(msg`People followed by @${route.params.name}`),
})} })}
/> />
<Stack.Screen <Stack.Screen
name="ProfileList" name="ProfileList"
getComponent={() => ProfileListScreen} getComponent={() => ProfileListScreen}
options={{title: title('List'), requireAuth: true}} options={{title: title(msg`List`), requireAuth: true}}
/> />
<Stack.Screen <Stack.Screen
name="PostThread" name="PostThread"
getComponent={() => PostThreadScreen} getComponent={() => PostThreadScreen}
options={({route}) => ({title: title(`Post by @${route.params.name}`)})} options={({route}) => ({
title: title(msg`Post by @${route.params.name}`),
})}
/> />
<Stack.Screen <Stack.Screen
name="PostLikedBy" name="PostLikedBy"
getComponent={() => PostLikedByScreen} getComponent={() => PostLikedByScreen}
options={({route}) => ({title: title(`Post by @${route.params.name}`)})} options={({route}) => ({
title: title(msg`Post by @${route.params.name}`),
})}
/> />
<Stack.Screen <Stack.Screen
name="PostRepostedBy" name="PostRepostedBy"
getComponent={() => PostRepostedByScreen} getComponent={() => PostRepostedByScreen}
options={({route}) => ({title: title(`Post by @${route.params.name}`)})} options={({route}) => ({
title: title(msg`Post by @${route.params.name}`),
})}
/> />
<Stack.Screen <Stack.Screen
name="ProfileFeed" name="ProfileFeed"
getComponent={() => ProfileFeedScreen} getComponent={() => ProfileFeedScreen}
options={{title: title('Feed'), requireAuth: true}} options={{title: title(msg`Feed`), requireAuth: true}}
/> />
<Stack.Screen <Stack.Screen
name="ProfileFeedLikedBy" name="ProfileFeedLikedBy"
getComponent={() => ProfileFeedLikedByScreen} getComponent={() => ProfileFeedLikedByScreen}
options={{title: title('Liked by')}} options={{title: title(msg`Liked by`)}}
/> />
<Stack.Screen <Stack.Screen
name="Debug" name="Debug"
getComponent={() => DebugScreen} getComponent={() => DebugScreen}
options={{title: title('Debug'), requireAuth: true}} options={{title: title(msg`Debug`), requireAuth: true}}
/> />
<Stack.Screen <Stack.Screen
name="Log" name="Log"
getComponent={() => LogScreen} getComponent={() => LogScreen}
options={{title: title('Log'), requireAuth: true}} options={{title: title(msg`Log`), requireAuth: true}}
/> />
<Stack.Screen <Stack.Screen
name="Support" name="Support"
getComponent={() => SupportScreen} getComponent={() => SupportScreen}
options={{title: title('Support')}} options={{title: title(msg`Support`)}}
/> />
<Stack.Screen <Stack.Screen
name="PrivacyPolicy" name="PrivacyPolicy"
getComponent={() => PrivacyPolicyScreen} getComponent={() => PrivacyPolicyScreen}
options={{title: title('Privacy Policy')}} options={{title: title(msg`Privacy Policy`)}}
/> />
<Stack.Screen <Stack.Screen
name="TermsOfService" name="TermsOfService"
getComponent={() => TermsOfServiceScreen} getComponent={() => TermsOfServiceScreen}
options={{title: title('Terms of Service')}} options={{title: title(msg`Terms of Service`)}}
/> />
<Stack.Screen <Stack.Screen
name="CommunityGuidelines" name="CommunityGuidelines"
getComponent={() => CommunityGuidelinesScreen} getComponent={() => CommunityGuidelinesScreen}
options={{title: title('Community Guidelines')}} options={{title: title(msg`Community Guidelines`)}}
/> />
<Stack.Screen <Stack.Screen
name="CopyrightPolicy" name="CopyrightPolicy"
getComponent={() => CopyrightPolicyScreen} getComponent={() => CopyrightPolicyScreen}
options={{title: title('Copyright Policy')}} options={{title: title(msg`Copyright Policy`)}}
/> />
<Stack.Screen <Stack.Screen
name="AppPasswords" name="AppPasswords"
getComponent={() => AppPasswords} getComponent={() => AppPasswords}
options={{title: title('App Passwords'), requireAuth: true}} options={{title: title(msg`App Passwords`), requireAuth: true}}
/> />
<Stack.Screen <Stack.Screen
name="SavedFeeds" name="SavedFeeds"
getComponent={() => SavedFeeds} getComponent={() => SavedFeeds}
options={{title: title('Edit My Feeds'), requireAuth: true}} options={{title: title(msg`Edit My Feeds`), requireAuth: true}}
/> />
<Stack.Screen <Stack.Screen
name="PreferencesHomeFeed" name="PreferencesHomeFeed"
getComponent={() => PreferencesHomeFeed} getComponent={() => PreferencesHomeFeed}
options={{title: title('Home Feed Preferences'), requireAuth: true}} options={{title: title(msg`Home Feed Preferences`), requireAuth: true}}
/> />
<Stack.Screen <Stack.Screen
name="PreferencesThreads" name="PreferencesThreads"
getComponent={() => PreferencesThreads} getComponent={() => PreferencesThreads}
options={{title: title('Threads Preferences'), requireAuth: true}} options={{title: title(msg`Threads Preferences`), requireAuth: true}}
/> />
<Stack.Screen <Stack.Screen
name="PreferencesExternalEmbeds" name="PreferencesExternalEmbeds"
getComponent={() => PreferencesExternalEmbeds} getComponent={() => PreferencesExternalEmbeds}
options={{ options={{
title: title('External Media Preferences'), title: title(msg`External Media Preferences`),
requireAuth: true, requireAuth: true,
}} }}
/> />
@ -407,7 +416,7 @@ const FlatNavigator = () => {
const pal = usePalette('default') const pal = usePalette('default')
const numUnread = useUnreadNotifications() const numUnread = useUnreadNotifications()
const title = (page: string) => bskyTitle(page, numUnread) const title = (page: MessageDescriptor) => bskyTitle(i18n._(page), numUnread)
return ( return (
<Flat.Navigator <Flat.Navigator
screenOptions={{ screenOptions={{
@ -420,22 +429,22 @@ const FlatNavigator = () => {
<Flat.Screen <Flat.Screen
name="Home" name="Home"
getComponent={() => HomeScreen} getComponent={() => HomeScreen}
options={{title: title('Home'), requireAuth: true}} options={{title: title(msg`Home`), requireAuth: true}}
/> />
<Flat.Screen <Flat.Screen
name="Search" name="Search"
getComponent={() => SearchScreen} getComponent={() => SearchScreen}
options={{title: title('Search')}} options={{title: title(msg`Search`)}}
/> />
<Flat.Screen <Flat.Screen
name="Feeds" name="Feeds"
getComponent={() => FeedsScreen} getComponent={() => FeedsScreen}
options={{title: title('Feeds'), requireAuth: true}} options={{title: title(msg`Feeds`), requireAuth: true}}
/> />
<Flat.Screen <Flat.Screen
name="Notifications" name="Notifications"
getComponent={() => NotificationsScreen} getComponent={() => NotificationsScreen}
options={{title: title('Notifications'), requireAuth: true}} options={{title: title(msg`Notifications`), requireAuth: true}}
/> />
{commonScreens(Flat as typeof HomeTab, numUnread)} {commonScreens(Flat as typeof HomeTab, numUnread)}
</Flat.Navigator> </Flat.Navigator>

View file

@ -2,7 +2,7 @@ import React from 'react'
import {View, Pressable} from 'react-native' import {View, Pressable} from 'react-native'
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
import {useLingui} from '@lingui/react' import {useLingui} from '@lingui/react'
import {msg} from '@lingui/macro' import {Trans, msg} from '@lingui/macro'
import {useNavigation} from '@react-navigation/native' import {useNavigation} from '@react-navigation/native'
import {isIOS, isNative} from 'platform/detection' import {isIOS, isNative} from 'platform/detection'
@ -119,7 +119,7 @@ export function LoggedOut({onDismiss}: {onDismiss?: () => void}) {
}} }}
onPress={onPressSearch}> onPress={onPressSearch}>
<Text type="lg-bold" style={[pal.text]}> <Text type="lg-bold" style={[pal.text]}>
Search{' '} <Trans>Search</Trans>{' '}
</Text> </Text>
<FontAwesomeIcon <FontAwesomeIcon
icon="search" icon="search"

View file

@ -74,7 +74,7 @@ export const SplashScreen = ({
// TODO: web accessibility // TODO: web accessibility
accessibilityRole="button"> accessibilityRole="button">
<Text style={[s.white, styles.btnLabel]}> <Text style={[s.white, styles.btnLabel]}>
Create a new account <Trans>Create a new account</Trans>
</Text> </Text>
</TouchableOpacity> </TouchableOpacity>
<TouchableOpacity <TouchableOpacity

View file

@ -77,7 +77,7 @@ export function Step1({
value={uiState.serviceUrl} value={uiState.serviceUrl}
editable editable
onChange={onChangeServiceUrl} onChange={onChangeServiceUrl}
accessibilityHint="Input hosting provider address" accessibilityHint={_(msg`Input hosting provider address`)}
accessibilityLabel={_(msg`Hosting provider address`)} accessibilityLabel={_(msg`Hosting provider address`)}
accessibilityLabelledBy="addressProvider" accessibilityLabelledBy="addressProvider"
/> />
@ -125,6 +125,7 @@ function Option({
}>) { }>) {
const theme = useTheme() const theme = useTheme()
const pal = usePalette('default') const pal = usePalette('default')
const {_} = useLingui()
const circleFillStyle = React.useMemo( const circleFillStyle = React.useMemo(
() => ({ () => ({
backgroundColor: theme.palette.primary.background, backgroundColor: theme.palette.primary.background,
@ -139,7 +140,7 @@ function Option({
testID={testID} testID={testID}
accessibilityRole="button" accessibilityRole="button"
accessibilityLabel={label} accessibilityLabel={label}
accessibilityHint={`Sets hosting provider to ${label}`}> accessibilityHint={_(msg`Sets hosting provider to ${label}`)}>
<View style={styles.optionHeading}> <View style={styles.optionHeading}>
<View style={[styles.circle, pal.border]}> <View style={[styles.circle, pal.border]}>
{isSelected ? ( {isSelected ? (

View file

@ -60,7 +60,7 @@ export function Step2({
{uiState.isInviteCodeRequired && ( {uiState.isInviteCodeRequired && (
<View style={s.pb20}> <View style={s.pb20}>
<Text type="md-medium" style={[pal.text, s.mb2]}> <Text type="md-medium" style={[pal.text, s.mb2]}>
Invite code <Trans>Invite code</Trans>
</Text> </Text>
<TextInput <TextInput
testID="inviteCodeInput" testID="inviteCodeInput"
@ -70,7 +70,7 @@ export function Step2({
editable editable
onChange={value => uiDispatch({type: 'set-invite-code', value})} onChange={value => uiDispatch({type: 'set-invite-code', value})}
accessibilityLabel={_(msg`Invite code`)} accessibilityLabel={_(msg`Invite code`)}
accessibilityHint="Input invite code to proceed" accessibilityHint={_(msg`Input invite code to proceed`)}
autoCapitalize="none" autoCapitalize="none"
autoComplete="off" autoComplete="off"
autoCorrect={false} autoCorrect={false}
@ -80,7 +80,7 @@ export function Step2({
{!uiState.inviteCode && uiState.isInviteCodeRequired ? ( {!uiState.inviteCode && uiState.isInviteCodeRequired ? (
<Text style={[s.alignBaseline, pal.text]}> <Text style={[s.alignBaseline, pal.text]}>
Don't have an invite code?{' '} <Trans>Don't have an invite code?</Trans>{' '}
<TouchableWithoutFeedback <TouchableWithoutFeedback
onPress={onPressWaitlist} onPress={onPressWaitlist}
accessibilityLabel={_(msg`Join the waitlist.`)} accessibilityLabel={_(msg`Join the waitlist.`)}
@ -106,7 +106,7 @@ export function Step2({
editable editable
onChange={value => uiDispatch({type: 'set-email', value})} onChange={value => uiDispatch({type: 'set-email', value})}
accessibilityLabel={_(msg`Email`)} accessibilityLabel={_(msg`Email`)}
accessibilityHint="Input email for Bluesky waitlist" accessibilityHint={_(msg`Input email for Bluesky waitlist`)}
accessibilityLabelledBy="email" accessibilityLabelledBy="email"
autoCapitalize="none" autoCapitalize="none"
autoComplete="off" autoComplete="off"
@ -130,7 +130,7 @@ export function Step2({
secureTextEntry secureTextEntry
onChange={value => uiDispatch({type: 'set-password', value})} onChange={value => uiDispatch({type: 'set-password', value})}
accessibilityLabel={_(msg`Password`)} accessibilityLabel={_(msg`Password`)}
accessibilityHint="Set password" accessibilityHint={_(msg`Set password`)}
accessibilityLabelledBy="password" accessibilityLabelledBy="password"
autoCapitalize="none" autoCapitalize="none"
autoComplete="off" autoComplete="off"
@ -154,7 +154,7 @@ export function Step2({
buttonStyle={[pal.border, styles.dateInputButton]} buttonStyle={[pal.border, styles.dateInputButton]}
buttonLabelType="lg" buttonLabelType="lg"
accessibilityLabel={_(msg`Birthday`)} accessibilityLabel={_(msg`Birthday`)}
accessibilityHint="Enter your birth date" accessibilityHint={_(msg`Enter your birth date`)}
accessibilityLabelledBy="birthDate" accessibilityLabelledBy="birthDate"
/> />
</View> </View>

View file

@ -36,7 +36,7 @@ export function Step3({
onChange={value => uiDispatch({type: 'set-handle', value})} onChange={value => uiDispatch({type: 'set-handle', value})}
// TODO: Add explicit text label // TODO: Add explicit text label
accessibilityLabel={_(msg`User handle`)} accessibilityLabel={_(msg`User handle`)}
accessibilityHint="Input your user handle" accessibilityHint={_(msg`Input your user handle`)}
/> />
<Text type="lg" style={[pal.text, s.pl5, s.pt10]}> <Text type="lg" style={[pal.text, s.pl5, s.pt10]}>
<Trans>Your full handle will be</Trans>{' '} <Trans>Your full handle will be</Trans>{' '}

View file

@ -2,13 +2,18 @@ import React from 'react'
import {StyleSheet, View} from 'react-native' import {StyleSheet, View} from 'react-native'
import {Text} from 'view/com/util/text/Text' import {Text} from 'view/com/util/text/Text'
import {usePalette} from 'lib/hooks/usePalette' import {usePalette} from 'lib/hooks/usePalette'
import {Trans} from '@lingui/macro'
export function StepHeader({step, title}: {step: string; title: string}) { export function StepHeader({step, title}: {step: string; title: string}) {
const pal = usePalette('default') const pal = usePalette('default')
return ( return (
<View style={styles.container}> <View style={styles.container}>
<Text type="lg" style={[pal.textLight]}> <Text type="lg" style={[pal.textLight]}>
{step === '3' ? 'Last step!' : <>Step {step} of 3</>} {step === '3' ? (
<Trans>Last step!</Trans>
) : (
<Trans>Step {step} of 3</Trans>
)}
</Text> </Text>
<Text style={[pal.text]} type="title-xl"> <Text style={[pal.text]} type="title-xl">
{title} {title}

View file

@ -42,7 +42,7 @@ function AccountItem({
onPress={onPress} onPress={onPress}
accessibilityRole="button" accessibilityRole="button"
accessibilityLabel={_(msg`Sign in as ${account.handle}`)} accessibilityLabel={_(msg`Sign in as ${account.handle}`)}
accessibilityHint="Double tap to sign in"> accessibilityHint={_(msg`Double tap to sign in`)}>
<View style={[pal.borderDark, styles.groupContent, styles.noTopBorder]}> <View style={[pal.borderDark, styles.groupContent, styles.noTopBorder]}>
<View style={s.p10}> <View style={s.p10}>
<UserAvatar avatar={profile?.avatar} size={30} /> <UserAvatar avatar={profile?.avatar} size={30} />
@ -95,19 +95,19 @@ export const ChooseAccountForm = ({
if (account.accessJwt) { if (account.accessJwt) {
if (account.did === currentAccount?.did) { if (account.did === currentAccount?.did) {
setShowLoggedOut(false) setShowLoggedOut(false)
Toast.show(`Already signed in as @${account.handle}`) Toast.show(_(msg`Already signed in as @${account.handle}`))
} else { } else {
await initSession(account) await initSession(account)
track('Sign In', {resumedSession: true}) track('Sign In', {resumedSession: true})
setTimeout(() => { setTimeout(() => {
Toast.show(`Signed in as @${account.handle}`) Toast.show(_(msg`Signed in as @${account.handle}`))
}, 100) }, 100)
} }
} else { } else {
onSelectAccount(account) onSelectAccount(account)
} }
}, },
[currentAccount, track, initSession, onSelectAccount, setShowLoggedOut], [currentAccount, track, initSession, onSelectAccount, setShowLoggedOut, _],
) )
return ( return (

View file

@ -67,7 +67,7 @@ export const ForgotPasswordForm = ({
const onPressNext = async () => { const onPressNext = async () => {
if (!EmailValidator.validate(email)) { if (!EmailValidator.validate(email)) {
return setError('Your email appears to be invalid.') return setError(_(msg`Your email appears to be invalid.`))
} }
setError('') setError('')
@ -83,7 +83,9 @@ export const ForgotPasswordForm = ({
setIsProcessing(false) setIsProcessing(false)
if (isNetworkError(e)) { if (isNetworkError(e)) {
setError( setError(
'Unable to contact your service. Please check your Internet connection.', _(
msg`Unable to contact your service. Please check your Internet connection.`,
),
) )
} else { } else {
setError(cleanError(errMsg)) setError(cleanError(errMsg))
@ -112,7 +114,9 @@ export const ForgotPasswordForm = ({
onPress={onPressSelectService} onPress={onPressSelectService}
accessibilityRole="button" accessibilityRole="button"
accessibilityLabel={_(msg`Hosting provider`)} accessibilityLabel={_(msg`Hosting provider`)}
accessibilityHint="Sets hosting provider for password reset"> accessibilityHint={_(
msg`Sets hosting provider for password reset`,
)}>
<FontAwesomeIcon <FontAwesomeIcon
icon="globe" icon="globe"
style={[pal.textLight, styles.groupContentIcon]} style={[pal.textLight, styles.groupContentIcon]}
@ -136,7 +140,7 @@ export const ForgotPasswordForm = ({
<TextInput <TextInput
testID="forgotPasswordEmail" testID="forgotPasswordEmail"
style={[pal.text, styles.textInput]} style={[pal.text, styles.textInput]}
placeholder="Email address" placeholder={_(msg`Email address`)}
placeholderTextColor={pal.colors.textLight} placeholderTextColor={pal.colors.textLight}
autoCapitalize="none" autoCapitalize="none"
autoFocus autoFocus
@ -146,7 +150,7 @@ export const ForgotPasswordForm = ({
onChangeText={setEmail} onChangeText={setEmail}
editable={!isProcessing} editable={!isProcessing}
accessibilityLabel={_(msg`Email`)} accessibilityLabel={_(msg`Email`)}
accessibilityHint="Sets email for password reset" accessibilityHint={_(msg`Sets email for password reset`)}
/> />
</View> </View>
</View> </View>
@ -179,7 +183,7 @@ export const ForgotPasswordForm = ({
onPress={onPressNext} onPress={onPressNext}
accessibilityRole="button" accessibilityRole="button"
accessibilityLabel={_(msg`Go to next`)} accessibilityLabel={_(msg`Go to next`)}
accessibilityHint="Navigates to the next screen"> accessibilityHint={_(msg`Navigates to the next screen`)}>
<Text type="xl-bold" style={[pal.link, s.pr5]}> <Text type="xl-bold" style={[pal.link, s.pr5]}>
<Trans>Next</Trans> <Trans>Next</Trans>
</Text> </Text>

View file

@ -145,7 +145,7 @@ export const LoginForm = ({
onPress={onPressSelectService} onPress={onPressSelectService}
accessibilityRole="button" accessibilityRole="button"
accessibilityLabel={_(msg`Select service`)} accessibilityLabel={_(msg`Select service`)}
accessibilityHint="Sets server for the Bluesky client"> accessibilityHint={_(msg`Sets server for the Bluesky client`)}>
<Text type="xl" style={[pal.text, styles.textBtnLabel]}> <Text type="xl" style={[pal.text, styles.textBtnLabel]}>
{toNiceDomain(serviceUrl)} {toNiceDomain(serviceUrl)}
</Text> </Text>
@ -190,7 +190,9 @@ export const LoginForm = ({
} }
editable={!isProcessing} editable={!isProcessing}
accessibilityLabel={_(msg`Username or email address`)} accessibilityLabel={_(msg`Username or email address`)}
accessibilityHint="Input the username or email address you used at signup" accessibilityHint={_(
msg`Input the username or email address you used at signup`,
)}
/> />
</View> </View>
<View style={[pal.borderDark, styles.groupContent]}> <View style={[pal.borderDark, styles.groupContent]}>
@ -221,8 +223,8 @@ export const LoginForm = ({
accessibilityLabel={_(msg`Password`)} accessibilityLabel={_(msg`Password`)}
accessibilityHint={ accessibilityHint={
identifier === '' identifier === ''
? 'Input your password' ? _(msg`Input your password`)
: `Input the password tied to ${identifier}` : _(msg`Input the password tied to ${identifier}`)
} }
/> />
<TouchableOpacity <TouchableOpacity
@ -231,7 +233,7 @@ export const LoginForm = ({
onPress={onPressForgotPassword} onPress={onPressForgotPassword}
accessibilityRole="button" accessibilityRole="button"
accessibilityLabel={_(msg`Forgot password`)} accessibilityLabel={_(msg`Forgot password`)}
accessibilityHint="Opens password reset form"> accessibilityHint={_(msg`Opens password reset form`)}>
<Text style={pal.link}> <Text style={pal.link}>
<Trans>Forgot</Trans> <Trans>Forgot</Trans>
</Text> </Text>
@ -261,7 +263,7 @@ export const LoginForm = ({
onPress={onPressRetryConnect} onPress={onPressRetryConnect}
accessibilityRole="button" accessibilityRole="button"
accessibilityLabel={_(msg`Retry`)} accessibilityLabel={_(msg`Retry`)}
accessibilityHint="Retries login"> accessibilityHint={_(msg`Retries login`)}>
<Text type="xl-bold" style={[pal.link, s.pr5]}> <Text type="xl-bold" style={[pal.link, s.pr5]}>
<Trans>Retry</Trans> <Trans>Retry</Trans>
</Text> </Text>
@ -281,7 +283,7 @@ export const LoginForm = ({
onPress={onPressNext} onPress={onPressNext}
accessibilityRole="button" accessibilityRole="button"
accessibilityLabel={_(msg`Go to next`)} accessibilityLabel={_(msg`Go to next`)}
accessibilityHint="Navigates to the next screen"> accessibilityHint={_(msg`Navigates to the next screen`)}>
<Text type="xl-bold" style={[pal.link, s.pr5]}> <Text type="xl-bold" style={[pal.link, s.pr5]}>
<Trans>Next</Trans> <Trans>Next</Trans>
</Text> </Text>

View file

@ -36,7 +36,7 @@ export const PasswordUpdatedForm = ({
onPress={onPressNext} onPress={onPressNext}
accessibilityRole="button" accessibilityRole="button"
accessibilityLabel={_(msg`Close alert`)} accessibilityLabel={_(msg`Close alert`)}
accessibilityHint="Closes password update alert"> accessibilityHint={_(msg`Closes password update alert`)}>
<Text type="xl-bold" style={[pal.link, s.pr5]}> <Text type="xl-bold" style={[pal.link, s.pr5]}>
<Trans>Okay</Trans> <Trans>Okay</Trans>
</Text> </Text>

View file

@ -95,7 +95,7 @@ export const SetNewPasswordForm = ({
<TextInput <TextInput
testID="resetCodeInput" testID="resetCodeInput"
style={[pal.text, styles.textInput]} style={[pal.text, styles.textInput]}
placeholder="Reset code" placeholder={_(msg`Reset code`)}
placeholderTextColor={pal.colors.textLight} placeholderTextColor={pal.colors.textLight}
autoCapitalize="none" autoCapitalize="none"
autoCorrect={false} autoCorrect={false}
@ -106,7 +106,9 @@ export const SetNewPasswordForm = ({
editable={!isProcessing} editable={!isProcessing}
accessible={true} accessible={true}
accessibilityLabel={_(msg`Reset code`)} accessibilityLabel={_(msg`Reset code`)}
accessibilityHint="Input code sent to your email for password reset" accessibilityHint={_(
msg`Input code sent to your email for password reset`,
)}
/> />
</View> </View>
<View style={[pal.borderDark, styles.groupContent]}> <View style={[pal.borderDark, styles.groupContent]}>
@ -117,7 +119,7 @@ export const SetNewPasswordForm = ({
<TextInput <TextInput
testID="newPasswordInput" testID="newPasswordInput"
style={[pal.text, styles.textInput]} style={[pal.text, styles.textInput]}
placeholder="New password" placeholder={_(msg`New password`)}
placeholderTextColor={pal.colors.textLight} placeholderTextColor={pal.colors.textLight}
autoCapitalize="none" autoCapitalize="none"
autoCorrect={false} autoCorrect={false}
@ -128,7 +130,7 @@ export const SetNewPasswordForm = ({
editable={!isProcessing} editable={!isProcessing}
accessible={true} accessible={true}
accessibilityLabel={_(msg`Password`)} accessibilityLabel={_(msg`Password`)}
accessibilityHint="Input new password" accessibilityHint={_(msg`Input new password`)}
/> />
</View> </View>
</View> </View>
@ -161,7 +163,7 @@ export const SetNewPasswordForm = ({
onPress={onPressNext} onPress={onPressNext}
accessibilityRole="button" accessibilityRole="button"
accessibilityLabel={_(msg`Go to next`)} accessibilityLabel={_(msg`Go to next`)}
accessibilityHint="Navigates to the next screen"> accessibilityHint={_(msg`Navigates to the next screen`)}>
<Text type="xl-bold" style={[pal.link, s.pr5]}> <Text type="xl-bold" style={[pal.link, s.pr5]}>
<Trans>Next</Trans> <Trans>Next</Trans>
</Text> </Text>

View file

@ -18,6 +18,8 @@ import {
} from '#/state/queries/preferences' } from '#/state/queries/preferences'
import {logger} from '#/logger' import {logger} from '#/logger'
import {useAnalytics} from '#/lib/analytics/analytics' import {useAnalytics} from '#/lib/analytics/analytics'
import {Trans, msg} from '@lingui/macro'
import {useLingui} from '@lingui/react'
export function RecommendedFeedsItem({ export function RecommendedFeedsItem({
item, item,
@ -26,6 +28,7 @@ export function RecommendedFeedsItem({
}) { }) {
const {isMobile} = useWebMediaQueries() const {isMobile} = useWebMediaQueries()
const pal = usePalette('default') const pal = usePalette('default')
const {_} = useLingui()
const {data: preferences} = usePreferencesQuery() const {data: preferences} = usePreferencesQuery()
const { const {
mutateAsync: pinFeed, mutateAsync: pinFeed,
@ -51,7 +54,7 @@ export function RecommendedFeedsItem({
await removeFeed({uri: item.uri}) await removeFeed({uri: item.uri})
resetRemoveFeed() resetRemoveFeed()
} catch (e) { } catch (e) {
Toast.show('There was an issue contacting your server') Toast.show(_(msg`There was an issue contacting your server`))
logger.error('Failed to unsave feed', {error: e}) logger.error('Failed to unsave feed', {error: e})
} }
} else { } else {
@ -60,7 +63,7 @@ export function RecommendedFeedsItem({
resetPinFeed() resetPinFeed()
track('Onboarding:CustomFeedAdded') track('Onboarding:CustomFeedAdded')
} catch (e) { } catch (e) {
Toast.show('There was an issue contacting your server') Toast.show(_(msg`There was an issue contacting your server`))
logger.error('Failed to pin feed', {error: e}) logger.error('Failed to pin feed', {error: e})
} }
} }
@ -94,7 +97,7 @@ export function RecommendedFeedsItem({
</Text> </Text>
<Text style={[pal.textLight, {marginBottom: 8}]} numberOfLines={1}> <Text style={[pal.textLight, {marginBottom: 8}]} numberOfLines={1}>
by {sanitizeHandle(item.creator.handle, '@')} <Trans>by {sanitizeHandle(item.creator.handle, '@')}</Trans>
</Text> </Text>
{item.description ? ( {item.description ? (
@ -133,7 +136,7 @@ export function RecommendedFeedsItem({
color={pal.colors.textInverted} color={pal.colors.textInverted}
/> />
<Text type="lg-medium" style={pal.textInverted}> <Text type="lg-medium" style={pal.textInverted}>
Added <Trans>Added</Trans>
</Text> </Text>
</> </>
) : ( ) : (
@ -144,7 +147,7 @@ export function RecommendedFeedsItem({
color={pal.colors.textInverted} color={pal.colors.textInverted}
/> />
<Text type="lg-medium" style={pal.textInverted}> <Text type="lg-medium" style={pal.textInverted}>
Add <Trans>Add</Trans>
</Text> </Text>
</> </>
)} )}

View file

@ -83,7 +83,7 @@ export function RecommendedFollows({next}: Props) {
<Text <Text
type="2xl-medium" type="2xl-medium"
style={{color: '#fff', position: 'relative', top: -1}}> style={{color: '#fff', position: 'relative', top: -1}}>
<Trans>Done</Trans> <Trans context="action">Done</Trans>
</Text> </Text>
<FontAwesomeIcon icon="angle-right" color="#fff" size={14} /> <FontAwesomeIcon icon="angle-right" color="#fff" size={14} />
</View> </View>

View file

@ -7,6 +7,7 @@ import {usePalette} from 'lib/hooks/usePalette'
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
import {TitleColumnLayout} from 'view/com/util/layouts/TitleColumnLayout' import {TitleColumnLayout} from 'view/com/util/layouts/TitleColumnLayout'
import {Button} from 'view/com/util/forms/Button' import {Button} from 'view/com/util/forms/Button'
import {Trans} from '@lingui/macro'
type Props = { type Props = {
next: () => void next: () => void
@ -17,7 +18,7 @@ export function WelcomeDesktop({next}: Props) {
const pal = usePalette('default') const pal = usePalette('default')
const horizontal = useMediaQuery({minWidth: 1300}) const horizontal = useMediaQuery({minWidth: 1300})
const title = ( const title = (
<> <Trans>
<Text <Text
style={[ style={[
pal.textLight, pal.textLight,
@ -40,7 +41,7 @@ export function WelcomeDesktop({next}: Props) {
]}> ]}>
Bluesky Bluesky
</Text> </Text>
</> </Trans>
) )
return ( return (
<TitleColumnLayout <TitleColumnLayout
@ -52,10 +53,12 @@ export function WelcomeDesktop({next}: Props) {
<FontAwesomeIcon icon={'globe'} size={36} color={pal.colors.link} /> <FontAwesomeIcon icon={'globe'} size={36} color={pal.colors.link} />
<View style={[styles.rowText]}> <View style={[styles.rowText]}>
<Text type="xl-bold" style={[pal.text]}> <Text type="xl-bold" style={[pal.text]}>
Bluesky is public. <Trans>Bluesky is public.</Trans>
</Text> </Text>
<Text type="xl" style={[pal.text, s.pt2]}> <Text type="xl" style={[pal.text, s.pt2]}>
<Trans>
Your posts, likes, and blocks are public. Mutes are private. Your posts, likes, and blocks are public. Mutes are private.
</Trans>
</Text> </Text>
</View> </View>
</View> </View>
@ -63,10 +66,10 @@ export function WelcomeDesktop({next}: Props) {
<FontAwesomeIcon icon={'at'} size={36} color={pal.colors.link} /> <FontAwesomeIcon icon={'at'} size={36} color={pal.colors.link} />
<View style={[styles.rowText]}> <View style={[styles.rowText]}>
<Text type="xl-bold" style={[pal.text]}> <Text type="xl-bold" style={[pal.text]}>
Bluesky is open. <Trans>Bluesky is open.</Trans>
</Text> </Text>
<Text type="xl" style={[pal.text, s.pt2]}> <Text type="xl" style={[pal.text, s.pt2]}>
Never lose access to your followers and data. <Trans>Never lose access to your followers and data.</Trans>
</Text> </Text>
</View> </View>
</View> </View>
@ -74,10 +77,13 @@ export function WelcomeDesktop({next}: Props) {
<FontAwesomeIcon icon={'gear'} size={36} color={pal.colors.link} /> <FontAwesomeIcon icon={'gear'} size={36} color={pal.colors.link} />
<View style={[styles.rowText]}> <View style={[styles.rowText]}>
<Text type="xl-bold" style={[pal.text]}> <Text type="xl-bold" style={[pal.text]}>
Bluesky is flexible. <Trans>Bluesky is flexible.</Trans>
</Text> </Text>
<Text type="xl" style={[pal.text, s.pt2]}> <Text type="xl" style={[pal.text, s.pt2]}>
Choose the algorithms that power your experience with custom feeds. <Trans>
Choose the algorithms that power your experience with custom
feeds.
</Trans>
</Text> </Text>
</View> </View>
</View> </View>
@ -94,7 +100,7 @@ export function WelcomeDesktop({next}: Props) {
<Text <Text
type="2xl-medium" type="2xl-medium"
style={{color: '#fff', position: 'relative', top: -1}}> style={{color: '#fff', position: 'relative', top: -1}}>
Next <Trans context="action">Next</Trans>
</Text> </Text>
<FontAwesomeIcon icon="angle-right" color="#fff" size={14} /> <FontAwesomeIcon icon="angle-right" color="#fff" size={14} />
</View> </View>

View file

@ -260,7 +260,11 @@ export const ComposePost = observer(function ComposePost({
setLangPrefs.savePostLanguageToHistory() setLangPrefs.savePostLanguageToHistory()
onPost?.() onPost?.()
onClose() onClose()
Toast.show(`Your ${replyTo ? 'reply' : 'post'} has been published`) Toast.show(
replyTo
? _(msg`Your reply has been published`)
: _(msg`Your post has been published`),
)
} }
const canPost = useMemo( const canPost = useMemo(
@ -269,7 +273,9 @@ export const ComposePost = observer(function ComposePost({
(!requireAltTextEnabled || !gallery.needsAltText), (!requireAltTextEnabled || !gallery.needsAltText),
[graphemeLength, requireAltTextEnabled, gallery.needsAltText], [graphemeLength, requireAltTextEnabled, gallery.needsAltText],
) )
const selectTextInputPlaceholder = replyTo ? 'Write your reply' : `What's up?` const selectTextInputPlaceholder = replyTo
? _(msg`Write your reply`)
: _(msg`What's up?`)
const canSelectImages = useMemo(() => gallery.size < 4, [gallery.size]) const canSelectImages = useMemo(() => gallery.size < 4, [gallery.size])
const hasMedia = gallery.size > 0 || Boolean(extLink) const hasMedia = gallery.size > 0 || Boolean(extLink)
@ -291,7 +297,9 @@ export const ComposePost = observer(function ComposePost({
onAccessibilityEscape={onPressCancel} onAccessibilityEscape={onPressCancel}
accessibilityRole="button" accessibilityRole="button"
accessibilityLabel={_(msg`Cancel`)} accessibilityLabel={_(msg`Cancel`)}
accessibilityHint="Closes post composer and discards post draft"> accessibilityHint={_(
msg`Closes post composer and discards post draft`,
)}>
<Text style={[pal.link, s.f18]}> <Text style={[pal.link, s.f18]}>
<Trans>Cancel</Trans> <Trans>Cancel</Trans>
</Text> </Text>
@ -323,7 +331,7 @@ export const ComposePost = observer(function ComposePost({
onPress={onPressPublish} onPress={onPressPublish}
accessibilityRole="button" accessibilityRole="button"
accessibilityLabel={ accessibilityLabel={
replyTo ? 'Publish reply' : 'Publish post' replyTo ? _(msg`Publish reply`) : _(msg`Publish post`)
} }
accessibilityHint=""> accessibilityHint="">
<LinearGradient <LinearGradient
@ -335,14 +343,18 @@ export const ComposePost = observer(function ComposePost({
end={{x: 1, y: 1}} end={{x: 1, y: 1}}
style={styles.postBtn}> style={styles.postBtn}>
<Text style={[s.white, s.f16, s.bold]}> <Text style={[s.white, s.f16, s.bold]}>
{replyTo ? 'Reply' : 'Post'} {replyTo ? (
<Trans context="action">Reply</Trans>
) : (
<Trans context="action">Post</Trans>
)}
</Text> </Text>
</LinearGradient> </LinearGradient>
</TouchableOpacity> </TouchableOpacity>
) : ( ) : (
<View style={[styles.postBtn, pal.btn]}> <View style={[styles.postBtn, pal.btn]}>
<Text style={[pal.textLight, s.f16, s.bold]}> <Text style={[pal.textLight, s.f16, s.bold]}>
<Trans>Post</Trans> <Trans context="action">Post</Trans>
</Text> </Text>
</View> </View>
)} )}
@ -400,7 +412,9 @@ export const ComposePost = observer(function ComposePost({
onError={setError} onError={setError}
accessible={true} accessible={true}
accessibilityLabel={_(msg`Write post`)} accessibilityLabel={_(msg`Write post`)}
accessibilityHint={`Compose posts up to ${MAX_GRAPHEME_LENGTH} characters in length`} accessibilityHint={_(
msg`Compose posts up to ${MAX_GRAPHEME_LENGTH} characters in length`,
)}
/> />
</View> </View>
@ -429,7 +443,9 @@ export const ComposePost = observer(function ComposePost({
onPress={() => onPressAddLinkCard(url)} onPress={() => onPressAddLinkCard(url)}
accessibilityRole="button" accessibilityRole="button"
accessibilityLabel={_(msg`Add link card`)} accessibilityLabel={_(msg`Add link card`)}
accessibilityHint={`Creates a card with a thumbnail. The card links to ${url}`}> accessibilityHint={_(
msg`Creates a card with a thumbnail. The card links to ${url}`,
)}>
<Text style={pal.text}> <Text style={pal.text}>
<Trans>Add link card:</Trans>{' '} <Trans>Add link card:</Trans>{' '}
<Text style={[pal.link, s.ml5]}>{toShortUrl(url)}</Text> <Text style={[pal.link, s.ml5]}>{toShortUrl(url)}</Text>

View file

@ -68,7 +68,7 @@ export const ExternalEmbed = ({
onPress={onRemove} onPress={onRemove}
accessibilityRole="button" accessibilityRole="button"
accessibilityLabel={_(msg`Remove image preview`)} accessibilityLabel={_(msg`Remove image preview`)}
accessibilityHint={`Removes default thumbnail from ${link.uri}`} accessibilityHint={_(msg`Removes default thumbnail from ${link.uri}`)}
onAccessibilityEscape={onRemove}> onAccessibilityEscape={onRemove}>
<FontAwesomeIcon size={18} icon="xmark" style={s.white} /> <FontAwesomeIcon size={18} icon="xmark" style={s.white} />
</TouchableOpacity> </TouchableOpacity>

View file

@ -22,7 +22,7 @@ export function ComposePrompt({onPressCompose}: {onPressCompose: () => void}) {
onPress={() => onPressCompose()} onPress={() => onPressCompose()}
accessibilityRole="button" accessibilityRole="button"
accessibilityLabel={_(msg`Compose reply`)} accessibilityLabel={_(msg`Compose reply`)}
accessibilityHint="Opens composer"> accessibilityHint={_(msg`Opens composer`)}>
<UserAvatar avatar={profile?.avatar} size={38} /> <UserAvatar avatar={profile?.avatar} size={38} />
<Text <Text
type="xl" type="xl"

View file

@ -58,7 +58,7 @@ export function OpenCameraBtn({gallery}: Props) {
hitSlop={HITSLOP_10} hitSlop={HITSLOP_10}
accessibilityRole="button" accessibilityRole="button"
accessibilityLabel={_(msg`Camera`)} accessibilityLabel={_(msg`Camera`)}
accessibilityHint="Opens camera on device"> accessibilityHint={_(msg`Opens camera on device`)}>
<FontAwesomeIcon <FontAwesomeIcon
icon="camera" icon="camera"
style={pal.link as FontAwesomeIconStyle} style={pal.link as FontAwesomeIconStyle}

View file

@ -41,7 +41,7 @@ export function SelectPhotoBtn({gallery}: Props) {
hitSlop={HITSLOP_10} hitSlop={HITSLOP_10}
accessibilityRole="button" accessibilityRole="button"
accessibilityLabel={_(msg`Gallery`)} accessibilityLabel={_(msg`Gallery`)}
accessibilityHint="Opens device photo gallery"> accessibilityHint={_(msg`Opens device photo gallery`)}>
<FontAwesomeIcon <FontAwesomeIcon
icon={['far', 'image']} icon={['far', 'image']}
style={pal.link as FontAwesomeIconStyle} style={pal.link as FontAwesomeIconStyle}

View file

@ -17,6 +17,7 @@ import {usePalette} from 'lib/hooks/usePalette'
import {Text} from 'view/com/util/text/Text' import {Text} from 'view/com/util/text/Text'
import {UserAvatar} from 'view/com/util/UserAvatar' import {UserAvatar} from 'view/com/util/UserAvatar'
import {useGrapheme} from '../hooks/useGrapheme' import {useGrapheme} from '../hooks/useGrapheme'
import {Trans} from '@lingui/macro'
interface MentionListRef { interface MentionListRef {
onKeyDown: (props: SuggestionKeyDownProps) => boolean onKeyDown: (props: SuggestionKeyDownProps) => boolean
@ -187,7 +188,7 @@ const MentionList = forwardRef<MentionListRef, SuggestionProps>(
}) })
) : ( ) : (
<Text type="sm" style={[pal.text, styles.noResult]}> <Text type="sm" style={[pal.text, styles.noResult]}>
No result <Trans>No result</Trans>
</Text> </Text>
)} )}
</View> </View>

View file

@ -197,7 +197,7 @@ export function FeedPage({
onPress={onPressCompose} onPress={onPressCompose}
icon={<ComposeIcon2 strokeWidth={1.5} size={29} style={s.white} />} icon={<ComposeIcon2 strokeWidth={1.5} size={29} style={s.white} />}
accessibilityRole="button" accessibilityRole="button"
accessibilityLabel={_(msg`New post`)} accessibilityLabel={_(msg({message: `New post`, context: 'action'}))}
accessibilityHint="" accessibilityHint=""
/> />
)} )}

View file

@ -14,7 +14,7 @@ import * as Toast from 'view/com/util/Toast'
import {sanitizeHandle} from 'lib/strings/handles' import {sanitizeHandle} from 'lib/strings/handles'
import {logger} from '#/logger' import {logger} from '#/logger'
import {useModalControls} from '#/state/modals' import {useModalControls} from '#/state/modals'
import {msg} from '@lingui/macro' import {Trans, msg} from '@lingui/macro'
import {useLingui} from '@lingui/react' import {useLingui} from '@lingui/react'
import { import {
usePinFeedMutation, usePinFeedMutation,
@ -108,9 +108,9 @@ export function FeedSourceCardLoaded({
try { try {
await removeFeed({uri: feed.uri}) await removeFeed({uri: feed.uri})
// await item.unsave() // await item.unsave()
Toast.show('Removed from my feeds') Toast.show(_(msg`Removed from my feeds`))
} catch (e) { } catch (e) {
Toast.show('There was an issue contacting your server') Toast.show(_(msg`There was an issue contacting your server`))
logger.error('Failed to unsave feed', {error: e}) logger.error('Failed to unsave feed', {error: e})
} }
}, },
@ -122,9 +122,9 @@ export function FeedSourceCardLoaded({
} else { } else {
await saveFeed({uri: feed.uri}) await saveFeed({uri: feed.uri})
} }
Toast.show('Added to my feeds') Toast.show(_(msg`Added to my feeds`))
} catch (e) { } catch (e) {
Toast.show('There was an issue contacting your server') Toast.show(_(msg`There was an issue contacting your server`))
logger.error('Failed to save feed', {error: e}) logger.error('Failed to save feed', {error: e})
} }
} }
@ -164,7 +164,7 @@ export function FeedSourceCardLoaded({
testID={`feed-${feedUri}-toggleSave`} testID={`feed-${feedUri}-toggleSave`}
disabled={isRemovePending} disabled={isRemovePending}
accessibilityRole="button" accessibilityRole="button"
accessibilityLabel={'Remove from my feeds'} accessibilityLabel={_(msg`Remove from my feeds`)}
accessibilityHint="" accessibilityHint=""
onPress={() => { onPress={() => {
openModal({ openModal({
@ -175,9 +175,11 @@ export function FeedSourceCardLoaded({
try { try {
await removeFeed({uri: feedUri}) await removeFeed({uri: feedUri})
// await item.unsave() // await item.unsave()
Toast.show('Removed from my feeds') Toast.show(_(msg`Removed from my feeds`))
} catch (e) { } catch (e) {
Toast.show('There was an issue contacting your server') Toast.show(
_(msg`There was an issue contacting your server`),
)
logger.error('Failed to unsave feed', {error: e}) logger.error('Failed to unsave feed', {error: e})
} }
}, },
@ -223,8 +225,11 @@ export function FeedSourceCardLoaded({
{feed.displayName} {feed.displayName}
</Text> </Text>
<Text style={[pal.textLight]} numberOfLines={3}> <Text style={[pal.textLight]} numberOfLines={3}>
{feed.type === 'feed' ? 'Feed' : 'List'} by{' '} {feed.type === 'feed' ? (
{sanitizeHandle(feed.creatorHandle, '@')} <Trans>Feed by {sanitizeHandle(feed.creatorHandle, '@')}</Trans>
) : (
<Trans>List by {sanitizeHandle(feed.creatorHandle, '@')}</Trans>
)}
</Text> </Text>
</View> </View>
@ -235,7 +240,7 @@ export function FeedSourceCardLoaded({
disabled={isSavePending || isPinPending || isRemovePending} disabled={isSavePending || isPinPending || isRemovePending}
accessibilityRole="button" accessibilityRole="button"
accessibilityLabel={ accessibilityLabel={
isSaved ? 'Remove from my feeds' : 'Add to my feeds' isSaved ? _(msg`Remove from my feeds`) : _(msg`Add to my feeds`)
} }
accessibilityHint="" accessibilityHint=""
onPress={onToggleSaved} onPress={onToggleSaved}
@ -269,8 +274,10 @@ export function FeedSourceCardLoaded({
{showLikes && feed.type === 'feed' ? ( {showLikes && feed.type === 'feed' ? (
<Text type="sm-medium" style={[pal.text, pal.textLight]}> <Text type="sm-medium" style={[pal.text, pal.textLight]}>
<Trans>
Liked by {feed.likeCount || 0}{' '} Liked by {feed.likeCount || 0}{' '}
{pluralize(feed.likeCount || 0, 'user')} {pluralize(feed.likeCount || 0, 'user')}
</Trans>
</Text> </Text>
) : null} ) : null}
</Pressable> </Pressable>

View file

@ -9,13 +9,14 @@ import {Text} from '../util/text/Text'
import {usePalette} from 'lib/hooks/usePalette' import {usePalette} from 'lib/hooks/usePalette'
import {useProfileFeedgensQuery, RQKEY} from '#/state/queries/profile-feedgens' import {useProfileFeedgensQuery, RQKEY} from '#/state/queries/profile-feedgens'
import {logger} from '#/logger' import {logger} from '#/logger'
import {Trans} from '@lingui/macro' import {Trans, msg} from '@lingui/macro'
import {cleanError} from '#/lib/strings/errors' import {cleanError} from '#/lib/strings/errors'
import {useTheme} from '#/lib/ThemeContext' import {useTheme} from '#/lib/ThemeContext'
import {usePreferencesQuery} from '#/state/queries/preferences' import {usePreferencesQuery} from '#/state/queries/preferences'
import {hydrateFeedGenerator} from '#/state/queries/feed' import {hydrateFeedGenerator} from '#/state/queries/feed'
import {FeedLoadingPlaceholder} from '#/view/com/util/LoadingPlaceholder' import {FeedLoadingPlaceholder} from '#/view/com/util/LoadingPlaceholder'
import {isNative} from '#/platform/detection' import {isNative} from '#/platform/detection'
import {useLingui} from '@lingui/react'
const LOADING = {_reactKey: '__loading__'} const LOADING = {_reactKey: '__loading__'}
const EMPTY = {_reactKey: '__empty__'} const EMPTY = {_reactKey: '__empty__'}
@ -43,6 +44,7 @@ export const ProfileFeedgens = React.forwardRef<
ref, ref,
) { ) {
const pal = usePalette('default') const pal = usePalette('default')
const {_} = useLingui()
const theme = useTheme() const theme = useTheme()
const [isPTRing, setIsPTRing] = React.useState(false) const [isPTRing, setIsPTRing] = React.useState(false)
const opts = React.useMemo(() => ({enabled}), [enabled]) const opts = React.useMemo(() => ({enabled}), [enabled])
@ -142,7 +144,9 @@ export const ProfileFeedgens = React.forwardRef<
} else if (item === LOAD_MORE_ERROR_ITEM) { } else if (item === LOAD_MORE_ERROR_ITEM) {
return ( return (
<LoadMoreRetryBtn <LoadMoreRetryBtn
label="There was an issue fetching your lists. Tap here to try again." label={_(
msg`There was an issue fetching your lists. Tap here to try again.`,
)}
onPress={onPressRetryLoadMore} onPress={onPressRetryLoadMore}
/> />
) )
@ -162,7 +166,7 @@ export const ProfileFeedgens = React.forwardRef<
} }
return null return null
}, },
[error, refetch, onPressRetryLoadMore, pal, preferences], [error, refetch, onPressRetryLoadMore, pal, preferences, _],
) )
return ( return (

View file

@ -24,7 +24,7 @@ const ImageDefaultHeader = ({onRequestClose}: Props) => (
hitSlop={HIT_SLOP} hitSlop={HIT_SLOP}
accessibilityRole="button" accessibilityRole="button"
accessibilityLabel={t`Close image`} accessibilityLabel={t`Close image`}
accessibilityHint="Closes viewer for header image" accessibilityHint={t`Closes viewer for header image`}
onAccessibilityEscape={onRequestClose}> onAccessibilityEscape={onRequestClose}>
<Text style={styles.closeText}></Text> <Text style={styles.closeText}></Text>
</TouchableOpacity> </TouchableOpacity>

View file

@ -15,6 +15,8 @@ import {
ProfileImageLightbox, ProfileImageLightbox,
ImagesLightbox, ImagesLightbox,
} from '#/state/lightbox' } from '#/state/lightbox'
import {Trans, msg} from '@lingui/macro'
import {useLingui} from '@lingui/react'
export function Lightbox() { export function Lightbox() {
const {activeLightbox} = useLightbox() const {activeLightbox} = useLightbox()
@ -53,6 +55,7 @@ export function Lightbox() {
} }
function LightboxFooter({imageIndex}: {imageIndex: number}) { function LightboxFooter({imageIndex}: {imageIndex: number}) {
const {_} = useLingui()
const {activeLightbox} = useLightbox() const {activeLightbox} = useLightbox()
const [isAltExpanded, setAltExpanded] = React.useState(false) const [isAltExpanded, setAltExpanded] = React.useState(false)
const [permissionResponse, requestPermission] = MediaLibrary.usePermissions() const [permissionResponse, requestPermission] = MediaLibrary.usePermissions()
@ -60,12 +63,14 @@ function LightboxFooter({imageIndex}: {imageIndex: number}) {
const saveImageToAlbumWithToasts = React.useCallback( const saveImageToAlbumWithToasts = React.useCallback(
async (uri: string) => { async (uri: string) => {
if (!permissionResponse || permissionResponse.granted === false) { if (!permissionResponse || permissionResponse.granted === false) {
Toast.show('Permission to access camera roll is required.') Toast.show(_(msg`Permission to access camera roll is required.`))
if (permissionResponse?.canAskAgain) { if (permissionResponse?.canAskAgain) {
requestPermission() requestPermission()
} else { } else {
Toast.show( Toast.show(
'Permission to access camera roll was denied. Please enable it in your system settings.', _(
msg`Permission to access camera roll was denied. Please enable it in your system settings.`,
),
) )
} }
return return
@ -78,7 +83,7 @@ function LightboxFooter({imageIndex}: {imageIndex: number}) {
Toast.show(`Failed to save image: ${String(e)}`) Toast.show(`Failed to save image: ${String(e)}`)
} }
}, },
[permissionResponse, requestPermission], [permissionResponse, requestPermission, _],
) )
const lightbox = activeLightbox const lightbox = activeLightbox
@ -117,7 +122,7 @@ function LightboxFooter({imageIndex}: {imageIndex: number}) {
onPress={() => saveImageToAlbumWithToasts(uri)}> onPress={() => saveImageToAlbumWithToasts(uri)}>
<FontAwesomeIcon icon={['far', 'floppy-disk']} style={s.white} /> <FontAwesomeIcon icon={['far', 'floppy-disk']} style={s.white} />
<Text type="xl" style={s.white}> <Text type="xl" style={s.white}>
Save <Trans context="action">Save</Trans>
</Text> </Text>
</Button> </Button>
<Button <Button
@ -126,7 +131,7 @@ function LightboxFooter({imageIndex}: {imageIndex: number}) {
onPress={() => shareImageModal({uri})}> onPress={() => shareImageModal({uri})}>
<FontAwesomeIcon icon="arrow-up-from-bracket" style={s.white} /> <FontAwesomeIcon icon="arrow-up-from-bracket" style={s.white} />
<Text type="xl" style={s.white}> <Text type="xl" style={s.white}>
Share <Trans context="action">Share</Trans>
</Text> </Text>
</Button> </Button>
</View> </View>

View file

@ -110,7 +110,7 @@ function LightboxInner({
onPress={onClose} onPress={onClose}
accessibilityRole="button" accessibilityRole="button"
accessibilityLabel={_(msg`Close image viewer`)} accessibilityLabel={_(msg`Close image viewer`)}
accessibilityHint="Exits image view" accessibilityHint={_(msg`Exits image view`)}
onAccessibilityEscape={onClose}> onAccessibilityEscape={onClose}>
<View style={styles.imageCenterer}> <View style={styles.imageCenterer}>
<Image <Image
@ -154,7 +154,9 @@ function LightboxInner({
<View style={styles.footer}> <View style={styles.footer}>
<Pressable <Pressable
accessibilityLabel={_(msg`Expand alt text`)} accessibilityLabel={_(msg`Expand alt text`)}
accessibilityHint="If alt text is long, toggles alt text expanded state" accessibilityHint={_(
msg`If alt text is long, toggles alt text expanded state`,
)}
onPress={() => { onPress={() => {
setAltExpanded(!isAltExpanded) setAltExpanded(!isAltExpanded)
}}> }}>

View file

@ -11,6 +11,7 @@ import {useSession} from '#/state/session'
import {sanitizeDisplayName} from 'lib/strings/display-names' import {sanitizeDisplayName} from 'lib/strings/display-names'
import {sanitizeHandle} from 'lib/strings/handles' import {sanitizeHandle} from 'lib/strings/handles'
import {makeProfileLink} from 'lib/routes/links' import {makeProfileLink} from 'lib/routes/links'
import {Trans} from '@lingui/macro'
export const ListCard = ({ export const ListCard = ({
testID, testID,
@ -76,19 +77,28 @@ export const ListCard = ({
{sanitizeDisplayName(list.name)} {sanitizeDisplayName(list.name)}
</Text> </Text>
<Text type="md" style={[pal.textLight]} numberOfLines={1}> <Text type="md" style={[pal.textLight]} numberOfLines={1}>
{list.purpose === 'app.bsky.graph.defs#curatelist' && 'User list '} {list.purpose === 'app.bsky.graph.defs#curatelist' &&
(list.creator.did === currentAccount?.did ? (
<Trans>User list by you</Trans>
) : (
<Trans>
User list by {sanitizeHandle(list.creator.handle, '@')}
</Trans>
))}
{list.purpose === 'app.bsky.graph.defs#modlist' && {list.purpose === 'app.bsky.graph.defs#modlist' &&
'Moderation list '} (list.creator.did === currentAccount?.did ? (
by{' '} <Trans>Moderation list by you</Trans>
{list.creator.did === currentAccount?.did ) : (
? 'you' <Trans>
: sanitizeHandle(list.creator.handle, '@')} Moderation list by {sanitizeHandle(list.creator.handle, '@')}
</Trans>
))}
</Text> </Text>
{!!list.viewer?.muted && ( {!!list.viewer?.muted && (
<View style={s.flexRow}> <View style={s.flexRow}>
<View style={[s.mt5, pal.btn, styles.pill]}> <View style={[s.mt5, pal.btn, styles.pill]}>
<Text type="xs" style={pal.text}> <Text type="xs" style={pal.text}>
Subscribed <Trans>Subscribed</Trans>
</Text> </Text>
</View> </View>
</View> </View>

View file

@ -20,6 +20,8 @@ import {logger} from '#/logger'
import {useModalControls} from '#/state/modals' import {useModalControls} from '#/state/modals'
import {useSession} from '#/state/session' import {useSession} from '#/state/session'
import {cleanError} from '#/lib/strings/errors' import {cleanError} from '#/lib/strings/errors'
import {useLingui} from '@lingui/react'
import {msg} from '@lingui/macro'
const LOADING_ITEM = {_reactKey: '__loading__'} const LOADING_ITEM = {_reactKey: '__loading__'}
const EMPTY_ITEM = {_reactKey: '__empty__'} const EMPTY_ITEM = {_reactKey: '__empty__'}
@ -50,6 +52,7 @@ export function ListMembers({
desktopFixedHeightOffset?: number desktopFixedHeightOffset?: number
}) { }) {
const {track} = useAnalytics() const {track} = useAnalytics()
const {_} = useLingui()
const [isRefreshing, setIsRefreshing] = React.useState(false) const [isRefreshing, setIsRefreshing] = React.useState(false)
const {isMobile} = useWebMediaQueries() const {isMobile} = useWebMediaQueries()
const {openModal} = useModalControls() const {openModal} = useModalControls()
@ -143,12 +146,12 @@ export function ListMembers({
<Button <Button
testID={`user-${profile.handle}-editBtn`} testID={`user-${profile.handle}-editBtn`}
type="default" type="default"
label="Edit" label={_(msg({message: 'Edit', context: 'action'}))}
onPress={() => onPressEditMembership(profile)} onPress={() => onPressEditMembership(profile)}
/> />
) )
}, },
[isOwner, onPressEditMembership], [isOwner, onPressEditMembership, _],
) )
const renderItem = React.useCallback( const renderItem = React.useCallback(
@ -165,7 +168,9 @@ export function ListMembers({
} else if (item === LOAD_MORE_ERROR_ITEM) { } else if (item === LOAD_MORE_ERROR_ITEM) {
return ( return (
<LoadMoreRetryBtn <LoadMoreRetryBtn
label="There was an issue fetching the list. Tap here to try again." label={_(
msg`There was an issue fetching the list. Tap here to try again.`,
)}
onPress={onPressRetryLoadMore} onPress={onPressRetryLoadMore}
/> />
) )
@ -191,6 +196,7 @@ export function ListMembers({
onPressTryAgain, onPressTryAgain,
onPressRetryLoadMore, onPressRetryLoadMore,
isMobile, isMobile,
_,
], ],
) )

View file

@ -10,11 +10,12 @@ import {useAnalytics} from 'lib/analytics/analytics'
import {usePalette} from 'lib/hooks/usePalette' import {usePalette} from 'lib/hooks/usePalette'
import {useProfileListsQuery, RQKEY} from '#/state/queries/profile-lists' import {useProfileListsQuery, RQKEY} from '#/state/queries/profile-lists'
import {logger} from '#/logger' import {logger} from '#/logger'
import {Trans} from '@lingui/macro' import {Trans, msg} from '@lingui/macro'
import {cleanError} from '#/lib/strings/errors' import {cleanError} from '#/lib/strings/errors'
import {useTheme} from '#/lib/ThemeContext' import {useTheme} from '#/lib/ThemeContext'
import {FeedLoadingPlaceholder} from '#/view/com/util/LoadingPlaceholder' import {FeedLoadingPlaceholder} from '#/view/com/util/LoadingPlaceholder'
import {isNative} from '#/platform/detection' import {isNative} from '#/platform/detection'
import {useLingui} from '@lingui/react'
const LOADING = {_reactKey: '__loading__'} const LOADING = {_reactKey: '__loading__'}
const EMPTY = {_reactKey: '__empty__'} const EMPTY = {_reactKey: '__empty__'}
@ -42,6 +43,7 @@ export const ProfileLists = React.forwardRef<SectionRef, ProfileListsProps>(
const pal = usePalette('default') const pal = usePalette('default')
const theme = useTheme() const theme = useTheme()
const {track} = useAnalytics() const {track} = useAnalytics()
const {_} = useLingui()
const [isPTRing, setIsPTRing] = React.useState(false) const [isPTRing, setIsPTRing] = React.useState(false)
const opts = React.useMemo(() => ({enabled}), [enabled]) const opts = React.useMemo(() => ({enabled}), [enabled])
const { const {
@ -149,7 +151,9 @@ export const ProfileLists = React.forwardRef<SectionRef, ProfileListsProps>(
} else if (item === LOAD_MORE_ERROR_ITEM) { } else if (item === LOAD_MORE_ERROR_ITEM) {
return ( return (
<LoadMoreRetryBtn <LoadMoreRetryBtn
label="There was an issue fetching your lists. Tap here to try again." label={_(
msg`There was an issue fetching your lists. Tap here to try again.`,
)}
onPress={onPressRetryLoadMore} onPress={onPressRetryLoadMore}
/> />
) )
@ -164,7 +168,7 @@ export const ProfileLists = React.forwardRef<SectionRef, ProfileListsProps>(
/> />
) )
}, },
[error, refetch, onPressRetryLoadMore, pal], [error, refetch, onPressRetryLoadMore, pal, _],
) )
return ( return (

View file

@ -72,10 +72,10 @@ export function Component({}: {}) {
const onCopy = React.useCallback(() => { const onCopy = React.useCallback(() => {
if (appPassword) { if (appPassword) {
Clipboard.setString(appPassword) Clipboard.setString(appPassword)
Toast.show('Copied to clipboard') Toast.show(_(msg`Copied to clipboard`))
setWasCopied(true) setWasCopied(true)
} }
}, [appPassword]) }, [appPassword, _])
const onDone = React.useCallback(() => { const onDone = React.useCallback(() => {
closeModal() closeModal()
@ -85,7 +85,9 @@ export function Component({}: {}) {
// if name is all whitespace, we don't allow it // if name is all whitespace, we don't allow it
if (!name || !name.trim()) { if (!name || !name.trim()) {
Toast.show( Toast.show(
'Please enter a name for your app password. All spaces is not allowed.', _(
msg`Please enter a name for your app password. All spaces is not allowed.`,
),
'times', 'times',
) )
return return
@ -93,14 +95,14 @@ export function Component({}: {}) {
// if name is too short (under 4 chars), we don't allow it // if name is too short (under 4 chars), we don't allow it
if (name.length < 4) { if (name.length < 4) {
Toast.show( Toast.show(
'App Password names must be at least 4 characters long.', _(msg`App Password names must be at least 4 characters long.`),
'times', 'times',
) )
return return
} }
if (passwords?.find(p => p.name === name)) { if (passwords?.find(p => p.name === name)) {
Toast.show('This name is already in use', 'times') Toast.show(_(msg`This name is already in use`), 'times')
return return
} }
@ -109,11 +111,11 @@ export function Component({}: {}) {
if (newPassword) { if (newPassword) {
setAppPassword(newPassword.password) setAppPassword(newPassword.password)
} else { } else {
Toast.show('Failed to create app password.', 'times') Toast.show(_(msg`Failed to create app password.`), 'times')
// TODO: better error handling (?) // TODO: better error handling (?)
} }
} catch (e) { } catch (e) {
Toast.show('Failed to create app password.', 'times') Toast.show(_(msg`Failed to create app password.`), 'times')
logger.error('Failed to create app password', {error: e}) logger.error('Failed to create app password', {error: e})
} }
} }
@ -127,7 +129,9 @@ export function Component({}: {}) {
setName(text) setName(text)
} else { } else {
Toast.show( Toast.show(
'App Password names can only contain letters, numbers, spaces, dashes, and underscores.', _(
msg`App Password names can only contain letters, numbers, spaces, dashes, and underscores.`,
),
) )
} }
} }
@ -158,7 +162,7 @@ export function Component({}: {}) {
style={[styles.input, pal.text]} style={[styles.input, pal.text]}
onChangeText={_onChangeText} onChangeText={_onChangeText}
value={name} value={name}
placeholder="Enter a name for this App Password" placeholder={_(msg`Enter a name for this App Password`)}
placeholderTextColor={pal.colors.textLight} placeholderTextColor={pal.colors.textLight}
autoCorrect={false} autoCorrect={false}
autoComplete="off" autoComplete="off"
@ -175,7 +179,7 @@ export function Component({}: {}) {
onEndEditing={createAppPassword} onEndEditing={createAppPassword}
accessible={true} accessible={true}
accessibilityLabel={_(msg`Name`)} accessibilityLabel={_(msg`Name`)}
accessibilityHint="Input name for app password" accessibilityHint={_(msg`Input name for app password`)}
/> />
</View> </View>
) : ( ) : (
@ -184,7 +188,7 @@ export function Component({}: {}) {
onPress={onCopy} onPress={onCopy}
accessibilityRole="button" accessibilityRole="button"
accessibilityLabel={_(msg`Copy`)} accessibilityLabel={_(msg`Copy`)}
accessibilityHint="Copies app password"> accessibilityHint={_(msg`Copies app password`)}>
<Text type="2xl-bold" style={[pal.text]}> <Text type="2xl-bold" style={[pal.text]}>
{appPassword} {appPassword}
</Text> </Text>
@ -221,7 +225,7 @@ export function Component({}: {}) {
<View style={styles.btnContainer}> <View style={styles.btnContainer}>
<Button <Button
type="primary" type="primary"
label={!appPassword ? 'Create App Password' : 'Done'} label={!appPassword ? _(msg`Create App Password`) : _(msg`Done`)}
style={styles.btn} style={styles.btn}
labelStyle={styles.btnLabel} labelStyle={styles.btnLabel}
onPress={!appPassword ? createAppPassword : onDone} onPress={!appPassword ? createAppPassword : onDone}

View file

@ -45,7 +45,7 @@ export function Component(props: ReportComponentProps) {
}, },
reason: details, reason: details,
}) })
Toast.show("We'll look into your appeal promptly.") Toast.show(_(msg`We'll look into your appeal promptly.`))
} finally { } finally {
closeModal() closeModal()
} }

View file

@ -71,7 +71,7 @@ function Inner({preferences}: {preferences: UsePreferencesQueryResponse}) {
buttonStyle={[pal.border, styles.dateInputButton]} buttonStyle={[pal.border, styles.dateInputButton]}
buttonLabelType="lg" buttonLabelType="lg"
accessibilityLabel={_(msg`Birthday`)} accessibilityLabel={_(msg`Birthday`)}
accessibilityHint="Enter your birth date" accessibilityHint={_(msg`Enter your birth date`)}
accessibilityLabelledBy="birthDate" accessibilityLabelledBy="birthDate"
/> />
</View> </View>

View file

@ -38,7 +38,7 @@ export function Component() {
const onRequestChange = async () => { const onRequestChange = async () => {
if (email === currentAccount?.email) { if (email === currentAccount?.email) {
setError('Enter your new email above') setError(_(msg`Enter your new email above`))
return return
} }
setError('') setError('')
@ -53,7 +53,7 @@ export function Component() {
email: email.trim(), email: email.trim(),
emailConfirmed: false, emailConfirmed: false,
}) })
Toast.show('Email updated') Toast.show(_(msg`Email updated`))
setStage(Stages.Done) setStage(Stages.Done)
} }
} catch (e) { } catch (e) {
@ -85,7 +85,7 @@ export function Component() {
email: email.trim(), email: email.trim(),
emailConfirmed: false, emailConfirmed: false,
}) })
Toast.show('Email updated') Toast.show(_(msg`Email updated`))
setStage(Stages.Done) setStage(Stages.Done)
} catch (e) { } catch (e) {
setError(cleanError(String(e))) setError(cleanError(String(e)))

View file

@ -147,7 +147,7 @@ export function Inner({
onPress={onPressCancel} onPress={onPressCancel}
accessibilityRole="button" accessibilityRole="button"
accessibilityLabel={_(msg`Cancel change handle`)} accessibilityLabel={_(msg`Cancel change handle`)}
accessibilityHint="Exits handle change process" accessibilityHint={_(msg`Exits handle change process`)}
onAccessibilityEscape={onPressCancel}> onAccessibilityEscape={onPressCancel}>
<Text type="lg" style={pal.textLight}> <Text type="lg" style={pal.textLight}>
Cancel Cancel
@ -168,7 +168,7 @@ export function Inner({
onPress={onPressSave} onPress={onPressSave}
accessibilityRole="button" accessibilityRole="button"
accessibilityLabel={_(msg`Save handle change`)} accessibilityLabel={_(msg`Save handle change`)}
accessibilityHint={`Saves handle change to ${handle}`}> accessibilityHint={_(msg`Saves handle change to ${handle}`)}>
<Text type="2xl-medium" style={pal.link}> <Text type="2xl-medium" style={pal.link}>
<Trans>Save</Trans> <Trans>Save</Trans>
</Text> </Text>
@ -263,14 +263,16 @@ function ProvidedHandleForm({
editable={!isProcessing} editable={!isProcessing}
accessible={true} accessible={true}
accessibilityLabel={_(msg`Handle`)} accessibilityLabel={_(msg`Handle`)}
accessibilityHint="Sets Bluesky username" accessibilityHint={_(msg`Sets Bluesky username`)}
/> />
</View> </View>
<Text type="md" style={[pal.textLight, s.pl10, s.pt10]}> <Text type="md" style={[pal.textLight, s.pl10, s.pt10]}>
<Trans>Your full handle will be</Trans>{' '} <Trans>
Your full handle will be{' '}
<Text type="md-bold" style={pal.textLight}> <Text type="md-bold" style={pal.textLight}>
@{createFullHandle(handle, userDomain)} @{createFullHandle(handle, userDomain)}
</Text> </Text>
</Trans>
</Text> </Text>
<TouchableOpacity <TouchableOpacity
onPress={onToggleCustom} onPress={onToggleCustom}

View file

@ -12,7 +12,7 @@ import {cleanError} from 'lib/strings/errors'
import {usePalette} from 'lib/hooks/usePalette' import {usePalette} from 'lib/hooks/usePalette'
import {isWeb} from 'platform/detection' import {isWeb} from 'platform/detection'
import {useLingui} from '@lingui/react' import {useLingui} from '@lingui/react'
import {msg} from '@lingui/macro' import {Trans, msg} from '@lingui/macro'
import type {ConfirmModal} from '#/state/modals' import type {ConfirmModal} from '#/state/modals'
import {useModalControls} from '#/state/modals' import {useModalControls} from '#/state/modals'
@ -72,10 +72,10 @@ export function Component({
onPress={onPress} onPress={onPress}
style={[styles.btn, confirmBtnStyle]} style={[styles.btn, confirmBtnStyle]}
accessibilityRole="button" accessibilityRole="button"
accessibilityLabel={_(msg`Confirm`)} accessibilityLabel={_(msg({message: 'Confirm', context: 'action'}))}
accessibilityHint=""> accessibilityHint="">
<Text style={[s.white, s.bold, s.f18]}> <Text style={[s.white, s.bold, s.f18]}>
{confirmBtnText ?? 'Confirm'} {confirmBtnText ?? <Trans context="action">Confirm</Trans>}
</Text> </Text>
</TouchableOpacity> </TouchableOpacity>
)} )}
@ -85,10 +85,10 @@ export function Component({
onPress={onPressCancel} onPress={onPressCancel}
style={[styles.btnCancel, s.mt10]} style={[styles.btnCancel, s.mt10]}
accessibilityRole="button" accessibilityRole="button"
accessibilityLabel={_(msg`Cancel`)} accessibilityLabel={_(msg({message: 'Cancel', context: 'action'}))}
accessibilityHint=""> accessibilityHint="">
<Text type="button-lg" style={pal.textLight}> <Text type="button-lg" style={pal.textLight}>
{cancelBtnText ?? 'Cancel'} {cancelBtnText ?? <Trans context="action">Cancel</Trans>}
</Text> </Text>
</TouchableOpacity> </TouchableOpacity>
)} )}

View file

@ -148,9 +148,13 @@ function AdultContentEnabledPref() {
) : typeof preferences?.birthDate === 'undefined' ? ( ) : typeof preferences?.birthDate === 'undefined' ? (
<View style={[pal.viewLight, styles.agePrompt]}> <View style={[pal.viewLight, styles.agePrompt]}>
<Text type="md" style={[pal.text, {flex: 1}]}> <Text type="md" style={[pal.text, {flex: 1}]}>
Confirm your age to enable adult content. <Trans>Confirm your age to enable adult content.</Trans>
</Text> </Text>
<Button type="primary" label="Set Age" onPress={onSetAge} /> <Button
type="primary"
label={_(msg({message: 'Set Age', context: 'action'}))}
onPress={onSetAge}
/>
</View> </View>
) : (preferences.userAge || 0) >= 18 ? ( ) : (preferences.userAge || 0) >= 18 ? (
<ToggleButton <ToggleButton
@ -165,7 +169,11 @@ function AdultContentEnabledPref() {
<Text type="md" style={[pal.text, {flex: 1}]}> <Text type="md" style={[pal.text, {flex: 1}]}>
<Trans>You must be 18 or older to enable adult content.</Trans> <Trans>You must be 18 or older to enable adult content.</Trans>
</Text> </Text>
<Button type="primary" label="Set Age" onPress={onSetAge} /> <Button
type="primary"
label={_(msg({message: 'Set Age', context: 'action'}))}
onPress={onSetAge}
/>
</View> </View>
)} )}
</View> </View>
@ -208,7 +216,7 @@ function ContentLabelPref({
{disabled || !visibility ? ( {disabled || !visibility ? (
<Text type="sm-bold" style={pal.textLight}> <Text type="sm-bold" style={pal.textLight}>
<Trans>Hide</Trans> <Trans context="action">Hide</Trans>
</Text> </Text>
) : ( ) : (
<SelectGroup <SelectGroup
@ -229,6 +237,7 @@ interface SelectGroupProps {
function SelectGroup({current, onChange, labelGroup}: SelectGroupProps) { function SelectGroup({current, onChange, labelGroup}: SelectGroupProps) {
const {_} = useLingui() const {_} = useLingui()
return ( return (
<View style={styles.selectableBtns}> <View style={styles.selectableBtns}>
<SelectableBtn <SelectableBtn
@ -279,6 +288,8 @@ function SelectableBtn({
}: SelectableBtnProps) { }: SelectableBtnProps) {
const pal = usePalette('default') const pal = usePalette('default')
const palPrimary = usePalette('inverted') const palPrimary = usePalette('inverted')
const {_} = useLingui()
return ( return (
<Pressable <Pressable
style={[ style={[
@ -291,7 +302,9 @@ function SelectableBtn({
onPress={() => onChange(value)} onPress={() => onChange(value)}
accessibilityRole="button" accessibilityRole="button"
accessibilityLabel={value} accessibilityLabel={value}
accessibilityHint={`Set ${value} for ${labelGroup} content moderation policy`}> accessibilityHint={_(
msg`Set ${value} for ${labelGroup} content moderation policy`,
)}>
<Text style={current === value ? palPrimary.text : pal.text}> <Text style={current === value ? palPrimary.text : pal.text}>
{label} {label}
</Text> </Text>

View file

@ -65,7 +65,6 @@ export function Component({
return 'app.bsky.graph.defs#curatelist' return 'app.bsky.graph.defs#curatelist'
}, [list, purpose]) }, [list, purpose])
const isCurateList = activePurpose === 'app.bsky.graph.defs#curatelist' const isCurateList = activePurpose === 'app.bsky.graph.defs#curatelist'
const purposeLabel = isCurateList ? 'User' : 'Moderation'
const [isProcessing, setProcessing] = useState<boolean>(false) const [isProcessing, setProcessing] = useState<boolean>(false)
const [name, setName] = useState<string>(list?.name || '') const [name, setName] = useState<string>(list?.name || '')
@ -106,7 +105,7 @@ export function Component({
} }
const nameTrimmed = name.trim() const nameTrimmed = name.trim()
if (!nameTrimmed) { if (!nameTrimmed) {
setError('Name is required') setError(_(msg`Name is required`))
return return
} }
setProcessing(true) setProcessing(true)
@ -121,7 +120,11 @@ export function Component({
description: description.trim(), description: description.trim(),
avatar: newAvatar, avatar: newAvatar,
}) })
Toast.show(`${purposeLabel} list updated`) Toast.show(
isCurateList
? _(msg`User list updated`)
: _(msg`Moderation list updated`),
)
onSave?.(list.uri) onSave?.(list.uri)
} else { } else {
const res = await listCreateMutation.mutateAsync({ const res = await listCreateMutation.mutateAsync({
@ -130,14 +133,20 @@ export function Component({
description, description,
avatar: newAvatar, avatar: newAvatar,
}) })
Toast.show(`${purposeLabel} list created`) Toast.show(
isCurateList
? _(msg`User list created`)
: _(msg`Moderation list created`),
)
onSave?.(res.uri) onSave?.(res.uri)
} }
closeModal() closeModal()
} catch (e: any) { } catch (e: any) {
if (isNetworkError(e)) { if (isNetworkError(e)) {
setError( setError(
'Failed to create the list. Check your internet connection and try again.', _(
msg`Failed to create the list. Check your internet connection and try again.`,
),
) )
} else { } else {
setError(cleanError(e)) setError(cleanError(e))
@ -153,13 +162,13 @@ export function Component({
closeModal, closeModal,
activePurpose, activePurpose,
isCurateList, isCurateList,
purposeLabel,
name, name,
description, description,
newAvatar, newAvatar,
list, list,
listMetadataMutation, listMetadataMutation,
listCreateMutation, listCreateMutation,
_,
]) ])
return ( return (
@ -174,7 +183,17 @@ export function Component({
testID="createOrEditListModal"> testID="createOrEditListModal">
<Text style={[styles.title, pal.text]}> <Text style={[styles.title, pal.text]}>
<Trans> <Trans>
{list ? 'Edit' : 'New'} {purposeLabel} List {isCurateList ? (
list ? (
<Trans>Edit User List</Trans>
) : (
<Trans>New User List</Trans>
)
) : list ? (
<Trans>Edit Moderation List</Trans>
) : (
<Trans>New Moderation List</Trans>
)}
</Trans> </Trans>
</Text> </Text>
{error !== '' && ( {error !== '' && (
@ -202,7 +221,9 @@ export function Component({
testID="editNameInput" testID="editNameInput"
style={[styles.textInput, pal.border, pal.text]} style={[styles.textInput, pal.border, pal.text]}
placeholder={ placeholder={
isCurateList ? 'e.g. Great Posters' : 'e.g. Spammers' isCurateList
? _(msg`e.g. Great Posters`)
: _(msg`e.g. Spammers`)
} }
placeholderTextColor={colors.gray4} placeholderTextColor={colors.gray4}
value={name} value={name}
@ -222,8 +243,8 @@ export function Component({
style={[styles.textArea, pal.border, pal.text]} style={[styles.textArea, pal.border, pal.text]}
placeholder={ placeholder={
isCurateList isCurateList
? 'e.g. The posters who never miss.' ? _(msg`e.g. The posters who never miss.`)
: 'e.g. Users that repeatedly reply with ads.' : _(msg`e.g. Users that repeatedly reply with ads.`)
} }
placeholderTextColor={colors.gray4} placeholderTextColor={colors.gray4}
keyboardAppearance={theme.colorScheme} keyboardAppearance={theme.colorScheme}
@ -254,7 +275,7 @@ export function Component({
end={{x: 1, y: 1}} end={{x: 1, y: 1}}
style={[styles.btn]}> style={[styles.btn]}>
<Text style={[s.white, s.bold]}> <Text style={[s.white, s.bold]}>
<Trans>Save</Trans> <Trans context="action">Save</Trans>
</Text> </Text>
</LinearGradient> </LinearGradient>
</TouchableOpacity> </TouchableOpacity>
@ -269,7 +290,7 @@ export function Component({
onAccessibilityEscape={onPressCancel}> onAccessibilityEscape={onPressCancel}>
<View style={[styles.btn]}> <View style={[styles.btn]}>
<Text style={[s.black, s.bold, pal.text]}> <Text style={[s.black, s.bold, pal.text]}>
<Trans>Cancel</Trans> <Trans context="action">Cancel</Trans>
</Text> </Text>
</View> </View>
</TouchableOpacity> </TouchableOpacity>

View file

@ -62,7 +62,7 @@ export function Component({}: {}) {
password, password,
token, token,
}) })
Toast.show('Your account has been deleted') Toast.show(_(msg`Your account has been deleted`))
resetToTab('HomeTab') resetToTab('HomeTab')
removeAccount(currentAccount) removeAccount(currentAccount)
clearCurrentAccount() clearCurrentAccount()
@ -125,7 +125,9 @@ export function Component({}: {}) {
onPress={onPressSendEmail} onPress={onPressSendEmail}
accessibilityRole="button" accessibilityRole="button"
accessibilityLabel={_(msg`Send email`)} accessibilityLabel={_(msg`Send email`)}
accessibilityHint="Sends email with confirmation code for account deletion"> accessibilityHint={_(
msg`Sends email with confirmation code for account deletion`,
)}>
<LinearGradient <LinearGradient
colors={[ colors={[
gradients.blueLight.start, gradients.blueLight.start,
@ -135,7 +137,7 @@ export function Component({}: {}) {
end={{x: 1, y: 1}} end={{x: 1, y: 1}}
style={[styles.btn]}> style={[styles.btn]}>
<Text type="button-lg" style={[s.white, s.bold]}> <Text type="button-lg" style={[s.white, s.bold]}>
<Trans>Send Email</Trans> <Trans context="action">Send Email</Trans>
</Text> </Text>
</LinearGradient> </LinearGradient>
</TouchableOpacity> </TouchableOpacity>
@ -147,7 +149,7 @@ export function Component({}: {}) {
accessibilityHint="" accessibilityHint=""
onAccessibilityEscape={onCancel}> onAccessibilityEscape={onCancel}>
<Text type="button-lg" style={pal.textLight}> <Text type="button-lg" style={pal.textLight}>
<Trans>Cancel</Trans> <Trans context="action">Cancel</Trans>
</Text> </Text>
</TouchableOpacity> </TouchableOpacity>
</> </>
@ -174,7 +176,9 @@ export function Component({}: {}) {
onChangeText={setConfirmCode} onChangeText={setConfirmCode}
accessibilityLabelledBy="confirmationCode" accessibilityLabelledBy="confirmationCode"
accessibilityLabel={_(msg`Confirmation code`)} accessibilityLabel={_(msg`Confirmation code`)}
accessibilityHint="Input confirmation code for account deletion" accessibilityHint={_(
msg`Input confirmation code for account deletion`,
)}
/> />
<Text type="lg" style={styles.description} nativeID="password"> <Text type="lg" style={styles.description} nativeID="password">
<Trans>Please enter your password as well:</Trans> <Trans>Please enter your password as well:</Trans>
@ -189,7 +193,7 @@ export function Component({}: {}) {
onChangeText={setPassword} onChangeText={setPassword}
accessibilityLabelledBy="password" accessibilityLabelledBy="password"
accessibilityLabel={_(msg`Password`)} accessibilityLabel={_(msg`Password`)}
accessibilityHint="Input password for account deletion" accessibilityHint={_(msg`Input password for account deletion`)}
/> />
{error ? ( {error ? (
<View style={styles.mt20}> <View style={styles.mt20}>
@ -220,7 +224,7 @@ export function Component({}: {}) {
accessibilityHint="Exits account deletion process" accessibilityHint="Exits account deletion process"
onAccessibilityEscape={onCancel}> onAccessibilityEscape={onCancel}>
<Text type="button-lg" style={pal.textLight}> <Text type="button-lg" style={pal.textLight}>
<Trans>Cancel</Trans> <Trans context="action">Cancel</Trans>
</Text> </Text>
</TouchableOpacity> </TouchableOpacity>
</> </>

View file

@ -112,16 +112,16 @@ export const Component = observer(function EditImageImpl({
// }, // },
{ {
name: 'flip' as const, name: 'flip' as const,
label: 'Flip horizontal', label: _(msg`Flip horizontal`),
onPress: onFlipHorizontal, onPress: onFlipHorizontal,
}, },
{ {
name: 'flip' as const, name: 'flip' as const,
label: 'Flip vertically', label: _(msg`Flip vertically`),
onPress: onFlipVertical, onPress: onFlipVertical,
}, },
], ],
[onFlipHorizontal, onFlipVertical], [onFlipHorizontal, onFlipVertical, _],
) )
useEffect(() => { useEffect(() => {
@ -284,7 +284,7 @@ export const Component = observer(function EditImageImpl({
size={label?.startsWith('Flip') ? 22 : 24} size={label?.startsWith('Flip') ? 22 : 24}
style={[ style={[
pal.text, pal.text,
label === 'Flip vertically' label === _(msg`Flip vertically`)
? styles.flipVertical ? styles.flipVertical
: undefined, : undefined,
]} ]}
@ -330,7 +330,7 @@ export const Component = observer(function EditImageImpl({
end={{x: 1, y: 1}} end={{x: 1, y: 1}}
style={[styles.btn]}> style={[styles.btn]}>
<Text type="xl-medium" style={s.white}> <Text type="xl-medium" style={s.white}>
<Trans>Done</Trans> <Trans context="action">Done</Trans>
</Text> </Text>
</LinearGradient> </LinearGradient>
</Pressable> </Pressable>

View file

@ -125,7 +125,7 @@ export function Component({
newUserAvatar, newUserAvatar,
newUserBanner, newUserBanner,
}) })
Toast.show('Profile updated') Toast.show(_(msg`Profile updated`))
onUpdate?.() onUpdate?.()
closeModal() closeModal()
} catch (e: any) { } catch (e: any) {
@ -142,6 +142,7 @@ export function Component({
newUserAvatar, newUserAvatar,
newUserBanner, newUserBanner,
setImageError, setImageError,
_,
]) ])
return ( return (
@ -181,7 +182,7 @@ export function Component({
<TextInput <TextInput
testID="editProfileDisplayNameInput" testID="editProfileDisplayNameInput"
style={[styles.textInput, pal.border, pal.text]} style={[styles.textInput, pal.border, pal.text]}
placeholder="e.g. Alice Roberts" placeholder={_(msg`e.g. Alice Roberts`)}
placeholderTextColor={colors.gray4} placeholderTextColor={colors.gray4}
value={displayName} value={displayName}
onChangeText={v => onChangeText={v =>
@ -189,7 +190,7 @@ export function Component({
} }
accessible={true} accessible={true}
accessibilityLabel={_(msg`Display name`)} accessibilityLabel={_(msg`Display name`)}
accessibilityHint="Edit your display name" accessibilityHint={_(msg`Edit your display name`)}
/> />
</View> </View>
<View style={s.pb10}> <View style={s.pb10}>
@ -199,7 +200,7 @@ export function Component({
<TextInput <TextInput
testID="editProfileDescriptionInput" testID="editProfileDescriptionInput"
style={[styles.textArea, pal.border, pal.text]} style={[styles.textArea, pal.border, pal.text]}
placeholder="e.g. Artist, dog-lover, and avid reader." placeholder={_(msg`e.g. Artist, dog-lover, and avid reader.`)}
placeholderTextColor={colors.gray4} placeholderTextColor={colors.gray4}
keyboardAppearance={theme.colorScheme} keyboardAppearance={theme.colorScheme}
multiline multiline
@ -207,7 +208,7 @@ export function Component({
onChangeText={v => setDescription(enforceLen(v, MAX_DESCRIPTION))} onChangeText={v => setDescription(enforceLen(v, MAX_DESCRIPTION))}
accessible={true} accessible={true}
accessibilityLabel={_(msg`Description`)} accessibilityLabel={_(msg`Description`)}
accessibilityHint="Edit your profile description" accessibilityHint={_(msg`Edit your profile description`)}
/> />
</View> </View>
{updateMutation.isPending ? ( {updateMutation.isPending ? (
@ -221,7 +222,7 @@ export function Component({
onPress={onPressSave} onPress={onPressSave}
accessibilityRole="button" accessibilityRole="button"
accessibilityLabel={_(msg`Save`)} accessibilityLabel={_(msg`Save`)}
accessibilityHint="Saves any changes to your profile"> accessibilityHint={_(msg`Saves any changes to your profile`)}>
<LinearGradient <LinearGradient
colors={[gradients.blueLight.start, gradients.blueLight.end]} colors={[gradients.blueLight.start, gradients.blueLight.end]}
start={{x: 0, y: 0}} start={{x: 0, y: 0}}

View file

@ -19,7 +19,6 @@ import {usePalette} from 'lib/hooks/usePalette'
import {isWeb} from 'platform/detection' import {isWeb} from 'platform/detection'
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
import {Trans, msg} from '@lingui/macro' import {Trans, msg} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {cleanError} from 'lib/strings/errors' import {cleanError} from 'lib/strings/errors'
import {useModalControls} from '#/state/modals' import {useModalControls} from '#/state/modals'
import {useInvitesState, useInvitesAPI} from '#/state/invites' import {useInvitesState, useInvitesAPI} from '#/state/invites'
@ -31,6 +30,7 @@ import {
useInviteCodesQuery, useInviteCodesQuery,
InviteCodesQueryResponse, InviteCodesQueryResponse,
} from '#/state/queries/invites' } from '#/state/queries/invites'
import {useLingui} from '@lingui/react'
export const snapPoints = ['70%'] export const snapPoints = ['70%']
@ -166,10 +166,10 @@ function InviteCode({
accessibilityRole="button" accessibilityRole="button"
accessibilityLabel={ accessibilityLabel={
invites.available.length === 1 invites.available.length === 1
? 'Invite codes: 1 available' ? _(msg`Invite codes: 1 available`)
: `Invite codes: ${invites.available.length} available` : _(msg`Invite codes: ${invites.available.length} available`)
} }
accessibilityHint="Opens list of invite codes"> accessibilityHint={_(msg`Opens list of invite codes`)}>
<Text <Text
testID={`${testID}-code`} testID={`${testID}-code`}
type={used ? 'md' : 'md-bold'} type={used ? 'md' : 'md-bold'}

View file

@ -67,7 +67,7 @@ export function Component({
<TextInput <TextInput
testID="searchInput" testID="searchInput"
style={[styles.searchInput, pal.border, pal.text]} style={[styles.searchInput, pal.border, pal.text]}
placeholder="Search for users" placeholder={_(msg`Search for users`)}
placeholderTextColor={pal.colors.textLight} placeholderTextColor={pal.colors.textLight}
value={query} value={query}
onChangeText={setQuery} onChangeText={setQuery}
@ -85,7 +85,7 @@ export function Component({
onPress={onPressCancelSearch} onPress={onPressCancelSearch}
accessibilityRole="button" accessibilityRole="button"
accessibilityLabel={_(msg`Cancel search`)} accessibilityLabel={_(msg`Cancel search`)}
accessibilityHint="Exits inputting search query" accessibilityHint={_(msg`Exits inputting search query`)}
onAccessibilityEscape={onPressCancelSearch} onAccessibilityEscape={onPressCancelSearch}
hitSlop={HITSLOP_20}> hitSlop={HITSLOP_20}>
<FontAwesomeIcon <FontAwesomeIcon
@ -141,7 +141,7 @@ export function Component({
}} }}
accessibilityLabel={_(msg`Done`)} accessibilityLabel={_(msg`Done`)}
accessibilityHint="" accessibilityHint=""
label="Done" label={_(msg({message: 'Done', context: 'action'}))}
labelContainerStyle={{justifyContent: 'center', padding: 4}} labelContainerStyle={{justifyContent: 'center', padding: 4}}
labelStyle={[s.f18]} labelStyle={[s.f18]}
/> />

View file

@ -10,6 +10,8 @@ import {isWeb} from 'platform/detection'
import {listUriToHref} from 'lib/strings/url-helpers' import {listUriToHref} from 'lib/strings/url-helpers'
import {Button} from '../util/forms/Button' import {Button} from '../util/forms/Button'
import {useModalControls} from '#/state/modals' import {useModalControls} from '#/state/modals'
import {useLingui} from '@lingui/react'
import {Trans, msg} from '@lingui/macro'
export const snapPoints = [300] export const snapPoints = [300]
@ -23,19 +25,21 @@ export function Component({
const {closeModal} = useModalControls() const {closeModal} = useModalControls()
const {isMobile} = useWebMediaQueries() const {isMobile} = useWebMediaQueries()
const pal = usePalette('default') const pal = usePalette('default')
const {_} = useLingui()
let name let name
let description let description
if (!moderation.cause) { if (!moderation.cause) {
name = 'Content Warning' name = _(msg`Content Warning`)
description = description = _(
'Moderator has chosen to set a general warning on the content.' msg`Moderator has chosen to set a general warning on the content.`,
)
} else if (moderation.cause.type === 'blocking') { } else if (moderation.cause.type === 'blocking') {
if (moderation.cause.source.type === 'list') { if (moderation.cause.source.type === 'list') {
const list = moderation.cause.source.list const list = moderation.cause.source.list
name = 'User Blocked by List' name = _(msg`User Blocked by List`)
description = ( description = (
<> <Trans>
This user is included in the{' '} This user is included in the{' '}
<TextLink <TextLink
type="2xl" type="2xl"
@ -44,25 +48,30 @@ export function Component({
style={pal.link} style={pal.link}
/>{' '} />{' '}
list which you have blocked. list which you have blocked.
</> </Trans>
) )
} else { } else {
name = 'User Blocked' name = _(msg`User Blocked`)
description = 'You have blocked this user. You cannot view their content.' description = _(
msg`You have blocked this user. You cannot view their content.`,
)
} }
} else if (moderation.cause.type === 'blocked-by') { } else if (moderation.cause.type === 'blocked-by') {
name = 'User Blocks You' name = _(msg`User Blocks You`)
description = 'This user has blocked you. You cannot view their content.' description = _(
msg`This user has blocked you. You cannot view their content.`,
)
} else if (moderation.cause.type === 'block-other') { } else if (moderation.cause.type === 'block-other') {
name = 'Content Not Available' name = _(msg`Content Not Available`)
description = description = _(
'This content is not available because one of the users involved has blocked the other.' msg`This content is not available because one of the users involved has blocked the other.`,
)
} else if (moderation.cause.type === 'muted') { } else if (moderation.cause.type === 'muted') {
if (moderation.cause.source.type === 'list') { if (moderation.cause.source.type === 'list') {
const list = moderation.cause.source.list const list = moderation.cause.source.list
name = <>Account Muted by List</> name = _(msg`Account Muted by List`)
description = ( description = (
<> <Trans>
This user is included the{' '} This user is included the{' '}
<TextLink <TextLink
type="2xl" type="2xl"
@ -71,11 +80,11 @@ export function Component({
style={pal.link} style={pal.link}
/>{' '} />{' '}
list which you have muted. list which you have muted.
</> </Trans>
) )
} else { } else {
name = 'Account Muted' name = _(msg`Account Muted`)
description = 'You have muted this user.' description = _(msg`You have muted this user.`)
} }
} else { } else {
name = moderation.cause.labelDef.strings[context].en.name name = moderation.cause.labelDef.strings[context].en.name

View file

@ -14,11 +14,14 @@ import {ErrorScreen} from '../util/error/ErrorScreen'
import {CenteredView} from '../util/Views' import {CenteredView} from '../util/Views'
import {cleanError} from '#/lib/strings/errors' import {cleanError} from '#/lib/strings/errors'
import {useProfileShadow} from '#/state/cache/profile-shadow' import {useProfileShadow} from '#/state/cache/profile-shadow'
import {Trans, msg} from '@lingui/macro'
import {useLingui} from '@lingui/react'
export const snapPoints = [520, '100%'] export const snapPoints = [520, '100%']
export function Component({did}: {did: string}) { export function Component({did}: {did: string}) {
const pal = usePalette('default') const pal = usePalette('default')
const {_} = useLingui()
const moderationOpts = useModerationOpts() const moderationOpts = useModerationOpts()
const { const {
data: profile, data: profile,
@ -43,7 +46,7 @@ export function Component({did}: {did: string}) {
if (profileError) { if (profileError) {
return ( return (
<ErrorScreen <ErrorScreen
title="Oops!" title={_(msg`Oops!`)}
message={cleanError(profileError)} message={cleanError(profileError)}
onPressTryAgain={refetchProfile} onPressTryAgain={refetchProfile}
/> />
@ -55,8 +58,8 @@ export function Component({did}: {did: string}) {
// should never happen // should never happen
return ( return (
<ErrorScreen <ErrorScreen
title="Oops!" title={_(msg`Oops!`)}
message="Something went wrong and we're not sure what." message={_(msg`Something went wrong and we're not sure what.`)}
onPressTryAgain={refetchProfile} onPressTryAgain={refetchProfile}
/> />
) )
@ -104,7 +107,7 @@ function ComponentLoaded({
<> <>
<InfoCircleIcon size={21} style={pal.textLight} /> <InfoCircleIcon size={21} style={pal.textLight} />
<ThemedText type="xl" fg="light"> <ThemedText type="xl" fg="light">
Swipe up to see more <Trans>Swipe up to see more</Trans>
</ThemedText> </ThemedText>
</> </>
)} )}

View file

@ -37,11 +37,23 @@ export function Component({
style={[styles.actionBtn]} style={[styles.actionBtn]}
onPress={onRepost} onPress={onRepost}
accessibilityRole="button" accessibilityRole="button"
accessibilityLabel={isReposted ? 'Undo repost' : 'Repost'} accessibilityLabel={
accessibilityHint={isReposted ? 'Remove repost' : 'Repost '}> isReposted
? _(msg`Undo repost`)
: _(msg({message: `Repost`, context: 'action'}))
}
accessibilityHint={
isReposted
? _(msg`Remove repost`)
: _(msg({message: `Repost`, context: 'action'}))
}>
<RepostIcon strokeWidth={2} size={24} style={s.blue3} /> <RepostIcon strokeWidth={2} size={24} style={s.blue3} />
<Text type="title-lg" style={[styles.actionBtnLabel, pal.text]}> <Text type="title-lg" style={[styles.actionBtnLabel, pal.text]}>
<Trans>{!isReposted ? 'Repost' : 'Undo repost'}</Trans> {!isReposted ? (
<Trans context="action">Repost</Trans>
) : (
<Trans>Undo repost</Trans>
)}
</Text> </Text>
</TouchableOpacity> </TouchableOpacity>
<TouchableOpacity <TouchableOpacity
@ -49,11 +61,13 @@ export function Component({
style={[styles.actionBtn]} style={[styles.actionBtn]}
onPress={onQuote} onPress={onQuote}
accessibilityRole="button" accessibilityRole="button"
accessibilityLabel={_(msg`Quote post`)} accessibilityLabel={_(
msg({message: `Quote post`, context: 'action'}),
)}
accessibilityHint=""> accessibilityHint="">
<FontAwesomeIcon icon="quote-left" size={24} style={s.blue3} /> <FontAwesomeIcon icon="quote-left" size={24} style={s.blue3} />
<Text type="title-lg" style={[styles.actionBtnLabel, pal.text]}> <Text type="title-lg" style={[styles.actionBtnLabel, pal.text]}>
<Trans>Quote Post</Trans> <Trans context="action">Quote Post</Trans>
</Text> </Text>
</TouchableOpacity> </TouchableOpacity>
</View> </View>

View file

@ -92,7 +92,7 @@ export function Component({
testID="sexualLabelBtn" testID="sexualLabelBtn"
selected={selected.includes('sexual')} selected={selected.includes('sexual')}
left left
label="Suggestive" label={_(msg`Suggestive`)}
onSelect={() => toggleAdultLabel('sexual')} onSelect={() => toggleAdultLabel('sexual')}
accessibilityHint="" accessibilityHint=""
style={s.flex1} style={s.flex1}
@ -100,7 +100,7 @@ export function Component({
<SelectableBtn <SelectableBtn
testID="nudityLabelBtn" testID="nudityLabelBtn"
selected={selected.includes('nudity')} selected={selected.includes('nudity')}
label="Nudity" label={_(msg`Nudity`)}
onSelect={() => toggleAdultLabel('nudity')} onSelect={() => toggleAdultLabel('nudity')}
accessibilityHint="" accessibilityHint=""
style={s.flex1} style={s.flex1}
@ -108,7 +108,7 @@ export function Component({
<SelectableBtn <SelectableBtn
testID="pornLabelBtn" testID="pornLabelBtn"
selected={selected.includes('porn')} selected={selected.includes('porn')}
label="Porn" label={_(msg`Porn`)}
right right
onSelect={() => toggleAdultLabel('porn')} onSelect={() => toggleAdultLabel('porn')}
accessibilityHint="" accessibilityHint=""
@ -154,7 +154,7 @@ export function Component({
accessibilityLabel={_(msg`Confirm`)} accessibilityLabel={_(msg`Confirm`)}
accessibilityHint=""> accessibilityHint="">
<Text style={[s.white, s.bold, s.f18]}> <Text style={[s.white, s.bold, s.f18]}>
<Trans>Done</Trans> <Trans context="action">Done</Trans>
</Text> </Text>
</TouchableOpacity> </TouchableOpacity>
</View> </View>

View file

@ -101,7 +101,9 @@ export function Component({onSelect}: {onSelect: (url: string) => void}) {
onChangeText={setCustomUrl} onChangeText={setCustomUrl}
accessibilityLabel={_(msg`Custom domain`)} accessibilityLabel={_(msg`Custom domain`)}
// TODO: Simplify this wording further to be understandable by everyone // TODO: Simplify this wording further to be understandable by everyone
accessibilityHint="Use your domain as your Bluesky client service provider" accessibilityHint={_(
msg`Use your domain as your Bluesky client service provider`,
)}
/> />
<TouchableOpacity <TouchableOpacity
testID="customServerSelectBtn" testID="customServerSelectBtn"
@ -110,7 +112,7 @@ export function Component({onSelect}: {onSelect: (url: string) => void}) {
accessibilityRole="button" accessibilityRole="button"
accessibilityLabel={`Confirm service. ${ accessibilityLabel={`Confirm service. ${
customUrl === '' customUrl === ''
? 'Button disabled. Input custom domain to proceed.' ? _(msg`Button disabled. Input custom domain to proceed.`)
: '' : ''
}`} }`}
accessibilityHint="" accessibilityHint=""

View file

@ -62,7 +62,9 @@ function SwitchAccountCard({account}: {account: SessionAccount}) {
onPress={isSwitchingAccounts ? undefined : onPressSignout} onPress={isSwitchingAccounts ? undefined : onPressSignout}
accessibilityRole="button" accessibilityRole="button"
accessibilityLabel={_(msg`Sign out`)} accessibilityLabel={_(msg`Sign out`)}
accessibilityHint={`Signs ${profile?.displayName} out of Bluesky`}> accessibilityHint={_(
msg`Signs ${profile?.displayName} out of Bluesky`,
)}>
<Text type="lg" style={pal.link}> <Text type="lg" style={pal.link}>
<Trans>Sign out</Trans> <Trans>Sign out</Trans>
</Text> </Text>
@ -92,8 +94,8 @@ function SwitchAccountCard({account}: {account: SessionAccount}) {
isSwitchingAccounts ? undefined : () => onPressSwitchAccount(account) isSwitchingAccounts ? undefined : () => onPressSwitchAccount(account)
} }
accessibilityRole="button" accessibilityRole="button"
accessibilityLabel={`Switch to ${account.handle}`} accessibilityLabel={_(msg`Switch to ${account.handle}`)}
accessibilityHint="Switches the account you are logged in to"> accessibilityHint={_(msg`Switches the account you are logged in to`)}>
{contents} {contents}
</TouchableOpacity> </TouchableOpacity>
) )

View file

@ -126,10 +126,10 @@ export function Component({
}} }}
style={styles.btn} style={styles.btn}
accessibilityRole="button" accessibilityRole="button"
accessibilityLabel={_(msg`Done`)} accessibilityLabel={_(msg({message: `Done`, context: 'action'}))}
accessibilityHint=""> accessibilityHint="">
<Text style={[s.white, s.bold, s.f18]}> <Text style={[s.white, s.bold, s.f18]}>
<Trans>Done</Trans> <Trans context="action">Done</Trans>
</Text> </Text>
</TouchableOpacity> </TouchableOpacity>
</View> </View>

View file

@ -76,10 +76,10 @@ export function Component({
type="default" type="default"
onPress={onPressDone} onPress={onPressDone}
style={styles.footerBtn} style={styles.footerBtn}
accessibilityLabel={_(msg`Done`)} accessibilityLabel={_(msg({message: `Done`, context: 'action'}))}
accessibilityHint="" accessibilityHint=""
onAccessibilityEscape={onPressDone} onAccessibilityEscape={onPressDone}
label={_(msg`Done`)} label={_(msg({message: `Done`, context: 'action'}))}
/> />
</View> </View>
</View> </View>
@ -175,12 +175,22 @@ function ListItem({
{sanitizeDisplayName(list.name)} {sanitizeDisplayName(list.name)}
</Text> </Text>
<Text type="md" style={[pal.textLight]} numberOfLines={1}> <Text type="md" style={[pal.textLight]} numberOfLines={1}>
{list.purpose === 'app.bsky.graph.defs#curatelist' && 'User list '} {list.purpose === 'app.bsky.graph.defs#curatelist' &&
{list.purpose === 'app.bsky.graph.defs#modlist' && 'Moderation list '} (list.creator.did === currentAccount?.did ? (
by{' '} <Trans>User list by you</Trans>
{list.creator.did === currentAccount?.did ) : (
? 'you' <Trans>
: sanitizeHandle(list.creator.handle, '@')} User list by {sanitizeHandle(list.creator.handle, '@')}
</Trans>
))}
{list.purpose === 'app.bsky.graph.defs#modlist' &&
(list.creator.did === currentAccount?.did ? (
<Trans>Moderation list by you</Trans>
) : (
<Trans>
Moderation list by {sanitizeHandle(list.creator.handle, '@')}
</Trans>
))}
</Text> </Text>
</View> </View>
<View> <View>

View file

@ -75,7 +75,7 @@ export function Component({showReminder}: {showReminder?: boolean}) {
token: confirmationCode.trim(), token: confirmationCode.trim(),
}) })
updateCurrentAccount({emailConfirmed: true}) updateCurrentAccount({emailConfirmed: true})
Toast.show('Email verified') Toast.show(_(msg`Email verified`))
closeModal() closeModal()
} catch (e) { } catch (e) {
setError(cleanError(String(e))) setError(cleanError(String(e)))
@ -97,9 +97,15 @@ export function Component({showReminder}: {showReminder?: boolean}) {
{stage === Stages.Reminder && <ReminderIllustration />} {stage === Stages.Reminder && <ReminderIllustration />}
<View style={styles.titleSection}> <View style={styles.titleSection}>
<Text type="title-lg" style={[pal.text, styles.title]}> <Text type="title-lg" style={[pal.text, styles.title]}>
{stage === Stages.Reminder ? 'Please Verify Your Email' : ''} {stage === Stages.Reminder ? (
{stage === Stages.ConfirmCode ? 'Enter Confirmation Code' : ''} <Trans>Please Verify Your Email</Trans>
{stage === Stages.Email ? 'Verify Your Email' : ''} ) : stage === Stages.Email ? (
<Trans>Verify Your Email</Trans>
) : stage === Stages.ConfirmCode ? (
<Trans>Enter Confirmation Code</Trans>
) : (
''
)}
</Text> </Text>
</View> </View>
@ -133,7 +139,7 @@ export function Component({showReminder}: {showReminder?: boolean}) {
size={16} size={16}
/> />
<Text type="xl-medium" style={[pal.text, s.flex1, {minWidth: 0}]}> <Text type="xl-medium" style={[pal.text, s.flex1, {minWidth: 0}]}>
{currentAccount?.email || '(no email)'} {currentAccount?.email || _(msg`(no email)`)}
</Text> </Text>
</View> </View>
<Pressable <Pressable
@ -182,7 +188,7 @@ export function Component({showReminder}: {showReminder?: boolean}) {
onPress={() => setStage(Stages.Email)} onPress={() => setStage(Stages.Email)}
accessibilityLabel={_(msg`Get Started`)} accessibilityLabel={_(msg`Get Started`)}
accessibilityHint="" accessibilityHint=""
label="Get Started" label={_(msg`Get Started`)}
labelContainerStyle={{justifyContent: 'center', padding: 4}} labelContainerStyle={{justifyContent: 'center', padding: 4}}
labelStyle={[s.f18]} labelStyle={[s.f18]}
/> />
@ -195,7 +201,7 @@ export function Component({showReminder}: {showReminder?: boolean}) {
onPress={onSendEmail} onPress={onSendEmail}
accessibilityLabel={_(msg`Send Confirmation Email`)} accessibilityLabel={_(msg`Send Confirmation Email`)}
accessibilityHint="" accessibilityHint=""
label="Send Confirmation Email" label={_(msg`Send Confirmation Email`)}
labelContainerStyle={{ labelContainerStyle={{
justifyContent: 'center', justifyContent: 'center',
padding: 4, padding: 4,
@ -207,7 +213,7 @@ export function Component({showReminder}: {showReminder?: boolean}) {
type="default" type="default"
accessibilityLabel={_(msg`I have a code`)} accessibilityLabel={_(msg`I have a code`)}
accessibilityHint="" accessibilityHint=""
label="I have a confirmation code" label={_(msg`I have a confirmation code`)}
labelContainerStyle={{ labelContainerStyle={{
justifyContent: 'center', justifyContent: 'center',
padding: 4, padding: 4,
@ -224,7 +230,7 @@ export function Component({showReminder}: {showReminder?: boolean}) {
onPress={onConfirm} onPress={onConfirm}
accessibilityLabel={_(msg`Confirm`)} accessibilityLabel={_(msg`Confirm`)}
accessibilityHint="" accessibilityHint=""
label="Confirm" label={_(msg`Confirm`)}
labelContainerStyle={{justifyContent: 'center', padding: 4}} labelContainerStyle={{justifyContent: 'center', padding: 4}}
labelStyle={[s.f18]} labelStyle={[s.f18]}
/> />
@ -236,10 +242,16 @@ export function Component({showReminder}: {showReminder?: boolean}) {
closeModal() closeModal()
}} }}
accessibilityLabel={ accessibilityLabel={
stage === Stages.Reminder ? 'Not right now' : 'Cancel' stage === Stages.Reminder
? _(msg`Not right now`)
: _(msg`Cancel`)
} }
accessibilityHint="" accessibilityHint=""
label={stage === Stages.Reminder ? 'Not right now' : 'Cancel'} label={
stage === Stages.Reminder
? _(msg`Not right now`)
: _(msg`Cancel`)
}
labelContainerStyle={{justifyContent: 'center', padding: 4}} labelContainerStyle={{justifyContent: 'center', padding: 4}}
labelStyle={[s.f18]} labelStyle={[s.f18]}
/> />

View file

@ -48,7 +48,7 @@ export function Component({}: {}) {
} else { } else {
setError( setError(
resBody.error || resBody.error ||
'Something went wrong. Check your email and try again.', _(msg`Something went wrong. Check your email and try again.`),
) )
} }
} catch (e: any) { } catch (e: any) {
@ -75,7 +75,7 @@ export function Component({}: {}) {
</Text> </Text>
<TextInput <TextInput
style={[styles.textInput, pal.borderDark, pal.text, s.mb10, s.mt10]} style={[styles.textInput, pal.borderDark, pal.text, s.mb10, s.mt10]}
placeholder="Enter your email" placeholder={_(msg`Enter your email`)}
placeholderTextColor={pal.textLight.color} placeholderTextColor={pal.textLight.color}
autoCapitalize="none" autoCapitalize="none"
autoCorrect={false} autoCorrect={false}
@ -86,7 +86,9 @@ export function Component({}: {}) {
enterKeyHint="done" enterKeyHint="done"
accessible={true} accessible={true}
accessibilityLabel={_(msg`Email`)} accessibilityLabel={_(msg`Email`)}
accessibilityHint="Input your email to get on the Bluesky waitlist" accessibilityHint={_(
msg`Input your email to get on the Bluesky waitlist`,
)}
/> />
{error ? ( {error ? (
<View style={s.mt10}> <View style={s.mt10}>
@ -114,7 +116,9 @@ export function Component({}: {}) {
<TouchableOpacity <TouchableOpacity
onPress={onPressSignup} onPress={onPressSignup}
accessibilityRole="button" accessibilityRole="button"
accessibilityHint={`Confirms signing up ${email} to the waitlist`}> accessibilityHint={_(
msg`Confirms signing up ${email} to the waitlist`,
)}>
<LinearGradient <LinearGradient
colors={[gradients.blueLight.start, gradients.blueLight.end]} colors={[gradients.blueLight.start, gradients.blueLight.end]}
start={{x: 0, y: 0}} start={{x: 0, y: 0}}
@ -130,7 +134,9 @@ export function Component({}: {}) {
onPress={onCancel} onPress={onCancel}
accessibilityRole="button" accessibilityRole="button"
accessibilityLabel={_(msg`Cancel waitlist signup`)} accessibilityLabel={_(msg`Cancel waitlist signup`)}
accessibilityHint={`Exits signing up for waitlist with ${email}`} accessibilityHint={_(
msg`Exits signing up for waitlist with ${email}`,
)}
onAccessibilityEscape={onCancel}> onAccessibilityEscape={onCancel}>
<Text type="button-lg" style={pal.textLight}> <Text type="button-lg" style={pal.textLight}>
<Trans>Cancel</Trans> <Trans>Cancel</Trans>

View file

@ -13,6 +13,8 @@ import {logger} from '#/logger'
import {cleanError} from '#/lib/strings/errors' import {cleanError} from '#/lib/strings/errors'
import {useModerationOpts} from '#/state/queries/preferences' import {useModerationOpts} from '#/state/queries/preferences'
import {List, ListRef} from '../util/List' import {List, ListRef} from '../util/List'
import {useLingui} from '@lingui/react'
import {msg} from '@lingui/macro'
const EMPTY_FEED_ITEM = {_reactKey: '__empty__'} const EMPTY_FEED_ITEM = {_reactKey: '__empty__'}
const LOAD_MORE_ERROR_ITEM = {_reactKey: '__load_more_error__'} const LOAD_MORE_ERROR_ITEM = {_reactKey: '__load_more_error__'}
@ -31,6 +33,7 @@ export function Feed({
}) { }) {
const [isPTRing, setIsPTRing] = React.useState(false) const [isPTRing, setIsPTRing] = React.useState(false)
const {_} = useLingui()
const moderationOpts = useModerationOpts() const moderationOpts = useModerationOpts()
const {checkUnread} = useUnreadNotificationsApi() const {checkUnread} = useUnreadNotificationsApi()
const { const {
@ -101,14 +104,16 @@ export function Feed({
return ( return (
<EmptyState <EmptyState
icon="bell" icon="bell"
message="No notifications yet!" message={_(msg`No notifications yet!`)}
style={styles.emptyState} style={styles.emptyState}
/> />
) )
} else if (item === LOAD_MORE_ERROR_ITEM) { } else if (item === LOAD_MORE_ERROR_ITEM) {
return ( return (
<LoadMoreRetryBtn <LoadMoreRetryBtn
label="There was an issue fetching notifications. Tap here to try again." label={_(
msg`There was an issue fetching notifications. Tap here to try again.`,
)}
onPress={onPressRetryLoadMore} onPress={onPressRetryLoadMore}
/> />
) )
@ -117,7 +122,7 @@ export function Feed({
} }
return <FeedItem item={item} moderationOpts={moderationOpts!} /> return <FeedItem item={item} moderationOpts={moderationOpts!} />
}, },
[onPressRetryLoadMore, moderationOpts], [onPressRetryLoadMore, moderationOpts, _],
) )
const FeedFooter = React.useCallback( const FeedFooter = React.useCallback(

View file

@ -65,6 +65,7 @@ let FeedItem = ({
moderationOpts: ModerationOpts moderationOpts: ModerationOpts
}): React.ReactNode => { }): React.ReactNode => {
const pal = usePalette('default') const pal = usePalette('default')
const {_} = useLingui()
const [isAuthorsExpanded, setAuthorsExpanded] = useState<boolean>(false) const [isAuthorsExpanded, setAuthorsExpanded] = useState<boolean>(false)
const itemHref = useMemo(() => { const itemHref = useMemo(() => {
if (item.type === 'post-like' || item.type === 'repost') { if (item.type === 'post-like' || item.type === 'repost') {
@ -151,24 +152,26 @@ let FeedItem = ({
let icon: Props['icon'] | 'HeartIconSolid' let icon: Props['icon'] | 'HeartIconSolid'
let iconStyle: Props['style'] = [] let iconStyle: Props['style'] = []
if (item.type === 'post-like') { if (item.type === 'post-like') {
action = 'liked your post' action = _(msg`liked your post`)
icon = 'HeartIconSolid' icon = 'HeartIconSolid'
iconStyle = [ iconStyle = [
s.likeColor as FontAwesomeIconStyle, s.likeColor as FontAwesomeIconStyle,
{position: 'relative', top: -4}, {position: 'relative', top: -4},
] ]
} else if (item.type === 'repost') { } else if (item.type === 'repost') {
action = 'reposted your post' action = _(msg`reposted your post`)
icon = 'retweet' icon = 'retweet'
iconStyle = [s.green3 as FontAwesomeIconStyle] iconStyle = [s.green3 as FontAwesomeIconStyle]
} else if (item.type === 'follow') { } else if (item.type === 'follow') {
action = 'followed you' action = _(msg`followed you`)
icon = 'user-plus' icon = 'user-plus'
iconStyle = [s.blue3 as FontAwesomeIconStyle] iconStyle = [s.blue3 as FontAwesomeIconStyle]
} else if (item.type === 'feedgen-like') { } else if (item.type === 'feedgen-like') {
action = `liked your custom feed${ action = _(
msg`liked your custom feed${
item.subjectUri ? ` '${new AtUri(item.subjectUri).rkey}'` : '' item.subjectUri ? ` '${new AtUri(item.subjectUri).rkey}'` : ''
}` }`,
)
icon = 'HeartIconSolid' icon = 'HeartIconSolid'
iconStyle = [ iconStyle = [
s.likeColor as FontAwesomeIconStyle, s.likeColor as FontAwesomeIconStyle,
@ -314,14 +317,16 @@ function CondensedAuthorsList({
onPress={onToggleAuthorsExpanded} onPress={onToggleAuthorsExpanded}
accessibilityRole="button" accessibilityRole="button"
accessibilityLabel={_(msg`Hide user list`)} accessibilityLabel={_(msg`Hide user list`)}
accessibilityHint="Collapses list of users for a given notification"> accessibilityHint={_(
msg`Collapses list of users for a given notification`,
)}>
<FontAwesomeIcon <FontAwesomeIcon
icon="angle-up" icon="angle-up"
size={18} size={18}
style={[styles.expandedAuthorsCloseBtnIcon, pal.text]} style={[styles.expandedAuthorsCloseBtnIcon, pal.text]}
/> />
<Text type="sm-medium" style={pal.text}> <Text type="sm-medium" style={pal.text}>
<Trans>Hide</Trans> <Trans context="action">Hide</Trans>
</Text> </Text>
</TouchableOpacity> </TouchableOpacity>
</View> </View>
@ -343,7 +348,9 @@ function CondensedAuthorsList({
return ( return (
<TouchableOpacity <TouchableOpacity
accessibilityLabel={_(msg`Show users`)} accessibilityLabel={_(msg`Show users`)}
accessibilityHint="Opens an expanded list of users in this notification" accessibilityHint={_(
msg`Opens an expanded list of users in this notification`,
)}
onPress={onToggleAuthorsExpanded}> onPress={onToggleAuthorsExpanded}>
<View style={styles.avis}> <View style={styles.avis}>
{authors.slice(0, MAX_AUTHORS).map(author => ( {authors.slice(0, MAX_AUTHORS).map(author => (

View file

@ -74,7 +74,9 @@ export function FeedsTabBar(
onPress={onPressAvi} onPress={onPressAvi}
accessibilityRole="button" accessibilityRole="button"
accessibilityLabel={_(msg`Open navigation`)} accessibilityLabel={_(msg`Open navigation`)}
accessibilityHint="Access profile and other navigation links" accessibilityHint={_(
msg`Access profile and other navigation links`,
)}
hitSlop={HITSLOP_10}> hitSlop={HITSLOP_10}>
<FontAwesomeIcon <FontAwesomeIcon
icon="bars" icon="bars"

View file

@ -222,7 +222,11 @@ function PostThreadLoaded({
const renderItem = React.useCallback( const renderItem = React.useCallback(
({item, index}: {item: YieldedItem; index: number}) => { ({item, index}: {item: YieldedItem; index: number}) => {
if (item === TOP_COMPONENT) { if (item === TOP_COMPONENT) {
return isTablet ? <ViewHeader title={_(msg`Post`)} /> : null return isTablet ? (
<ViewHeader
title={_(msg({message: `Post`, context: 'description'}))}
/>
) : null
} else if (item === PARENT_SPINNER) { } else if (item === PARENT_SPINNER) {
return ( return (
<View style={styles.parentSpinner}> <View style={styles.parentSpinner}>
@ -393,7 +397,7 @@ function PostThreadBlocked() {
style={[pal.link as FontAwesomeIconStyle, s.mr5]} style={[pal.link as FontAwesomeIconStyle, s.mr5]}
size={14} size={14}
/> />
Back <Trans context="action">Back</Trans>
</Text> </Text>
</TouchableOpacity> </TouchableOpacity>
</View> </View>

View file

@ -158,6 +158,7 @@ let PostThreadItemLoaded = ({
onPostReply: () => void onPostReply: () => void
}): React.ReactNode => { }): React.ReactNode => {
const pal = usePalette('default') const pal = usePalette('default')
const {_} = useLingui()
const langPrefs = useLanguagePrefs() const langPrefs = useLanguagePrefs()
const {openComposer} = useComposerControls() const {openComposer} = useComposerControls()
const {currentAccount} = useSession() const {currentAccount} = useSession()
@ -172,7 +173,7 @@ let PostThreadItemLoaded = ({
const urip = new AtUri(post.uri) const urip = new AtUri(post.uri)
return makeProfileLink(post.author, 'post', urip.rkey) return makeProfileLink(post.author, 'post', urip.rkey)
}, [post.uri, post.author]) }, [post.uri, post.author])
const itemTitle = `Post by ${post.author.handle}` const itemTitle = _(msg`Post by ${post.author.handle}`)
const authorHref = makeProfileLink(post.author) const authorHref = makeProfileLink(post.author)
const authorTitle = post.author.handle const authorTitle = post.author.handle
const isAuthorMuted = post.author.viewer?.muted const isAuthorMuted = post.author.viewer?.muted
@ -180,12 +181,12 @@ let PostThreadItemLoaded = ({
const urip = new AtUri(post.uri) const urip = new AtUri(post.uri)
return makeProfileLink(post.author, 'post', urip.rkey, 'liked-by') return makeProfileLink(post.author, 'post', urip.rkey, 'liked-by')
}, [post.uri, post.author]) }, [post.uri, post.author])
const likesTitle = 'Likes on this post' const likesTitle = _(msg`Likes on this post`)
const repostsHref = React.useMemo(() => { const repostsHref = React.useMemo(() => {
const urip = new AtUri(post.uri) const urip = new AtUri(post.uri)
return makeProfileLink(post.author, 'post', urip.rkey, 'reposted-by') return makeProfileLink(post.author, 'post', urip.rkey, 'reposted-by')
}, [post.uri, post.author]) }, [post.uri, post.author])
const repostsTitle = 'Reposts of this post' const repostsTitle = _(msg`Reposts of this post`)
const isModeratedPost = const isModeratedPost =
moderation.decisions.post.cause?.type === 'label' && moderation.decisions.post.cause?.type === 'label' &&
moderation.decisions.post.cause.label.src !== currentAccount?.did moderation.decisions.post.cause.label.src !== currentAccount?.did
@ -225,7 +226,7 @@ let PostThreadItemLoaded = ({
}, [setLimitLines]) }, [setLimitLines])
if (!record) { if (!record) {
return <ErrorMessage message="Invalid or unsupported post record" /> return <ErrorMessage message={_(msg`Invalid or unsupported post record`)} />
} }
if (isHighlightedPost) { if (isHighlightedPost) {
@ -563,7 +564,7 @@ let PostThreadItemLoaded = ({
) : undefined} ) : undefined}
{limitLines ? ( {limitLines ? (
<TextLink <TextLink
text="Show More" text={_(msg`Show More`)}
style={pal.link} style={pal.link}
onPress={onPressShowMore} onPress={onPressShowMore}
href="#" href="#"

View file

@ -27,6 +27,8 @@ import {countLines} from 'lib/strings/helpers'
import {useModerationOpts} from '#/state/queries/preferences' import {useModerationOpts} from '#/state/queries/preferences'
import {useComposerControls} from '#/state/shell/composer' import {useComposerControls} from '#/state/shell/composer'
import {Shadow, usePostShadow, POST_TOMBSTONE} from '#/state/cache/post-shadow' import {Shadow, usePostShadow, POST_TOMBSTONE} from '#/state/cache/post-shadow'
import {Trans, msg} from '@lingui/macro'
import {useLingui} from '@lingui/react'
export function Post({ export function Post({
post, post,
@ -95,6 +97,7 @@ function PostInner({
style?: StyleProp<ViewStyle> style?: StyleProp<ViewStyle>
}) { }) {
const pal = usePalette('default') const pal = usePalette('default')
const {_} = useLingui()
const {openComposer} = useComposerControls() const {openComposer} = useComposerControls()
const [limitLines, setLimitLines] = useState( const [limitLines, setLimitLines] = useState(
() => countLines(richText?.text) >= MAX_POST_LINES, () => countLines(richText?.text) >= MAX_POST_LINES,
@ -159,6 +162,7 @@ function PostInner({
style={[pal.textLight, s.mr2]} style={[pal.textLight, s.mr2]}
lineHeight={1.2} lineHeight={1.2}
numberOfLines={1}> numberOfLines={1}>
<Trans context="description">
Reply to{' '} Reply to{' '}
<UserInfoText <UserInfoText
type="sm" type="sm"
@ -166,6 +170,7 @@ function PostInner({
attr="displayName" attr="displayName"
style={[pal.textLight]} style={[pal.textLight]}
/> />
</Trans>
</Text> </Text>
</View> </View>
)} )}
@ -188,7 +193,7 @@ function PostInner({
) : undefined} ) : undefined}
{limitLines ? ( {limitLines ? (
<TextLink <TextLink
text="Show More" text={_(msg`Show More`)}
style={pal.link} style={pal.link}
onPress={onPressShowMore} onPress={onPressShowMore}
href="#" href="#"

View file

@ -12,6 +12,7 @@ import {NavigationProp} from 'lib/routes/types'
import {usePalette} from 'lib/hooks/usePalette' import {usePalette} from 'lib/hooks/usePalette'
import {s} from 'lib/styles' import {s} from 'lib/styles'
import {isWeb} from 'platform/detection' import {isWeb} from 'platform/detection'
import {Trans} from '@lingui/macro'
export function CustomFeedEmptyState() { export function CustomFeedEmptyState() {
const pal = usePalette('default') const pal = usePalette('default')
@ -33,15 +34,17 @@ export function CustomFeedEmptyState() {
<MagnifyingGlassIcon style={[styles.emptyIcon, pal.text]} size={62} /> <MagnifyingGlassIcon style={[styles.emptyIcon, pal.text]} size={62} />
</View> </View>
<Text type="xl-medium" style={[s.textCenter, pal.text]}> <Text type="xl-medium" style={[s.textCenter, pal.text]}>
<Trans>
This feed is empty! You may need to follow more users or tune your This feed is empty! You may need to follow more users or tune your
language settings. language settings.
</Trans>
</Text> </Text>
<Button <Button
type="inverted" type="inverted"
style={styles.emptyBtn} style={styles.emptyBtn}
onPress={onPressFindAccounts}> onPress={onPressFindAccounts}>
<Text type="lg-medium" style={palInverted.text}> <Text type="lg-medium" style={palInverted.text}>
Find accounts to follow <Trans>Find accounts to follow</Trans>
</Text> </Text>
<FontAwesomeIcon <FontAwesomeIcon
icon="angle-right" icon="angle-right"

View file

@ -28,6 +28,8 @@ import {isWeb} from '#/platform/detection'
import {listenPostCreated} from '#/state/events' import {listenPostCreated} from '#/state/events'
import {useSession} from '#/state/session' import {useSession} from '#/state/session'
import {STALE} from '#/state/queries' import {STALE} from '#/state/queries'
import {msg} from '@lingui/macro'
import {useLingui} from '@lingui/react'
const LOADING_ITEM = {_reactKey: '__loading__'} const LOADING_ITEM = {_reactKey: '__loading__'}
const EMPTY_FEED_ITEM = {_reactKey: '__empty__'} const EMPTY_FEED_ITEM = {_reactKey: '__empty__'}
@ -74,6 +76,7 @@ let Feed = ({
}): React.ReactNode => { }): React.ReactNode => {
const theme = useTheme() const theme = useTheme()
const {track} = useAnalytics() const {track} = useAnalytics()
const {_} = useLingui()
const queryClient = useQueryClient() const queryClient = useQueryClient()
const {currentAccount} = useSession() const {currentAccount} = useSession()
const [isPTRing, setIsPTRing] = React.useState(false) const [isPTRing, setIsPTRing] = React.useState(false)
@ -250,7 +253,9 @@ let Feed = ({
} else if (item === LOAD_MORE_ERROR_ITEM) { } else if (item === LOAD_MORE_ERROR_ITEM) {
return ( return (
<LoadMoreRetryBtn <LoadMoreRetryBtn
label="There was an issue fetching posts. Tap here to try again." label={_(
msg`There was an issue fetching posts. Tap here to try again.`,
)}
onPress={onPressRetryLoadMore} onPress={onPressRetryLoadMore}
/> />
) )
@ -259,7 +264,7 @@ let Feed = ({
} }
return <FeedSlice slice={item} /> return <FeedSlice slice={item} />
}, },
[feed, error, onPressTryAgain, onPressRetryLoadMore, renderEmptyState], [feed, error, onPressTryAgain, onPressRetryLoadMore, renderEmptyState, _],
) )
const shouldRenderEndOfFeed = const shouldRenderEndOfFeed =

View file

@ -38,6 +38,7 @@ export function FeedErrorMessage({
error?: Error error?: Error
onPressTryAgain: () => void onPressTryAgain: () => void
}) { }) {
const {_: _l} = useLingui()
const knownError = React.useMemo( const knownError = React.useMemo(
() => detectKnownError(feedDesc, error), () => detectKnownError(feedDesc, error),
[feedDesc, error], [feedDesc, error],
@ -60,7 +61,7 @@ export function FeedErrorMessage({
return ( return (
<EmptyState <EmptyState
icon="ban" icon="ban"
message="Posts hidden" message={_l(msgLingui`Posts hidden`)}
style={{paddingVertical: 40}} style={{paddingVertical: 40}}
/> />
) )
@ -134,7 +135,9 @@ function FeedgenErrorMessage({
await removeFeed({uri}) await removeFeed({uri})
} catch (err) { } catch (err) {
Toast.show( Toast.show(
'There was an an issue removing this feed. Please check your internet connection and try again.', _l(
msgLingui`There was an an issue removing this feed. Please check your internet connection and try again.`,
),
) )
logger.error('Failed to remove feed', {error: err}) logger.error('Failed to remove feed', {error: err})
} }
@ -160,20 +163,20 @@ function FeedgenErrorMessage({
{knownError === KnownError.FeedgenDoesNotExist && ( {knownError === KnownError.FeedgenDoesNotExist && (
<Button <Button
type="inverted" type="inverted"
label="Remove feed" label={_l(msgLingui`Remove feed`)}
onPress={onRemoveFeed} onPress={onRemoveFeed}
/> />
)} )}
<Button <Button
type="default-light" type="default-light"
label="View profile" label={_l(msgLingui`View profile`)}
onPress={onViewProfile} onPress={onViewProfile}
/> />
</View> </View>
) )
} }
} }
}, [knownError, onViewProfile, onRemoveFeed]) }, [knownError, onViewProfile, onRemoveFeed, _l])
return ( return (
<View <View
@ -191,7 +194,7 @@ function FeedgenErrorMessage({
{rawError?.message && ( {rawError?.message && (
<Text style={pal.textLight}> <Text style={pal.textLight}>
<Trans>Message from server</Trans>: {rawError.message} <Trans>Message from server: {rawError.message}</Trans>
</Text> </Text>
)} )}

View file

@ -35,6 +35,8 @@ import {useComposerControls} from '#/state/shell/composer'
import {Shadow, usePostShadow, POST_TOMBSTONE} from '#/state/cache/post-shadow' import {Shadow, usePostShadow, POST_TOMBSTONE} from '#/state/cache/post-shadow'
import {FeedNameText} from '../util/FeedInfoText' import {FeedNameText} from '../util/FeedInfoText'
import {useSession} from '#/state/session' import {useSession} from '#/state/session'
import {Trans, msg} from '@lingui/macro'
import {useLingui} from '@lingui/react'
export function FeedItem({ export function FeedItem({
post, post,
@ -103,6 +105,7 @@ let FeedItemInner = ({
}): React.ReactNode => { }): React.ReactNode => {
const {openComposer} = useComposerControls() const {openComposer} = useComposerControls()
const pal = usePalette('default') const pal = usePalette('default')
const {_} = useLingui()
const {currentAccount} = useSession() const {currentAccount} = useSession()
const href = useMemo(() => { const href = useMemo(() => {
const urip = new AtUri(post.uri) const urip = new AtUri(post.uri)
@ -182,6 +185,7 @@ let FeedItemInner = ({
style={pal.textLight} style={pal.textLight}
lineHeight={1.2} lineHeight={1.2}
numberOfLines={1}> numberOfLines={1}>
<Trans context="from-feed">
From{' '} From{' '}
<FeedNameText <FeedNameText
type="sm-bold" type="sm-bold"
@ -191,15 +195,18 @@ let FeedItemInner = ({
numberOfLines={1} numberOfLines={1}
style={pal.textLight} style={pal.textLight}
/> />
</Trans>
</Text> </Text>
</Link> </Link>
) : AppBskyFeedDefs.isReasonRepost(reason) ? ( ) : AppBskyFeedDefs.isReasonRepost(reason) ? (
<Link <Link
style={styles.includeReason} style={styles.includeReason}
href={makeProfileLink(reason.by)} href={makeProfileLink(reason.by)}
title={`Reposted by ${sanitizeDisplayName( title={_(
msg`Reposted by ${sanitizeDisplayName(
reason.by.displayName || reason.by.handle, reason.by.displayName || reason.by.handle,
)}`}> )})`,
)}>
<FontAwesomeIcon <FontAwesomeIcon
icon="retweet" icon="retweet"
style={{ style={{
@ -213,6 +220,7 @@ let FeedItemInner = ({
style={pal.textLight} style={pal.textLight}
lineHeight={1.2} lineHeight={1.2}
numberOfLines={1}> numberOfLines={1}>
<Trans>
Reposted by{' '} Reposted by{' '}
<TextLinkOnWebOnly <TextLinkOnWebOnly
type="sm-bold" type="sm-bold"
@ -224,6 +232,7 @@ let FeedItemInner = ({
)} )}
href={makeProfileLink(reason.by)} href={makeProfileLink(reason.by)}
/> />
</Trans>
</Text> </Text>
</Link> </Link>
) : null} ) : null}
@ -274,6 +283,7 @@ let FeedItemInner = ({
style={[pal.textLight, s.mr2]} style={[pal.textLight, s.mr2]}
lineHeight={1.2} lineHeight={1.2}
numberOfLines={1}> numberOfLines={1}>
<Trans context="description">
Reply to{' '} Reply to{' '}
<UserInfoText <UserInfoText
type="md" type="md"
@ -281,6 +291,7 @@ let FeedItemInner = ({
attr="displayName" attr="displayName"
style={[pal.textLight, s.ml2]} style={[pal.textLight, s.ml2]}
/> />
</Trans>
</Text> </Text>
</View> </View>
)} )}
@ -317,6 +328,7 @@ let PostContent = ({
postAuthor: AppBskyFeedDefs.PostView['author'] postAuthor: AppBskyFeedDefs.PostView['author']
}): React.ReactNode => { }): React.ReactNode => {
const pal = usePalette('default') const pal = usePalette('default')
const {_} = useLingui()
const [limitLines, setLimitLines] = useState( const [limitLines, setLimitLines] = useState(
() => countLines(richText.text) >= MAX_POST_LINES, () => countLines(richText.text) >= MAX_POST_LINES,
) )
@ -346,7 +358,7 @@ let PostContent = ({
) : undefined} ) : undefined}
{limitLines ? ( {limitLines ? (
<TextLink <TextLink
text="Show More" text={_(msg`Show More`)}
style={pal.link} style={pal.link}
onPress={onPressShowMore} onPress={onPressShowMore}
href="#" href="#"

View file

@ -8,6 +8,7 @@ import Svg, {Circle, Line} from 'react-native-svg'
import {FeedItem} from './FeedItem' import {FeedItem} from './FeedItem'
import {usePalette} from 'lib/hooks/usePalette' import {usePalette} from 'lib/hooks/usePalette'
import {makeProfileLink} from 'lib/routes/links' import {makeProfileLink} from 'lib/routes/links'
import {Trans} from '@lingui/macro'
let FeedSlice = ({slice}: {slice: FeedPostSlice}): React.ReactNode => { let FeedSlice = ({slice}: {slice: FeedPostSlice}): React.ReactNode => {
if (slice.isThread && slice.items.length > 3) { if (slice.isThread && slice.items.length > 3) {
@ -99,7 +100,7 @@ function ViewFullThread({slice}: {slice: FeedPostSlice}) {
</View> </View>
<Text type="md" style={[pal.link, {paddingTop: 18, paddingBottom: 4}]}> <Text type="md" style={[pal.link, {paddingTop: 18, paddingBottom: 4}]}>
View full thread <Trans>View full thread</Trans>
</Text> </Text>
</Link> </Link>
) )

View file

@ -12,6 +12,7 @@ import {NavigationProp} from 'lib/routes/types'
import {usePalette} from 'lib/hooks/usePalette' import {usePalette} from 'lib/hooks/usePalette'
import {s} from 'lib/styles' import {s} from 'lib/styles'
import {isWeb} from 'platform/detection' import {isWeb} from 'platform/detection'
import {Trans} from '@lingui/macro'
export function FollowingEmptyState() { export function FollowingEmptyState() {
const pal = usePalette('default') const pal = usePalette('default')
@ -43,15 +44,17 @@ export function FollowingEmptyState() {
<MagnifyingGlassIcon style={[styles.icon, pal.text]} size={62} /> <MagnifyingGlassIcon style={[styles.icon, pal.text]} size={62} />
</View> </View>
<Text type="xl-medium" style={[s.textCenter, pal.text]}> <Text type="xl-medium" style={[s.textCenter, pal.text]}>
<Trans>
Your following feed is empty! Follow more users to see what's Your following feed is empty! Follow more users to see what's
happening. happening.
</Trans>
</Text> </Text>
<Button <Button
type="inverted" type="inverted"
style={styles.emptyBtn} style={styles.emptyBtn}
onPress={onPressFindAccounts}> onPress={onPressFindAccounts}>
<Text type="lg-medium" style={palInverted.text}> <Text type="lg-medium" style={palInverted.text}>
Find accounts to follow <Trans>Find accounts to follow</Trans>
</Text> </Text>
<FontAwesomeIcon <FontAwesomeIcon
icon="angle-right" icon="angle-right"
@ -61,14 +64,14 @@ export function FollowingEmptyState() {
</Button> </Button>
<Text type="xl-medium" style={[s.textCenter, pal.text, s.mt20]}> <Text type="xl-medium" style={[s.textCenter, pal.text, s.mt20]}>
You can also discover new Custom Feeds to follow. <Trans>You can also discover new Custom Feeds to follow.</Trans>
</Text> </Text>
<Button <Button
type="inverted" type="inverted"
style={[styles.emptyBtn, s.mt10]} style={[styles.emptyBtn, s.mt10]}
onPress={onPressDiscoverFeeds}> onPress={onPressDiscoverFeeds}>
<Text type="lg-medium" style={palInverted.text}> <Text type="lg-medium" style={palInverted.text}>
Discover new custom feeds <Trans>Discover new custom feeds</Trans>
</Text> </Text>
<FontAwesomeIcon <FontAwesomeIcon
icon="angle-right" icon="angle-right"

View file

@ -11,6 +11,7 @@ import {NavigationProp} from 'lib/routes/types'
import {usePalette} from 'lib/hooks/usePalette' import {usePalette} from 'lib/hooks/usePalette'
import {s} from 'lib/styles' import {s} from 'lib/styles'
import {isWeb} from 'platform/detection' import {isWeb} from 'platform/detection'
import {Trans} from '@lingui/macro'
export function FollowingEndOfFeed() { export function FollowingEndOfFeed() {
const pal = usePalette('default') const pal = usePalette('default')
@ -44,15 +45,17 @@ export function FollowingEndOfFeed() {
]}> ]}>
<View style={styles.inner}> <View style={styles.inner}>
<Text type="xl-medium" style={[s.textCenter, pal.text]}> <Text type="xl-medium" style={[s.textCenter, pal.text]}>
<Trans>
You've reached the end of your feed! Find some more accounts to You've reached the end of your feed! Find some more accounts to
follow. follow.
</Trans>
</Text> </Text>
<Button <Button
type="inverted" type="inverted"
style={styles.emptyBtn} style={styles.emptyBtn}
onPress={onPressFindAccounts}> onPress={onPressFindAccounts}>
<Text type="lg-medium" style={palInverted.text}> <Text type="lg-medium" style={palInverted.text}>
Find accounts to follow <Trans>Find accounts to follow</Trans>
</Text> </Text>
<FontAwesomeIcon <FontAwesomeIcon
icon="angle-right" icon="angle-right"
@ -62,14 +65,14 @@ export function FollowingEndOfFeed() {
</Button> </Button>
<Text type="xl-medium" style={[s.textCenter, pal.text, s.mt20]}> <Text type="xl-medium" style={[s.textCenter, pal.text, s.mt20]}>
You can also discover new Custom Feeds to follow. <Trans>You can also discover new Custom Feeds to follow.</Trans>
</Text> </Text>
<Button <Button
type="inverted" type="inverted"
style={[styles.emptyBtn, s.mt10]} style={[styles.emptyBtn, s.mt10]}
onPress={onPressDiscoverFeeds}> onPress={onPressDiscoverFeeds}>
<Text type="lg-medium" style={palInverted.text}> <Text type="lg-medium" style={palInverted.text}>
Discover new custom feeds <Trans>Discover new custom feeds</Trans>
</Text> </Text>
<FontAwesomeIcon <FontAwesomeIcon
icon="angle-right" icon="angle-right"

View file

@ -5,6 +5,8 @@ import {Button, ButtonType} from '../util/forms/Button'
import * as Toast from '../util/Toast' import * as Toast from '../util/Toast'
import {useProfileFollowMutationQueue} from '#/state/queries/profile' import {useProfileFollowMutationQueue} from '#/state/queries/profile'
import {Shadow} from '#/state/cache/types' import {Shadow} from '#/state/cache/types'
import {useLingui} from '@lingui/react'
import {msg} from '@lingui/macro'
export function FollowButton({ export function FollowButton({
unfollowedType = 'inverted', unfollowedType = 'inverted',
@ -18,13 +20,14 @@ export function FollowButton({
labelStyle?: StyleProp<TextStyle> labelStyle?: StyleProp<TextStyle>
}) { }) {
const [queueFollow, queueUnfollow] = useProfileFollowMutationQueue(profile) const [queueFollow, queueUnfollow] = useProfileFollowMutationQueue(profile)
const {_} = useLingui()
const onPressFollow = async () => { const onPressFollow = async () => {
try { try {
await queueFollow() await queueFollow()
} catch (e: any) { } catch (e: any) {
if (e?.name !== 'AbortError') { if (e?.name !== 'AbortError') {
Toast.show(`An issue occurred, please try again.`) Toast.show(_(msg`An issue occurred, please try again.`))
} }
} }
} }
@ -34,7 +37,7 @@ export function FollowButton({
await queueUnfollow() await queueUnfollow()
} catch (e: any) { } catch (e: any) {
if (e?.name !== 'AbortError') { if (e?.name !== 'AbortError') {
Toast.show(`An issue occurred, please try again.`) Toast.show(_(msg`An issue occurred, please try again.`))
} }
} }
} }
@ -49,7 +52,7 @@ export function FollowButton({
type={followedType} type={followedType}
labelStyle={labelStyle} labelStyle={labelStyle}
onPress={onPressUnfollow} onPress={onPressUnfollow}
label="Unfollow" label={_(msg({message: 'Unfollow', context: 'action'}))}
/> />
) )
} else { } else {
@ -58,7 +61,7 @@ export function FollowButton({
type={unfollowedType} type={unfollowedType}
labelStyle={labelStyle} labelStyle={labelStyle}
onPress={onPressFollow} onPress={onPressFollow}
label="Follow" label={_(msg({message: 'Follow', context: 'action'}))}
/> />
) )
} }

View file

@ -23,6 +23,7 @@ import {Shadow} from '#/state/cache/types'
import {useModerationOpts} from '#/state/queries/preferences' import {useModerationOpts} from '#/state/queries/preferences'
import {useProfileShadow} from '#/state/cache/profile-shadow' import {useProfileShadow} from '#/state/cache/profile-shadow'
import {useSession} from '#/state/session' import {useSession} from '#/state/session'
import {Trans} from '@lingui/macro'
export function ProfileCard({ export function ProfileCard({
testID, testID,
@ -137,7 +138,7 @@ function ProfileCardPills({
{followedBy && ( {followedBy && (
<View style={[s.mt5, pal.btn, styles.pill]}> <View style={[s.mt5, pal.btn, styles.pill]}>
<Text type="xs" style={pal.text}> <Text type="xs" style={pal.text}>
Follows You <Trans>Follows You</Trans>
</Text> </Text>
</View> </View>
)} )}
@ -190,8 +191,10 @@ function FollowersList({
style={[styles.followsByDesc, pal.textLight]} style={[styles.followsByDesc, pal.textLight]}
numberOfLines={2} numberOfLines={2}
lineHeight={1.2}> lineHeight={1.2}>
<Trans>
Followed by{' '} Followed by{' '}
{followersWithMods.map(({f}) => f.displayName || f.handle).join(', ')} {followersWithMods.map(({f}) => f.displayName || f.handle).join(', ')}
</Trans>
</Text> </Text>
{followersWithMods.slice(0, 3).map(({f, mod}) => ( {followersWithMods.slice(0, 3).map(({f, mod}) => (
<View key={f.did} style={styles.followedByAviContainer}> <View key={f.did} style={styles.followedByAviContainer}>

View file

@ -192,14 +192,16 @@ let ProfileHeaderLoaded = ({
track('ProfileHeader:FollowButtonClicked') track('ProfileHeader:FollowButtonClicked')
await queueFollow() await queueFollow()
Toast.show( Toast.show(
`Following ${sanitizeDisplayName( _(
msg`Following ${sanitizeDisplayName(
profile.displayName || profile.handle, profile.displayName || profile.handle,
)}`, )}`,
),
) )
} catch (e: any) { } catch (e: any) {
if (e?.name !== 'AbortError') { if (e?.name !== 'AbortError') {
logger.error('Failed to follow', {error: String(e)}) logger.error('Failed to follow', {error: String(e)})
Toast.show(`There was an issue! ${e.toString()}`) Toast.show(_(msg`There was an issue! ${e.toString()}`))
} }
} }
}) })
@ -211,14 +213,16 @@ let ProfileHeaderLoaded = ({
track('ProfileHeader:UnfollowButtonClicked') track('ProfileHeader:UnfollowButtonClicked')
await queueUnfollow() await queueUnfollow()
Toast.show( Toast.show(
`No longer following ${sanitizeDisplayName( _(
msg`No longer following ${sanitizeDisplayName(
profile.displayName || profile.handle, profile.displayName || profile.handle,
)}`, )}`,
),
) )
} catch (e: any) { } catch (e: any) {
if (e?.name !== 'AbortError') { if (e?.name !== 'AbortError') {
logger.error('Failed to unfollow', {error: String(e)}) logger.error('Failed to unfollow', {error: String(e)})
Toast.show(`There was an issue! ${e.toString()}`) Toast.show(_(msg`There was an issue! ${e.toString()}`))
} }
} }
}) })
@ -253,27 +257,27 @@ let ProfileHeaderLoaded = ({
track('ProfileHeader:MuteAccountButtonClicked') track('ProfileHeader:MuteAccountButtonClicked')
try { try {
await queueMute() await queueMute()
Toast.show('Account muted') Toast.show(_(msg`Account muted`))
} catch (e: any) { } catch (e: any) {
if (e?.name !== 'AbortError') { if (e?.name !== 'AbortError') {
logger.error('Failed to mute account', {error: e}) logger.error('Failed to mute account', {error: e})
Toast.show(`There was an issue! ${e.toString()}`) Toast.show(_(msg`There was an issue! ${e.toString()}`))
} }
} }
}, [track, queueMute]) }, [track, queueMute, _])
const onPressUnmuteAccount = React.useCallback(async () => { const onPressUnmuteAccount = React.useCallback(async () => {
track('ProfileHeader:UnmuteAccountButtonClicked') track('ProfileHeader:UnmuteAccountButtonClicked')
try { try {
await queueUnmute() await queueUnmute()
Toast.show('Account unmuted') Toast.show(_(msg`Account unmuted`))
} catch (e: any) { } catch (e: any) {
if (e?.name !== 'AbortError') { if (e?.name !== 'AbortError') {
logger.error('Failed to unmute account', {error: e}) logger.error('Failed to unmute account', {error: e})
Toast.show(`There was an issue! ${e.toString()}`) Toast.show(_(msg`There was an issue! ${e.toString()}`))
} }
} }
}, [track, queueUnmute]) }, [track, queueUnmute, _])
const onPressBlockAccount = React.useCallback(async () => { const onPressBlockAccount = React.useCallback(async () => {
track('ProfileHeader:BlockAccountButtonClicked') track('ProfileHeader:BlockAccountButtonClicked')
@ -286,11 +290,11 @@ let ProfileHeaderLoaded = ({
onPressConfirm: async () => { onPressConfirm: async () => {
try { try {
await queueBlock() await queueBlock()
Toast.show('Account blocked') Toast.show(_(msg`Account blocked`))
} catch (e: any) { } catch (e: any) {
if (e?.name !== 'AbortError') { if (e?.name !== 'AbortError') {
logger.error('Failed to block account', {error: e}) logger.error('Failed to block account', {error: e})
Toast.show(`There was an issue! ${e.toString()}`) Toast.show(_(msg`There was an issue! ${e.toString()}`))
} }
} }
}, },
@ -308,11 +312,11 @@ let ProfileHeaderLoaded = ({
onPressConfirm: async () => { onPressConfirm: async () => {
try { try {
await queueUnblock() await queueUnblock()
Toast.show('Account unblocked') Toast.show(_(msg`Account unblocked`))
} catch (e: any) { } catch (e: any) {
if (e?.name !== 'AbortError') { if (e?.name !== 'AbortError') {
logger.error('Failed to unblock account', {error: e}) logger.error('Failed to unblock account', {error: e})
Toast.show(`There was an issue! ${e.toString()}`) Toast.show(_(msg`There was an issue! ${e.toString()}`))
} }
} }
}, },
@ -451,7 +455,9 @@ let ProfileHeaderLoaded = ({
style={[styles.btn, styles.mainBtn, pal.btn]} style={[styles.btn, styles.mainBtn, pal.btn]}
accessibilityRole="button" accessibilityRole="button"
accessibilityLabel={_(msg`Edit profile`)} accessibilityLabel={_(msg`Edit profile`)}
accessibilityHint="Opens editor for profile display name, avatar, background image, and description"> accessibilityHint={_(
msg`Opens editor for profile display name, avatar, background image, and description`,
)}>
<Text type="button" style={pal.text}> <Text type="button" style={pal.text}>
<Trans>Edit Profile</Trans> <Trans>Edit Profile</Trans>
</Text> </Text>
@ -466,7 +472,7 @@ let ProfileHeaderLoaded = ({
accessibilityLabel={_(msg`Unblock`)} accessibilityLabel={_(msg`Unblock`)}
accessibilityHint=""> accessibilityHint="">
<Text type="button" style={[pal.text, s.bold]}> <Text type="button" style={[pal.text, s.bold]}>
<Trans>Unblock</Trans> <Trans context="action">Unblock</Trans>
</Text> </Text>
</TouchableOpacity> </TouchableOpacity>
) )
@ -488,8 +494,12 @@ let ProfileHeaderLoaded = ({
}, },
]} ]}
accessibilityRole="button" accessibilityRole="button"
accessibilityLabel={`Show follows similar to ${profile.handle}`} accessibilityLabel={_(
accessibilityHint={`Shows a list of users similar to this user.`}> msg`Show follows similar to ${profile.handle}`,
)}
accessibilityHint={_(
msg`Shows a list of users similar to this user.`,
)}>
<FontAwesomeIcon <FontAwesomeIcon
icon="user-plus" icon="user-plus"
style={[ style={[
@ -511,8 +521,10 @@ let ProfileHeaderLoaded = ({
onPress={onPressUnfollow} onPress={onPressUnfollow}
style={[styles.btn, styles.mainBtn, pal.btn]} style={[styles.btn, styles.mainBtn, pal.btn]}
accessibilityRole="button" accessibilityRole="button"
accessibilityLabel={`Unfollow ${profile.handle}`} accessibilityLabel={_(msg`Unfollow ${profile.handle}`)}
accessibilityHint={`Hides posts from ${profile.handle} in your feed`}> accessibilityHint={_(
msg`Hides posts from ${profile.handle} in your feed`,
)}>
<FontAwesomeIcon <FontAwesomeIcon
icon="check" icon="check"
style={[pal.text, s.mr5]} style={[pal.text, s.mr5]}
@ -528,8 +540,10 @@ let ProfileHeaderLoaded = ({
onPress={onPressFollow} onPress={onPressFollow}
style={[styles.btn, styles.mainBtn, palInverted.view]} style={[styles.btn, styles.mainBtn, palInverted.view]}
accessibilityRole="button" accessibilityRole="button"
accessibilityLabel={`Follow ${profile.handle}`} accessibilityLabel={_(msg`Follow ${profile.handle}`)}
accessibilityHint={`Shows posts from ${profile.handle} in your feed`}> accessibilityHint={_(
msg`Shows posts from ${profile.handle} in your feed`,
)}>
<FontAwesomeIcon <FontAwesomeIcon
icon="plus" icon="plus"
style={[palInverted.text, s.mr5]} style={[palInverted.text, s.mr5]}
@ -580,7 +594,7 @@ let ProfileHeaderLoaded = ({
invalidHandle ? styles.invalidHandle : undefined, invalidHandle ? styles.invalidHandle : undefined,
styles.handle, styles.handle,
]}> ]}>
{invalidHandle ? '⚠Invalid Handle' : `@${profile.handle}`} {invalidHandle ? _(msg`⚠Invalid Handle`) : `@${profile.handle}`}
</ThemedText> </ThemedText>
</View> </View>
{!blockHide && ( {!blockHide && (
@ -597,7 +611,7 @@ let ProfileHeaderLoaded = ({
} }
asAnchor asAnchor
accessibilityLabel={`${followers} ${pluralizedFollowers}`} accessibilityLabel={`${followers} ${pluralizedFollowers}`}
accessibilityHint={'Opens followers list'}> accessibilityHint={_(msg`Opens followers list`)}>
<Text type="md" style={[s.bold, pal.text]}> <Text type="md" style={[s.bold, pal.text]}>
{followers}{' '} {followers}{' '}
</Text> </Text>
@ -615,14 +629,16 @@ let ProfileHeaderLoaded = ({
}) })
} }
asAnchor asAnchor
accessibilityLabel={`${following} following`} accessibilityLabel={_(msg`${following} following`)}
accessibilityHint={'Opens following list'}> accessibilityHint={_(msg`Opens following list`)}>
<Trans>
<Text type="md" style={[s.bold, pal.text]}> <Text type="md" style={[s.bold, pal.text]}>
{following}{' '} {following}{' '}
</Text> </Text>
<Text type="md" style={[pal.textLight]}> <Text type="md" style={[pal.textLight]}>
<Trans>following</Trans> following
</Text> </Text>
</Trans>
</Link> </Link>
<Text type="md" style={[s.bold, pal.text]}> <Text type="md" style={[s.bold, pal.text]}>
{formatCount(profile.postsCount || 0)}{' '} {formatCount(profile.postsCount || 0)}{' '}
@ -682,7 +698,7 @@ let ProfileHeaderLoaded = ({
testID="profileHeaderAviButton" testID="profileHeaderAviButton"
onPress={onPressAvi} onPress={onPressAvi}
accessibilityRole="image" accessibilityRole="image"
accessibilityLabel={`View ${profile.handle}'s avatar`} accessibilityLabel={_(msg`View ${profile.handle}'s avatar`)}
accessibilityHint=""> accessibilityHint="">
<View <View
style={[pal.view, {borderColor: pal.colors.background}, styles.avi]}> style={[pal.view, {borderColor: pal.colors.background}, styles.avi]}>

View file

@ -21,6 +21,7 @@ import {useModerationOpts} from '#/state/queries/preferences'
import {useSuggestedFollowsByActorQuery} from '#/state/queries/suggested-follows' import {useSuggestedFollowsByActorQuery} from '#/state/queries/suggested-follows'
import {useProfileShadow} from '#/state/cache/profile-shadow' import {useProfileShadow} from '#/state/cache/profile-shadow'
import {useProfileFollowMutationQueue} from '#/state/queries/profile' import {useProfileFollowMutationQueue} from '#/state/queries/profile'
import {Trans} from '@lingui/macro'
const OUTER_PADDING = 10 const OUTER_PADDING = 10
const INNER_PADDING = 14 const INNER_PADDING = 14
@ -60,7 +61,7 @@ export function ProfileHeaderSuggestedFollows({
paddingRight: INNER_PADDING / 2, paddingRight: INNER_PADDING / 2,
}}> }}>
<Text type="sm-bold" style={[pal.textLight]}> <Text type="sm-bold" style={[pal.textLight]}>
Suggested for you <Trans>Suggested for you</Trans>
</Text> </Text>
<Pressable <Pressable

View file

@ -16,7 +16,7 @@ import {BACK_HITSLOP} from 'lib/constants'
import {isNative} from 'platform/detection' import {isNative} from 'platform/detection'
import {useLightboxControls, ImagesLightbox} from '#/state/lightbox' import {useLightboxControls, ImagesLightbox} from '#/state/lightbox'
import {useLingui} from '@lingui/react' import {useLingui} from '@lingui/react'
import {msg} from '@lingui/macro' import {Trans, msg} from '@lingui/macro'
import {useSetDrawerOpen} from '#/state/shell' import {useSetDrawerOpen} from '#/state/shell'
import {emitSoftReset} from '#/state/events' import {emitSoftReset} from '#/state/events'
@ -153,17 +153,19 @@ export function ProfileSubpageHeader({
<LoadingPlaceholder width={50} height={8} /> <LoadingPlaceholder width={50} height={8} />
) : ( ) : (
<Text type="xl" style={[pal.textLight]} numberOfLines={1}> <Text type="xl" style={[pal.textLight]} numberOfLines={1}>
by{' '}
{!creator ? ( {!creator ? (
'—' <Trans>by </Trans>
) : isOwner ? ( ) : isOwner ? (
'you' <Trans>by you</Trans>
) : ( ) : (
<Trans>
by{' '}
<TextLink <TextLink
text={sanitizeHandle(creator.handle, '@')} text={sanitizeHandle(creator.handle, '@')}
href={makeProfileLink(creator)} href={makeProfileLink(creator)}
style={pal.textLight} style={pal.textLight}
/> />
</Trans>
)} )}
</Text> </Text>
)} )}

View file

@ -22,7 +22,7 @@ export function AccountDropdownBtn({account}: {account: SessionAccount}) {
label: _(msg`Remove account`), label: _(msg`Remove account`),
onPress: () => { onPress: () => {
removeAccount(account) removeAccount(account)
Toast.show('Account removed from quick access') Toast.show(_(msg`Account removed from quick access`))
}, },
icon: { icon: {
ios: { ios: {

View file

@ -2,6 +2,8 @@ import React, {createRef, useState, useMemo, useRef} from 'react'
import {Animated, Pressable, StyleSheet, View} from 'react-native' import {Animated, Pressable, StyleSheet, View} from 'react-native'
import {Text} from './text/Text' import {Text} from './text/Text'
import {usePalette} from 'lib/hooks/usePalette' import {usePalette} from 'lib/hooks/usePalette'
import {useLingui} from '@lingui/react'
import {msg} from '@lingui/macro'
interface Layout { interface Layout {
x: number x: number
@ -19,6 +21,7 @@ export function Selector({
panX: Animated.Value panX: Animated.Value
onSelect?: (index: number) => void onSelect?: (index: number) => void
}) { }) {
const {_} = useLingui()
const containerRef = useRef<View>(null) const containerRef = useRef<View>(null)
const pal = usePalette('default') const pal = usePalette('default')
const [itemLayouts, setItemLayouts] = useState<undefined | Layout[]>( const [itemLayouts, setItemLayouts] = useState<undefined | Layout[]>(
@ -100,8 +103,8 @@ export function Selector({
testID={`selector-${i}`} testID={`selector-${i}`}
key={item} key={item}
onPress={() => onPressItem(i)} onPress={() => onPressItem(i)}
accessibilityLabel={`Select ${item}`} accessibilityLabel={_(msg`Select ${item}`)}
accessibilityHint={`Select option ${i} of ${numItems}`}> accessibilityHint={_(msg`Select option ${i} of ${numItems}`)}>
<View style={styles.item} ref={itemRefs[i]}> <View style={styles.item} ref={itemRefs[i]}>
<Text <Text
style={ style={

View file

@ -11,6 +11,8 @@ import {NavigationProp} from 'lib/routes/types'
import {useMinimalShellMode} from 'lib/hooks/useMinimalShellMode' import {useMinimalShellMode} from 'lib/hooks/useMinimalShellMode'
import Animated from 'react-native-reanimated' import Animated from 'react-native-reanimated'
import {useSetDrawerOpen} from '#/state/shell' import {useSetDrawerOpen} from '#/state/shell'
import {msg} from '@lingui/macro'
import {useLingui} from '@lingui/react'
const BACK_HITSLOP = {left: 20, top: 20, right: 50, bottom: 20} const BACK_HITSLOP = {left: 20, top: 20, right: 50, bottom: 20}
@ -32,6 +34,7 @@ export function ViewHeader({
renderButton?: () => JSX.Element renderButton?: () => JSX.Element
}) { }) {
const pal = usePalette('default') const pal = usePalette('default')
const {_} = useLingui()
const setDrawerOpen = useSetDrawerOpen() const setDrawerOpen = useSetDrawerOpen()
const navigation = useNavigation<NavigationProp>() const navigation = useNavigation<NavigationProp>()
const {track} = useAnalytics() const {track} = useAnalytics()
@ -75,9 +78,9 @@ export function ViewHeader({
hitSlop={BACK_HITSLOP} hitSlop={BACK_HITSLOP}
style={canGoBack ? styles.backBtn : styles.backBtnWide} style={canGoBack ? styles.backBtn : styles.backBtnWide}
accessibilityRole="button" accessibilityRole="button"
accessibilityLabel={canGoBack ? 'Back' : 'Menu'} accessibilityLabel={canGoBack ? _(msg`Back`) : _(msg`Menu`)}
accessibilityHint={ accessibilityHint={
canGoBack ? '' : 'Access navigation links and settings' canGoBack ? '' : _(msg`Access navigation links and settings`)
}> }>
{canGoBack ? ( {canGoBack ? (
<FontAwesomeIcon <FontAwesomeIcon

View file

@ -53,7 +53,9 @@ export function ErrorMessage({
onPress={onPressTryAgain} onPress={onPressTryAgain}
accessibilityRole="button" accessibilityRole="button"
accessibilityLabel={_(msg`Retry`)} accessibilityLabel={_(msg`Retry`)}
accessibilityHint="Retries the last action, which errored out"> accessibilityHint={_(
msg`Retries the last action, which errored out`,
)}>
<FontAwesomeIcon <FontAwesomeIcon
icon="arrows-rotate" icon="arrows-rotate"
style={{color: theme.palette.error.icon}} style={{color: theme.palette.error.icon}}

View file

@ -63,14 +63,16 @@ export function ErrorScreen({
style={[styles.btn]} style={[styles.btn]}
onPress={onPressTryAgain} onPress={onPressTryAgain}
accessibilityLabel={_(msg`Retry`)} accessibilityLabel={_(msg`Retry`)}
accessibilityHint="Retries the last action, which errored out"> accessibilityHint={_(
msg`Retries the last action, which errored out`,
)}>
<FontAwesomeIcon <FontAwesomeIcon
icon="arrows-rotate" icon="arrows-rotate"
style={pal.link as FontAwesomeIconStyle} style={pal.link as FontAwesomeIconStyle}
size={16} size={16}
/> />
<Text type="button" style={[styles.btnText, pal.link]}> <Text type="button" style={[styles.btnText, pal.link]}>
<Trans>Try again</Trans> <Trans context="action">Try again</Trans>
</Text> </Text>
</Button> </Button>
</View> </View>

View file

@ -75,6 +75,8 @@ export function DropdownButton({
bottomOffset = 0, bottomOffset = 0,
accessibilityLabel, accessibilityLabel,
}: PropsWithChildren<DropdownButtonProps>) { }: PropsWithChildren<DropdownButtonProps>) {
const {_} = useLingui()
const ref1 = useRef<TouchableOpacity>(null) const ref1 = useRef<TouchableOpacity>(null)
const ref2 = useRef<View>(null) const ref2 = useRef<View>(null)
@ -141,7 +143,9 @@ export function DropdownButton({
hitSlop={HITSLOP_10} hitSlop={HITSLOP_10}
ref={ref1} ref={ref1}
accessibilityRole="button" accessibilityRole="button"
accessibilityLabel={accessibilityLabel || `Opens ${numItems} options`} accessibilityLabel={
accessibilityLabel || _(msg`Opens ${numItems} options`)
}
accessibilityHint=""> accessibilityHint="">
{children} {children}
</TouchableOpacity> </TouchableOpacity>
@ -247,7 +251,7 @@ const DropdownItems = ({
onPress={() => onPressItem(index)} onPress={() => onPressItem(index)}
accessibilityRole="button" accessibilityRole="button"
accessibilityLabel={item.label} accessibilityLabel={item.label}
accessibilityHint={`Option ${index + 1} of ${numItems}`}> accessibilityHint={_(msg`Option ${index + 1} of ${numItems}`)}>
{item.icon && ( {item.icon && (
<FontAwesomeIcon <FontAwesomeIcon
style={styles.icon} style={styles.icon}

View file

@ -71,32 +71,34 @@ let PostDropdownBtn = ({
const onDeletePost = React.useCallback(() => { const onDeletePost = React.useCallback(() => {
postDeleteMutation.mutateAsync({uri: postUri}).then( postDeleteMutation.mutateAsync({uri: postUri}).then(
() => { () => {
Toast.show('Post deleted') Toast.show(_(msg`Post deleted`))
}, },
e => { e => {
logger.error('Failed to delete post', {error: e}) logger.error('Failed to delete post', {error: e})
Toast.show('Failed to delete post, please try again') Toast.show(_(msg`Failed to delete post, please try again`))
}, },
) )
}, [postUri, postDeleteMutation]) }, [postUri, postDeleteMutation, _])
const onToggleThreadMute = React.useCallback(() => { const onToggleThreadMute = React.useCallback(() => {
try { try {
const muted = toggleThreadMute(rootUri) const muted = toggleThreadMute(rootUri)
if (muted) { if (muted) {
Toast.show('You will no longer receive notifications for this thread') Toast.show(
_(msg`You will no longer receive notifications for this thread`),
)
} else { } else {
Toast.show('You will now receive notifications for this thread') Toast.show(_(msg`You will now receive notifications for this thread`))
} }
} catch (e) { } catch (e) {
logger.error('Failed to toggle thread mute', {error: e}) logger.error('Failed to toggle thread mute', {error: e})
} }
}, [rootUri, toggleThreadMute]) }, [rootUri, toggleThreadMute, _])
const onCopyPostText = React.useCallback(() => { const onCopyPostText = React.useCallback(() => {
Clipboard.setString(record?.text || '') Clipboard.setString(record?.text || '')
Toast.show('Copied to clipboard') Toast.show(_(msg`Copied to clipboard`))
}, [record]) }, [record, _])
const onOpenTranslate = React.useCallback(() => { const onOpenTranslate = React.useCallback(() => {
Linking.openURL(translatorUrl) Linking.openURL(translatorUrl)
@ -253,7 +255,7 @@ let PostDropdownBtn = ({
<NativeDropdown <NativeDropdown
testID={testID} testID={testID}
items={dropdownItems} items={dropdownItems}
accessibilityLabel="More post options" accessibilityLabel={_(msg`More post options`)}
accessibilityHint=""> accessibilityHint="">
<View style={style}> <View style={style}>
<FontAwesomeIcon icon="ellipsis" size={20} color={defaultCtrlColor} /> <FontAwesomeIcon icon="ellipsis" size={20} color={defaultCtrlColor} />

View file

@ -50,7 +50,7 @@ export function SearchInput({
<TextInput <TextInput
testID="searchTextInput" testID="searchTextInput"
ref={textInput} ref={textInput}
placeholder="Search" placeholder={_(msg`Search`)}
placeholderTextColor={pal.colors.textLight} placeholderTextColor={pal.colors.textLight}
selectTextOnFocus selectTextOnFocus
returnKeyType="search" returnKeyType="search"

View file

@ -4,6 +4,8 @@ import {Image} from 'expo-image'
import {clamp} from 'lib/numbers' import {clamp} from 'lib/numbers'
import {Dimensions} from 'lib/media/types' import {Dimensions} from 'lib/media/types'
import * as imageSizes from 'lib/media/image-sizes' import * as imageSizes from 'lib/media/image-sizes'
import {msg} from '@lingui/macro'
import {useLingui} from '@lingui/react'
const MIN_ASPECT_RATIO = 0.33 // 1/3 const MIN_ASPECT_RATIO = 0.33 // 1/3
const MAX_ASPECT_RATIO = 10 // 10/1 const MAX_ASPECT_RATIO = 10 // 10/1
@ -29,6 +31,7 @@ export function AutoSizedImage({
style, style,
children = null, children = null,
}: Props) { }: Props) {
const {_} = useLingui()
const [dim, setDim] = React.useState<Dimensions | undefined>( const [dim, setDim] = React.useState<Dimensions | undefined>(
dimensionsHint || imageSizes.get(uri), dimensionsHint || imageSizes.get(uri),
) )
@ -64,7 +67,7 @@ export function AutoSizedImage({
accessible={true} // Must set for `accessibilityLabel` to work accessible={true} // Must set for `accessibilityLabel` to work
accessibilityIgnoresInvertColors accessibilityIgnoresInvertColors
accessibilityLabel={alt} accessibilityLabel={alt}
accessibilityHint="Tap to view fully" accessibilityHint={_(msg`Tap to view fully`)}
/> />
{children} {children}
</Pressable> </Pressable>

View file

@ -2,6 +2,8 @@ import {AppBskyEmbedImages} from '@atproto/api'
import React, {ComponentProps, FC} from 'react' import React, {ComponentProps, FC} from 'react'
import {StyleSheet, Text, Pressable, View} from 'react-native' import {StyleSheet, Text, Pressable, View} from 'react-native'
import {Image} from 'expo-image' import {Image} from 'expo-image'
import {msg} from '@lingui/macro'
import {useLingui} from '@lingui/react'
type EventFunction = (index: number) => void type EventFunction = (index: number) => void
@ -22,6 +24,7 @@ export const GalleryItem: FC<GalleryItemProps> = ({
onPressIn, onPressIn,
onLongPress, onLongPress,
}) => { }) => {
const {_} = useLingui()
const image = images[index] const image = images[index]
return ( return (
<View style={styles.fullWidth}> <View style={styles.fullWidth}>
@ -31,7 +34,7 @@ export const GalleryItem: FC<GalleryItemProps> = ({
onLongPress={onLongPress ? () => onLongPress(index) : undefined} onLongPress={onLongPress ? () => onLongPress(index) : undefined}
style={styles.fullWidth} style={styles.fullWidth}
accessibilityRole="button" accessibilityRole="button"
accessibilityLabel={image.alt || 'Image'} accessibilityLabel={image.alt || _(msg`Image`)}
accessibilityHint=""> accessibilityHint="">
<Image <Image
source={{uri: image.thumb}} source={{uri: image.thumb}}

View file

@ -63,7 +63,9 @@ export function ContentHider({
} }
}} }}
accessibilityRole="button" accessibilityRole="button"
accessibilityHint={override ? 'Hide the content' : 'Show the content'} accessibilityHint={
override ? _(msg`Hide the content`) : _(msg`Show the content`)
}
accessibilityLabel="" accessibilityLabel=""
style={[ style={[
styles.cover, styles.cover,

View file

@ -9,7 +9,7 @@ import {addStyle} from 'lib/styles'
import {describeModerationCause} from 'lib/moderation' import {describeModerationCause} from 'lib/moderation'
import {ShieldExclamation} from 'lib/icons' import {ShieldExclamation} from 'lib/icons'
import {useLingui} from '@lingui/react' import {useLingui} from '@lingui/react'
import {msg} from '@lingui/macro' import {Trans, msg} from '@lingui/macro'
import {useModalControls} from '#/state/modals' import {useModalControls} from '#/state/modals'
interface Props extends ComponentProps<typeof Link> { interface Props extends ComponentProps<typeof Link> {
@ -57,7 +57,9 @@ export function PostHider({
} }
}} }}
accessibilityRole="button" accessibilityRole="button"
accessibilityHint={override ? 'Hide the content' : 'Show the content'} accessibilityHint={
override ? _(msg`Hide the content`) : _(msg`Show the content`)
}
accessibilityLabel="" accessibilityLabel=""
style={[ style={[
styles.description, styles.description,
@ -103,7 +105,7 @@ export function PostHider({
</Text> </Text>
{!moderation.noOverride && ( {!moderation.noOverride && (
<Text type="sm" style={[styles.showBtn, pal.link]}> <Text type="sm" style={[styles.showBtn, pal.link]}>
{override ? 'Hide' : 'Show'} {override ? <Trans>Hide</Trans> : <Trans>Show</Trans>}
</Text> </Text>
)} )}
</Pressable> </Pressable>

View file

@ -26,6 +26,8 @@ import {
import {useComposerControls} from '#/state/shell/composer' import {useComposerControls} from '#/state/shell/composer'
import {Shadow} from '#/state/cache/types' import {Shadow} from '#/state/cache/types'
import {useRequireAuth} from '#/state/session' import {useRequireAuth} from '#/state/session'
import {msg} from '@lingui/macro'
import {useLingui} from '@lingui/react'
let PostCtrls = ({ let PostCtrls = ({
big, big,
@ -43,6 +45,7 @@ let PostCtrls = ({
onPressReply: () => void onPressReply: () => void
}): React.ReactNode => { }): React.ReactNode => {
const theme = useTheme() const theme = useTheme()
const {_} = useLingui()
const {openComposer} = useComposerControls() const {openComposer} = useComposerControls()
const {closeModal} = useModalControls() const {closeModal} = useModalControls()
const postLikeMutation = usePostLikeMutation() const postLikeMutation = usePostLikeMutation()
@ -176,9 +179,9 @@ let PostCtrls = ({
requireAuth(() => onPressToggleLike()) requireAuth(() => onPressToggleLike())
}} }}
accessibilityRole="button" accessibilityRole="button"
accessibilityLabel={`${post.viewer?.like ? 'Unlike' : 'Like'} (${ accessibilityLabel={`${
post.likeCount post.viewer?.like ? _(msg`Unlike`) : _(msg`Like`)
} ${pluralize(post.likeCount || 0, 'like')})`} } (${post.likeCount} ${pluralize(post.likeCount || 0, 'like')})`}
accessibilityHint="" accessibilityHint=""
hitSlop={big ? HITSLOP_20 : HITSLOP_10}> hitSlop={big ? HITSLOP_20 : HITSLOP_10}>
{post.viewer?.like ? ( {post.viewer?.like ? (

View file

@ -8,6 +8,8 @@ import {pluralize} from 'lib/strings/helpers'
import {HITSLOP_10, HITSLOP_20} from 'lib/constants' import {HITSLOP_10, HITSLOP_20} from 'lib/constants'
import {useModalControls} from '#/state/modals' import {useModalControls} from '#/state/modals'
import {useRequireAuth} from '#/state/session' import {useRequireAuth} from '#/state/session'
import {msg} from '@lingui/macro'
import {useLingui} from '@lingui/react'
interface Props { interface Props {
isReposted: boolean isReposted: boolean
@ -25,6 +27,7 @@ let RepostButton = ({
onQuote, onQuote,
}: Props): React.ReactNode => { }: Props): React.ReactNode => {
const theme = useTheme() const theme = useTheme()
const {_} = useLingui()
const {openModal} = useModalControls() const {openModal} = useModalControls()
const requireAuth = useRequireAuth() const requireAuth = useRequireAuth()
@ -53,7 +56,9 @@ let RepostButton = ({
style={[styles.control, !big && styles.controlPad]} style={[styles.control, !big && styles.controlPad]}
accessibilityRole="button" accessibilityRole="button"
accessibilityLabel={`${ accessibilityLabel={`${
isReposted ? 'Undo repost' : 'Repost' isReposted
? _(msg`Undo repost`)
: _(msg({message: 'Repost', context: 'action'}))
} (${repostCount} ${pluralize(repostCount || 0, 'repost')})`} } (${repostCount} ${pluralize(repostCount || 0, 'repost')})`}
accessibilityHint="" accessibilityHint=""
hitSlop={big ? HITSLOP_20 : HITSLOP_10}> hitSlop={big ? HITSLOP_20 : HITSLOP_10}>

View file

@ -17,6 +17,7 @@ import {PostEmbeds} from '.'
import {PostAlerts} from '../moderation/PostAlerts' import {PostAlerts} from '../moderation/PostAlerts'
import {makeProfileLink} from 'lib/routes/links' import {makeProfileLink} from 'lib/routes/links'
import {InfoCircleIcon} from 'lib/icons' import {InfoCircleIcon} from 'lib/icons'
import {Trans} from '@lingui/macro'
export function MaybeQuoteEmbed({ export function MaybeQuoteEmbed({
embed, embed,
@ -52,7 +53,7 @@ export function MaybeQuoteEmbed({
<View style={[styles.errorContainer, pal.borderDark]}> <View style={[styles.errorContainer, pal.borderDark]}>
<InfoCircleIcon size={18} style={pal.text} /> <InfoCircleIcon size={18} style={pal.text} />
<Text type="lg" style={pal.text}> <Text type="lg" style={pal.text}>
Blocked <Trans>Blocked</Trans>
</Text> </Text>
</View> </View>
) )
@ -61,7 +62,7 @@ export function MaybeQuoteEmbed({
<View style={[styles.errorContainer, pal.borderDark]}> <View style={[styles.errorContainer, pal.borderDark]}>
<InfoCircleIcon size={18} style={pal.text} /> <InfoCircleIcon size={18} style={pal.text} />
<Text type="lg" style={pal.text}> <Text type="lg" style={pal.text}>
Deleted <Trans>Deleted</Trans>
</Text> </Text>
</View> </View>
) )

View file

@ -62,8 +62,8 @@ export function AppPasswords({}: Props) {
]} ]}
testID="appPasswordsScreen"> testID="appPasswordsScreen">
<ErrorScreen <ErrorScreen
title="Oops!" title={_(msg`Oops!`)}
message="There was an issue with fetching your app passwords" message={_(msg`There was an issue with fetching your app passwords`)}
details={cleanError(error)} details={cleanError(error)}
/> />
</CenteredView> </CenteredView>

View file

@ -16,6 +16,8 @@ import {ToggleButton} from '../com/util/forms/ToggleButton'
import {RadioGroup} from '../com/util/forms/RadioGroup' import {RadioGroup} from '../com/util/forms/RadioGroup'
import {ErrorScreen} from '../com/util/error/ErrorScreen' import {ErrorScreen} from '../com/util/error/ErrorScreen'
import {ErrorMessage} from '../com/util/error/ErrorMessage' import {ErrorMessage} from '../com/util/error/ErrorMessage'
import {msg} from '@lingui/macro'
import {useLingui} from '@lingui/react'
const MAIN_VIEWS = ['Base', 'Controls', 'Error', 'Notifs'] const MAIN_VIEWS = ['Base', 'Controls', 'Error', 'Notifs']
@ -48,6 +50,7 @@ function DebugInner({
}) { }) {
const [currentView, setCurrentView] = React.useState<number>(0) const [currentView, setCurrentView] = React.useState<number>(0)
const pal = usePalette('default') const pal = usePalette('default')
const {_} = useLingui()
const renderItem = (item: any) => { const renderItem = (item: any) => {
return ( return (
@ -57,7 +60,7 @@ function DebugInner({
type="default-light" type="default-light"
onPress={onToggleColorScheme} onPress={onToggleColorScheme}
isSelected={colorScheme === 'dark'} isSelected={colorScheme === 'dark'}
label="Dark mode" label={_(msg`Dark mode`)}
/> />
</View> </View>
{item.currentView === 3 ? ( {item.currentView === 3 ? (
@ -77,7 +80,7 @@ function DebugInner({
return ( return (
<View style={[s.hContentRegion, pal.view]}> <View style={[s.hContentRegion, pal.view]}>
<ViewHeader title="Debug panel" /> <ViewHeader title={_(msg`Debug panel`)} />
<ViewSelector <ViewSelector
swipeEnabled swipeEnabled
sections={MAIN_VIEWS} sections={MAIN_VIEWS}

View file

@ -328,7 +328,7 @@ export function FeedsScreen(_props: Props) {
hitSlop={10} hitSlop={10}
accessibilityRole="button" accessibilityRole="button"
accessibilityLabel={_(msg`Edit Saved Feeds`)} accessibilityLabel={_(msg`Edit Saved Feeds`)}
accessibilityHint="Opens screen to edit Saved Feeds"> accessibilityHint={_(msg`Opens screen to edit Saved Feeds`)}>
<CogIcon size={22} strokeWidth={2} style={pal.textLight} /> <CogIcon size={22} strokeWidth={2} style={pal.textLight} />
</Link> </Link>
) )

View file

@ -73,7 +73,7 @@ export function ListsScreen({}: Props) {
}}> }}>
<FontAwesomeIcon icon="plus" color={pal.colors.text} /> <FontAwesomeIcon icon="plus" color={pal.colors.text} />
<Text type="button" style={pal.text}> <Text type="button" style={pal.text}>
<Trans>New</Trans> <Trans context="action">New</Trans>
</Text> </Text>
</Button> </Button>
</View> </View>

View file

@ -50,7 +50,9 @@ export function LogScreen({}: NativeStackScreenProps<
style={[styles.entry, pal.border, pal.view]} style={[styles.entry, pal.border, pal.view]}
onPress={toggler(entry.id)} onPress={toggler(entry.id)}
accessibilityLabel={_(msg`View debug entry`)} accessibilityLabel={_(msg`View debug entry`)}
accessibilityHint="Opens additional details for a debug entry"> accessibilityHint={_(
msg`Opens additional details for a debug entry`,
)}>
{entry.level === 'debug' ? ( {entry.level === 'debug' ? (
<FontAwesomeIcon icon="info" /> <FontAwesomeIcon icon="info" />
) : ( ) : (

View file

@ -78,7 +78,9 @@ export function PostThreadScreen({route}: Props) {
return ( return (
<View style={s.hContentRegion}> <View style={s.hContentRegion}>
{isMobile && <ViewHeader title={_(msg`Post`)} />} {isMobile && (
<ViewHeader title={_(msg({message: 'Post', context: 'description'}))} />
)}
<View style={s.flex1}> <View style={s.flex1}>
{uriError ? ( {uriError ? (
<CenteredView> <CenteredView>

View file

@ -72,7 +72,7 @@ export function PreferencesExternalEmbeds({}: Props) {
</View> </View>
</View> </View>
<Text type="xl-bold" style={[pal.text, styles.heading]}> <Text type="xl-bold" style={[pal.text, styles.heading]}>
Enable media players for <Trans>Enable media players for</Trans>
</Text> </Text>
{Object.entries(externalEmbedLabels).map(([key, label]) => ( {Object.entries(externalEmbedLabels).map(([key, label]) => (
<PrefSelector <PrefSelector

View file

@ -27,6 +27,7 @@ function RepliesThresholdInput({
initialValue: number initialValue: number
}) { }) {
const pal = usePalette('default') const pal = usePalette('default')
const {_} = useLingui()
const [value, setValue] = useState(initialValue) const [value, setValue] = useState(initialValue)
const {mutate: setFeedViewPref} = useSetFeedViewPreferencesMutation() const {mutate: setFeedViewPref} = useSetFeedViewPreferencesMutation()
const preValue = React.useRef(initialValue) const preValue = React.useRef(initialValue)
@ -64,10 +65,12 @@ function RepliesThresholdInput({
/> />
<Text type="xs" style={pal.text}> <Text type="xs" style={pal.text}>
{value === 0 {value === 0
? `Show all replies` ? _(msg`Show all replies`)
: `Show replies with at least ${value} ${ : _(
msg`Show replies with at least ${value} ${
value > 1 ? `likes` : `like` value > 1 ? `likes` : `like`
}`} }`,
)}
</Text> </Text>
</View> </View>
) )

View file

@ -159,7 +159,7 @@ export function PreferencesThreads({navigation}: Props) {
accessibilityLabel={_(msg`Confirm`)} accessibilityLabel={_(msg`Confirm`)}
accessibilityHint=""> accessibilityHint="">
<Text style={[s.white, s.bold, s.f18]}> <Text style={[s.white, s.bold, s.f18]}>
<Trans>Done</Trans> <Trans context="action">Done</Trans>
</Text> </Text>
</TouchableOpacity> </TouchableOpacity>
</View> </View>

View file

@ -371,6 +371,7 @@ const FeedSection = React.forwardRef<SectionRef, FeedSectionProps>(
{feed, headerHeight, isFocused, scrollElRef, ignoreFilterFor}, {feed, headerHeight, isFocused, scrollElRef, ignoreFilterFor},
ref, ref,
) { ) {
const {_} = useLingui()
const queryClient = useQueryClient() const queryClient = useQueryClient()
const [hasNew, setHasNew] = React.useState(false) const [hasNew, setHasNew] = React.useState(false)
const [isScrolledDown, setIsScrolledDown] = React.useState(false) const [isScrolledDown, setIsScrolledDown] = React.useState(false)
@ -388,8 +389,8 @@ const FeedSection = React.forwardRef<SectionRef, FeedSectionProps>(
})) }))
const renderPostsEmpty = React.useCallback(() => { const renderPostsEmpty = React.useCallback(() => {
return <EmptyState icon="feed" message="This feed is empty!" /> return <EmptyState icon="feed" message={_(msg`This feed is empty!`)} />
}, []) }, [_])
return ( return (
<View> <View>
@ -408,7 +409,7 @@ const FeedSection = React.forwardRef<SectionRef, FeedSectionProps>(
{(isScrolledDown || hasNew) && ( {(isScrolledDown || hasNew) && (
<LoadLatestBtn <LoadLatestBtn
onPress={onScrollToTop} onPress={onScrollToTop}
label="Load new posts" label={_(msg`Load new posts`)}
showIndicator={hasNew} showIndicator={hasNew}
/> />
)} )}

View file

@ -214,11 +214,21 @@ export function ProfileFeedScreenInner({
} }
} catch (err) { } catch (err) {
Toast.show( Toast.show(
'There was an an issue updating your feeds, please check your internet connection and try again.', _(
msg`There was an an issue updating your feeds, please check your internet connection and try again.`,
),
) )
logger.error('Failed up update feeds', {error: err}) logger.error('Failed up update feeds', {error: err})
} }
}, [feedInfo, isSaved, saveFeed, removeFeed, resetSaveFeed, resetRemoveFeed]) }, [
feedInfo,
isSaved,
saveFeed,
removeFeed,
resetSaveFeed,
resetRemoveFeed,
_,
])
const onTogglePinned = React.useCallback(async () => { const onTogglePinned = React.useCallback(async () => {
try { try {
@ -232,10 +242,10 @@ export function ProfileFeedScreenInner({
resetPinFeed() resetPinFeed()
} }
} catch (e) { } catch (e) {
Toast.show('There was an issue contacting the server') Toast.show(_(msg`There was an issue contacting the server`))
logger.error('Failed to toggle pinned feed', {error: e}) logger.error('Failed to toggle pinned feed', {error: e})
} }
}, [isPinned, feedInfo, pinFeed, unpinFeed, resetPinFeed, resetUnpinFeed]) }, [isPinned, feedInfo, pinFeed, unpinFeed, resetPinFeed, resetUnpinFeed, _])
const onPressShare = React.useCallback(() => { const onPressShare = React.useCallback(() => {
const url = toShareUrl(feedInfo.route.href) const url = toShareUrl(feedInfo.route.href)
@ -341,7 +351,7 @@ export function ProfileFeedScreenInner({
<Button <Button
disabled={isSavePending || isRemovePending} disabled={isSavePending || isRemovePending}
type="default" type="default"
label={isSaved ? 'Unsave' : 'Save'} label={isSaved ? _(msg`Unsave`) : _(msg`Save`)}
onPress={onToggleSaved} onPress={onToggleSaved}
style={styles.btn} style={styles.btn}
/> />
@ -349,7 +359,7 @@ export function ProfileFeedScreenInner({
testID={isPinned ? 'unpinBtn' : 'pinBtn'} testID={isPinned ? 'unpinBtn' : 'pinBtn'}
disabled={isPinPending || isUnpinPending} disabled={isPinPending || isUnpinPending}
type={isPinned ? 'default' : 'inverted'} type={isPinned ? 'default' : 'inverted'}
label={isPinned ? 'Unpin' : 'Pin to home'} label={isPinned ? _(msg`Unpin`) : _(msg`Pin to home`)}
onPress={onTogglePinned} onPress={onTogglePinned}
style={styles.btn} style={styles.btn}
/> />
@ -444,6 +454,7 @@ interface FeedSectionProps {
} }
const FeedSection = React.forwardRef<SectionRef, FeedSectionProps>( const FeedSection = React.forwardRef<SectionRef, FeedSectionProps>(
function FeedSectionImpl({feed, headerHeight, scrollElRef, isFocused}, ref) { function FeedSectionImpl({feed, headerHeight, scrollElRef, isFocused}, ref) {
const {_} = useLingui()
const [hasNew, setHasNew] = React.useState(false) const [hasNew, setHasNew] = React.useState(false)
const [isScrolledDown, setIsScrolledDown] = React.useState(false) const [isScrolledDown, setIsScrolledDown] = React.useState(false)
const queryClient = useQueryClient() const queryClient = useQueryClient()
@ -470,8 +481,8 @@ const FeedSection = React.forwardRef<SectionRef, FeedSectionProps>(
}, [onScrollToTop, isScreenFocused]) }, [onScrollToTop, isScreenFocused])
const renderPostsEmpty = useCallback(() => { const renderPostsEmpty = useCallback(() => {
return <EmptyState icon="feed" message="This feed is empty!" /> return <EmptyState icon="feed" message={_(msg`This feed is empty!`)} />
}, []) }, [_])
return ( return (
<View> <View>
@ -488,7 +499,7 @@ const FeedSection = React.forwardRef<SectionRef, FeedSectionProps>(
{(isScrolledDown || hasNew) && ( {(isScrolledDown || hasNew) && (
<LoadLatestBtn <LoadLatestBtn
onPress={onScrollToTop} onPress={onScrollToTop}
label="Load new posts" label={_(msg`Load new posts`)}
showIndicator={hasNew} showIndicator={hasNew}
/> />
)} )}
@ -542,11 +553,13 @@ function AboutSection({
} }
} catch (err) { } catch (err) {
Toast.show( Toast.show(
'There was an an issue contacting the server, please check your internet connection and try again.', _(
msg`There was an an issue contacting the server, please check your internet connection and try again.`,
),
) )
logger.error('Failed up toggle like', {error: err}) logger.error('Failed up toggle like', {error: err})
} }
}, [likeUri, isLiked, feedInfo, likeFeed, unlikeFeed, track]) }, [likeUri, isLiked, feedInfo, likeFeed, unlikeFeed, track, _])
return ( return (
<ScrollView <ScrollView
@ -597,16 +610,19 @@ function AboutSection({
{typeof likeCount === 'number' && ( {typeof likeCount === 'number' && (
<TextLink <TextLink
href={makeCustomFeedLink(feedOwnerDid, feedRkey, 'liked-by')} href={makeCustomFeedLink(feedOwnerDid, feedRkey, 'liked-by')}
text={`Liked by ${likeCount} ${pluralize(likeCount, 'user')}`} text={_(
msg`Liked by ${likeCount} ${pluralize(likeCount, 'user')}`,
)}
style={[pal.textLight, s.semiBold]} style={[pal.textLight, s.semiBold]}
/> />
)} )}
</View> </View>
<Text type="md" style={[pal.textLight]} numberOfLines={1}> <Text type="md" style={[pal.textLight]} numberOfLines={1}>
Created by{' '}
{isOwner ? ( {isOwner ? (
'you' <Trans>Created by you</Trans>
) : ( ) : (
<Trans>
Created by{' '}
<TextLink <TextLink
text={sanitizeHandle(feedInfo.creatorHandle, '@')} text={sanitizeHandle(feedInfo.creatorHandle, '@')}
href={makeProfileLink({ href={makeProfileLink({
@ -615,6 +631,7 @@ function AboutSection({
})} })}
style={pal.textLight} style={pal.textLight}
/> />
</Trans>
)} )}
</Text> </Text>
</View> </View>

View file

@ -68,6 +68,7 @@ interface SectionRef {
type Props = NativeStackScreenProps<CommonNavigatorParams, 'ProfileList'> type Props = NativeStackScreenProps<CommonNavigatorParams, 'ProfileList'>
export function ProfileListScreen(props: Props) { export function ProfileListScreen(props: Props) {
const {_} = useLingui()
const {name: handleOrDid, rkey} = props.route.params const {name: handleOrDid, rkey} = props.route.params
const {data: resolvedUri, error: resolveError} = useResolveUriQuery( const {data: resolvedUri, error: resolveError} = useResolveUriQuery(
AtUri.make(handleOrDid, 'app.bsky.graph.list', rkey).toString(), AtUri.make(handleOrDid, 'app.bsky.graph.list', rkey).toString(),
@ -78,7 +79,9 @@ export function ProfileListScreen(props: Props) {
return ( return (
<CenteredView> <CenteredView>
<ErrorScreen <ErrorScreen
error={`We're sorry, but we were unable to resolve this list. If this persists, please contact the list creator, @${handleOrDid}.`} error={_(
msg`We're sorry, but we were unable to resolve this list. If this persists, please contact the list creator, @${handleOrDid}.`,
)}
/> />
</CenteredView> </CenteredView>
) )
@ -260,10 +263,10 @@ function Header({rkey, list}: {rkey: string; list: AppBskyGraphDefs.ListView}) {
await pinFeed({uri: list.uri}) await pinFeed({uri: list.uri})
} }
} catch (e) { } catch (e) {
Toast.show('There was an issue contacting the server') Toast.show(_(msg`There was an issue contacting the server`))
logger.error('Failed to toggle pinned feed', {error: e}) logger.error('Failed to toggle pinned feed', {error: e})
} }
}, [list.uri, isPinned, pinFeed, unpinFeed]) }, [list.uri, isPinned, pinFeed, unpinFeed, _])
const onSubscribeMute = useCallback(() => { const onSubscribeMute = useCallback(() => {
openModal({ openModal({
@ -272,15 +275,17 @@ function Header({rkey, list}: {rkey: string; list: AppBskyGraphDefs.ListView}) {
message: _( message: _(
msg`Muting is private. Muted accounts can interact with you, but you will not see their posts or receive notifications from them.`, msg`Muting is private. Muted accounts can interact with you, but you will not see their posts or receive notifications from them.`,
), ),
confirmBtnText: 'Mute this List', confirmBtnText: _(msg`Mute this List`),
async onPressConfirm() { async onPressConfirm() {
try { try {
await listMuteMutation.mutateAsync({uri: list.uri, mute: true}) await listMuteMutation.mutateAsync({uri: list.uri, mute: true})
Toast.show('List muted') Toast.show(_(msg`List muted`))
track('Lists:Mute') track('Lists:Mute')
} catch { } catch {
Toast.show( Toast.show(
'There was an issue. Please check your internet connection and try again.', _(
msg`There was an issue. Please check your internet connection and try again.`,
),
) )
} }
}, },
@ -293,14 +298,16 @@ function Header({rkey, list}: {rkey: string; list: AppBskyGraphDefs.ListView}) {
const onUnsubscribeMute = useCallback(async () => { const onUnsubscribeMute = useCallback(async () => {
try { try {
await listMuteMutation.mutateAsync({uri: list.uri, mute: false}) await listMuteMutation.mutateAsync({uri: list.uri, mute: false})
Toast.show('List unmuted') Toast.show(_(msg`List unmuted`))
track('Lists:Unmute') track('Lists:Unmute')
} catch { } catch {
Toast.show( Toast.show(
'There was an issue. Please check your internet connection and try again.', _(
msg`There was an issue. Please check your internet connection and try again.`,
),
) )
} }
}, [list, listMuteMutation, track]) }, [list, listMuteMutation, track, _])
const onSubscribeBlock = useCallback(() => { const onSubscribeBlock = useCallback(() => {
openModal({ openModal({
@ -309,15 +316,17 @@ function Header({rkey, list}: {rkey: string; list: AppBskyGraphDefs.ListView}) {
message: _( message: _(
msg`Blocking is public. Blocked accounts cannot reply in your threads, mention you, or otherwise interact with you.`, msg`Blocking is public. Blocked accounts cannot reply in your threads, mention you, or otherwise interact with you.`,
), ),
confirmBtnText: 'Block this List', confirmBtnText: _(msg`Block this List`),
async onPressConfirm() { async onPressConfirm() {
try { try {
await listBlockMutation.mutateAsync({uri: list.uri, block: true}) await listBlockMutation.mutateAsync({uri: list.uri, block: true})
Toast.show('List blocked') Toast.show(_(msg`List blocked`))
track('Lists:Block') track('Lists:Block')
} catch { } catch {
Toast.show( Toast.show(
'There was an issue. Please check your internet connection and try again.', _(
msg`There was an issue. Please check your internet connection and try again.`,
),
) )
} }
}, },
@ -330,14 +339,16 @@ function Header({rkey, list}: {rkey: string; list: AppBskyGraphDefs.ListView}) {
const onUnsubscribeBlock = useCallback(async () => { const onUnsubscribeBlock = useCallback(async () => {
try { try {
await listBlockMutation.mutateAsync({uri: list.uri, block: false}) await listBlockMutation.mutateAsync({uri: list.uri, block: false})
Toast.show('List unblocked') Toast.show(_(msg`List unblocked`))
track('Lists:Unblock') track('Lists:Unblock')
} catch { } catch {
Toast.show( Toast.show(
'There was an issue. Please check your internet connection and try again.', _(
msg`There was an issue. Please check your internet connection and try again.`,
),
) )
} }
}, [list, listBlockMutation, track]) }, [list, listBlockMutation, track, _])
const onPressEdit = useCallback(() => { const onPressEdit = useCallback(() => {
openModal({ openModal({
@ -353,7 +364,7 @@ function Header({rkey, list}: {rkey: string; list: AppBskyGraphDefs.ListView}) {
message: _(msg`Are you sure?`), message: _(msg`Are you sure?`),
async onPressConfirm() { async onPressConfirm() {
await listDeleteMutation.mutateAsync({uri: list.uri}) await listDeleteMutation.mutateAsync({uri: list.uri})
Toast.show('List deleted') Toast.show(_(msg`List deleted`))
track('Lists:Delete') track('Lists:Delete')
if (navigation.canGoBack()) { if (navigation.canGoBack()) {
navigation.goBack() navigation.goBack()
@ -545,7 +556,7 @@ function Header({rkey, list}: {rkey: string; list: AppBskyGraphDefs.ListView}) {
<Button <Button
testID={isPinned ? 'unpinBtn' : 'pinBtn'} testID={isPinned ? 'unpinBtn' : 'pinBtn'}
type={isPinned ? 'default' : 'inverted'} type={isPinned ? 'default' : 'inverted'}
label={isPinned ? 'Unpin' : 'Pin to home'} label={isPinned ? _(msg`Unpin`) : _(msg`Pin to home`)}
onPress={onTogglePinned} onPress={onTogglePinned}
disabled={isPending} disabled={isPending}
/> />
@ -554,14 +565,14 @@ function Header({rkey, list}: {rkey: string; list: AppBskyGraphDefs.ListView}) {
<Button <Button
testID="unblockBtn" testID="unblockBtn"
type="default" type="default"
label="Unblock" label={_(msg`Unblock`)}
onPress={onUnsubscribeBlock} onPress={onUnsubscribeBlock}
/> />
) : isMuting ? ( ) : isMuting ? (
<Button <Button
testID="unmuteBtn" testID="unmuteBtn"
type="default" type="default"
label="Unmute" label={_(msg`Unmute`)}
onPress={onUnsubscribeMute} onPress={onUnsubscribeMute}
/> />
) : ( ) : (
@ -603,6 +614,7 @@ const FeedSection = React.forwardRef<SectionRef, FeedSectionProps>(
const [hasNew, setHasNew] = React.useState(false) const [hasNew, setHasNew] = React.useState(false)
const [isScrolledDown, setIsScrolledDown] = React.useState(false) const [isScrolledDown, setIsScrolledDown] = React.useState(false)
const isScreenFocused = useIsFocused() const isScreenFocused = useIsFocused()
const {_} = useLingui()
const onScrollToTop = useCallback(() => { const onScrollToTop = useCallback(() => {
scrollElRef.current?.scrollToOffset({ scrollElRef.current?.scrollToOffset({
@ -624,8 +636,8 @@ const FeedSection = React.forwardRef<SectionRef, FeedSectionProps>(
}, [onScrollToTop, isScreenFocused]) }, [onScrollToTop, isScreenFocused])
const renderPostsEmpty = useCallback(() => { const renderPostsEmpty = useCallback(() => {
return <EmptyState icon="feed" message="This feed is empty!" /> return <EmptyState icon="feed" message={_(msg`This feed is empty!`)} />
}, []) }, [_])
return ( return (
<View> <View>
@ -643,7 +655,7 @@ const FeedSection = React.forwardRef<SectionRef, FeedSectionProps>(
{(isScrolledDown || hasNew) && ( {(isScrolledDown || hasNew) && (
<LoadLatestBtn <LoadLatestBtn
onPress={onScrollToTop} onPress={onScrollToTop}
label="Load new posts" label={_(msg`Load new posts`)}
showIndicator={hasNew} showIndicator={hasNew}
/> />
)} )}
@ -721,15 +733,30 @@ const AboutSection = React.forwardRef<SectionRef, AboutSectionProps>(
</Text> </Text>
)} )}
<Text type="md" style={[pal.textLight]} numberOfLines={1}> <Text type="md" style={[pal.textLight]} numberOfLines={1}>
{isCurateList ? 'User list' : 'Moderation list'} by{' '} {isCurateList ? (
{isOwner ? ( isOwner ? (
'you' <Trans>User list by you</Trans>
) : ( ) : (
<Trans>
User list by{' '}
<TextLink <TextLink
text={sanitizeHandle(list.creator.handle || '', '@')} text={sanitizeHandle(list.creator.handle || '', '@')}
href={makeProfileLink(list.creator)} href={makeProfileLink(list.creator)}
style={pal.textLight} style={pal.textLight}
/> />
</Trans>
)
) : isOwner ? (
<Trans>Moderation list by you</Trans>
) : (
<Trans>
Moderation list by{' '}
<TextLink
text={sanitizeHandle(list.creator.handle || '', '@')}
href={makeProfileLink(list.creator)}
style={pal.textLight}
/>
</Trans>
)} )}
</Text> </Text>
</View> </View>
@ -782,11 +809,11 @@ const AboutSection = React.forwardRef<SectionRef, AboutSectionProps>(
return ( return (
<EmptyState <EmptyState
icon="users-slash" icon="users-slash"
message="This list is empty!" message={_(msg`This list is empty!`)}
style={{paddingTop: 40}} style={{paddingTop: 40}}
/> />
) )
}, []) }, [_])
return ( return (
<View> <View>
@ -802,7 +829,7 @@ const AboutSection = React.forwardRef<SectionRef, AboutSectionProps>(
{isScrolledDown && ( {isScrolledDown && (
<LoadLatestBtn <LoadLatestBtn
onPress={onScrollToTop} onPress={onScrollToTop}
label="Scroll to top" label={_(msg`Scroll to top`)}
showIndicator={false} showIndicator={false}
/> />
)} )}
@ -846,7 +873,7 @@ function ErrorScreen({error}: {error: string}) {
<Button <Button
type="default" type="default"
accessibilityLabel={_(msg`Go Back`)} accessibilityLabel={_(msg`Go Back`)}
accessibilityHint="Return to previous page" accessibilityHint={_(msg`Return to previous page`)}
onPress={onPressBack} onPress={onPressBack}
style={{flexShrink: 1}}> style={{flexShrink: 1}}>
<Text type="button" style={pal.text}> <Text type="button" style={pal.text}>

View file

@ -160,7 +160,7 @@ export function SavedFeeds({}: Props) {
type="sm" type="sm"
style={pal.link} style={pal.link}
href="https://github.com/bluesky-social/feed-generator" href="https://github.com/bluesky-social/feed-generator"
text="See this guide" text={_(msg`See this guide`)}
/>{' '} />{' '}
for more information. for more information.
</Trans> </Trans>
@ -188,6 +188,7 @@ function ListItem({
>['reset'] >['reset']
}) { }) {
const pal = usePalette('default') const pal = usePalette('default')
const {_} = useLingui()
const {isPending: isPinPending, mutateAsync: pinFeed} = usePinFeedMutation() const {isPending: isPinPending, mutateAsync: pinFeed} = usePinFeedMutation()
const {isPending: isUnpinPending, mutateAsync: unpinFeed} = const {isPending: isUnpinPending, mutateAsync: unpinFeed} =
useUnpinFeedMutation() useUnpinFeedMutation()
@ -205,10 +206,10 @@ function ListItem({
await pinFeed({uri: feedUri}) await pinFeed({uri: feedUri})
} }
} catch (e) { } catch (e) {
Toast.show('There was an issue contacting the server') Toast.show(_(msg`There was an issue contacting the server`))
logger.error('Failed to toggle pinned feed', {error: e}) logger.error('Failed to toggle pinned feed', {error: e})
} }
}, [feedUri, isPinned, pinFeed, unpinFeed, resetSaveFeedsMutationState]) }, [feedUri, isPinned, pinFeed, unpinFeed, resetSaveFeedsMutationState, _])
const onPressUp = React.useCallback(async () => { const onPressUp = React.useCallback(async () => {
if (!isPinned) return if (!isPinned) return
@ -227,10 +228,10 @@ function ListItem({
index: pinned.indexOf(feedUri), index: pinned.indexOf(feedUri),
}) })
} catch (e) { } catch (e) {
Toast.show('There was an issue contacting the server') Toast.show(_(msg`There was an issue contacting the server`))
logger.error('Failed to set pinned feed order', {error: e}) logger.error('Failed to set pinned feed order', {error: e})
} }
}, [feedUri, isPinned, setSavedFeeds, currentFeeds]) }, [feedUri, isPinned, setSavedFeeds, currentFeeds, _])
const onPressDown = React.useCallback(async () => { const onPressDown = React.useCallback(async () => {
if (!isPinned) return if (!isPinned) return
@ -248,10 +249,10 @@ function ListItem({
index: pinned.indexOf(feedUri), index: pinned.indexOf(feedUri),
}) })
} catch (e) { } catch (e) {
Toast.show('There was an issue contacting the server') Toast.show(_(msg`There was an issue contacting the server`))
logger.error('Failed to set pinned feed order', {error: e}) logger.error('Failed to set pinned feed order', {error: e})
} }
}, [feedUri, isPinned, setSavedFeeds, currentFeeds]) }, [feedUri, isPinned, setSavedFeeds, currentFeeds, _])
return ( return (
<Pressable <Pressable

Some files were not shown because too many files have changed in this diff Show more