Internationalize more strings (#2440)

Co-authored-by: Ansh <anshnanda10@gmail.com>
zio/stable
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 * as persisted from '#/state/persisted'
import {Splash} from '#/Splash'
import {msg} from '@lingui/macro'
import {useLingui} from '@lingui/react'
SplashScreen.preventAutoHideAsync()
@ -46,17 +48,18 @@ function InnerApp() {
const colorMode = useColorMode()
const {isInitialLoad, currentAccount} = useSession()
const {resumeSession} = useSessionApi()
const {_} = useLingui()
// init
useEffect(() => {
notifications.init(queryClient)
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
resumeSession(account)
}, [resumeSession])
}, [resumeSession, _])
return (
<SafeAreaProvider initialMetrics={initialWindowMetrics}>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -36,7 +36,7 @@ export function Step3({
onChange={value => uiDispatch({type: 'set-handle', value})}
// TODO: Add explicit text label
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]}>
<Trans>Your full handle will be</Trans>{' '}

View File

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

View File

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

View File

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

View File

@ -145,7 +145,7 @@ export const LoginForm = ({
onPress={onPressSelectService}
accessibilityRole="button"
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]}>
{toNiceDomain(serviceUrl)}
</Text>
@ -190,7 +190,9 @@ export const LoginForm = ({
}
editable={!isProcessing}
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 style={[pal.borderDark, styles.groupContent]}>
@ -221,8 +223,8 @@ export const LoginForm = ({
accessibilityLabel={_(msg`Password`)}
accessibilityHint={
identifier === ''
? 'Input your password'
: `Input the password tied to ${identifier}`
? _(msg`Input your password`)
: _(msg`Input the password tied to ${identifier}`)
}
/>
<TouchableOpacity
@ -231,7 +233,7 @@ export const LoginForm = ({
onPress={onPressForgotPassword}
accessibilityRole="button"
accessibilityLabel={_(msg`Forgot password`)}
accessibilityHint="Opens password reset form">
accessibilityHint={_(msg`Opens password reset form`)}>
<Text style={pal.link}>
<Trans>Forgot</Trans>
</Text>
@ -261,7 +263,7 @@ export const LoginForm = ({
onPress={onPressRetryConnect}
accessibilityRole="button"
accessibilityLabel={_(msg`Retry`)}
accessibilityHint="Retries login">
accessibilityHint={_(msg`Retries login`)}>
<Text type="xl-bold" style={[pal.link, s.pr5]}>
<Trans>Retry</Trans>
</Text>
@ -281,7 +283,7 @@ export const LoginForm = ({
onPress={onPressNext}
accessibilityRole="button"
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]}>
<Trans>Next</Trans>
</Text>

View File

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

View File

@ -95,7 +95,7 @@ export const SetNewPasswordForm = ({
<TextInput
testID="resetCodeInput"
style={[pal.text, styles.textInput]}
placeholder="Reset code"
placeholder={_(msg`Reset code`)}
placeholderTextColor={pal.colors.textLight}
autoCapitalize="none"
autoCorrect={false}
@ -106,7 +106,9 @@ export const SetNewPasswordForm = ({
editable={!isProcessing}
accessible={true}
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 style={[pal.borderDark, styles.groupContent]}>
@ -117,7 +119,7 @@ export const SetNewPasswordForm = ({
<TextInput
testID="newPasswordInput"
style={[pal.text, styles.textInput]}
placeholder="New password"
placeholder={_(msg`New password`)}
placeholderTextColor={pal.colors.textLight}
autoCapitalize="none"
autoCorrect={false}
@ -128,7 +130,7 @@ export const SetNewPasswordForm = ({
editable={!isProcessing}
accessible={true}
accessibilityLabel={_(msg`Password`)}
accessibilityHint="Input new password"
accessibilityHint={_(msg`Input new password`)}
/>
</View>
</View>
@ -161,7 +163,7 @@ export const SetNewPasswordForm = ({
onPress={onPressNext}
accessibilityRole="button"
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]}>
<Trans>Next</Trans>
</Text>

View File

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

View File

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

View File

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

View File

@ -260,7 +260,11 @@ export const ComposePost = observer(function ComposePost({
setLangPrefs.savePostLanguageToHistory()
onPost?.()
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(
@ -269,7 +273,9 @@ export const ComposePost = observer(function ComposePost({
(!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 hasMedia = gallery.size > 0 || Boolean(extLink)
@ -291,7 +297,9 @@ export const ComposePost = observer(function ComposePost({
onAccessibilityEscape={onPressCancel}
accessibilityRole="button"
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]}>
<Trans>Cancel</Trans>
</Text>
@ -323,7 +331,7 @@ export const ComposePost = observer(function ComposePost({
onPress={onPressPublish}
accessibilityRole="button"
accessibilityLabel={
replyTo ? 'Publish reply' : 'Publish post'
replyTo ? _(msg`Publish reply`) : _(msg`Publish post`)
}
accessibilityHint="">
<LinearGradient
@ -335,14 +343,18 @@ export const ComposePost = observer(function ComposePost({
end={{x: 1, y: 1}}
style={styles.postBtn}>
<Text style={[s.white, s.f16, s.bold]}>
{replyTo ? 'Reply' : 'Post'}
{replyTo ? (
<Trans context="action">Reply</Trans>
) : (
<Trans context="action">Post</Trans>
)}
</Text>
</LinearGradient>
</TouchableOpacity>
) : (
<View style={[styles.postBtn, pal.btn]}>
<Text style={[pal.textLight, s.f16, s.bold]}>
<Trans>Post</Trans>
<Trans context="action">Post</Trans>
</Text>
</View>
)}
@ -400,7 +412,9 @@ export const ComposePost = observer(function ComposePost({
onError={setError}
accessible={true}
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>
@ -429,7 +443,9 @@ export const ComposePost = observer(function ComposePost({
onPress={() => onPressAddLinkCard(url)}
accessibilityRole="button"
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}>
<Trans>Add link card:</Trans>{' '}
<Text style={[pal.link, s.ml5]}>{toShortUrl(url)}</Text>

View File

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

View File

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

View File

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

View File

@ -41,7 +41,7 @@ export function SelectPhotoBtn({gallery}: Props) {
hitSlop={HITSLOP_10}
accessibilityRole="button"
accessibilityLabel={_(msg`Gallery`)}
accessibilityHint="Opens device photo gallery">
accessibilityHint={_(msg`Opens device photo gallery`)}>
<FontAwesomeIcon
icon={['far', 'image']}
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 {UserAvatar} from 'view/com/util/UserAvatar'
import {useGrapheme} from '../hooks/useGrapheme'
import {Trans} from '@lingui/macro'
interface MentionListRef {
onKeyDown: (props: SuggestionKeyDownProps) => boolean
@ -187,7 +188,7 @@ const MentionList = forwardRef<MentionListRef, SuggestionProps>(
})
) : (
<Text type="sm" style={[pal.text, styles.noResult]}>
No result
<Trans>No result</Trans>
</Text>
)}
</View>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -110,7 +110,7 @@ function LightboxInner({
onPress={onClose}
accessibilityRole="button"
accessibilityLabel={_(msg`Close image viewer`)}
accessibilityHint="Exits image view"
accessibilityHint={_(msg`Exits image view`)}
onAccessibilityEscape={onClose}>
<View style={styles.imageCenterer}>
<Image
@ -154,7 +154,9 @@ function LightboxInner({
<View style={styles.footer}>
<Pressable
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={() => {
setAltExpanded(!isAltExpanded)
}}>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -10,6 +10,8 @@ import {isWeb} from 'platform/detection'
import {listUriToHref} from 'lib/strings/url-helpers'
import {Button} from '../util/forms/Button'
import {useModalControls} from '#/state/modals'
import {useLingui} from '@lingui/react'
import {Trans, msg} from '@lingui/macro'
export const snapPoints = [300]
@ -23,19 +25,21 @@ export function Component({
const {closeModal} = useModalControls()
const {isMobile} = useWebMediaQueries()
const pal = usePalette('default')
const {_} = useLingui()
let name
let description
if (!moderation.cause) {
name = 'Content Warning'
description =
'Moderator has chosen to set a general warning on the content.'
name = _(msg`Content Warning`)
description = _(
msg`Moderator has chosen to set a general warning on the content.`,
)
} else if (moderation.cause.type === 'blocking') {
if (moderation.cause.source.type === 'list') {
const list = moderation.cause.source.list
name = 'User Blocked by List'
name = _(msg`User Blocked by List`)
description = (
<>
<Trans>
This user is included in the{' '}
<TextLink
type="2xl"
@ -44,25 +48,30 @@ export function Component({
style={pal.link}
/>{' '}
list which you have blocked.
</>
</Trans>
)
} else {
name = 'User Blocked'
description = 'You have blocked this user. You cannot view their content.'
name = _(msg`User Blocked`)
description = _(
msg`You have blocked this user. You cannot view their content.`,
)
}
} else if (moderation.cause.type === 'blocked-by') {
name = 'User Blocks You'
description = 'This user has blocked you. You cannot view their content.'
name = _(msg`User Blocks You`)
description = _(
msg`This user has blocked you. You cannot view their content.`,
)
} else if (moderation.cause.type === 'block-other') {
name = 'Content Not Available'
description =
'This content is not available because one of the users involved has blocked the other.'
name = _(msg`Content Not Available`)
description = _(
msg`This content is not available because one of the users involved has blocked the other.`,
)
} else if (moderation.cause.type === 'muted') {
if (moderation.cause.source.type === 'list') {
const list = moderation.cause.source.list
name = <>Account Muted by List</>
name = _(msg`Account Muted by List`)
description = (
<>
<Trans>
This user is included the{' '}
<TextLink
type="2xl"
@ -71,11 +80,11 @@ export function Component({
style={pal.link}
/>{' '}
list which you have muted.
</>
</Trans>
)
} else {
name = 'Account Muted'
description = 'You have muted this user.'
name = _(msg`Account Muted`)
description = _(msg`You have muted this user.`)
}
} else {
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 {cleanError} from '#/lib/strings/errors'
import {useProfileShadow} from '#/state/cache/profile-shadow'
import {Trans, msg} from '@lingui/macro'
import {useLingui} from '@lingui/react'
export const snapPoints = [520, '100%']
export function Component({did}: {did: string}) {
const pal = usePalette('default')
const {_} = useLingui()
const moderationOpts = useModerationOpts()
const {
data: profile,
@ -43,7 +46,7 @@ export function Component({did}: {did: string}) {
if (profileError) {
return (
<ErrorScreen
title="Oops!"
title={_(msg`Oops!`)}
message={cleanError(profileError)}
onPressTryAgain={refetchProfile}
/>
@ -55,8 +58,8 @@ export function Component({did}: {did: string}) {
// should never happen
return (
<ErrorScreen
title="Oops!"
message="Something went wrong and we're not sure what."
title={_(msg`Oops!`)}
message={_(msg`Something went wrong and we're not sure what.`)}
onPressTryAgain={refetchProfile}
/>
)
@ -104,7 +107,7 @@ function ComponentLoaded({
<>
<InfoCircleIcon size={21} style={pal.textLight} />
<ThemedText type="xl" fg="light">
Swipe up to see more
<Trans>Swipe up to see more</Trans>
</ThemedText>
</>
)}

View File

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

View File

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

View File

@ -101,7 +101,9 @@ export function Component({onSelect}: {onSelect: (url: string) => void}) {
onChangeText={setCustomUrl}
accessibilityLabel={_(msg`Custom domain`)}
// 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
testID="customServerSelectBtn"
@ -110,7 +112,7 @@ export function Component({onSelect}: {onSelect: (url: string) => void}) {
accessibilityRole="button"
accessibilityLabel={`Confirm service. ${
customUrl === ''
? 'Button disabled. Input custom domain to proceed.'
? _(msg`Button disabled. Input custom domain to proceed.`)
: ''
}`}
accessibilityHint=""

View File

@ -62,7 +62,9 @@ function SwitchAccountCard({account}: {account: SessionAccount}) {
onPress={isSwitchingAccounts ? undefined : onPressSignout}
accessibilityRole="button"
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}>
<Trans>Sign out</Trans>
</Text>
@ -92,8 +94,8 @@ function SwitchAccountCard({account}: {account: SessionAccount}) {
isSwitchingAccounts ? undefined : () => onPressSwitchAccount(account)
}
accessibilityRole="button"
accessibilityLabel={`Switch to ${account.handle}`}
accessibilityHint="Switches the account you are logged in to">
accessibilityLabel={_(msg`Switch to ${account.handle}`)}
accessibilityHint={_(msg`Switches the account you are logged in to`)}>
{contents}
</TouchableOpacity>
)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -222,7 +222,11 @@ function PostThreadLoaded({
const renderItem = React.useCallback(
({item, index}: {item: YieldedItem; index: number}) => {
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) {
return (
<View style={styles.parentSpinner}>
@ -393,7 +397,7 @@ function PostThreadBlocked() {
style={[pal.link as FontAwesomeIconStyle, s.mr5]}
size={14}
/>
Back
<Trans context="action">Back</Trans>
</Text>
</TouchableOpacity>
</View>

View File

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

View File

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

View File

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

View File

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

View File

@ -38,6 +38,7 @@ export function FeedErrorMessage({
error?: Error
onPressTryAgain: () => void
}) {
const {_: _l} = useLingui()
const knownError = React.useMemo(
() => detectKnownError(feedDesc, error),
[feedDesc, error],
@ -60,7 +61,7 @@ export function FeedErrorMessage({
return (
<EmptyState
icon="ban"
message="Posts hidden"
message={_l(msgLingui`Posts hidden`)}
style={{paddingVertical: 40}}
/>
)
@ -134,7 +135,9 @@ function FeedgenErrorMessage({
await removeFeed({uri})
} catch (err) {
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})
}
@ -160,20 +163,20 @@ function FeedgenErrorMessage({
{knownError === KnownError.FeedgenDoesNotExist && (
<Button
type="inverted"
label="Remove feed"
label={_l(msgLingui`Remove feed`)}
onPress={onRemoveFeed}
/>
)}
<Button
type="default-light"
label="View profile"
label={_l(msgLingui`View profile`)}
onPress={onViewProfile}
/>
</View>
)
}
}
}, [knownError, onViewProfile, onRemoveFeed])
}, [knownError, onViewProfile, onRemoveFeed, _l])
return (
<View
@ -191,7 +194,7 @@ function FeedgenErrorMessage({
{rawError?.message && (
<Text style={pal.textLight}>
<Trans>Message from server</Trans>: {rawError.message}
<Trans>Message from server: {rawError.message}</Trans>
</Text>
)}

View File

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

View File

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

View File

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

View File

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

View File

@ -5,6 +5,8 @@ import {Button, ButtonType} from '../util/forms/Button'
import * as Toast from '../util/Toast'
import {useProfileFollowMutationQueue} from '#/state/queries/profile'
import {Shadow} from '#/state/cache/types'
import {useLingui} from '@lingui/react'
import {msg} from '@lingui/macro'
export function FollowButton({
unfollowedType = 'inverted',
@ -18,13 +20,14 @@ export function FollowButton({
labelStyle?: StyleProp<TextStyle>
}) {
const [queueFollow, queueUnfollow] = useProfileFollowMutationQueue(profile)
const {_} = useLingui()
const onPressFollow = async () => {
try {
await queueFollow()
} catch (e: any) {
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()
} catch (e: any) {
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}
labelStyle={labelStyle}
onPress={onPressUnfollow}
label="Unfollow"
label={_(msg({message: 'Unfollow', context: 'action'}))}
/>
)
} else {
@ -58,7 +61,7 @@ export function FollowButton({
type={unfollowedType}
labelStyle={labelStyle}
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 {useProfileShadow} from '#/state/cache/profile-shadow'
import {useSession} from '#/state/session'
import {Trans} from '@lingui/macro'
export function ProfileCard({
testID,
@ -137,7 +138,7 @@ function ProfileCardPills({
{followedBy && (
<View style={[s.mt5, pal.btn, styles.pill]}>
<Text type="xs" style={pal.text}>
Follows You
<Trans>Follows You</Trans>
</Text>
</View>
)}
@ -190,8 +191,10 @@ function FollowersList({
style={[styles.followsByDesc, pal.textLight]}
numberOfLines={2}
lineHeight={1.2}>
Followed by{' '}
{followersWithMods.map(({f}) => f.displayName || f.handle).join(', ')}
<Trans>
Followed by{' '}
{followersWithMods.map(({f}) => f.displayName || f.handle).join(', ')}
</Trans>
</Text>
{followersWithMods.slice(0, 3).map(({f, mod}) => (
<View key={f.did} style={styles.followedByAviContainer}>

View File

@ -192,14 +192,16 @@ let ProfileHeaderLoaded = ({
track('ProfileHeader:FollowButtonClicked')
await queueFollow()
Toast.show(
`Following ${sanitizeDisplayName(
profile.displayName || profile.handle,
)}`,
_(
msg`Following ${sanitizeDisplayName(
profile.displayName || profile.handle,
)}`,
),
)
} catch (e: any) {
if (e?.name !== 'AbortError') {
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')
await queueUnfollow()
Toast.show(
`No longer following ${sanitizeDisplayName(
profile.displayName || profile.handle,
)}`,
_(
msg`No longer following ${sanitizeDisplayName(
profile.displayName || profile.handle,
)}`,
),
)
} catch (e: any) {
if (e?.name !== 'AbortError') {
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')
try {
await queueMute()
Toast.show('Account muted')
Toast.show(_(msg`Account muted`))
} catch (e: any) {
if (e?.name !== 'AbortError') {
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 () => {
track('ProfileHeader:UnmuteAccountButtonClicked')
try {
await queueUnmute()
Toast.show('Account unmuted')
Toast.show(_(msg`Account unmuted`))
} catch (e: any) {
if (e?.name !== 'AbortError') {
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 () => {
track('ProfileHeader:BlockAccountButtonClicked')
@ -286,11 +290,11 @@ let ProfileHeaderLoaded = ({
onPressConfirm: async () => {
try {
await queueBlock()
Toast.show('Account blocked')
Toast.show(_(msg`Account blocked`))
} catch (e: any) {
if (e?.name !== 'AbortError') {
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 () => {
try {
await queueUnblock()
Toast.show('Account unblocked')
Toast.show(_(msg`Account unblocked`))
} catch (e: any) {
if (e?.name !== 'AbortError') {
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]}
accessibilityRole="button"
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}>
<Trans>Edit Profile</Trans>
</Text>
@ -466,7 +472,7 @@ let ProfileHeaderLoaded = ({
accessibilityLabel={_(msg`Unblock`)}
accessibilityHint="">
<Text type="button" style={[pal.text, s.bold]}>
<Trans>Unblock</Trans>
<Trans context="action">Unblock</Trans>
</Text>
</TouchableOpacity>
)
@ -488,8 +494,12 @@ let ProfileHeaderLoaded = ({
},
]}
accessibilityRole="button"
accessibilityLabel={`Show follows similar to ${profile.handle}`}
accessibilityHint={`Shows a list of users similar to this user.`}>
accessibilityLabel={_(
msg`Show follows similar to ${profile.handle}`,
)}
accessibilityHint={_(
msg`Shows a list of users similar to this user.`,
)}>
<FontAwesomeIcon
icon="user-plus"
style={[
@ -511,8 +521,10 @@ let ProfileHeaderLoaded = ({
onPress={onPressUnfollow}
style={[styles.btn, styles.mainBtn, pal.btn]}
accessibilityRole="button"
accessibilityLabel={`Unfollow ${profile.handle}`}
accessibilityHint={`Hides posts from ${profile.handle} in your feed`}>
accessibilityLabel={_(msg`Unfollow ${profile.handle}`)}
accessibilityHint={_(
msg`Hides posts from ${profile.handle} in your feed`,
)}>
<FontAwesomeIcon
icon="check"
style={[pal.text, s.mr5]}
@ -528,8 +540,10 @@ let ProfileHeaderLoaded = ({
onPress={onPressFollow}
style={[styles.btn, styles.mainBtn, palInverted.view]}
accessibilityRole="button"
accessibilityLabel={`Follow ${profile.handle}`}
accessibilityHint={`Shows posts from ${profile.handle} in your feed`}>
accessibilityLabel={_(msg`Follow ${profile.handle}`)}
accessibilityHint={_(
msg`Shows posts from ${profile.handle} in your feed`,
)}>
<FontAwesomeIcon
icon="plus"
style={[palInverted.text, s.mr5]}
@ -580,7 +594,7 @@ let ProfileHeaderLoaded = ({
invalidHandle ? styles.invalidHandle : undefined,
styles.handle,
]}>
{invalidHandle ? '⚠Invalid Handle' : `@${profile.handle}`}
{invalidHandle ? _(msg`⚠Invalid Handle`) : `@${profile.handle}`}
</ThemedText>
</View>
{!blockHide && (
@ -597,7 +611,7 @@ let ProfileHeaderLoaded = ({
}
asAnchor
accessibilityLabel={`${followers} ${pluralizedFollowers}`}
accessibilityHint={'Opens followers list'}>
accessibilityHint={_(msg`Opens followers list`)}>
<Text type="md" style={[s.bold, pal.text]}>
{followers}{' '}
</Text>
@ -615,14 +629,16 @@ let ProfileHeaderLoaded = ({
})
}
asAnchor
accessibilityLabel={`${following} following`}
accessibilityHint={'Opens following list'}>
<Text type="md" style={[s.bold, pal.text]}>
{following}{' '}
</Text>
<Text type="md" style={[pal.textLight]}>
<Trans>following</Trans>
</Text>
accessibilityLabel={_(msg`${following} following`)}
accessibilityHint={_(msg`Opens following list`)}>
<Trans>
<Text type="md" style={[s.bold, pal.text]}>
{following}{' '}
</Text>
<Text type="md" style={[pal.textLight]}>
following
</Text>
</Trans>
</Link>
<Text type="md" style={[s.bold, pal.text]}>
{formatCount(profile.postsCount || 0)}{' '}
@ -682,7 +698,7 @@ let ProfileHeaderLoaded = ({
testID="profileHeaderAviButton"
onPress={onPressAvi}
accessibilityRole="image"
accessibilityLabel={`View ${profile.handle}'s avatar`}
accessibilityLabel={_(msg`View ${profile.handle}'s avatar`)}
accessibilityHint="">
<View
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 {useProfileShadow} from '#/state/cache/profile-shadow'
import {useProfileFollowMutationQueue} from '#/state/queries/profile'
import {Trans} from '@lingui/macro'
const OUTER_PADDING = 10
const INNER_PADDING = 14
@ -60,7 +61,7 @@ export function ProfileHeaderSuggestedFollows({
paddingRight: INNER_PADDING / 2,
}}>
<Text type="sm-bold" style={[pal.textLight]}>
Suggested for you
<Trans>Suggested for you</Trans>
</Text>
<Pressable

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -328,7 +328,7 @@ export function FeedsScreen(_props: Props) {
hitSlop={10}
accessibilityRole="button"
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} />
</Link>
)

View File

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

View File

@ -50,7 +50,9 @@ export function LogScreen({}: NativeStackScreenProps<
style={[styles.entry, pal.border, pal.view]}
onPress={toggler(entry.id)}
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' ? (
<FontAwesomeIcon icon="info" />
) : (

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -214,11 +214,21 @@ export function ProfileFeedScreenInner({
}
} catch (err) {
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})
}
}, [feedInfo, isSaved, saveFeed, removeFeed, resetSaveFeed, resetRemoveFeed])
}, [
feedInfo,
isSaved,
saveFeed,
removeFeed,
resetSaveFeed,
resetRemoveFeed,
_,
])
const onTogglePinned = React.useCallback(async () => {
try {
@ -232,10 +242,10 @@ export function ProfileFeedScreenInner({
resetPinFeed()
}
} 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})
}
}, [isPinned, feedInfo, pinFeed, unpinFeed, resetPinFeed, resetUnpinFeed])
}, [isPinned, feedInfo, pinFeed, unpinFeed, resetPinFeed, resetUnpinFeed, _])
const onPressShare = React.useCallback(() => {
const url = toShareUrl(feedInfo.route.href)
@ -341,7 +351,7 @@ export function ProfileFeedScreenInner({
<Button
disabled={isSavePending || isRemovePending}
type="default"
label={isSaved ? 'Unsave' : 'Save'}
label={isSaved ? _(msg`Unsave`) : _(msg`Save`)}
onPress={onToggleSaved}
style={styles.btn}
/>
@ -349,7 +359,7 @@ export function ProfileFeedScreenInner({
testID={isPinned ? 'unpinBtn' : 'pinBtn'}
disabled={isPinPending || isUnpinPending}
type={isPinned ? 'default' : 'inverted'}
label={isPinned ? 'Unpin' : 'Pin to home'}
label={isPinned ? _(msg`Unpin`) : _(msg`Pin to home`)}
onPress={onTogglePinned}
style={styles.btn}
/>
@ -444,6 +454,7 @@ interface FeedSectionProps {
}
const FeedSection = React.forwardRef<SectionRef, FeedSectionProps>(
function FeedSectionImpl({feed, headerHeight, scrollElRef, isFocused}, ref) {
const {_} = useLingui()
const [hasNew, setHasNew] = React.useState(false)
const [isScrolledDown, setIsScrolledDown] = React.useState(false)
const queryClient = useQueryClient()
@ -470,8 +481,8 @@ const FeedSection = React.forwardRef<SectionRef, FeedSectionProps>(
}, [onScrollToTop, isScreenFocused])
const renderPostsEmpty = useCallback(() => {
return <EmptyState icon="feed" message="This feed is empty!" />
}, [])
return <EmptyState icon="feed" message={_(msg`This feed is empty!`)} />
}, [_])
return (
<View>
@ -488,7 +499,7 @@ const FeedSection = React.forwardRef<SectionRef, FeedSectionProps>(
{(isScrolledDown || hasNew) && (
<LoadLatestBtn
onPress={onScrollToTop}
label="Load new posts"
label={_(msg`Load new posts`)}
showIndicator={hasNew}
/>
)}
@ -542,11 +553,13 @@ function AboutSection({
}
} catch (err) {
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})
}
}, [likeUri, isLiked, feedInfo, likeFeed, unlikeFeed, track])
}, [likeUri, isLiked, feedInfo, likeFeed, unlikeFeed, track, _])
return (
<ScrollView
@ -597,24 +610,28 @@ function AboutSection({
{typeof likeCount === 'number' && (
<TextLink
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]}
/>
)}
</View>
<Text type="md" style={[pal.textLight]} numberOfLines={1}>
Created by{' '}
{isOwner ? (
'you'
<Trans>Created by you</Trans>
) : (
<TextLink
text={sanitizeHandle(feedInfo.creatorHandle, '@')}
href={makeProfileLink({
did: feedInfo.creatorDid,
handle: feedInfo.creatorHandle,
})}
style={pal.textLight}
/>
<Trans>
Created by{' '}
<TextLink
text={sanitizeHandle(feedInfo.creatorHandle, '@')}
href={makeProfileLink({
did: feedInfo.creatorDid,
handle: feedInfo.creatorHandle,
})}
style={pal.textLight}
/>
</Trans>
)}
</Text>
</View>

View File

@ -68,6 +68,7 @@ interface SectionRef {
type Props = NativeStackScreenProps<CommonNavigatorParams, 'ProfileList'>
export function ProfileListScreen(props: Props) {
const {_} = useLingui()
const {name: handleOrDid, rkey} = props.route.params
const {data: resolvedUri, error: resolveError} = useResolveUriQuery(
AtUri.make(handleOrDid, 'app.bsky.graph.list', rkey).toString(),
@ -78,7 +79,9 @@ export function ProfileListScreen(props: Props) {
return (
<CenteredView>
<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>
)
@ -260,10 +263,10 @@ function Header({rkey, list}: {rkey: string; list: AppBskyGraphDefs.ListView}) {
await pinFeed({uri: list.uri})
}
} 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})
}
}, [list.uri, isPinned, pinFeed, unpinFeed])
}, [list.uri, isPinned, pinFeed, unpinFeed, _])
const onSubscribeMute = useCallback(() => {
openModal({
@ -272,15 +275,17 @@ function Header({rkey, list}: {rkey: string; list: AppBskyGraphDefs.ListView}) {
message: _(
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() {
try {
await listMuteMutation.mutateAsync({uri: list.uri, mute: true})
Toast.show('List muted')
Toast.show(_(msg`List muted`))
track('Lists:Mute')
} catch {
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 () => {
try {
await listMuteMutation.mutateAsync({uri: list.uri, mute: false})
Toast.show('List unmuted')
Toast.show(_(msg`List unmuted`))
track('Lists:Unmute')
} catch {
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(() => {
openModal({
@ -309,15 +316,17 @@ function Header({rkey, list}: {rkey: string; list: AppBskyGraphDefs.ListView}) {
message: _(
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() {
try {
await listBlockMutation.mutateAsync({uri: list.uri, block: true})
Toast.show('List blocked')
Toast.show(_(msg`List blocked`))
track('Lists:Block')
} catch {
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 () => {
try {
await listBlockMutation.mutateAsync({uri: list.uri, block: false})
Toast.show('List unblocked')
Toast.show(_(msg`List unblocked`))
track('Lists:Unblock')
} catch {
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(() => {
openModal({
@ -353,7 +364,7 @@ function Header({rkey, list}: {rkey: string; list: AppBskyGraphDefs.ListView}) {
message: _(msg`Are you sure?`),
async onPressConfirm() {
await listDeleteMutation.mutateAsync({uri: list.uri})
Toast.show('List deleted')
Toast.show(_(msg`List deleted`))
track('Lists:Delete')
if (navigation.canGoBack()) {
navigation.goBack()
@ -545,7 +556,7 @@ function Header({rkey, list}: {rkey: string; list: AppBskyGraphDefs.ListView}) {
<Button
testID={isPinned ? 'unpinBtn' : 'pinBtn'}
type={isPinned ? 'default' : 'inverted'}
label={isPinned ? 'Unpin' : 'Pin to home'}
label={isPinned ? _(msg`Unpin`) : _(msg`Pin to home`)}
onPress={onTogglePinned}
disabled={isPending}
/>
@ -554,14 +565,14 @@ function Header({rkey, list}: {rkey: string; list: AppBskyGraphDefs.ListView}) {
<Button
testID="unblockBtn"
type="default"
label="Unblock"
label={_(msg`Unblock`)}
onPress={onUnsubscribeBlock}
/>
) : isMuting ? (
<Button
testID="unmuteBtn"
type="default"
label="Unmute"
label={_(msg`Unmute`)}
onPress={onUnsubscribeMute}
/>
) : (
@ -603,6 +614,7 @@ const FeedSection = React.forwardRef<SectionRef, FeedSectionProps>(
const [hasNew, setHasNew] = React.useState(false)
const [isScrolledDown, setIsScrolledDown] = React.useState(false)
const isScreenFocused = useIsFocused()
const {_} = useLingui()
const onScrollToTop = useCallback(() => {
scrollElRef.current?.scrollToOffset({
@ -624,8 +636,8 @@ const FeedSection = React.forwardRef<SectionRef, FeedSectionProps>(
}, [onScrollToTop, isScreenFocused])
const renderPostsEmpty = useCallback(() => {
return <EmptyState icon="feed" message="This feed is empty!" />
}, [])
return <EmptyState icon="feed" message={_(msg`This feed is empty!`)} />
}, [_])
return (
<View>
@ -643,7 +655,7 @@ const FeedSection = React.forwardRef<SectionRef, FeedSectionProps>(
{(isScrolledDown || hasNew) && (
<LoadLatestBtn
onPress={onScrollToTop}
label="Load new posts"
label={_(msg`Load new posts`)}
showIndicator={hasNew}
/>
)}
@ -721,15 +733,30 @@ const AboutSection = React.forwardRef<SectionRef, AboutSectionProps>(
</Text>
)}
<Text type="md" style={[pal.textLight]} numberOfLines={1}>
{isCurateList ? 'User list' : 'Moderation list'} by{' '}
{isOwner ? (
'you'
{isCurateList ? (
isOwner ? (
<Trans>User list by you</Trans>
) : (
<Trans>
User list by{' '}
<TextLink
text={sanitizeHandle(list.creator.handle || '', '@')}
href={makeProfileLink(list.creator)}
style={pal.textLight}
/>
</Trans>
)
) : isOwner ? (
<Trans>Moderation list by you</Trans>
) : (
<TextLink
text={sanitizeHandle(list.creator.handle || '', '@')}
href={makeProfileLink(list.creator)}
style={pal.textLight}
/>
<Trans>
Moderation list by{' '}
<TextLink
text={sanitizeHandle(list.creator.handle || '', '@')}
href={makeProfileLink(list.creator)}
style={pal.textLight}
/>
</Trans>
)}
</Text>
</View>
@ -782,11 +809,11 @@ const AboutSection = React.forwardRef<SectionRef, AboutSectionProps>(
return (
<EmptyState
icon="users-slash"
message="This list is empty!"
message={_(msg`This list is empty!`)}
style={{paddingTop: 40}}
/>
)
}, [])
}, [_])
return (
<View>
@ -802,7 +829,7 @@ const AboutSection = React.forwardRef<SectionRef, AboutSectionProps>(
{isScrolledDown && (
<LoadLatestBtn
onPress={onScrollToTop}
label="Scroll to top"
label={_(msg`Scroll to top`)}
showIndicator={false}
/>
)}
@ -846,7 +873,7 @@ function ErrorScreen({error}: {error: string}) {
<Button
type="default"
accessibilityLabel={_(msg`Go Back`)}
accessibilityHint="Return to previous page"
accessibilityHint={_(msg`Return to previous page`)}
onPress={onPressBack}
style={{flexShrink: 1}}>
<Text type="button" style={pal.text}>

View File

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

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