Hindi Internationalization (#1914)

* get basic hindi support to work

* get web app language switcher in

* Refactor i18n implementation and remove unused
code

* add missing strings

* add dropdowns and modals missing strings

* complete all hindi translations

* fix merge conflicts

* fix legeacy persisted state

* fix data in RecommendedFeeds

* fix lint
This commit is contained in:
Ansh 2023-11-20 13:29:27 -08:00 committed by GitHub
parent 019aae5f01
commit c5b6f88e9a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
68 changed files with 5121 additions and 2058 deletions

View file

@ -183,9 +183,10 @@ export const AppPasswords = withAuthRequired(
function AppPasswordsHeader() {
const {isTabletOrDesktop} = useWebMediaQueries()
const pal = usePalette('default')
const {_} = useLingui()
return (
<>
<ViewHeader title="App Passwords" showOnDesktop />
<ViewHeader title={_(msg`App Passwords`)} showOnDesktop />
<Text
type="sm"
style={[
@ -220,14 +221,16 @@ function AppPassword({
const onDelete = React.useCallback(async () => {
openModal({
name: 'confirm',
title: 'Delete App Password',
message: `Are you sure you want to delete the app password "${name}"?`,
title: _(msg`Delete app password`),
message: _(
msg`Are you sure you want to delete the app password "${name}"?`,
),
async onPressConfirm() {
await deleteMutation.mutateAsync({name})
Toast.show('App password deleted')
},
})
}, [deleteMutation, openModal, name])
}, [deleteMutation, openModal, name, _])
const primaryLocale =
contentLanguages.length > 0 ? contentLanguages[0] : 'en-US'
@ -245,15 +248,17 @@ function AppPassword({
{name}
</Text>
<Text type="md" style={[pal.text, styles.pr10]} numberOfLines={1}>
Created{' '}
{Intl.DateTimeFormat(primaryLocale, {
year: 'numeric',
month: 'numeric',
day: 'numeric',
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
}).format(new Date(createdAt))}
<Trans>
Created{' '}
{Intl.DateTimeFormat(primaryLocale, {
year: 'numeric',
month: 'numeric',
day: 'numeric',
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
}).format(new Date(createdAt))}
</Trans>
</Text>
</View>
<FontAwesomeIcon icon={['far', 'trash-can']} style={styles.trashIcon} />

View file

@ -9,6 +9,8 @@ import {ScrollView} from 'view/com/util/Views'
import {usePalette} from 'lib/hooks/usePalette'
import {s} from 'lib/styles'
import {useSetMinimalShellMode} from '#/state/shell'
import {Trans, msg} from '@lingui/macro'
import {useLingui} from '@lingui/react'
type Props = NativeStackScreenProps<
CommonNavigatorParams,
@ -16,6 +18,7 @@ type Props = NativeStackScreenProps<
>
export const CommunityGuidelinesScreen = (_props: Props) => {
const pal = usePalette('default')
const {_} = useLingui()
const setMinimalShellMode = useSetMinimalShellMode()
useFocusEffect(
@ -26,16 +29,18 @@ export const CommunityGuidelinesScreen = (_props: Props) => {
return (
<View>
<ViewHeader title="Community Guidelines" />
<ViewHeader title={_(msg`Community Guidelines`)} />
<ScrollView style={[s.hContentRegion, pal.view]}>
<View style={[s.p20]}>
<Text style={pal.text}>
The Community Guidelines have been moved to{' '}
<TextLink
style={pal.link}
href="https://blueskyweb.xyz/support/community-guidelines"
text="blueskyweb.xyz/support/community-guidelines"
/>
<Trans>
The Community Guidelines have been moved to{' '}
<TextLink
style={pal.link}
href="https://blueskyweb.xyz/support/community-guidelines"
text="blueskyweb.xyz/support/community-guidelines"
/>
</Trans>
</Text>
</View>
<View style={s.footerSpacer} />

View file

@ -9,10 +9,13 @@ import {ScrollView} from 'view/com/util/Views'
import {usePalette} from 'lib/hooks/usePalette'
import {s} from 'lib/styles'
import {useSetMinimalShellMode} from '#/state/shell'
import {Trans, msg} from '@lingui/macro'
import {useLingui} from '@lingui/react'
type Props = NativeStackScreenProps<CommonNavigatorParams, 'CopyrightPolicy'>
export const CopyrightPolicyScreen = (_props: Props) => {
const pal = usePalette('default')
const {_} = useLingui()
const setMinimalShellMode = useSetMinimalShellMode()
useFocusEffect(
@ -23,16 +26,18 @@ export const CopyrightPolicyScreen = (_props: Props) => {
return (
<View>
<ViewHeader title="Copyright Policy" />
<ViewHeader title={_(msg`Copyright Policy`)} />
<ScrollView style={[s.hContentRegion, pal.view]}>
<View style={[s.p20]}>
<Text style={pal.text}>
The Copyright Policy has been moved to{' '}
<TextLink
style={pal.link}
href="https://blueskyweb.xyz/support/community-guidelines"
text="blueskyweb.xyz/support/community-guidelines"
/>
<Trans>
The Copyright Policy has been moved to{' '}
<TextLink
style={pal.link}
href="https://blueskyweb.xyz/support/community-guidelines"
text="blueskyweb.xyz/support/community-guidelines"
/>
</Trans>
</Text>
</View>
<View style={s.footerSpacer} />

View file

@ -467,7 +467,7 @@ export const FeedsScreen = withAuthRequired(function FeedsScreenImpl(
<View style={[pal.view, styles.container]}>
{isMobile && (
<ViewHeader
title="Feeds"
title={_(msg`Feeds`)}
canGoBack={false}
renderButton={renderHeaderBtn}
showBorder

View file

@ -14,16 +14,19 @@ import {
} from '@fortawesome/react-native-fontawesome'
import {useAnalytics} from 'lib/analytics/analytics'
import {useFocusEffect} from '@react-navigation/native'
import {LANGUAGES} from 'lib/../locale/languages'
import {APP_LANGUAGES, LANGUAGES} from 'lib/../locale/languages'
import RNPickerSelect, {PickerSelectProps} from 'react-native-picker-select'
import {useSetMinimalShellMode} from '#/state/shell'
import {useModalControls} from '#/state/modals'
import {useLanguagePrefs, useLanguagePrefsApi} from '#/state/preferences'
import {Trans, msg} from '@lingui/macro'
import {useLingui} from '@lingui/react'
type Props = NativeStackScreenProps<CommonNavigatorParams, 'LanguageSettings'>
export function LanguageSettingsScreen(_: Props) {
export function LanguageSettingsScreen(_props: Props) {
const pal = usePalette('default')
const {_} = useLingui()
const langPrefs = useLanguagePrefs()
const setLangPrefs = useLanguagePrefsApi()
const {isTabletOrDesktop} = useWebMediaQueries()
@ -52,6 +55,15 @@ export function LanguageSettingsScreen(_: Props) {
[langPrefs, setLangPrefs],
)
const onChangeAppLanguage = React.useCallback(
(value: Parameters<PickerSelectProps['onValueChange']>[0]) => {
if (langPrefs.appLanguage !== value) {
setLangPrefs.setAppLanguage(value)
}
},
[langPrefs, setLangPrefs],
)
const myLanguages = React.useMemo(() => {
return (
langPrefs.contentLanguages
@ -71,15 +83,109 @@ export function LanguageSettingsScreen(_: Props) {
styles.container,
isTabletOrDesktop && styles.desktopContainer,
]}>
<ViewHeader title="Language Settings" showOnDesktop />
<ViewHeader title={_(msg`Language Settings`)} showOnDesktop />
<View style={{paddingTop: 20, paddingHorizontal: 20}}>
{/* APP LANGUAGE */}
<View style={{paddingBottom: 20}}>
<Text type="title-sm" style={[pal.text, s.pb5]}>
Primary Language
<Trans>App Language</Trans>
</Text>
<Text style={[pal.text, s.pb10]}>
Select your preferred language for translations in your feed.
<Trans>
Select your app language for the default text to display in the
app
</Trans>
</Text>
<View style={{position: 'relative'}}>
<RNPickerSelect
value={langPrefs.appLanguage}
onValueChange={onChangeAppLanguage}
items={APP_LANGUAGES.filter(l => Boolean(l.code2)).map(l => ({
label: l.name,
value: l.code2,
key: l.code2,
}))}
style={{
inputAndroid: {
backgroundColor: pal.viewLight.backgroundColor,
color: pal.text.color,
fontSize: 14,
letterSpacing: 0.5,
fontWeight: '500',
paddingHorizontal: 14,
paddingVertical: 8,
borderRadius: 24,
},
inputIOS: {
backgroundColor: pal.viewLight.backgroundColor,
color: pal.text.color,
fontSize: 14,
letterSpacing: 0.5,
fontWeight: '500',
paddingHorizontal: 14,
paddingVertical: 8,
borderRadius: 24,
},
inputWeb: {
// @ts-ignore web only
cursor: 'pointer',
'-moz-appearance': 'none',
'-webkit-appearance': 'none',
appearance: 'none',
outline: 0,
borderWidth: 0,
backgroundColor: pal.viewLight.backgroundColor,
color: pal.text.color,
fontSize: 14,
letterSpacing: 0.5,
fontWeight: '500',
paddingHorizontal: 14,
paddingVertical: 8,
borderRadius: 24,
},
}}
/>
<View
style={{
position: 'absolute',
top: 1,
right: 1,
bottom: 1,
width: 40,
backgroundColor: pal.viewLight.backgroundColor,
borderRadius: 24,
pointerEvents: 'none',
alignItems: 'center',
justifyContent: 'center',
}}>
<FontAwesomeIcon
icon="chevron-down"
style={pal.text as FontAwesomeIconStyle}
/>
</View>
</View>
</View>
<View
style={{
height: 1,
backgroundColor: pal.border.borderColor,
marginBottom: 20,
}}
/>
{/* PRIMARY LANGUAGE */}
<View style={{paddingBottom: 20}}>
<Text type="title-sm" style={[pal.text, s.pb5]}>
<Trans>Primary Language</Trans>
</Text>
<Text style={[pal.text, s.pb10]}>
<Trans>
Select your preferred language for translations in your feed.
</Trans>
</Text>
<View style={{position: 'relative'}}>
@ -161,13 +267,16 @@ export function LanguageSettingsScreen(_: Props) {
}}
/>
{/* CONTENT LANGUAGES */}
<View style={{paddingBottom: 20}}>
<Text type="title-sm" style={[pal.text, s.pb5]}>
Content Languages
<Trans>Content Languages</Trans>
</Text>
<Text style={[pal.text, s.pb10]}>
Select which languages you want your subscribed feeds to include. If
none are selected, all languages will be shown.
<Trans>
Select which languages you want your subscribed feeds to include.
If none are selected, all languages will be shown.
</Trans>
</Text>
<Button

View file

@ -15,6 +15,7 @@ import {SimpleViewHeader} from 'view/com/util/SimpleViewHeader'
import {s} from 'lib/styles'
import {useSetMinimalShellMode} from '#/state/shell'
import {useModalControls} from '#/state/modals'
import {Trans} from '@lingui/macro'
type Props = NativeStackScreenProps<CommonNavigatorParams, 'Lists'>
export const ListsScreen = withAuthRequired(
@ -56,10 +57,10 @@ export const ListsScreen = withAuthRequired(
}>
<View style={{flex: 1}}>
<Text type="title-lg" style={[pal.text, {fontWeight: 'bold'}]}>
User Lists
<Trans>User Lists</Trans>
</Text>
<Text style={pal.textLight}>
Public, shareable lists which can drive feeds.
<Trans>Public, shareable lists which can drive feeds.</Trans>
</Text>
</View>
<View>
@ -74,7 +75,7 @@ export const ListsScreen = withAuthRequired(
}}>
<FontAwesomeIcon icon="plus" color={pal.colors.text} />
<Text type="button" style={pal.text}>
New
<Trans>New</Trans>
</Text>
</Button>
</View>

View file

@ -17,11 +17,14 @@ import {useAnalytics} from 'lib/analytics/analytics'
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
import {useSetMinimalShellMode} from '#/state/shell'
import {useModalControls} from '#/state/modals'
import {Trans, msg} from '@lingui/macro'
import {useLingui} from '@lingui/react'
type Props = NativeStackScreenProps<CommonNavigatorParams, 'Moderation'>
export const ModerationScreen = withAuthRequired(
function Moderation({}: Props) {
const pal = usePalette('default')
const {_} = useLingui()
const setMinimalShellMode = useSetMinimalShellMode()
const {screen, track} = useAnalytics()
const {isTabletOrDesktop} = useWebMediaQueries()
@ -47,7 +50,7 @@ export const ModerationScreen = withAuthRequired(
isTabletOrDesktop ? styles.desktopContainer : pal.viewLight,
]}
testID="moderationScreen">
<ViewHeader title="Moderation" showOnDesktop />
<ViewHeader title={_(msg`Moderation`)} showOnDesktop />
<View style={styles.spacer} />
<TouchableOpacity
testID="contentFilteringBtn"
@ -63,7 +66,7 @@ export const ModerationScreen = withAuthRequired(
/>
</View>
<Text type="lg" style={pal.text}>
Content filtering
<Trans>Content filtering</Trans>
</Text>
</TouchableOpacity>
<Link
@ -77,7 +80,7 @@ export const ModerationScreen = withAuthRequired(
/>
</View>
<Text type="lg" style={pal.text}>
Moderation lists
<Trans>Moderation lists</Trans>
</Text>
</Link>
<Link
@ -91,7 +94,7 @@ export const ModerationScreen = withAuthRequired(
/>
</View>
<Text type="lg" style={pal.text}>
Muted accounts
<Trans>Muted accounts</Trans>
</Text>
</Link>
<Link
@ -105,7 +108,7 @@ export const ModerationScreen = withAuthRequired(
/>
</View>
<Text type="lg" style={pal.text}>
Blocked accounts
<Trans>Blocked accounts</Trans>
</Text>
</Link>
</CenteredView>

View file

@ -21,6 +21,8 @@ import {ErrorScreen} from '../com/util/error/ErrorScreen'
import {ProfileCard} from 'view/com/profile/ProfileCard'
import {logger} from '#/logger'
import {useSetMinimalShellMode} from '#/state/shell'
import {Trans, msg} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {useMyBlockedAccountsQuery} from '#/state/queries/my-blocked-accounts'
import {cleanError} from '#/lib/strings/errors'
@ -31,6 +33,7 @@ type Props = NativeStackScreenProps<
export const ModerationBlockedAccounts = withAuthRequired(
function ModerationBlockedAccountsImpl({}: Props) {
const pal = usePalette('default')
const {_} = useLingui()
const setMinimalShellMode = useSetMinimalShellMode()
const {isTabletOrDesktop} = useWebMediaQueries()
const {screen} = useAnalytics()
@ -104,7 +107,7 @@ export const ModerationBlockedAccounts = withAuthRequired(
pal.border,
]}
testID="blockedAccountsScreen">
<ViewHeader title="Blocked Accounts" showOnDesktop />
<ViewHeader title={_(msg`Blocked Accounts`)} showOnDesktop />
<Text
type="sm"
style={[
@ -112,9 +115,11 @@ export const ModerationBlockedAccounts = withAuthRequired(
pal.text,
isTabletOrDesktop && styles.descriptionDesktop,
]}>
Blocked accounts cannot reply in your threads, mention you, or
otherwise interact with you. You will not see their content and they
will be prevented from seeing yours.
<Trans>
Blocked accounts cannot reply in your threads, mention you, or
otherwise interact with you. You will not see their content and they
will be prevented from seeing yours.
</Trans>
</Text>
{isEmpty ? (
<View style={[pal.border, !isTabletOrDesktop && styles.flex1]}>
@ -127,9 +132,11 @@ export const ModerationBlockedAccounts = withAuthRequired(
) : (
<View style={[styles.empty, pal.viewLight]}>
<Text type="lg" style={[pal.text, styles.emptyText]}>
You have not blocked any accounts yet. To block an account, go
to their profile and selected "Block account" from the menu on
their account.
<Trans>
You have not blocked any accounts yet. To block an account,
go to their profile and selected "Block account" from the
menu on their account.
</Trans>
</Text>
</View>
)}

View file

@ -21,6 +21,8 @@ import {ErrorScreen} from '../com/util/error/ErrorScreen'
import {ProfileCard} from 'view/com/profile/ProfileCard'
import {logger} from '#/logger'
import {useSetMinimalShellMode} from '#/state/shell'
import {Trans, msg} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {useMyMutedAccountsQuery} from '#/state/queries/my-muted-accounts'
import {cleanError} from '#/lib/strings/errors'
@ -31,6 +33,7 @@ type Props = NativeStackScreenProps<
export const ModerationMutedAccounts = withAuthRequired(
function ModerationMutedAccountsImpl({}: Props) {
const pal = usePalette('default')
const {_} = useLingui()
const setMinimalShellMode = useSetMinimalShellMode()
const {isTabletOrDesktop} = useWebMediaQueries()
const {screen} = useAnalytics()
@ -104,7 +107,7 @@ export const ModerationMutedAccounts = withAuthRequired(
pal.border,
]}
testID="mutedAccountsScreen">
<ViewHeader title="Muted Accounts" showOnDesktop />
<ViewHeader title={_(msg`Muted Accounts`)} showOnDesktop />
<Text
type="sm"
style={[
@ -112,8 +115,10 @@ export const ModerationMutedAccounts = withAuthRequired(
pal.text,
isTabletOrDesktop && styles.descriptionDesktop,
]}>
Muted accounts have their posts removed from your feed and from your
notifications. Mutes are completely private.
<Trans>
Muted accounts have their posts removed from your feed and from your
notifications. Mutes are completely private.
</Trans>
</Text>
{isEmpty ? (
<View style={[pal.border, !isTabletOrDesktop && styles.flex1]}>
@ -126,9 +131,11 @@ export const ModerationMutedAccounts = withAuthRequired(
) : (
<View style={[styles.empty, pal.viewLight]}>
<Text type="lg" style={[pal.text, styles.emptyText]}>
You have not muted any accounts yet. To mute an account, go to
their profile and selected "Mute account" from the menu on
their account.
<Trans>
You have not muted any accounts yet. To mute an account, go
to their profile and selected "Mute account" from the menu
on their account.
</Trans>
</Text>
</View>
)}

View file

@ -12,9 +12,12 @@ import {NavigationProp} from 'lib/routes/types'
import {usePalette} from 'lib/hooks/usePalette'
import {s} from 'lib/styles'
import {useSetMinimalShellMode} from '#/state/shell'
import {Trans, msg} from '@lingui/macro'
import {useLingui} from '@lingui/react'
export const NotFoundScreen = () => {
const pal = usePalette('default')
const {_} = useLingui()
const navigation = useNavigation<NavigationProp>()
const setMinimalShellMode = useSetMinimalShellMode()
@ -36,13 +39,15 @@ export const NotFoundScreen = () => {
return (
<View testID="notFoundView" style={pal.view}>
<ViewHeader title="Page not found" />
<ViewHeader title={_(msg`Page not found`)} />
<View style={styles.container}>
<Text type="title-2xl" style={[pal.text, s.mb10]}>
Page not found
<Trans>Page not found</Trans>
</Text>
<Text type="md" style={[pal.text, s.mb10]}>
We're sorry! We can't find the page you were looking for.
<Trans>
We're sorry! We can't find the page you were looking for.
</Trans>
</Text>
<Button
type="primary"

View file

@ -18,6 +18,8 @@ import {s, colors} from 'lib/styles'
import {useAnalytics} from 'lib/analytics/analytics'
import {logger} from '#/logger'
import {useSetMinimalShellMode} from '#/state/shell'
import {Trans, msg} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {useUnreadNotifications} from '#/state/queries/notifications/unread'
import {RQKEY as NOTIFS_RQKEY} from '#/state/queries/notifications/feed'
import {listenSoftReset, emitSoftReset} from '#/state/events'
@ -28,6 +30,7 @@ type Props = NativeStackScreenProps<
>
export const NotificationsScreen = withAuthRequired(
function NotificationsScreenImpl({}: Props) {
const {_} = useLingui()
const setMinimalShellMode = useSetMinimalShellMode()
const [onMainScroll, isScrolledDown, resetMainScroll] = useOnMainScroll()
const scrollElRef = React.useRef<FlatList>(null)
@ -83,7 +86,7 @@ export const NotificationsScreen = withAuthRequired(
style={[pal.text, {fontWeight: 'bold'}]}
text={
<>
Notifications{' '}
<Trans>Notifications</Trans>{' '}
{hasNew && (
<View
style={{
@ -107,7 +110,7 @@ export const NotificationsScreen = withAuthRequired(
return (
<View testID="notificationsScreen" style={s.hContentRegion}>
<ViewHeader title="Notifications" canGoBack={false} />
<ViewHeader title={_(msg`Notifications`)} canGoBack={false} />
<Feed
onScroll={onMainScroll}
scrollElRef={scrollElRef}
@ -116,7 +119,7 @@ export const NotificationsScreen = withAuthRequired(
{(isScrolledDown || hasNew) && (
<LoadLatestBtn
onPress={onPressLoadLatest}
label="Load new notifications"
label={_(msg`Load new notifications`)}
showIndicator={hasNew}
/>
)}

View file

@ -7,12 +7,15 @@ import {ViewHeader} from '../com/util/ViewHeader'
import {PostLikedBy as PostLikedByComponent} from '../com/post-thread/PostLikedBy'
import {makeRecordUri} from 'lib/strings/url-helpers'
import {useSetMinimalShellMode} from '#/state/shell'
import {msg} from '@lingui/macro'
import {useLingui} from '@lingui/react'
type Props = NativeStackScreenProps<CommonNavigatorParams, 'PostLikedBy'>
export const PostLikedByScreen = withAuthRequired(({route}: Props) => {
const setMinimalShellMode = useSetMinimalShellMode()
const {name, rkey} = route.params
const uri = makeRecordUri(name, 'app.bsky.feed.post', rkey)
const {_} = useLingui()
useFocusEffect(
React.useCallback(() => {
@ -22,7 +25,7 @@ export const PostLikedByScreen = withAuthRequired(({route}: Props) => {
return (
<View>
<ViewHeader title="Liked by" />
<ViewHeader title={_(msg`Liked by`)} />
<PostLikedByComponent uri={uri} />
</View>
)

View file

@ -7,12 +7,15 @@ import {ViewHeader} from '../com/util/ViewHeader'
import {PostRepostedBy as PostRepostedByComponent} from '../com/post-thread/PostRepostedBy'
import {makeRecordUri} from 'lib/strings/url-helpers'
import {useSetMinimalShellMode} from '#/state/shell'
import {useLingui} from '@lingui/react'
import {msg} from '@lingui/macro'
type Props = NativeStackScreenProps<CommonNavigatorParams, 'PostRepostedBy'>
export const PostRepostedByScreen = withAuthRequired(({route}: Props) => {
const {name, rkey} = route.params
const uri = makeRecordUri(name, 'app.bsky.feed.post', rkey)
const setMinimalShellMode = useSetMinimalShellMode()
const {_} = useLingui()
useFocusEffect(
React.useCallback(() => {
@ -22,7 +25,7 @@ export const PostRepostedByScreen = withAuthRequired(({route}: Props) => {
return (
<View>
<ViewHeader title="Reposted by" />
<ViewHeader title={_(msg`Reposted by`)} />
<PostRepostedByComponent uri={uri} />
</View>
)

View file

@ -19,6 +19,8 @@ import {clamp} from 'lodash'
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
import {useMinimalShellMode} from 'lib/hooks/useMinimalShellMode'
import {useSetMinimalShellMode} from '#/state/shell'
import {useLingui} from '@lingui/react'
import {msg} from '@lingui/macro'
import {useResolveUriQuery} from '#/state/queries/resolve-uri'
import {ErrorMessage} from '../com/util/error/ErrorMessage'
import {CenteredView} from '../com/util/Views'
@ -29,6 +31,7 @@ export const PostThreadScreen = withAuthRequired(function PostThreadScreenImpl({
route,
}: Props) {
const queryClient = useQueryClient()
const {_} = useLingui()
const {fabMinimalShellTransform} = useMinimalShellMode()
const setMinimalShellMode = useSetMinimalShellMode()
const {openComposer} = useComposerControls()
@ -74,7 +77,7 @@ export const PostThreadScreen = withAuthRequired(function PostThreadScreenImpl({
return (
<View style={s.hContentRegion}>
{isMobile && <ViewHeader title="Post" />}
{isMobile && <ViewHeader title={_(msg`Post`)} />}
<View style={s.flex1}>
{uriError ? (
<CenteredView>

View file

@ -92,7 +92,7 @@ export function PreferencesHomeFeed({navigation}: Props) {
styles.container,
isTabletOrDesktop && styles.desktopContainer,
]}>
<ViewHeader title="Home Feed Preferences" showOnDesktop />
<ViewHeader title={_(msg`Home Feed Preferences`)} showOnDesktop />
<View
style={[
styles.titleSection,
@ -142,7 +142,7 @@ export function PreferencesHomeFeed({navigation}: Props) {
</Text>
<ToggleButton
type="default-light"
label="Followed users only"
label={_(msg`Followed users only`)}
isSelected={Boolean(
variables?.hideRepliesByUnfollowed ??
preferences?.feedViewPrefs?.hideRepliesByUnfollowed,
@ -188,8 +188,8 @@ export function PreferencesHomeFeed({navigation}: Props) {
label={
variables?.hideReposts ??
preferences?.feedViewPrefs?.hideReposts
? 'No'
: 'Yes'
? _(msg`No`)
: _(msg`Yes`)
}
isSelected={
!(
@ -223,8 +223,8 @@ export function PreferencesHomeFeed({navigation}: Props) {
label={
variables?.hideQuotePosts ??
preferences?.feedViewPrefs?.hideQuotePosts
? 'No'
: 'Yes'
? _(msg`No`)
: _(msg`Yes`)
}
isSelected={
!(
@ -259,8 +259,8 @@ export function PreferencesHomeFeed({navigation}: Props) {
label={
variables?.lab_mergeFeedEnabled ??
preferences?.feedViewPrefs?.lab_mergeFeedEnabled
? 'Yes'
: 'No'
? _(msg`Yes`)
: _(msg`No`)
}
isSelected={
!!(

View file

@ -50,7 +50,7 @@ export function PreferencesThreads({navigation}: Props) {
styles.container,
isTabletOrDesktop && styles.desktopContainer,
]}>
<ViewHeader title="Thread Preferences" showOnDesktop />
<ViewHeader title={_(msg`Thread Preferences`)} showOnDesktop />
<View
style={[
styles.titleSection,

View file

@ -9,10 +9,13 @@ import {ScrollView} from 'view/com/util/Views'
import {usePalette} from 'lib/hooks/usePalette'
import {s} from 'lib/styles'
import {useSetMinimalShellMode} from '#/state/shell'
import {Trans, msg} from '@lingui/macro'
import {useLingui} from '@lingui/react'
type Props = NativeStackScreenProps<CommonNavigatorParams, 'PrivacyPolicy'>
export const PrivacyPolicyScreen = (_props: Props) => {
const pal = usePalette('default')
const {_} = useLingui()
const setMinimalShellMode = useSetMinimalShellMode()
useFocusEffect(
@ -23,16 +26,18 @@ export const PrivacyPolicyScreen = (_props: Props) => {
return (
<View>
<ViewHeader title="Privacy Policy" />
<ViewHeader title={_(msg`Privacy Policy`)} />
<ScrollView style={[s.hContentRegion, pal.view]}>
<View style={[s.p20]}>
<Text style={pal.text}>
The Privacy Policy has been moved to{' '}
<TextLink
style={pal.link}
href="https://blueskyweb.xyz/support/privacy-policy"
text="blueskyweb.xyz/support/privacy-policy"
/>
<Trans>
The Privacy Policy has been moved to{' '}
<TextLink
style={pal.link}
href="https://blueskyweb.xyz/support/privacy-policy"
text="blueskyweb.xyz/support/privacy-policy"
/>
</Trans>
</Text>
</View>
<View style={s.footerSpacer} />

View file

@ -269,7 +269,7 @@ export function ProfileFeedScreenInner({
return [
{
testID: 'feedHeaderDropdownToggleSavedBtn',
label: isSaved ? 'Remove from my feeds' : 'Add to my feeds',
label: isSaved ? _(msg`Remove from my feeds`) : _(msg`Add to my feeds`),
onPress: isSavePending || isRemovePending ? undefined : onToggleSaved,
icon: isSaved
? {
@ -289,7 +289,7 @@ export function ProfileFeedScreenInner({
},
{
testID: 'feedHeaderDropdownReportBtn',
label: 'Report feed',
label: _(msg`Report feed`),
onPress: onPressReport,
icon: {
ios: {
@ -301,7 +301,7 @@ export function ProfileFeedScreenInner({
},
{
testID: 'feedHeaderDropdownShareBtn',
label: 'Share link',
label: _(msg`Share feed`),
onPress: onPressShare,
icon: {
ios: {
@ -319,6 +319,7 @@ export function ProfileFeedScreenInner({
isSaved,
isSavePending,
isRemovePending,
_,
])
const renderHeader = useCallback(() => {

View file

@ -7,12 +7,15 @@ import {ViewHeader} from '../com/util/ViewHeader'
import {PostLikedBy as PostLikedByComponent} from '../com/post-thread/PostLikedBy'
import {makeRecordUri} from 'lib/strings/url-helpers'
import {useSetMinimalShellMode} from '#/state/shell'
import {useLingui} from '@lingui/react'
import {msg} from '@lingui/macro'
type Props = NativeStackScreenProps<CommonNavigatorParams, 'ProfileFeedLikedBy'>
export const ProfileFeedLikedByScreen = withAuthRequired(({route}: Props) => {
const setMinimalShellMode = useSetMinimalShellMode()
const {name, rkey} = route.params
const uri = makeRecordUri(name, 'app.bsky.feed.generator', rkey)
const {_} = useLingui()
useFocusEffect(
React.useCallback(() => {
@ -22,7 +25,7 @@ export const ProfileFeedLikedByScreen = withAuthRequired(({route}: Props) => {
return (
<View>
<ViewHeader title="Liked by" />
<ViewHeader title={_(msg`Liked by`)} />
<PostLikedByComponent uri={uri} />
</View>
)

View file

@ -6,11 +6,14 @@ import {withAuthRequired} from 'view/com/auth/withAuthRequired'
import {ViewHeader} from '../com/util/ViewHeader'
import {ProfileFollowers as ProfileFollowersComponent} from '../com/profile/ProfileFollowers'
import {useSetMinimalShellMode} from '#/state/shell'
import {useLingui} from '@lingui/react'
import {msg} from '@lingui/macro'
type Props = NativeStackScreenProps<CommonNavigatorParams, 'ProfileFollowers'>
export const ProfileFollowersScreen = withAuthRequired(({route}: Props) => {
const {name} = route.params
const setMinimalShellMode = useSetMinimalShellMode()
const {_} = useLingui()
useFocusEffect(
React.useCallback(() => {
@ -20,7 +23,7 @@ export const ProfileFollowersScreen = withAuthRequired(({route}: Props) => {
return (
<View>
<ViewHeader title="Followers" />
<ViewHeader title={_(msg`Followers`)} />
<ProfileFollowersComponent name={name} />
</View>
)

View file

@ -6,11 +6,14 @@ import {withAuthRequired} from 'view/com/auth/withAuthRequired'
import {ViewHeader} from '../com/util/ViewHeader'
import {ProfileFollows as ProfileFollowsComponent} from '../com/profile/ProfileFollows'
import {useSetMinimalShellMode} from '#/state/shell'
import {useLingui} from '@lingui/react'
import {msg} from '@lingui/macro'
type Props = NativeStackScreenProps<CommonNavigatorParams, 'ProfileFollows'>
export const ProfileFollowsScreen = withAuthRequired(({route}: Props) => {
const {name} = route.params
const setMinimalShellMode = useSetMinimalShellMode()
const {_} = useLingui()
useFocusEffect(
React.useCallback(() => {
@ -20,7 +23,7 @@ export const ProfileFollowsScreen = withAuthRequired(({route}: Props) => {
return (
<View>
<ViewHeader title="Following" />
<ViewHeader title={_(msg`Following`)} />
<ProfileFollowsComponent name={name} />
</View>
)

View file

@ -268,9 +268,10 @@ function Header({rkey, list}: {rkey: string; list: AppBskyGraphDefs.ListView}) {
const onSubscribeMute = useCallback(() => {
openModal({
name: 'confirm',
title: 'Mute these accounts?',
message:
'Muting is private. Muted accounts can interact with you, but you will not see their posts or receive notifications from them.',
title: _(msg`Mute these accounts?`),
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',
async onPressConfirm() {
try {
@ -286,7 +287,7 @@ function Header({rkey, list}: {rkey: string; list: AppBskyGraphDefs.ListView}) {
closeModal()
},
})
}, [openModal, closeModal, list, listMuteMutation])
}, [openModal, closeModal, list, listMuteMutation, _])
const onUnsubscribeMute = useCallback(async () => {
try {
@ -302,9 +303,10 @@ function Header({rkey, list}: {rkey: string; list: AppBskyGraphDefs.ListView}) {
const onSubscribeBlock = useCallback(() => {
openModal({
name: 'confirm',
title: 'Block these accounts?',
message:
'Blocking is public. Blocked accounts cannot reply in your threads, mention you, or otherwise interact with you.',
title: _(msg`Block these accounts?`),
message: _(
msg`Blocking is public. Blocked accounts cannot reply in your threads, mention you, or otherwise interact with you.`,
),
confirmBtnText: 'Block this List',
async onPressConfirm() {
try {
@ -320,7 +322,7 @@ function Header({rkey, list}: {rkey: string; list: AppBskyGraphDefs.ListView}) {
closeModal()
},
})
}, [openModal, closeModal, list, listBlockMutation])
}, [openModal, closeModal, list, listBlockMutation, _])
const onUnsubscribeBlock = useCallback(async () => {
try {
@ -343,8 +345,8 @@ function Header({rkey, list}: {rkey: string; list: AppBskyGraphDefs.ListView}) {
const onPressDelete = useCallback(() => {
openModal({
name: 'confirm',
title: 'Delete List',
message: 'Are you sure?',
title: _(msg`Delete List`),
message: _(msg`Are you sure?`),
async onPressConfirm() {
await listDeleteMutation.mutateAsync({uri: list.uri})
Toast.show('List deleted')
@ -355,7 +357,7 @@ function Header({rkey, list}: {rkey: string; list: AppBskyGraphDefs.ListView}) {
}
},
})
}, [openModal, list, listDeleteMutation, navigation])
}, [openModal, list, listDeleteMutation, navigation, _])
const onPressReport = useCallback(() => {
openModal({
@ -374,7 +376,7 @@ function Header({rkey, list}: {rkey: string; list: AppBskyGraphDefs.ListView}) {
let items: DropdownItem[] = [
{
testID: 'listHeaderDropdownShareBtn',
label: 'Share',
label: _(msg`Share`),
onPress: onPressShare,
icon: {
ios: {
@ -389,7 +391,7 @@ function Header({rkey, list}: {rkey: string; list: AppBskyGraphDefs.ListView}) {
items.push({label: 'separator'})
items.push({
testID: 'listHeaderDropdownEditBtn',
label: 'Edit List Details',
label: _(msg`Edit list details`),
onPress: onPressEdit,
icon: {
ios: {
@ -401,7 +403,7 @@ function Header({rkey, list}: {rkey: string; list: AppBskyGraphDefs.ListView}) {
})
items.push({
testID: 'listHeaderDropdownDeleteBtn',
label: 'Delete List',
label: _(msg`Delete List`),
onPress: onPressDelete,
icon: {
ios: {
@ -415,7 +417,7 @@ function Header({rkey, list}: {rkey: string; list: AppBskyGraphDefs.ListView}) {
items.push({label: 'separator'})
items.push({
testID: 'listHeaderDropdownReportBtn',
label: 'Report List',
label: _(msg`Report List`),
onPress: onPressReport,
icon: {
ios: {
@ -427,13 +429,13 @@ function Header({rkey, list}: {rkey: string; list: AppBskyGraphDefs.ListView}) {
})
}
return items
}, [isOwner, onPressShare, onPressEdit, onPressDelete, onPressReport])
}, [isOwner, onPressShare, onPressEdit, onPressDelete, onPressReport, _])
const subscribeDropdownItems: DropdownItem[] = useMemo(() => {
return [
{
testID: 'subscribeDropdownMuteBtn',
label: 'Mute accounts',
label: _(msg`Mute accounts`),
onPress: onSubscribeMute,
icon: {
ios: {
@ -445,7 +447,7 @@ function Header({rkey, list}: {rkey: string; list: AppBskyGraphDefs.ListView}) {
},
{
testID: 'subscribeDropdownBlockBtn',
label: 'Block accounts',
label: _(msg`Block accounts`),
onPress: onSubscribeBlock,
icon: {
ios: {
@ -456,7 +458,7 @@ function Header({rkey, list}: {rkey: string; list: AppBskyGraphDefs.ListView}) {
},
},
]
}, [onSubscribeMute, onSubscribeBlock])
}, [onSubscribeMute, onSubscribeBlock, _])
return (
<ProfileSubpageHeader

View file

@ -9,7 +9,6 @@ import {
import {useFocusEffect} from '@react-navigation/native'
import {NativeStackScreenProps} from '@react-navigation/native-stack'
import {useQueryClient} from '@tanstack/react-query'
import {track} from '#/lib/analytics/analytics'
import {useAnalytics} from 'lib/analytics/analytics'
import {usePalette} from 'lib/hooks/usePalette'
@ -27,6 +26,8 @@ import {Haptics} from 'lib/haptics'
import {TextLink} from 'view/com/util/Link'
import {logger} from '#/logger'
import {useSetMinimalShellMode} from '#/state/shell'
import {Trans, msg} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {
usePreferencesQuery,
usePinFeedMutation,
@ -52,6 +53,7 @@ const HITSLOP_BOTTOM = {
type Props = NativeStackScreenProps<CommonNavigatorParams, 'SavedFeeds'>
export const SavedFeeds = withAuthRequired(function SavedFeedsImpl({}: Props) {
const pal = usePalette('default')
const {_} = useLingui()
const {isMobile, isTabletOrDesktop} = useWebMediaQueries()
const {screen} = useAnalytics()
const setMinimalShellMode = useSetMinimalShellMode()
@ -71,11 +73,11 @@ export const SavedFeeds = withAuthRequired(function SavedFeedsImpl({}: Props) {
pal.border,
isTabletOrDesktop && styles.desktopContainer,
]}>
<ViewHeader title="Edit My Feeds" showOnDesktop showBorder />
<ViewHeader title={_(msg`Edit My Feeds`)} showOnDesktop showBorder />
<ScrollView style={s.flex1}>
<View style={[pal.text, pal.border, styles.title]}>
<Text type="title" style={pal.text}>
Pinned Feeds
<Trans>Pinned Feeds</Trans>
</Text>
</View>
{preferences?.feeds ? (
@ -88,7 +90,7 @@ export const SavedFeeds = withAuthRequired(function SavedFeedsImpl({}: Props) {
styles.empty,
]}>
<Text type="lg" style={[pal.text]}>
You don't have any pinned feeds.
<Trans>You don't have any pinned feeds.</Trans>
</Text>
</View>
) : (
@ -101,7 +103,7 @@ export const SavedFeeds = withAuthRequired(function SavedFeedsImpl({}: Props) {
)}
<View style={[pal.text, pal.border, styles.title]}>
<Text type="title" style={pal.text}>
Saved Feeds
<Trans>Saved Feeds</Trans>
</Text>
</View>
{preferences?.feeds ? (
@ -114,7 +116,7 @@ export const SavedFeeds = withAuthRequired(function SavedFeedsImpl({}: Props) {
styles.empty,
]}>
<Text type="lg" style={[pal.text]}>
You don't have any saved feeds.
<Trans>You don't have any saved feeds.</Trans>
</Text>
</View>
) : (
@ -128,15 +130,17 @@ export const SavedFeeds = withAuthRequired(function SavedFeedsImpl({}: Props) {
<View style={styles.footerText}>
<Text type="sm" style={pal.textLight}>
Feeds are custom algorithms that users build with a little coding
expertise.{' '}
<TextLink
type="sm"
style={pal.link}
href="https://github.com/bluesky-social/feed-generator"
text="See this guide"
/>{' '}
for more information.
<Trans>
Feeds are custom algorithms that users build with a little coding
expertise.{' '}
<TextLink
type="sm"
style={pal.link}
href="https://github.com/bluesky-social/feed-generator"
text="See this guide"
/>{' '}
for more information.
</Trans>
</Text>
</View>
<View style={{height: 100}} />

View file

@ -222,10 +222,10 @@ function SearchScreenPostResults({query}: {query: string}) {
return results?.pages.flatMap(page => page.posts) || []
}, [results])
const items = React.useMemo(() => {
let items: SearchResultSlice[] = []
let temp: SearchResultSlice[] = []
for (const post of posts) {
items.push({
temp.push({
type: 'post',
key: post.uri,
post,
@ -233,13 +233,13 @@ function SearchScreenPostResults({query}: {query: string}) {
}
if (isFetchingNextPage) {
items.push({
temp.push({
type: 'loadingMore',
key: 'loadingMore',
})
}
return items
return temp
}, [posts, isFetchingNextPage])
return error ? (
@ -299,9 +299,9 @@ function SearchScreenUserResults({query}: {query: string}) {
React.useEffect(() => {
async function getResults() {
const results = await search({query, limit: 30})
const searchResults = await search({query, limit: 30})
if (results) {
if (searchResults) {
setDataUpdatedAt(Date.now())
setResults(results)
setIsFetched(true)
@ -314,7 +314,7 @@ function SearchScreenUserResults({query}: {query: string}) {
setResults([])
setIsFetched(false)
}
}, [query, setDataUpdatedAt, search])
}, [query, setDataUpdatedAt, search, results])
return isFetched ? (
<>

View file

@ -268,7 +268,7 @@ export const SettingsScreen = withAuthRequired(function Settings({}: Props) {
return (
<View style={[s.hContentRegion]} testID="settingsScreen">
<ViewHeader title="Settings" />
<ViewHeader title={_(msg`Settings`)} />
<ScrollView
style={[s.hContentRegion]}
contentContainerStyle={isMobile && pal.viewLight}
@ -281,7 +281,7 @@ export const SettingsScreen = withAuthRequired(function Settings({}: Props) {
</Text>
<View style={[styles.infoLine]}>
<Text type="lg-medium" style={pal.text}>
Email:{' '}
<Trans>Email:</Trans>{' '}
</Text>
{currentAccount.emailConfirmed && (
<>

View file

@ -10,11 +10,14 @@ import {usePalette} from 'lib/hooks/usePalette'
import {s} from 'lib/styles'
import {HELP_DESK_URL} from 'lib/constants'
import {useSetMinimalShellMode} from '#/state/shell'
import {Trans, msg} from '@lingui/macro'
import {useLingui} from '@lingui/react'
type Props = NativeStackScreenProps<CommonNavigatorParams, 'Support'>
export const SupportScreen = (_props: Props) => {
const pal = usePalette('default')
const setMinimalShellMode = useSetMinimalShellMode()
const {_} = useLingui()
useFocusEffect(
React.useCallback(() => {
@ -24,19 +27,21 @@ export const SupportScreen = (_props: Props) => {
return (
<View>
<ViewHeader title="Support" />
<ViewHeader title={_(msg`Support`)} />
<CenteredView>
<Text type="title-xl" style={[pal.text, s.p20, s.pb5]}>
Support
<Trans>Support</Trans>
</Text>
<Text style={[pal.text, s.p20]}>
The support form has been moved. If you need help, please
<TextLink
href={HELP_DESK_URL}
text=" click here"
style={pal.link}
/>{' '}
or visit {HELP_DESK_URL} to get in touch with us.
<Trans>
The support form has been moved. If you need help, please
<TextLink
href={HELP_DESK_URL}
text=" click here"
style={pal.link}
/>{' '}
or visit {HELP_DESK_URL} to get in touch with us.
</Trans>
</Text>
</CenteredView>
</View>

View file

@ -9,11 +9,14 @@ import {ScrollView} from 'view/com/util/Views'
import {usePalette} from 'lib/hooks/usePalette'
import {s} from 'lib/styles'
import {useSetMinimalShellMode} from '#/state/shell'
import {Trans, msg} from '@lingui/macro'
import {useLingui} from '@lingui/react'
type Props = NativeStackScreenProps<CommonNavigatorParams, 'TermsOfService'>
export const TermsOfServiceScreen = (_props: Props) => {
const pal = usePalette('default')
const setMinimalShellMode = useSetMinimalShellMode()
const {_} = useLingui()
useFocusEffect(
React.useCallback(() => {
@ -23,11 +26,11 @@ export const TermsOfServiceScreen = (_props: Props) => {
return (
<View>
<ViewHeader title="Terms of Service" />
<ViewHeader title={_(msg`Terms of Service`)} />
<ScrollView style={[s.hContentRegion, pal.view]}>
<View style={[s.p20]}>
<Text style={pal.text}>
The Terms of Service have been moved to{' '}
<Trans>The Terms of Service have been moved to</Trans>{' '}
<TextLink
style={pal.link}
href="https://blueskyweb.xyz/support/tos"