diff options
108 files changed, 925 insertions, 558 deletions
diff --git a/src/App.native.tsx b/src/App.native.tsx index 6402b4a89..9de901767 100644 --- a/src/App.native.tsx +++ b/src/App.native.tsx @@ -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}> diff --git a/src/Navigation.tsx b/src/Navigation.tsx index 76a893c68..c68cb0580 100644 --- a/src/Navigation.tsx +++ b/src/Navigation.tsx @@ -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> diff --git a/src/view/com/auth/LoggedOut.tsx b/src/view/com/auth/LoggedOut.tsx index c0427ff54..603abbab2 100644 --- a/src/view/com/auth/LoggedOut.tsx +++ b/src/view/com/auth/LoggedOut.tsx @@ -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" diff --git a/src/view/com/auth/SplashScreen.web.tsx b/src/view/com/auth/SplashScreen.web.tsx index 1cc7b9146..d2b1a47e3 100644 --- a/src/view/com/auth/SplashScreen.web.tsx +++ b/src/view/com/auth/SplashScreen.web.tsx @@ -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 diff --git a/src/view/com/auth/create/Step1.tsx b/src/view/com/auth/create/Step1.tsx index c9d19e868..0f8581c0b 100644 --- a/src/view/com/auth/create/Step1.tsx +++ b/src/view/com/auth/create/Step1.tsx @@ -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 ? ( diff --git a/src/view/com/auth/create/Step2.tsx b/src/view/com/auth/create/Step2.tsx index 8b143ff37..53e1e02c9 100644 --- a/src/view/com/auth/create/Step2.tsx +++ b/src/view/com/auth/create/Step2.tsx @@ -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> diff --git a/src/view/com/auth/create/Step3.tsx b/src/view/com/auth/create/Step3.tsx index 4c8a58519..2b2b9f7fe 100644 --- a/src/view/com/auth/create/Step3.tsx +++ b/src/view/com/auth/create/Step3.tsx @@ -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>{' '} diff --git a/src/view/com/auth/create/StepHeader.tsx b/src/view/com/auth/create/StepHeader.tsx index 4b4eb5d23..41f912051 100644 --- a/src/view/com/auth/create/StepHeader.tsx +++ b/src/view/com/auth/create/StepHeader.tsx @@ -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} diff --git a/src/view/com/auth/login/ChooseAccountForm.tsx b/src/view/com/auth/login/ChooseAccountForm.tsx index 73ddfc9d6..32cd8315d 100644 --- a/src/view/com/auth/login/ChooseAccountForm.tsx +++ b/src/view/com/auth/login/ChooseAccountForm.tsx @@ -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 ( diff --git a/src/view/com/auth/login/ForgotPasswordForm.tsx b/src/view/com/auth/login/ForgotPasswordForm.tsx index 215c393d9..f9bb64f98 100644 --- a/src/view/com/auth/login/ForgotPasswordForm.tsx +++ b/src/view/com/auth/login/ForgotPasswordForm.tsx @@ -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> diff --git a/src/view/com/auth/login/LoginForm.tsx b/src/view/com/auth/login/LoginForm.tsx index 98c5eb374..10608a54b 100644 --- a/src/view/com/auth/login/LoginForm.tsx +++ b/src/view/com/auth/login/LoginForm.tsx @@ -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> diff --git a/src/view/com/auth/login/PasswordUpdatedForm.tsx b/src/view/com/auth/login/PasswordUpdatedForm.tsx index 1e07588a9..71f750b14 100644 --- a/src/view/com/auth/login/PasswordUpdatedForm.tsx +++ b/src/view/com/auth/login/PasswordUpdatedForm.tsx @@ -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> diff --git a/src/view/com/auth/login/SetNewPasswordForm.tsx b/src/view/com/auth/login/SetNewPasswordForm.tsx index 2bb614df2..630c6afde 100644 --- a/src/view/com/auth/login/SetNewPasswordForm.tsx +++ b/src/view/com/auth/login/SetNewPasswordForm.tsx @@ -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> diff --git a/src/view/com/auth/onboarding/RecommendedFeedsItem.tsx b/src/view/com/auth/onboarding/RecommendedFeedsItem.tsx index fcc4572af..63fb0ec15 100644 --- a/src/view/com/auth/onboarding/RecommendedFeedsItem.tsx +++ b/src/view/com/auth/onboarding/RecommendedFeedsItem.tsx @@ -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> </> )} diff --git a/src/view/com/auth/onboarding/RecommendedFollows.tsx b/src/view/com/auth/onboarding/RecommendedFollows.tsx index 372bbec6a..93cfb7386 100644 --- a/src/view/com/auth/onboarding/RecommendedFollows.tsx +++ b/src/view/com/auth/onboarding/RecommendedFollows.tsx @@ -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> diff --git a/src/view/com/auth/onboarding/WelcomeDesktop.tsx b/src/view/com/auth/onboarding/WelcomeDesktop.tsx index 1a30c17f9..fdb31197c 100644 --- a/src/view/com/auth/onboarding/WelcomeDesktop.tsx +++ b/src/view/com/auth/onboarding/WelcomeDesktop.tsx @@ -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> diff --git a/src/view/com/composer/Composer.tsx b/src/view/com/composer/Composer.tsx index a834cfc0e..e24fdcf3e 100644 --- a/src/view/com/composer/Composer.tsx +++ b/src/view/com/composer/Composer.tsx @@ -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> diff --git a/src/view/com/composer/ExternalEmbed.tsx b/src/view/com/composer/ExternalEmbed.tsx index 502e4b4d2..02dd1bbd7 100644 --- a/src/view/com/composer/ExternalEmbed.tsx +++ b/src/view/com/composer/ExternalEmbed.tsx @@ -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> diff --git a/src/view/com/composer/Prompt.tsx b/src/view/com/composer/Prompt.tsx index 9964359ac..632bb2634 100644 --- a/src/view/com/composer/Prompt.tsx +++ b/src/view/com/composer/Prompt.tsx @@ -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" diff --git a/src/view/com/composer/photos/OpenCameraBtn.tsx b/src/view/com/composer/photos/OpenCameraBtn.tsx index 69f63c55f..a288e7310 100644 --- a/src/view/com/composer/photos/OpenCameraBtn.tsx +++ b/src/view/com/composer/photos/OpenCameraBtn.tsx @@ -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} diff --git a/src/view/com/composer/photos/SelectPhotoBtn.tsx b/src/view/com/composer/photos/SelectPhotoBtn.tsx index af0a22b01..f7fa9502d 100644 --- a/src/view/com/composer/photos/SelectPhotoBtn.tsx +++ b/src/view/com/composer/photos/SelectPhotoBtn.tsx @@ -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} diff --git a/src/view/com/composer/text-input/web/Autocomplete.tsx b/src/view/com/composer/text-input/web/Autocomplete.tsx index 51197b8e4..76058fed3 100644 --- a/src/view/com/composer/text-input/web/Autocomplete.tsx +++ b/src/view/com/composer/text-input/web/Autocomplete.tsx @@ -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> diff --git a/src/view/com/feeds/FeedPage.tsx b/src/view/com/feeds/FeedPage.tsx index 84d49e3b0..2d0b17f38 100644 --- a/src/view/com/feeds/FeedPage.tsx +++ b/src/view/com/feeds/FeedPage.tsx @@ -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="" /> )} diff --git a/src/view/com/feeds/FeedSourceCard.tsx b/src/view/com/feeds/FeedSourceCard.tsx index 338ffc3d0..487163840 100644 --- a/src/view/com/feeds/FeedSourceCard.tsx +++ b/src/view/com/feeds/FeedSourceCard.tsx @@ -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> diff --git a/src/view/com/feeds/ProfileFeedgens.tsx b/src/view/com/feeds/ProfileFeedgens.tsx index 8665fbfac..f558eb18c 100644 --- a/src/view/com/feeds/ProfileFeedgens.tsx +++ b/src/view/com/feeds/ProfileFeedgens.tsx @@ -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 ( diff --git a/src/view/com/lightbox/ImageViewing/components/ImageDefaultHeader.tsx b/src/view/com/lightbox/ImageViewing/components/ImageDefaultHeader.tsx index c806bc6a6..3401adaff 100644 --- a/src/view/com/lightbox/ImageViewing/components/ImageDefaultHeader.tsx +++ b/src/view/com/lightbox/ImageViewing/components/ImageDefaultHeader.tsx @@ -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> diff --git a/src/view/com/lightbox/Lightbox.tsx b/src/view/com/lightbox/Lightbox.tsx index 8a18df33f..2271bb9fb 100644 --- a/src/view/com/lightbox/Lightbox.tsx +++ b/src/view/com/lightbox/Lightbox.tsx @@ -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> diff --git a/src/view/com/lightbox/Lightbox.web.tsx b/src/view/com/lightbox/Lightbox.web.tsx index 45e1fa5a3..a258d25ab 100644 --- a/src/view/com/lightbox/Lightbox.web.tsx +++ b/src/view/com/lightbox/Lightbox.web.tsx @@ -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) }}> diff --git a/src/view/com/lists/ListCard.tsx b/src/view/com/lists/ListCard.tsx index 774e9e916..28e98144a 100644 --- a/src/view/com/lists/ListCard.tsx +++ b/src/view/com/lists/ListCard.tsx @@ -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> diff --git a/src/view/com/lists/ListMembers.tsx b/src/view/com/lists/ListMembers.tsx index 932f4b512..212244cd8 100644 --- a/src/view/com/lists/ListMembers.tsx +++ b/src/view/com/lists/ListMembers.tsx @@ -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, + _, ], ) diff --git a/src/view/com/lists/ProfileLists.tsx b/src/view/com/lists/ProfileLists.tsx index db981717f..89d6ab480 100644 --- a/src/view/com/lists/ProfileLists.tsx +++ b/src/view/com/lists/ProfileLists.tsx @@ -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 ( diff --git a/src/view/com/modals/AddAppPasswords.tsx b/src/view/com/modals/AddAppPasswords.tsx index 812a36f45..7ec8268be 100644 --- a/src/view/com/modals/AddAppPasswords.tsx +++ b/src/view/com/modals/AddAppPasswords.tsx @@ -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} diff --git a/src/view/com/modals/AppealLabel.tsx b/src/view/com/modals/AppealLabel.tsx index edc6f4cd0..1a1947a9a 100644 --- a/src/view/com/modals/AppealLabel.tsx +++ b/src/view/com/modals/AppealLabel.tsx @@ -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() } diff --git a/src/view/com/modals/BirthDateSettings.tsx b/src/view/com/modals/BirthDateSettings.tsx index 1505d224f..5ebc61137 100644 --- a/src/view/com/modals/BirthDateSettings.tsx +++ b/src/view/com/modals/BirthDateSettings.tsx @@ -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> diff --git a/src/view/com/modals/ChangeEmail.tsx b/src/view/com/modals/ChangeEmail.tsx index 44b102fa0..c5672bc81 100644 --- a/src/view/com/modals/ChangeEmail.tsx +++ b/src/view/com/modals/ChangeEmail.tsx @@ -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))) diff --git a/src/view/com/modals/ChangeHandle.tsx b/src/view/com/modals/ChangeHandle.tsx index 31f6d6ea7..e578fa7da 100644 --- a/src/view/com/modals/ChangeHandle.tsx +++ b/src/view/com/modals/ChangeHandle.tsx @@ -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} diff --git a/src/view/com/modals/Confirm.tsx b/src/view/com/modals/Confirm.tsx index 5e869f396..307897fb8 100644 --- a/src/view/com/modals/Confirm.tsx +++ b/src/view/com/modals/Confirm.tsx @@ -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> )} diff --git a/src/view/com/modals/ContentFilteringSettings.tsx b/src/view/com/modals/ContentFilteringSettings.tsx index 88fb43443..d681fbf0b 100644 --- a/src/view/com/modals/ContentFilteringSettings.tsx +++ b/src/view/com/modals/ContentFilteringSettings.tsx @@ -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> diff --git a/src/view/com/modals/CreateOrEditList.tsx b/src/view/com/modals/CreateOrEditList.tsx index 8d13cdf2f..bd1eb3393 100644 --- a/src/view/com/modals/CreateOrEditList.tsx +++ b/src/view/com/modals/CreateOrEditList.tsx @@ -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> diff --git a/src/view/com/modals/DeleteAccount.tsx b/src/view/com/modals/DeleteAccount.tsx index ee16d46b3..0cfc098d4 100644 --- a/src/view/com/modals/DeleteAccount.tsx +++ b/src/view/com/modals/DeleteAccount.tsx @@ -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> </> diff --git a/src/view/com/modals/EditImage.tsx b/src/view/com/modals/EditImage.tsx index 753907472..3b35ffee2 100644 --- a/src/view/com/modals/EditImage.tsx +++ b/src/view/com/modals/EditImage.tsx @@ -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> diff --git a/src/view/com/modals/EditProfile.tsx b/src/view/com/modals/EditProfile.tsx index e044f8c0e..dd8ac9ae7 100644 --- a/src/view/com/modals/EditProfile.tsx +++ b/src/view/com/modals/EditProfile.tsx @@ -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}} diff --git a/src/view/com/modals/InviteCodes.tsx b/src/view/com/modals/InviteCodes.tsx index a0bb5f4af..c0318df01 100644 --- a/src/view/com/modals/InviteCodes.tsx +++ b/src/view/com/modals/InviteCodes.tsx @@ -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'} diff --git a/src/view/com/modals/ListAddRemoveUsers.tsx b/src/view/com/modals/ListAddRemoveUsers.tsx index 14e16d6bf..27c33f806 100644 --- a/src/view/com/modals/ListAddRemoveUsers.tsx +++ b/src/view/com/modals/ListAddRemoveUsers.tsx @@ -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]} /> diff --git a/src/view/com/modals/ModerationDetails.tsx b/src/view/com/modals/ModerationDetails.tsx index c117023d4..ba7f76db1 100644 --- a/src/view/com/modals/ModerationDetails.tsx +++ b/src/view/com/modals/ModerationDetails.tsx @@ -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 diff --git a/src/view/com/modals/ProfilePreview.tsx b/src/view/com/modals/ProfilePreview.tsx index edfbf6a82..77e68db70 100644 --- a/src/view/com/modals/ProfilePreview.tsx +++ b/src/view/com/modals/ProfilePreview.tsx @@ -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> </> )} diff --git a/src/view/com/modals/Repost.tsx b/src/view/com/modals/Repost.tsx index a72da29b4..6e4881adc 100644 --- a/src/view/com/modals/Repost.tsx +++ b/src/view/com/modals/Repost.tsx @@ -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> diff --git a/src/view/com/modals/SelfLabel.tsx b/src/view/com/modals/SelfLabel.tsx index 092dd2d32..779a9e71b 100644 --- a/src/view/com/modals/SelfLabel.tsx +++ b/src/view/com/modals/SelfLabel.tsx @@ -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> diff --git a/src/view/com/modals/ServerInput.tsx b/src/view/com/modals/ServerInput.tsx index b30293859..550dffa1c 100644 --- a/src/view/com/modals/ServerInput.tsx +++ b/src/view/com/modals/ServerInput.tsx @@ -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="" diff --git a/src/view/com/modals/SwitchAccount.tsx b/src/view/com/modals/SwitchAccount.tsx index 37691e717..c034c4b52 100644 --- a/src/view/com/modals/SwitchAccount.tsx +++ b/src/view/com/modals/SwitchAccount.tsx @@ -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> ) diff --git a/src/view/com/modals/Threadgate.tsx b/src/view/com/modals/Threadgate.tsx index 0deef185b..0e49fc2f3 100644 --- a/src/view/com/modals/Threadgate.tsx +++ b/src/view/com/modals/Threadgate.tsx @@ -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> diff --git a/src/view/com/modals/UserAddRemoveLists.tsx b/src/view/com/modals/UserAddRemoveLists.tsx index 78a3de0b6..23adbe1a8 100644 --- a/src/view/com/modals/UserAddRemoveLists.tsx +++ b/src/view/com/modals/UserAddRemoveLists.tsx @@ -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> diff --git a/src/view/com/modals/VerifyEmail.tsx b/src/view/com/modals/VerifyEmail.tsx index 4f2b1aadf..30a57afc5 100644 --- a/src/view/com/modals/VerifyEmail.tsx +++ b/src/view/com/modals/VerifyEmail.tsx @@ -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]} /> diff --git a/src/view/com/modals/Waitlist.tsx b/src/view/com/modals/Waitlist.tsx index a31545c0a..263dd27a2 100644 --- a/src/view/com/modals/Waitlist.tsx +++ b/src/view/com/modals/Waitlist.tsx @@ -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> diff --git a/src/view/com/notifications/Feed.tsx b/src/view/com/notifications/Feed.tsx index a99fe2c1d..2088acbac 100644 --- a/src/view/com/notifications/Feed.tsx +++ b/src/view/com/notifications/Feed.tsx @@ -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( diff --git a/src/view/com/notifications/FeedItem.tsx b/src/view/com/notifications/FeedItem.tsx index 24b7e4fb6..e9d8b63e2 100644 --- a/src/view/com/notifications/FeedItem.tsx +++ b/src/view/com/notifications/FeedItem.tsx @@ -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 => ( diff --git a/src/view/com/pager/FeedsTabBarMobile.tsx b/src/view/com/pager/FeedsTabBarMobile.tsx index 024f9bfab..2c5ba5dfb 100644 --- a/src/view/com/pager/FeedsTabBarMobile.tsx +++ b/src/view/com/pager/FeedsTabBarMobile.tsx @@ -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" diff --git a/src/view/com/post-thread/PostThread.tsx b/src/view/com/post-thread/PostThread.tsx index 6cd1f3551..cb7fd3f41 100644 --- a/src/view/com/post-thread/PostThread.tsx +++ b/src/view/com/post-thread/PostThread.tsx @@ -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> diff --git a/src/view/com/post-thread/PostThreadItem.tsx b/src/view/com/post-thread/PostThreadItem.tsx index fc03c0d95..bb3c7e2cc 100644 --- a/src/view/com/post-thread/PostThreadItem.tsx +++ b/src/view/com/post-thread/PostThreadItem.tsx @@ -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="#" diff --git a/src/view/com/post/Post.tsx b/src/view/com/post/Post.tsx index ac4689d2f..0e5a459fa 100644 --- a/src/view/com/post/Post.tsx +++ b/src/view/com/post/Post.tsx @@ -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="#" diff --git a/src/view/com/posts/CustomFeedEmptyState.tsx b/src/view/com/posts/CustomFeedEmptyState.tsx index e83a94f03..62a10fd19 100644 --- a/src/view/com/posts/CustomFeedEmptyState.tsx +++ b/src/view/com/posts/CustomFeedEmptyState.tsx @@ -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" diff --git a/src/view/com/posts/Feed.tsx b/src/view/com/posts/Feed.tsx index 02a3537eb..6b6ad6e92 100644 --- a/src/view/com/posts/Feed.tsx +++ b/src/view/com/posts/Feed.tsx @@ -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 = diff --git a/src/view/com/posts/FeedErrorMessage.tsx b/src/view/com/posts/FeedErrorMessage.tsx index aeac45980..48ed49bb1 100644 --- a/src/view/com/posts/FeedErrorMessage.tsx +++ b/src/view/com/posts/FeedErrorMessage.tsx @@ -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> )} diff --git a/src/view/com/posts/FeedItem.tsx b/src/view/com/posts/FeedItem.tsx index 2a5abcd68..f2b5605dd 100644 --- a/src/view/com/posts/FeedItem.tsx +++ b/src/view/com/posts/FeedItem.tsx @@ -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="#" diff --git a/src/view/com/posts/FeedSlice.tsx b/src/view/com/posts/FeedSlice.tsx index c1a8c0e18..84edee4a1 100644 --- a/src/view/com/posts/FeedSlice.tsx +++ b/src/view/com/posts/FeedSlice.tsx @@ -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> ) diff --git a/src/view/com/posts/FollowingEmptyState.tsx b/src/view/com/posts/FollowingEmptyState.tsx index aac29603d..ef02039af 100644 --- a/src/view/com/posts/FollowingEmptyState.tsx +++ b/src/view/com/posts/FollowingEmptyState.tsx @@ -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" diff --git a/src/view/com/posts/FollowingEndOfFeed.tsx b/src/view/com/posts/FollowingEndOfFeed.tsx index 3f1297547..bea5bedea 100644 --- a/src/view/com/posts/FollowingEndOfFeed.tsx +++ b/src/view/com/posts/FollowingEndOfFeed.tsx @@ -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" diff --git a/src/view/com/profile/FollowButton.tsx b/src/view/com/profile/FollowButton.tsx index 1252f8ca8..9cc635b66 100644 --- a/src/view/com/profile/FollowButton.tsx +++ b/src/view/com/profile/FollowButton.tsx @@ -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'}))} /> ) } diff --git a/src/view/com/profile/ProfileCard.tsx b/src/view/com/profile/ProfileCard.tsx index ef95f5924..266adc51d 100644 --- a/src/view/com/profile/ProfileCard.tsx +++ b/src/view/com/profile/ProfileCard.tsx @@ -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}> diff --git a/src/view/com/profile/ProfileHeader.tsx b/src/view/com/profile/ProfileHeader.tsx index 7d52216b0..d831ad777 100644 --- a/src/view/com/profile/ProfileHeader.tsx +++ b/src/view/com/profile/ProfileHeader.tsx @@ -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]}> diff --git a/src/view/com/profile/ProfileHeaderSuggestedFollows.tsx b/src/view/com/profile/ProfileHeaderSuggestedFollows.tsx index ce5cf92c5..6edc61fcf 100644 --- a/src/view/com/profile/ProfileHeaderSuggestedFollows.tsx +++ b/src/view/com/profile/ProfileHeaderSuggestedFollows.tsx @@ -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 diff --git a/src/view/com/profile/ProfileSubpageHeader.tsx b/src/view/com/profile/ProfileSubpageHeader.tsx index 0e245f0f4..eaf00f3e6 100644 --- a/src/view/com/profile/ProfileSubpageHeader.tsx +++ b/src/view/com/profile/ProfileSubpageHeader.tsx @@ -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> )} diff --git a/src/view/com/util/AccountDropdownBtn.tsx b/src/view/com/util/AccountDropdownBtn.tsx index 76d493886..221879df7 100644 --- a/src/view/com/util/AccountDropdownBtn.tsx +++ b/src/view/com/util/AccountDropdownBtn.tsx @@ -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: { diff --git a/src/view/com/util/Selector.tsx b/src/view/com/util/Selector.tsx index 223a069c8..66e363cd4 100644 --- a/src/view/com/util/Selector.tsx +++ b/src/view/com/util/Selector.tsx @@ -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={ diff --git a/src/view/com/util/ViewHeader.tsx b/src/view/com/util/ViewHeader.tsx index 082cae59c..1ccfcf56c 100644 --- a/src/view/com/util/ViewHeader.tsx +++ b/src/view/com/util/ViewHeader.tsx @@ -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 diff --git a/src/view/com/util/error/ErrorMessage.tsx b/src/view/com/util/error/ErrorMessage.tsx index b4adbb557..a4238b8a4 100644 --- a/src/view/com/util/error/ErrorMessage.tsx +++ b/src/view/com/util/error/ErrorMessage.tsx @@ -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}} diff --git a/src/view/com/util/error/ErrorScreen.tsx b/src/view/com/util/error/ErrorScreen.tsx index 4cd6dd4b4..45444331c 100644 --- a/src/view/com/util/error/ErrorScreen.tsx +++ b/src/view/com/util/error/ErrorScreen.tsx @@ -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> diff --git a/src/view/com/util/forms/DropdownButton.tsx b/src/view/com/util/forms/DropdownButton.tsx index ad8f50f5e..411b77484 100644 --- a/src/view/com/util/forms/DropdownButton.tsx +++ b/src/view/com/util/forms/DropdownButton.tsx @@ -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} diff --git a/src/view/com/util/forms/PostDropdownBtn.tsx b/src/view/com/util/forms/PostDropdownBtn.tsx index 1f2e067c2..82373822e 100644 --- a/src/view/com/util/forms/PostDropdownBtn.tsx +++ b/src/view/com/util/forms/PostDropdownBtn.tsx @@ -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} /> diff --git a/src/view/com/util/forms/SearchInput.tsx b/src/view/com/util/forms/SearchInput.tsx index a88046d4c..a78d23c9b 100644 --- a/src/view/com/util/forms/SearchInput.tsx +++ b/src/view/com/util/forms/SearchInput.tsx @@ -50,7 +50,7 @@ export function SearchInput({ <TextInput testID="searchTextInput" ref={textInput} - placeholder="Search" + placeholder={_(msg`Search`)} placeholderTextColor={pal.colors.textLight} selectTextOnFocus returnKeyType="search" diff --git a/src/view/com/util/images/AutoSizedImage.tsx b/src/view/com/util/images/AutoSizedImage.tsx index 6f203bf06..61cb6f69f 100644 --- a/src/view/com/util/images/AutoSizedImage.tsx +++ b/src/view/com/util/images/AutoSizedImage.tsx @@ -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> diff --git a/src/view/com/util/images/Gallery.tsx b/src/view/com/util/images/Gallery.tsx index 094b0c56c..e7110372c 100644 --- a/src/view/com/util/images/Gallery.tsx +++ b/src/view/com/util/images/Gallery.tsx @@ -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}} diff --git a/src/view/com/util/moderation/ContentHider.tsx b/src/view/com/util/moderation/ContentHider.tsx index 1269b7ebf..249c556ec 100644 --- a/src/view/com/util/moderation/ContentHider.tsx +++ b/src/view/com/util/moderation/ContentHider.tsx @@ -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, diff --git a/src/view/com/util/moderation/PostHider.tsx b/src/view/com/util/moderation/PostHider.tsx index bffb7ea1a..b1fa71d4a 100644 --- a/src/view/com/util/moderation/PostHider.tsx +++ b/src/view/com/util/moderation/PostHider.tsx @@ -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> diff --git a/src/view/com/util/post-ctrls/PostCtrls.tsx b/src/view/com/util/post-ctrls/PostCtrls.tsx index a50b52175..575f19bfc 100644 --- a/src/view/com/util/post-ctrls/PostCtrls.tsx +++ b/src/view/com/util/post-ctrls/PostCtrls.tsx @@ -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 ? ( diff --git a/src/view/com/util/post-ctrls/RepostButton.tsx b/src/view/com/util/post-ctrls/RepostButton.tsx index 620852d8e..d45bf1d87 100644 --- a/src/view/com/util/post-ctrls/RepostButton.tsx +++ b/src/view/com/util/post-ctrls/RepostButton.tsx @@ -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}> diff --git a/src/view/com/util/post-embeds/QuoteEmbed.tsx b/src/view/com/util/post-embeds/QuoteEmbed.tsx index fbb89af27..bb8375df6 100644 --- a/src/view/com/util/post-embeds/QuoteEmbed.tsx +++ b/src/view/com/util/post-embeds/QuoteEmbed.tsx @@ -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> ) diff --git a/src/view/screens/AppPasswords.tsx b/src/view/screens/AppPasswords.tsx index ab4a6a6a8..dc439c367 100644 --- a/src/view/screens/AppPasswords.tsx +++ b/src/view/screens/AppPasswords.tsx @@ -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> diff --git a/src/view/screens/Debug.tsx b/src/view/screens/Debug.tsx index 0e0464200..f26b1505a 100644 --- a/src/view/screens/Debug.tsx +++ b/src/view/screens/Debug.tsx @@ -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} diff --git a/src/view/screens/Feeds.tsx b/src/view/screens/Feeds.tsx index dbdb6d9ca..e0126bd48 100644 --- a/src/view/screens/Feeds.tsx +++ b/src/view/screens/Feeds.tsx @@ -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> ) diff --git a/src/view/screens/Lists.tsx b/src/view/screens/Lists.tsx index d28db7c6c..23d6c8fac 100644 --- a/src/view/screens/Lists.tsx +++ b/src/view/screens/Lists.tsx @@ -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> diff --git a/src/view/screens/Log.tsx b/src/view/screens/Log.tsx index 8680b851b..e727a1fb8 100644 --- a/src/view/screens/Log.tsx +++ b/src/view/screens/Log.tsx @@ -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" /> ) : ( diff --git a/src/view/screens/PostThread.tsx b/src/view/screens/PostThread.tsx index 6f8434412..aaadbf399 100644 --- a/src/view/screens/PostThread.tsx +++ b/src/view/screens/PostThread.tsx @@ -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> diff --git a/src/view/screens/PreferencesExternalEmbeds.tsx b/src/view/screens/PreferencesExternalEmbeds.tsx index 24e7d56df..1e8cedf7e 100644 --- a/src/view/screens/PreferencesExternalEmbeds.tsx +++ b/src/view/screens/PreferencesExternalEmbeds.tsx @@ -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 diff --git a/src/view/screens/PreferencesHomeFeed.tsx b/src/view/screens/PreferencesHomeFeed.tsx index 874272831..7ad870937 100644 --- a/src/view/screens/PreferencesHomeFeed.tsx +++ b/src/view/screens/PreferencesHomeFeed.tsx @@ -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> ) diff --git a/src/view/screens/PreferencesThreads.tsx b/src/view/screens/PreferencesThreads.tsx index 35a010b55..321c67293 100644 --- a/src/view/screens/PreferencesThreads.tsx +++ b/src/view/screens/PreferencesThreads.tsx @@ -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> diff --git a/src/view/screens/Profile.tsx b/src/view/screens/Profile.tsx index 4558ae33d..7fc4d7a20 100644 --- a/src/view/screens/Profile.tsx +++ b/src/view/screens/Profile.tsx @@ -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} /> )} diff --git a/src/view/screens/ProfileFeed.tsx b/src/view/screens/ProfileFeed.tsx index 211306c0d..4f5f300af 100644 --- a/src/view/screens/ProfileFeed.tsx +++ b/src/view/screens/ProfileFeed.tsx @@ -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> diff --git a/src/view/screens/ProfileList.tsx b/src/view/screens/ProfileList.tsx index c51758ae5..30999b518 100644 --- a/src/view/screens/ProfileList.tsx +++ b/src/view/screens/ProfileList.tsx @@ -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}> diff --git a/src/view/screens/SavedFeeds.tsx b/src/view/screens/SavedFeeds.tsx index 8a16ffdf2..19ae37f0c 100644 --- a/src/view/screens/SavedFeeds.tsx +++ b/src/view/screens/SavedFeeds.tsx @@ -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 diff --git a/src/view/screens/Search/Search.tsx b/src/view/screens/Search/Search.tsx index da3a82a4d..33356662a 100644 --- a/src/view/screens/Search/Search.tsx +++ b/src/view/screens/Search/Search.tsx @@ -535,7 +535,7 @@ export function SearchScreen( style={styles.headerMenuBtn} accessibilityRole="button" accessibilityLabel={_(msg`Menu`)} - accessibilityHint="Access navigation links and settings"> + accessibilityHint={_(msg`Access navigation links and settings`)}> <FontAwesomeIcon icon="bars" size={18} @@ -556,7 +556,7 @@ export function SearchScreen( <TextInput testID="searchTextInput" ref={textInput} - placeholder="Search" + placeholder={_(msg`Search`)} placeholderTextColor={pal.colors.textLight} selectTextOnFocus returnKeyType="search" diff --git a/src/view/screens/Settings.tsx b/src/view/screens/Settings.tsx index 8278b753e..c078e7a23 100644 --- a/src/view/screens/Settings.tsx +++ b/src/view/screens/Settings.tsx @@ -117,7 +117,7 @@ function SettingsAccountCard({account}: {account: SessionAccount}) { did: currentAccount?.did, handle: currentAccount?.handle, })} - title="Your profile" + title={_(msg`Your profile`)} noFeedback> {contents} </Link> @@ -129,8 +129,8 @@ function SettingsAccountCard({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> ) @@ -318,7 +318,7 @@ export function SettingsScreen({}: Props) { </Text> <Link onPress={() => openModal({name: 'change-email'})}> <Text type="lg" style={pal.link}> - <Trans>Change</Trans> + <Trans context="action">Change</Trans> </Text> </Link> </View> @@ -368,7 +368,7 @@ export function SettingsScreen({}: Props) { onPress={isSwitchingAccounts ? undefined : onPressAddAccount} accessibilityRole="button" accessibilityLabel={_(msg`Add account`)} - accessibilityHint="Create a new Bluesky account"> + accessibilityHint={_(msg`Create a new Bluesky account`)}> <View style={[styles.iconContainer, pal.btn]}> <FontAwesomeIcon icon="plus" @@ -396,7 +396,7 @@ export function SettingsScreen({}: Props) { onPress={isSwitchingAccounts ? undefined : onPressInviteCodes} accessibilityRole="button" accessibilityLabel={_(msg`Invite`)} - accessibilityHint="Opens invite code list" + accessibilityHint={_(msg`Opens invite code list`)} disabled={invites?.disabled}> <View style={[ @@ -453,20 +453,20 @@ export function SettingsScreen({}: Props) { label={_(msg`System`)} left onSelect={() => setColorMode('system')} - accessibilityHint="Set color theme to system setting" + accessibilityHint={_(msg`Set color theme to system setting`)} /> <SelectableBtn selected={colorMode === 'light'} label={_(msg`Light`)} onSelect={() => setColorMode('light')} - accessibilityHint="Set color theme to light" + accessibilityHint={_(msg`Set color theme to light`)} /> <SelectableBtn selected={colorMode === 'dark'} label={_(msg`Dark`)} right onSelect={() => setColorMode('dark')} - accessibilityHint="Set color theme to dark" + accessibilityHint={_(msg`Set color theme to dark`)} /> </View> </View> @@ -544,8 +544,8 @@ export function SettingsScreen({}: Props) { ]} onPress={isSwitchingAccounts ? undefined : onPressLanguageSettings} accessibilityRole="button" - accessibilityHint="Language settings" - accessibilityLabel={_(msg`Opens configurable language settings`)}> + accessibilityLabel={_(msg`Language settings`)} + accessibilityHint={_(msg`Opens configurable language settings`)}> <View style={[styles.iconContainer, pal.btn]}> <FontAwesomeIcon icon="language" @@ -569,8 +569,8 @@ export function SettingsScreen({}: Props) { : () => navigation.navigate('Moderation') } accessibilityRole="button" - accessibilityHint="" - accessibilityLabel={_(msg`Opens moderation settings`)}> + accessibilityLabel={_(msg`Moderation settings`)} + accessibilityHint={_(msg`Opens moderation settings`)}> <View style={[styles.iconContainer, pal.btn]}> <HandIcon style={pal.text} size={18} strokeWidth={6} /> </View> @@ -598,8 +598,8 @@ export function SettingsScreen({}: Props) { : () => navigation.navigate('PreferencesExternalEmbeds') } accessibilityRole="button" - accessibilityHint="" - accessibilityLabel={_(msg`Opens external embeds settings`)}> + accessibilityLabel={_(msg`External media settings`)} + accessibilityHint={_(msg`Opens external embeds settings`)}> <View style={[styles.iconContainer, pal.btn]}> <FontAwesomeIcon icon={['far', 'circle-play']} @@ -625,8 +625,8 @@ export function SettingsScreen({}: Props) { ]} onPress={onPressAppPasswords} accessibilityRole="button" - accessibilityHint="Open app password settings" - accessibilityLabel={_(msg`Opens the app password settings page`)}> + accessibilityLabel={_(msg`App password settings`)} + accessibilityHint={_(msg`Opens the app password settings page`)}> <View style={[styles.iconContainer, pal.btn]}> <FontAwesomeIcon icon="lock" @@ -647,7 +647,7 @@ export function SettingsScreen({}: Props) { onPress={isSwitchingAccounts ? undefined : onPressChangeHandle} accessibilityRole="button" accessibilityLabel={_(msg`Change handle`)} - accessibilityHint="Choose a new Bluesky username or create"> + accessibilityHint={_(msg`Choose a new Bluesky username or create`)}> <View style={[styles.iconContainer, pal.btn]}> <FontAwesomeIcon icon="at" @@ -668,7 +668,9 @@ export function SettingsScreen({}: Props) { accessible={true} accessibilityRole="button" accessibilityLabel={_(msg`Delete account`)} - accessibilityHint="Opens modal for account deletion confirmation. Requires email code."> + accessibilityHint={_( + msg`Opens modal for account deletion confirmation. Requires email code.`, + )}> <View style={[styles.iconContainer, dangerBg]}> <FontAwesomeIcon icon={['far', 'trash-can']} @@ -708,8 +710,8 @@ export function SettingsScreen({}: Props) { style={[pal.view, styles.linkCardNoIcon]} onPress={onPressStorybook} accessibilityRole="button" - accessibilityHint="Open storybook page" - accessibilityLabel={_(msg`Opens the storybook page`)}> + accessibilityLabel={_(msg`Open storybook page`)} + accessibilityHint={_(msg`Opens the storybook page`)}> <Text type="lg" style={pal.text}> <Trans>Storybook</Trans> </Text> @@ -718,8 +720,8 @@ export function SettingsScreen({}: Props) { style={[pal.view, styles.linkCardNoIcon]} onPress={onPressResetPreferences} accessibilityRole="button" - accessibilityHint="Reset preferences" - accessibilityLabel={_(msg`Resets the preferences state`)}> + accessibilityLabel={_(msg`Reset preferences`)} + accessibilityHint={_(msg`Resets the preferences state`)}> <Text type="lg" style={pal.text}> <Trans>Reset preferences state</Trans> </Text> @@ -728,8 +730,8 @@ export function SettingsScreen({}: Props) { style={[pal.view, styles.linkCardNoIcon]} onPress={onPressResetOnboarding} accessibilityRole="button" - accessibilityHint="Reset onboarding" - accessibilityLabel={_(msg`Resets the onboarding state`)}> + accessibilityLabel={_(msg`Reset onboarding`)} + accessibilityHint={_(msg`Resets the onboarding state`)}> <Text type="lg" style={pal.text}> <Trans>Reset onboarding state</Trans> </Text> @@ -738,8 +740,8 @@ export function SettingsScreen({}: Props) { style={[pal.view, styles.linkCardNoIcon]} onPress={clearAllLegacyStorage} accessibilityRole="button" - accessibilityHint="Clear all legacy storage data" - accessibilityLabel={_(msg`Clear all legacy storage data`)}> + accessibilityLabel={_(msg`Clear all legacy storage data`)} + accessibilityHint={_(msg`Clear all legacy storage data`)}> <Text type="lg" style={pal.text}> <Trans> Clear all legacy storage data (restart after this) @@ -750,8 +752,8 @@ export function SettingsScreen({}: Props) { style={[pal.view, styles.linkCardNoIcon]} onPress={clearAllStorage} accessibilityRole="button" - accessibilityHint="Clear all storage data" - accessibilityLabel={_(msg`Clear all storage data`)}> + accessibilityLabel={_(msg`Clear all storage data`)} + accessibilityHint={_(msg`Clear all storage data`)}> <Text type="lg" style={pal.text}> <Trans>Clear all storage data (restart after this)</Trans> </Text> diff --git a/src/view/screens/Support.tsx b/src/view/screens/Support.tsx index 6856f6759..9e7d36ec7 100644 --- a/src/view/screens/Support.tsx +++ b/src/view/screens/Support.tsx @@ -34,10 +34,10 @@ export const SupportScreen = (_props: Props) => { </Text> <Text style={[pal.text, s.p20]}> <Trans> - The support form has been moved. If you need help, please + The support form has been moved. If you need help, please{' '} <TextLink href={HELP_DESK_URL} - text=" click here" + text={_(msg`click here`)} style={pal.link} />{' '} or visit {HELP_DESK_URL} to get in touch with us. diff --git a/src/view/shell/Drawer.tsx b/src/view/shell/Drawer.tsx index 14bc6af26..6f748755a 100644 --- a/src/view/shell/Drawer.tsx +++ b/src/view/shell/Drawer.tsx @@ -68,7 +68,7 @@ let DrawerProfileCard = ({ <TouchableOpacity testID="profileCardButton" accessibilityLabel={_(msg`Profile`)} - accessibilityHint="Navigates to your profile" + accessibilityHint={_(msg`Navigates to your profile`)} onPress={onPressProfile}> <UserAvatar size={80} @@ -435,7 +435,9 @@ let NotificationsMenuItem = ({ label={_(msg`Notifications`)} accessibilityLabel={_(msg`Notifications`)} accessibilityHint={ - numUnreadNotifications === '' ? '' : `${numUnreadNotifications} unread` + numUnreadNotifications === '' + ? '' + : _(msg`${numUnreadNotifications} unread`) } count={numUnreadNotifications} bold={isActive} diff --git a/src/view/shell/desktop/LeftNav.tsx b/src/view/shell/desktop/LeftNav.tsx index 3925b5180..c84e86b95 100644 --- a/src/view/shell/desktop/LeftNav.tsx +++ b/src/view/shell/desktop/LeftNav.tsx @@ -255,7 +255,7 @@ function ComposeBtn() { /> </View> <Text type="button" style={styles.newPostBtnLabel}> - <Trans>New Post</Trans> + <Trans context="action">New Post</Trans> </Text> </TouchableOpacity> </View> diff --git a/src/view/shell/desktop/RightNav.tsx b/src/view/shell/desktop/RightNav.tsx index 894624a6e..842991d6f 100644 --- a/src/view/shell/desktop/RightNav.tsx +++ b/src/view/shell/desktop/RightNav.tsx @@ -56,7 +56,7 @@ export function DesktopRightNav({routeName}: {routeName: string}) { {isSandbox ? ( <View style={[palError.view, styles.messageLine, s.p10]}> <Text type="md" style={[palError.text, s.bold]}> - SANDBOX. Posts and accounts are not permanent. + <Trans>SANDBOX. Posts and accounts are not permanent.</Trans> </Text> </View> ) : undefined} diff --git a/src/view/shell/desktop/Search.tsx b/src/view/shell/desktop/Search.tsx index 6201f828f..f2a3de424 100644 --- a/src/view/shell/desktop/Search.tsx +++ b/src/view/shell/desktop/Search.tsx @@ -176,7 +176,7 @@ export function DesktopSearch() { onPress={onPressCancelSearch} accessibilityRole="button" accessibilityLabel={_(msg`Cancel search`)} - accessibilityHint="Exits inputting search query" + accessibilityHint={_(msg`Exits inputting search query`)} onAccessibilityEscape={onPressCancelSearch}> <Text type="lg" style={[pal.link]}> <Trans>Cancel</Trans> diff --git a/src/view/shell/index.web.tsx b/src/view/shell/index.web.tsx index 38da860bd..20bc0dff1 100644 --- a/src/view/shell/index.web.tsx +++ b/src/view/shell/index.web.tsx @@ -47,7 +47,7 @@ function ShellInner() { onPress={() => setDrawerOpen(false)} style={styles.drawerMask} accessibilityLabel={t`Close navigation footer`} - accessibilityHint="Closes bottom navigation bar"> + accessibilityHint={t`Closes bottom navigation bar`}> <View style={styles.drawerContainer}> <DrawerContent /> </View> |