Internationalization & localization (#1822)

* install and setup lingui

* setup dynamic locale activation and async loading

* first pass of automated replacement of text messages

* add some more documentaton

* fix nits

* add `es` and `hi`locales for testing purposes

* make accessibilityLabel localized

* compile and extract new messages

* fix merge conflicts

* fix eslint warning

* change instructions from sending email to opening PR

* fix comments
This commit is contained in:
Ansh 2023-11-09 10:04:16 -08:00 committed by GitHub
parent 82059b7ee1
commit 4c7850f8c4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
108 changed files with 10334 additions and 1365 deletions

View file

@ -16,6 +16,8 @@ import {useAnalytics} from 'lib/analytics/analytics'
import {useFocusEffect} from '@react-navigation/native'
import {ViewHeader} from '../com/util/ViewHeader'
import {CenteredView} from 'view/com/util/Views'
import {Trans, msg} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {useSetMinimalShellMode} from '#/state/shell'
import {useModalControls} from '#/state/modals'
import {useLanguagePrefs} from '#/state/preferences'
@ -55,8 +57,10 @@ export const AppPasswords = withAuthRequired(
<AppPasswordsHeader />
<View style={[styles.empty, pal.viewLight]}>
<Text type="lg" style={[pal.text, styles.emptyText]}>
You have not created any app passwords yet. You can create one by
pressing the button below.
<Trans>
You have not created any app passwords yet. You can create one
by pressing the button below.
</Trans>
</Text>
</View>
{!isTabletOrDesktop && <View style={styles.flex1} />}
@ -146,8 +150,10 @@ function AppPasswordsHeader() {
pal.text,
isTabletOrDesktop && styles.descriptionDesktop,
]}>
Use app passwords to login to other Bluesky clients without giving full
access to your account or password.
<Trans>
Use app passwords to login to other Bluesky clients without giving
full access to your account or password.
</Trans>
</Text>
</>
)
@ -164,6 +170,7 @@ function AppPassword({
}) {
const pal = usePalette('default')
const store = useStores()
const {_} = useLingui()
const {openModal} = useModalControls()
const {contentLanguages} = useLanguagePrefs()
@ -188,7 +195,7 @@ function AppPassword({
style={[styles.item, pal.border]}
onPress={onDelete}
accessibilityRole="button"
accessibilityLabel="Delete app password"
accessibilityLabel={_(msg`Delete app password`)}
accessibilityHint="">
<View>
<Text type="md-bold" style={pal.text}>

View file

@ -27,6 +27,8 @@ import {FeedSourceModel} from 'state/models/content/feed-source'
import {FlatList} from 'view/com/util/Views'
import {useFocusEffect} from '@react-navigation/native'
import {FeedSourceCard} from 'view/com/feeds/FeedSourceCard'
import {Trans, msg} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {useSetMinimalShellMode} from '#/state/shell'
type Props = NativeStackScreenProps<FeedsTabNavigatorParams, 'Feeds'>
@ -34,6 +36,7 @@ export const FeedsScreen = withAuthRequired(
observer<Props>(function FeedsScreenImpl({}: Props) {
const pal = usePalette('default')
const store = useStores()
const {_} = useLingui()
const setMinimalShellMode = useSetMinimalShellMode()
const {isMobile, isTabletOrDesktop} = useWebMediaQueries()
const myFeeds = store.me.myFeeds
@ -88,12 +91,12 @@ export const FeedsScreen = withAuthRequired(
href="/settings/saved-feeds"
hitSlop={10}
accessibilityRole="button"
accessibilityLabel="Edit Saved Feeds"
accessibilityLabel={_(msg`Edit Saved Feeds`)}
accessibilityHint="Opens screen to edit Saved Feeds">
<CogIcon size={22} strokeWidth={2} style={pal.textLight} />
</Link>
)
}, [pal])
}, [pal, _])
const onRefresh = React.useCallback(() => {
myFeeds.refresh()
@ -124,11 +127,11 @@ export const FeedsScreen = withAuthRequired(
},
]}>
<Text type="title-lg" style={[pal.text, s.bold]}>
My Feeds
<Trans>My Feeds</Trans>
</Text>
<Link
href="/settings/saved-feeds"
accessibilityLabel="Edit My Feeds"
accessibilityLabel={_(msg`Edit My Feeds`)}
accessibilityHint="">
<CogIcon strokeWidth={1.5} style={pal.icon} size={28} />
</Link>
@ -139,7 +142,7 @@ export const FeedsScreen = withAuthRequired(
} else if (item.type === 'saved-feeds-loading') {
return (
<>
{Array.from(Array(item.numItems)).map((_, i) => (
{Array.from(Array(item.numItems)).map((_i, i) => (
<SavedFeedLoadingPlaceholder key={`placeholder-${i}`} />
))}
</>
@ -161,7 +164,7 @@ export const FeedsScreen = withAuthRequired(
},
]}>
<Text type="title-lg" style={[pal.text, s.bold]}>
Discover new feeds
<Trans>Discover new feeds</Trans>
</Text>
{!isMobile && (
<SearchInput
@ -203,14 +206,22 @@ export const FeedsScreen = withAuthRequired(
paddingBottom: '150%',
}}>
<Text type="lg" style={pal.textLight}>
No results found for "{query}"
<Trans>No results found for "{query}"</Trans>
</Text>
</View>
)
}
return null
},
[isMobile, pal, query, onChangeQuery, onPressCancelSearch, onSubmitQuery],
[
isMobile,
pal,
query,
onChangeQuery,
onPressCancelSearch,
onSubmitQuery,
_,
],
)
return (
@ -249,7 +260,7 @@ export const FeedsScreen = withAuthRequired(
onPress={onPressCompose}
icon={<ComposeIcon2 strokeWidth={1.5} size={29} style={s.white} />}
accessibilityRole="button"
accessibilityLabel="New post"
accessibilityLabel={_(msg`New post`)}
accessibilityHint=""
/>
</View>
@ -289,7 +300,7 @@ function SavedFeed({feed}: {feed: FeedSourceModel}) {
{feed.error ? (
<View style={[styles.offlineSlug, pal.borderDark]}>
<Text type="xs" style={pal.textLight}>
Feed offline
<Trans>Feed offline</Trans>
</Text>
</View>
) : null}

View file

@ -11,6 +11,8 @@ import {Text} from '../com/util/text/Text'
import {usePalette} from 'lib/hooks/usePalette'
import {getEntries} from '#/logger/logDump'
import {ago} from 'lib/strings/time'
import {useLingui} from '@lingui/react'
import {msg} from '@lingui/macro'
import {useSetMinimalShellMode} from '#/state/shell'
export const LogScreen = observer(function Log({}: NativeStackScreenProps<
@ -18,6 +20,7 @@ export const LogScreen = observer(function Log({}: NativeStackScreenProps<
'Log'
>) {
const pal = usePalette('default')
const {_} = useLingui()
const setMinimalShellMode = useSetMinimalShellMode()
const [expanded, setExpanded] = React.useState<string[]>([])
@ -47,7 +50,7 @@ export const LogScreen = observer(function Log({}: NativeStackScreenProps<
<TouchableOpacity
style={[styles.entry, pal.border, pal.view]}
onPress={toggler(entry.id)}
accessibilityLabel="View debug entry"
accessibilityLabel={_(msg`View debug entry`)}
accessibilityHint="Opens additional details for a debug entry">
{entry.level === 'debug' ? (
<FontAwesomeIcon icon="info" />

View file

@ -14,6 +14,8 @@ import {CommonNavigatorParams, NativeStackScreenProps} from 'lib/routes/types'
import {ViewHeader} from 'view/com/util/ViewHeader'
import {CenteredView} from 'view/com/util/Views'
import debounce from 'lodash.debounce'
import {Trans, msg} from '@lingui/macro'
import {useLingui} from '@lingui/react'
function RepliesThresholdInput({enabled}: {enabled: boolean}) {
const store = useStores()
@ -66,6 +68,7 @@ export const PreferencesHomeFeed = observer(function PreferencesHomeFeedImpl({
}: Props) {
const pal = usePalette('default')
const store = useStores()
const {_} = useLingui()
const {isTabletOrDesktop} = useWebMediaQueries()
return (
@ -84,7 +87,7 @@ export const PreferencesHomeFeed = observer(function PreferencesHomeFeedImpl({
isTabletOrDesktop && {paddingTop: 20, paddingBottom: 20},
]}>
<Text type="xl" style={[pal.textLight, styles.description]}>
Fine-tune the content you see on your home screen.
<Trans>Fine-tune the content you see on your home screen.</Trans>
</Text>
</View>
@ -92,10 +95,12 @@ export const PreferencesHomeFeed = observer(function PreferencesHomeFeedImpl({
<View style={styles.cardsContainer}>
<View style={[pal.viewLight, styles.card]}>
<Text type="title-sm" style={[pal.text, s.pb5]}>
Show Replies
<Trans>Show Replies</Trans>
</Text>
<Text style={[pal.text, s.pb10]}>
Set this setting to "No" to hide all replies from your feed.
<Trans>
Set this setting to "No" to hide all replies from your feed.
</Trans>
</Text>
<ToggleButton
testID="toggleRepliesBtn"
@ -112,10 +117,13 @@ export const PreferencesHomeFeed = observer(function PreferencesHomeFeedImpl({
store.preferences.homeFeed.hideReplies && styles.dimmed,
]}>
<Text type="title-sm" style={[pal.text, s.pb5]}>
Reply Filters
<Trans>Reply Filters</Trans>
</Text>
<Text style={[pal.text, s.pb10]}>
Enable this setting to only see replies between people you follow.
<Trans>
Enable this setting to only see replies between people you
follow.
</Trans>
</Text>
<ToggleButton
type="default-light"
@ -129,8 +137,10 @@ export const PreferencesHomeFeed = observer(function PreferencesHomeFeedImpl({
style={[s.mb10]}
/>
<Text style={[pal.text]}>
Adjust the number of likes a reply must have to be shown in your
feed.
<Trans>
Adjust the number of likes a reply must have to be shown in your
feed.
</Trans>
</Text>
<RepliesThresholdInput
enabled={!store.preferences.homeFeed.hideReplies}
@ -139,10 +149,12 @@ export const PreferencesHomeFeed = observer(function PreferencesHomeFeedImpl({
<View style={[pal.viewLight, styles.card]}>
<Text type="title-sm" style={[pal.text, s.pb5]}>
Show Reposts
<Trans>Show Reposts</Trans>
</Text>
<Text style={[pal.text, s.pb10]}>
Set this setting to "No" to hide all reposts from your feed.
<Trans>
Set this setting to "No" to hide all reposts from your feed.
</Trans>
</Text>
<ToggleButton
type="default-light"
@ -154,11 +166,13 @@ export const PreferencesHomeFeed = observer(function PreferencesHomeFeedImpl({
<View style={[pal.viewLight, styles.card]}>
<Text type="title-sm" style={[pal.text, s.pb5]}>
Show Quote Posts
<Trans>Show Quote Posts</Trans>
</Text>
<Text style={[pal.text, s.pb10]}>
Set this setting to "No" to hide all quote posts from your feed.
Reposts will still be visible.
<Trans>
Set this setting to "No" to hide all quote posts from your feed.
Reposts will still be visible.
</Trans>
</Text>
<ToggleButton
type="default-light"
@ -170,12 +184,14 @@ export const PreferencesHomeFeed = observer(function PreferencesHomeFeedImpl({
<View style={[pal.viewLight, styles.card]}>
<Text type="title-sm" style={[pal.text, s.pb5]}>
<FontAwesomeIcon icon="flask" color={pal.colors.text} /> Show
Posts from My Feeds
<FontAwesomeIcon icon="flask" color={pal.colors.text} />
<Trans>Show Posts from My Feeds</Trans>
</Text>
<Text style={[pal.text, s.pb10]}>
Set this setting to "Yes" to show samples of your saved feeds in
your following feed. This is an experimental feature.
<Trans>
Set this setting to "Yes" to show samples of your saved feeds in
your following feed. This is an experimental feature.
</Trans>
</Text>
<ToggleButton
type="default-light"
@ -204,9 +220,11 @@ export const PreferencesHomeFeed = observer(function PreferencesHomeFeedImpl({
}}
style={[styles.btn, isTabletOrDesktop && styles.btnDesktop]}
accessibilityRole="button"
accessibilityLabel="Confirm"
accessibilityLabel={_(msg`Confirm`)}
accessibilityHint="">
<Text style={[s.white, s.bold, s.f18]}>Done</Text>
<Text style={[s.white, s.bold, s.f18]}>
<Trans>Done</Trans>
</Text>
</TouchableOpacity>
</View>
</CenteredView>

View file

@ -12,6 +12,8 @@ import {RadioGroup} from 'view/com/util/forms/RadioGroup'
import {CommonNavigatorParams, NativeStackScreenProps} from 'lib/routes/types'
import {ViewHeader} from 'view/com/util/ViewHeader'
import {CenteredView} from 'view/com/util/Views'
import {Trans, msg} from '@lingui/macro'
import {useLingui} from '@lingui/react'
type Props = NativeStackScreenProps<CommonNavigatorParams, 'PreferencesThreads'>
export const PreferencesThreads = observer(function PreferencesThreadsImpl({
@ -19,6 +21,7 @@ export const PreferencesThreads = observer(function PreferencesThreadsImpl({
}: Props) {
const pal = usePalette('default')
const store = useStores()
const {_} = useLingui()
const {isTabletOrDesktop} = useWebMediaQueries()
return (
@ -37,7 +40,7 @@ export const PreferencesThreads = observer(function PreferencesThreadsImpl({
isTabletOrDesktop && {paddingTop: 20, paddingBottom: 20},
]}>
<Text type="xl" style={[pal.textLight, styles.description]}>
Fine-tune the discussion threads.
<Trans>Fine-tune the discussion threads.</Trans>
</Text>
</View>
@ -45,10 +48,10 @@ export const PreferencesThreads = observer(function PreferencesThreadsImpl({
<View style={styles.cardsContainer}>
<View style={[pal.viewLight, styles.card]}>
<Text type="title-sm" style={[pal.text, s.pb5]}>
Sort Replies
<Trans>Sort Replies</Trans>
</Text>
<Text style={[pal.text, s.pb10]}>
Sort replies to the same post by:
<Trans>Sort replies to the same post by:</Trans>
</Text>
<View style={[pal.view, {borderRadius: 8, paddingVertical: 6}]}>
<RadioGroup
@ -67,10 +70,12 @@ export const PreferencesThreads = observer(function PreferencesThreadsImpl({
<View style={[pal.viewLight, styles.card]}>
<Text type="title-sm" style={[pal.text, s.pb5]}>
Prioritize Your Follows
<Trans>Prioritize Your Follows</Trans>
</Text>
<Text style={[pal.text, s.pb10]}>
Show replies by people you follow before all other replies.
<Trans>
Show replies by people you follow before all other replies.
</Trans>
</Text>
<ToggleButton
type="default-light"
@ -84,12 +89,14 @@ export const PreferencesThreads = observer(function PreferencesThreadsImpl({
<View style={[pal.viewLight, styles.card]}>
<Text type="title-sm" style={[pal.text, s.pb5]}>
<FontAwesomeIcon icon="flask" color={pal.colors.text} /> Threaded
Mode
<FontAwesomeIcon icon="flask" color={pal.colors.text} />{' '}
<Trans>Threaded Mode</Trans>
</Text>
<Text style={[pal.text, s.pb10]}>
Set this setting to "Yes" to show replies in a threaded view. This
is an experimental feature.
<Trans>
Set this setting to "Yes" to show replies in a threaded view.
This is an experimental feature.
</Trans>
</Text>
<ToggleButton
type="default-light"
@ -118,9 +125,11 @@ export const PreferencesThreads = observer(function PreferencesThreadsImpl({
}}
style={[styles.btn, isTabletOrDesktop && styles.btnDesktop]}
accessibilityRole="button"
accessibilityLabel="Confirm"
accessibilityLabel={_(msg`Confirm`)}
accessibilityHint="">
<Text style={[s.white, s.bold, s.f18]}>Done</Text>
<Text style={[s.white, s.bold, s.f18]}>
<Trans>Done</Trans>
</Text>
</TouchableOpacity>
</View>
</CenteredView>

View file

@ -30,6 +30,8 @@ import {FeedSourceModel} from 'state/models/content/feed-source'
import {useSetTitle} from 'lib/hooks/useSetTitle'
import {combinedDisplayName} from 'lib/strings/display-names'
import {logger} from '#/logger'
import {Trans, msg} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {useSetMinimalShellMode} from '#/state/shell'
type Props = NativeStackScreenProps<CommonNavigatorParams, 'Profile'>
@ -38,6 +40,7 @@ export const ProfileScreen = withAuthRequired(
const store = useStores()
const setMinimalShellMode = useSetMinimalShellMode()
const {screen, track} = useAnalytics()
const {_} = useLingui()
const viewSelectorRef = React.useRef<ViewSelectorHandle>(null)
const name = route.params.name === 'me' ? store.me.did : route.params.name
@ -206,7 +209,11 @@ export const ProfileScreen = withAuthRequired(
// if section is posts or posts & replies
} else {
if (item === ProfileUiModel.END_ITEM) {
return <Text style={styles.endItem}>- end of feed -</Text>
return (
<Text style={styles.endItem}>
<Trans>- end of feed -</Trans>
</Text>
)
} else if (item === ProfileUiModel.LOADING_ITEM) {
return <PostFeedLoadingPlaceholder />
} else if (item._reactKey === '__error__') {
@ -296,7 +303,7 @@ export const ProfileScreen = withAuthRequired(
onPress={onPressCompose}
icon={<ComposeIcon2 strokeWidth={1.5} size={29} style={s.white} />}
accessibilityRole="button"
accessibilityLabel="New post"
accessibilityLabel={_(msg`New post`)}
accessibilityHint=""
/>
</ScreenHider>

View file

@ -47,6 +47,8 @@ import {sanitizeHandle} from 'lib/strings/handles'
import {makeProfileLink} from 'lib/routes/links'
import {ComposeIcon2} from 'lib/icons'
import {logger} from '#/logger'
import {Trans, msg} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {useModalControls} from '#/state/modals'
const SECTION_TITLES = ['Posts', 'About']
@ -60,6 +62,7 @@ export const ProfileFeedScreen = withAuthRequired(
observer(function ProfileFeedScreenImpl(props: Props) {
const pal = usePalette('default')
const store = useStores()
const {_} = useLingui()
const navigation = useNavigation<NavigationProp>()
const {name: handleOrDid} = props.route.params
@ -98,7 +101,7 @@ export const ProfileFeedScreen = withAuthRequired(
<CenteredView>
<View style={[pal.view, pal.border, styles.notFoundContainer]}>
<Text type="title-lg" style={[pal.text, s.mb10]}>
Could not load feed
<Trans>Could not load feed</Trans>
</Text>
<Text type="md" style={[pal.text, s.mb20]}>
{error}
@ -107,12 +110,12 @@ export const ProfileFeedScreen = withAuthRequired(
<View style={{flexDirection: 'row'}}>
<Button
type="default"
accessibilityLabel="Go Back"
accessibilityLabel={_(msg`Go Back`)}
accessibilityHint="Return to previous page"
onPress={onPressBack}
style={{flexShrink: 1}}>
<Text type="button" style={pal.text}>
Go Back
<Trans>Go Back</Trans>
</Text>
</Button>
</View>
@ -142,6 +145,7 @@ export const ProfileFeedScreenInner = observer(
const pal = usePalette('default')
const store = useStores()
const {track} = useAnalytics()
const {_} = useLingui()
const feedSectionRef = React.useRef<SectionRef>(null)
const {rkey, name: handleOrDid} = route.params
const uri = useMemo(
@ -313,7 +317,7 @@ export const ProfileFeedScreenInner = observer(
<NativeDropdown
testID="headerDropdownBtn"
items={dropdownItems}
accessibilityLabel="More options"
accessibilityLabel={_(msg`More options`)}
accessibilityHint="">
<View style={[pal.viewLight, styles.btn]}>
<FontAwesomeIcon
@ -334,6 +338,7 @@ export const ProfileFeedScreenInner = observer(
onTogglePinned,
onToggleSaved,
dropdownItems,
_,
])
return (
@ -374,7 +379,7 @@ export const ProfileFeedScreenInner = observer(
/>
}
accessibilityRole="button"
accessibilityLabel="New post"
accessibilityLabel={_(msg`New post`)}
accessibilityHint=""
/>
</View>
@ -448,6 +453,7 @@ const AboutSection = observer(function AboutPageImpl({
onScroll: (e: NativeScrollEvent) => void
}) {
const pal = usePalette('default')
const {_} = useLingui()
const scrollHandler = useAnimatedScrollHandler({onScroll})
if (!feedInfo) {
@ -478,14 +484,14 @@ const AboutSection = observer(function AboutPageImpl({
/>
) : (
<Text type="lg" style={[{fontStyle: 'italic'}, pal.textLight]}>
No description
<Trans>No description</Trans>
</Text>
)}
<View style={{flexDirection: 'row', alignItems: 'center', gap: 10}}>
<Button
type="default"
testID="toggleLikeBtn"
accessibilityLabel="Like this feed"
accessibilityLabel={_(msg`Like this feed`)}
accessibilityHint=""
onPress={onToggleLiked}
style={{paddingHorizontal: 10}}>

View file

@ -45,6 +45,8 @@ import {makeProfileLink, makeListLink} from 'lib/routes/links'
import {ComposeIcon2} from 'lib/icons'
import {ListItems} from 'view/com/lists/ListItems'
import {logger} from '#/logger'
import {Trans, msg} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {useSetMinimalShellMode} from '#/state/shell'
import {useModalControls} from '#/state/modals'
@ -107,6 +109,7 @@ export const ProfileListScreenInner = observer(
listOwnerDid,
}: Props & {listOwnerDid: string}) {
const store = useStores()
const {_} = useLingui()
const setMinimalShellMode = useSetMinimalShellMode()
const {rkey} = route.params
const feedSectionRef = React.useRef<SectionRef>(null)
@ -208,7 +211,7 @@ export const ProfileListScreenInner = observer(
/>
}
accessibilityRole="button"
accessibilityLabel="New post"
accessibilityLabel={_(msg`New post`)}
accessibilityHint=""
/>
</View>
@ -246,7 +249,7 @@ export const ProfileListScreenInner = observer(
/>
}
accessibilityRole="button"
accessibilityLabel="New post"
accessibilityLabel={_(msg`New post`)}
accessibilityHint=""
/>
</View>
@ -270,6 +273,7 @@ const Header = observer(function HeaderImpl({
}) {
const pal = usePalette('default')
const palInverted = usePalette('inverted')
const {_} = useLingui()
const navigation = useNavigation<NavigationProp>()
const {openModal, closeModal} = useModalControls()
@ -526,10 +530,12 @@ const Header = observer(function HeaderImpl({
<NativeDropdown
testID="subscribeBtn"
items={subscribeDropdownItems}
accessibilityLabel="Subscribe to this list"
accessibilityLabel={_(msg`Subscribe to this list`)}
accessibilityHint="">
<View style={[palInverted.view, styles.btn]}>
<Text style={palInverted.text}>Subscribe</Text>
<Text style={palInverted.text}>
<Trans>Subscribe</Trans>
</Text>
</View>
</NativeDropdown>
)
@ -537,7 +543,7 @@ const Header = observer(function HeaderImpl({
<NativeDropdown
testID="headerDropdownBtn"
items={dropdownItems}
accessibilityLabel="More options"
accessibilityLabel={_(msg`More options`)}
accessibilityHint="">
<View style={[pal.viewLight, styles.btn]}>
<FontAwesomeIcon icon="ellipsis" size={20} color={pal.colors.text} />
@ -624,6 +630,7 @@ const AboutSection = React.forwardRef<SectionRef, AboutSectionProps>(
ref,
) {
const pal = usePalette('default')
const {_} = useLingui()
const {isMobile} = useWebMediaQueries()
const scrollElRef = React.useRef<FlatList>(null)
@ -662,7 +669,7 @@ const AboutSection = React.forwardRef<SectionRef, AboutSectionProps>(
testID="listDescriptionEmpty"
type="lg"
style={[{fontStyle: 'italic'}, pal.textLight]}>
No description
<Trans>No description</Trans>
</Text>
)}
<Text type="md" style={[pal.textLight]} numberOfLines={1}>
@ -688,12 +695,14 @@ const AboutSection = React.forwardRef<SectionRef, AboutSectionProps>(
paddingBottom: isMobile ? 14 : 18,
},
]}>
<Text type="lg-bold">Users</Text>
<Text type="lg-bold">
<Trans>Users</Trans>
</Text>
{isOwner && (
<Pressable
testID="addUserBtn"
accessibilityRole="button"
accessibilityLabel="Add a user to this list"
accessibilityLabel={_(msg`Add a user to this list`)}
accessibilityHint=""
onPress={onPressAddUser}
style={{flexDirection: 'row', alignItems: 'center', gap: 6}}>
@ -702,7 +711,9 @@ const AboutSection = React.forwardRef<SectionRef, AboutSectionProps>(
color={pal.colors.link}
size={16}
/>
<Text style={pal.link}>Add</Text>
<Text style={pal.link}>
<Trans>Add</Trans>
</Text>
</Pressable>
)}
</View>
@ -717,6 +728,7 @@ const AboutSection = React.forwardRef<SectionRef, AboutSectionProps>(
isCurateList,
isOwner,
onPressAddUser,
_,
])
const renderEmptyState = useCallback(() => {
@ -757,6 +769,7 @@ const AboutSection = React.forwardRef<SectionRef, AboutSectionProps>(
function ErrorScreen({error}: {error: string}) {
const pal = usePalette('default')
const navigation = useNavigation<NavigationProp>()
const {_} = useLingui()
const onPressBack = useCallback(() => {
if (navigation.canGoBack()) {
navigation.goBack()
@ -778,7 +791,7 @@ function ErrorScreen({error}: {error: string}) {
},
]}>
<Text type="title-lg" style={[pal.text, s.mb10]}>
Could not load list
<Trans>Could not load list</Trans>
</Text>
<Text type="md" style={[pal.text, s.mb20]}>
{error}
@ -787,12 +800,12 @@ function ErrorScreen({error}: {error: string}) {
<View style={{flexDirection: 'row'}}>
<Button
type="default"
accessibilityLabel="Go Back"
accessibilityLabel={_(msg`Go Back`)}
accessibilityHint="Return to previous page"
onPress={onPressBack}
style={{flexShrink: 1}}>
<Text type="button" style={pal.text}>
Go Back
<Trans>Go Back</Trans>
</Text>
</Button>
</View>

View file

@ -63,6 +63,8 @@ import {
// -prf
import {useDebugHeaderSetting} from 'lib/api/debug-appview-proxy-header'
import {STATUS_PAGE_URL} from 'lib/constants'
import {Trans, msg} from '@lingui/macro'
import {useLingui} from '@lingui/react'
type Props = NativeStackScreenProps<CommonNavigatorParams, 'Settings'>
export const SettingsScreen = withAuthRequired(
@ -71,6 +73,7 @@ export const SettingsScreen = withAuthRequired(
const setColorMode = useSetColorMode()
const pal = usePalette('default')
const store = useStores()
const {_} = useLingui()
const setMinimalShellMode = useSetMinimalShellMode()
const requireAltTextEnabled = useRequireAltTextEnabled()
const setRequireAltTextEnabled = useSetRequireAltTextEnabled()
@ -213,7 +216,7 @@ export const SettingsScreen = withAuthRequired(
{store.session.currentSession !== undefined ? (
<>
<Text type="xl-bold" style={[pal.text, styles.heading]}>
Account
<Trans>Account</Trans>
</Text>
<View style={[styles.infoLine]}>
<Text type="lg-medium" style={pal.text}>
@ -233,17 +236,17 @@ export const SettingsScreen = withAuthRequired(
</Text>
<Link onPress={() => openModal({name: 'change-email'})}>
<Text type="lg" style={pal.link}>
Change
<Trans>Change</Trans>
</Text>
</Link>
</View>
<View style={[styles.infoLine]}>
<Text type="lg-medium" style={pal.text}>
Birthday:{' '}
<Trans>Birthday: </Trans>
</Text>
<Link onPress={() => openModal({name: 'birth-date-settings'})}>
<Text type="lg" style={pal.link}>
Show
<Trans>Show</Trans>
</Text>
</Link>
</View>
@ -253,7 +256,7 @@ export const SettingsScreen = withAuthRequired(
) : null}
<View style={[s.flexRow, styles.heading]}>
<Text type="xl-bold" style={pal.text}>
Signed in as
<Trans>Signed in as</Trans>
</Text>
<View style={s.flex1} />
</View>
@ -282,10 +285,10 @@ export const SettingsScreen = withAuthRequired(
testID="signOutBtn"
onPress={isSwitching ? undefined : onPressSignout}
accessibilityRole="button"
accessibilityLabel="Sign out"
accessibilityLabel={_(msg`Sign out`)}
accessibilityHint={`Signs ${store.me.displayName} out of Bluesky`}>
<Text type="lg" style={pal.link}>
Sign out
<Trans>Sign out</Trans>
</Text>
</TouchableOpacity>
</View>
@ -321,7 +324,7 @@ export const SettingsScreen = withAuthRequired(
style={[styles.linkCard, pal.view, isSwitching && styles.dimmed]}
onPress={isSwitching ? undefined : onPressAddAccount}
accessibilityRole="button"
accessibilityLabel="Add account"
accessibilityLabel={_(msg`Add account`)}
accessibilityHint="Create a new Bluesky account">
<View style={[styles.iconContainer, pal.btn]}>
<FontAwesomeIcon
@ -330,21 +333,21 @@ export const SettingsScreen = withAuthRequired(
/>
</View>
<Text type="lg" style={pal.text}>
Add account
<Trans>Add account</Trans>
</Text>
</TouchableOpacity>
<View style={styles.spacer20} />
<Text type="xl-bold" style={[pal.text, styles.heading]}>
Invite a Friend
<Trans>Invite a Friend</Trans>
</Text>
<TouchableOpacity
testID="inviteFriendBtn"
style={[styles.linkCard, pal.view, isSwitching && styles.dimmed]}
onPress={isSwitching ? undefined : onPressInviteCodes}
accessibilityRole="button"
accessibilityLabel="Invite"
accessibilityLabel={_(msg`Invite`)}
accessibilityHint="Opens invite code list">
<View
style={[
@ -371,7 +374,7 @@ export const SettingsScreen = withAuthRequired(
<View style={styles.spacer20} />
<Text type="xl-bold" style={[pal.text, styles.heading]}>
Accessibility
<Trans>Accessibility</Trans>
</Text>
<View style={[pal.view, styles.toggleCard]}>
<ToggleButton
@ -386,7 +389,7 @@ export const SettingsScreen = withAuthRequired(
<View style={styles.spacer20} />
<Text type="xl-bold" style={[pal.text, styles.heading]}>
Appearance
<Trans>Appearance</Trans>
</Text>
<View>
<View style={[styles.linkCard, pal.view, styles.selectableBtns]}>
@ -415,7 +418,7 @@ export const SettingsScreen = withAuthRequired(
<View style={styles.spacer20} />
<Text type="xl-bold" style={[pal.text, styles.heading]}>
Basics
<Trans>Basics</Trans>
</Text>
<TouchableOpacity
testID="preferencesHomeFeedButton"
@ -423,7 +426,7 @@ export const SettingsScreen = withAuthRequired(
onPress={openHomeFeedPreferences}
accessibilityRole="button"
accessibilityHint=""
accessibilityLabel="Opens the home feed preferences">
accessibilityLabel={_(msg`Opens the home feed preferences`)}>
<View style={[styles.iconContainer, pal.btn]}>
<FontAwesomeIcon
icon="sliders"
@ -431,7 +434,7 @@ export const SettingsScreen = withAuthRequired(
/>
</View>
<Text type="lg" style={pal.text}>
Home Feed Preferences
<Trans>Home Feed Preferences</Trans>
</Text>
</TouchableOpacity>
<TouchableOpacity
@ -440,7 +443,7 @@ export const SettingsScreen = withAuthRequired(
onPress={openThreadsPreferences}
accessibilityRole="button"
accessibilityHint=""
accessibilityLabel="Opens the threads preferences">
accessibilityLabel={_(msg`Opens the threads preferences`)}>
<View style={[styles.iconContainer, pal.btn]}>
<FontAwesomeIcon
icon={['far', 'comments']}
@ -449,20 +452,20 @@ export const SettingsScreen = withAuthRequired(
/>
</View>
<Text type="lg" style={pal.text}>
Thread Preferences
<Trans>Thread Preferences</Trans>
</Text>
</TouchableOpacity>
<TouchableOpacity
testID="savedFeedsBtn"
style={[styles.linkCard, pal.view, isSwitching && styles.dimmed]}
accessibilityHint="My Saved Feeds"
accessibilityLabel="Opens screen with all saved feeds"
accessibilityLabel={_(msg`Opens screen with all saved feeds`)}
onPress={onPressSavedFeeds}>
<View style={[styles.iconContainer, pal.btn]}>
<HashtagIcon style={pal.text} size={18} strokeWidth={3} />
</View>
<Text type="lg" style={pal.text}>
My Saved Feeds
<Trans>My Saved Feeds</Trans>
</Text>
</TouchableOpacity>
<TouchableOpacity
@ -471,7 +474,7 @@ export const SettingsScreen = withAuthRequired(
onPress={isSwitching ? undefined : onPressLanguageSettings}
accessibilityRole="button"
accessibilityHint="Language settings"
accessibilityLabel="Opens configurable language settings">
accessibilityLabel={_(msg`Opens configurable language settings`)}>
<View style={[styles.iconContainer, pal.btn]}>
<FontAwesomeIcon
icon="language"
@ -479,7 +482,7 @@ export const SettingsScreen = withAuthRequired(
/>
</View>
<Text type="lg" style={pal.text}>
Languages
<Trans>Languages</Trans>
</Text>
</TouchableOpacity>
<TouchableOpacity
@ -490,18 +493,18 @@ export const SettingsScreen = withAuthRequired(
}
accessibilityRole="button"
accessibilityHint=""
accessibilityLabel="Opens moderation settings">
accessibilityLabel={_(msg`Opens moderation settings`)}>
<View style={[styles.iconContainer, pal.btn]}>
<HandIcon style={pal.text} size={18} strokeWidth={6} />
</View>
<Text type="lg" style={pal.text}>
Moderation
<Trans>Moderation</Trans>
</Text>
</TouchableOpacity>
<View style={styles.spacer20} />
<Text type="xl-bold" style={[pal.text, styles.heading]}>
Advanced
<Trans>Advanced</Trans>
</Text>
<TouchableOpacity
testID="appPasswordBtn"
@ -509,7 +512,7 @@ export const SettingsScreen = withAuthRequired(
onPress={onPressAppPasswords}
accessibilityRole="button"
accessibilityHint="Open app password settings"
accessibilityLabel="Opens the app password settings page">
accessibilityLabel={_(msg`Opens the app password settings page`)}>
<View style={[styles.iconContainer, pal.btn]}>
<FontAwesomeIcon
icon="lock"
@ -517,7 +520,7 @@ export const SettingsScreen = withAuthRequired(
/>
</View>
<Text type="lg" style={pal.text}>
App passwords
<Trans>App passwords</Trans>
</Text>
</TouchableOpacity>
<TouchableOpacity
@ -525,7 +528,7 @@ export const SettingsScreen = withAuthRequired(
style={[styles.linkCard, pal.view, isSwitching && styles.dimmed]}
onPress={isSwitching ? undefined : onPressChangeHandle}
accessibilityRole="button"
accessibilityLabel="Change handle"
accessibilityLabel={_(msg`Change handle`)}
accessibilityHint="Choose a new Bluesky username or create">
<View style={[styles.iconContainer, pal.btn]}>
<FontAwesomeIcon
@ -534,19 +537,19 @@ export const SettingsScreen = withAuthRequired(
/>
</View>
<Text type="lg" style={pal.text} numberOfLines={1}>
Change handle
<Trans>Change handle</Trans>
</Text>
</TouchableOpacity>
<View style={styles.spacer20} />
<Text type="xl-bold" style={[pal.text, styles.heading]}>
Danger Zone
<Trans>Danger Zone</Trans>
</Text>
<TouchableOpacity
style={[pal.view, styles.linkCard]}
onPress={onPressDeleteAccount}
accessible={true}
accessibilityRole="button"
accessibilityLabel="Delete account"
accessibilityLabel={_(msg`Delete account`)}
accessibilityHint="Opens modal for account deletion confirmation. Requires email code.">
<View style={[styles.iconContainer, dangerBg]}>
<FontAwesomeIcon
@ -556,21 +559,21 @@ export const SettingsScreen = withAuthRequired(
/>
</View>
<Text type="lg" style={dangerText}>
Delete my account
<Trans>Delete my account</Trans>
</Text>
</TouchableOpacity>
<View style={styles.spacer20} />
<Text type="xl-bold" style={[pal.text, styles.heading]}>
Developer Tools
<Trans>Developer Tools</Trans>
</Text>
<TouchableOpacity
style={[pal.view, styles.linkCardNoIcon]}
onPress={onPressSystemLog}
accessibilityRole="button"
accessibilityHint="Open system log"
accessibilityLabel="Opens the system log page">
accessibilityLabel={_(msg`Opens the system log page`)}>
<Text type="lg" style={pal.text}>
System log
<Trans>System log</Trans>
</Text>
</TouchableOpacity>
{__DEV__ ? (
@ -588,9 +591,9 @@ export const SettingsScreen = withAuthRequired(
onPress={onPressStorybook}
accessibilityRole="button"
accessibilityHint="Open storybook page"
accessibilityLabel="Opens the storybook page">
accessibilityLabel={_(msg`Opens the storybook page`)}>
<Text type="lg" style={pal.text}>
Storybook
<Trans>Storybook</Trans>
</Text>
</TouchableOpacity>
<TouchableOpacity
@ -598,9 +601,9 @@ export const SettingsScreen = withAuthRequired(
onPress={onPressResetPreferences}
accessibilityRole="button"
accessibilityHint="Reset preferences"
accessibilityLabel="Resets the preferences state">
accessibilityLabel={_(msg`Resets the preferences state`)}>
<Text type="lg" style={pal.text}>
Reset preferences state
<Trans>Reset preferences state</Trans>
</Text>
</TouchableOpacity>
<TouchableOpacity
@ -608,9 +611,9 @@ export const SettingsScreen = withAuthRequired(
onPress={onPressResetOnboarding}
accessibilityRole="button"
accessibilityHint="Reset onboarding"
accessibilityLabel="Resets the onboarding state">
accessibilityLabel={_(msg`Resets the onboarding state`)}>
<Text type="lg" style={pal.text}>
Reset onboarding state
<Trans>Reset onboarding state</Trans>
</Text>
</TouchableOpacity>
</>
@ -620,7 +623,9 @@ export const SettingsScreen = withAuthRequired(
accessibilityRole="button"
onPress={onPressBuildInfo}>
<Text type="sm" style={[styles.buildInfo, pal.textLight]}>
Build version {AppInfo.appVersion} {AppInfo.updateChannel}
<Trans>
Build version {AppInfo.appVersion} {AppInfo.updateChannel}
</Trans>
</Text>
</TouchableOpacity>
<Text type="sm" style={[pal.textLight]}>
@ -630,7 +635,7 @@ export const SettingsScreen = withAuthRequired(
accessibilityRole="button"
onPress={onPressStatusPage}>
<Text type="sm" style={[styles.buildInfo, pal.textLight]}>
Status page
<Trans>Status page</Trans>
</Text>
</TouchableOpacity>
</View>
@ -646,6 +651,7 @@ const EmailConfirmationNotice = observer(
const pal = usePalette('default')
const palInverted = usePalette('inverted')
const store = useStores()
const {_} = useLingui()
const {isMobile} = useWebMediaQueries()
const {openModal} = useModalControls()
@ -656,7 +662,7 @@ const EmailConfirmationNotice = observer(
return (
<View style={{marginBottom: 20}}>
<Text type="xl-bold" style={[pal.text, styles.heading]}>
Verify email
<Trans>Verify email</Trans>
</Text>
<View
style={[
@ -681,7 +687,7 @@ const EmailConfirmationNotice = observer(
isMobile && {flex: 1},
]}
accessibilityRole="button"
accessibilityLabel="Verify my email"
accessibilityLabel={_(msg`Verify my email`)}
accessibilityHint=""
onPress={() => openModal({name: 'verify-email'})}>
<FontAwesomeIcon
@ -690,12 +696,12 @@ const EmailConfirmationNotice = observer(
size={16}
/>
<Text type="button" style={palInverted.text}>
Verify My Email
<Trans>Verify My Email</Trans>
</Text>
</Pressable>
</View>
<Text style={pal.textLight}>
Protect your account by verifying your email.
<Trans>Protect your account by verifying your email.</Trans>
</Text>
</View>
</View>