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
zio/stable
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

@ -28,6 +28,7 @@ import {Provider as LightboxStateProvider} from 'state/lightbox'
import {Provider as MutedThreadsProvider} from 'state/muted-threads'
import {Provider as InvitesStateProvider} from 'state/invites'
import {Provider as PrefsStateProvider} from 'state/preferences'
import I18nProvider from './locale/i18nProvider'
import {
Provider as SessionProvider,
useSession,
@ -35,11 +36,6 @@ import {
} from 'state/session'
import {Provider as UnreadNotifsProvider} from 'state/queries/notifications/unread'
import * as persisted from '#/state/persisted'
import {i18n} from '@lingui/core'
import {I18nProvider} from '@lingui/react'
import {messages} from './locale/locales/en/messages'
i18n.load('en', messages)
i18n.activate('en')
enableFreeze(true)
SplashScreen.preventAutoHideAsync()
@ -76,15 +72,13 @@ function InnerApp() {
<UnreadNotifsProvider>
<ThemeProvider theme={colorMode}>
<analytics.Provider>
<I18nProvider i18n={i18n}>
{/* All components should be within this provider */}
<RootSiblingParent>
<GestureHandlerRootView style={s.h100pct}>
<TestCtrls />
<Shell />
</GestureHandlerRootView>
</RootSiblingParent>
</I18nProvider>
{/* All components should be within this provider */}
<RootSiblingParent>
<GestureHandlerRootView style={s.h100pct}>
<TestCtrls />
<Shell />
</GestureHandlerRootView>
</RootSiblingParent>
</analytics.Provider>
</ThemeProvider>
</UnreadNotifsProvider>
@ -115,7 +109,9 @@ function App() {
<InvitesStateProvider>
<ModalStateProvider>
<LightboxStateProvider>
<InnerApp />
<I18nProvider>
<InnerApp />
</I18nProvider>
</LightboxStateProvider>
</ModalStateProvider>
</InvitesStateProvider>

View File

@ -16,15 +16,13 @@ import {Shell} from 'view/shell/index'
import {ToastContainer} from 'view/com/util/Toast.web'
import {ThemeProvider} from 'lib/ThemeContext'
import {queryClient} from 'lib/react-query'
import {i18n} from '@lingui/core'
import {I18nProvider} from '@lingui/react'
import {defaultLocale, dynamicActivate} from './locale/i18n'
import {Provider as ShellStateProvider} from 'state/shell'
import {Provider as ModalStateProvider} from 'state/modals'
import {Provider as LightboxStateProvider} from 'state/lightbox'
import {Provider as MutedThreadsProvider} from 'state/muted-threads'
import {Provider as InvitesStateProvider} from 'state/invites'
import {Provider as PrefsStateProvider} from 'state/preferences'
import I18nProvider from './locale/i18nProvider'
import {
Provider as SessionProvider,
useSession,
@ -44,8 +42,6 @@ function InnerApp() {
useEffect(() => {
initReminders()
analytics.init()
dynamicActivate(defaultLocale) // async import of locale data
const account = persisted.get('session').currentAccount
resumeSession(account)
}, [resumeSession])
@ -64,14 +60,12 @@ function InnerApp() {
<UnreadNotifsProvider>
<ThemeProvider theme={colorMode}>
<analytics.Provider>
<I18nProvider i18n={i18n}>
{/* All components should be within this provider */}
<RootSiblingParent>
<SafeAreaProvider>
<Shell />
</SafeAreaProvider>
</RootSiblingParent>
</I18nProvider>
{/* All components should be within this provider */}
<RootSiblingParent>
<SafeAreaProvider>
<Shell />
</SafeAreaProvider>
</RootSiblingParent>
<ToastContainer />
</analytics.Provider>
</ThemeProvider>
@ -103,7 +97,9 @@ function App() {
<InvitesStateProvider>
<ModalStateProvider>
<LightboxStateProvider>
<InnerApp />
<I18nProvider>
<InnerApp />
</I18nProvider>
</LightboxStateProvider>
</ModalStateProvider>
</InvitesStateProvider>

View File

@ -3,6 +3,7 @@ import {useCallback, useEffect} from 'react'
import {AppState} from 'react-native'
import {logger} from '#/logger'
import {useModalControls} from '#/state/modals'
import {t} from '@lingui/macro'
export function useOTAUpdate() {
const {openModal} = useModalControls()
@ -11,9 +12,8 @@ export function useOTAUpdate() {
const showUpdatePopup = useCallback(() => {
openModal({
name: 'confirm',
title: 'Update Available',
message:
'A new version of the app is available. Please update to continue using the app.',
title: t`Update Available`,
message: t`A new version of the app is available. Please update to continue using the app.`,
onPressConfirm: async () => {
Updates.reloadAsync().catch(err => {
throw err

View File

@ -1,4 +1,8 @@
import {useLanguagePrefs} from '#/state/preferences'
import {i18n} from '@lingui/core'
import {useEffect} from 'react'
import {messages as messagesEn} from './locales/en/messages'
import {messages as messagesHi} from './locales/hi/messages'
export const locales = {
en: 'English',
@ -14,7 +18,22 @@ export const defaultLocale = 'en'
* @param locale any locale string
*/
export async function dynamicActivate(locale: string) {
const {messages} = await import(`./locales/${locale}/messages`)
i18n.load(locale, messages)
i18n.activate(locale)
console.log('dynamicActivate', locale)
if (locale === 'en') {
i18n.loadAndActivate({locale, messages: messagesEn})
return
} else if (locale === 'hi') {
i18n.loadAndActivate({locale, messages: messagesHi})
return
} else {
i18n.loadAndActivate({locale, messages: messagesEn})
return
}
}
export async function useLocaleLanguage() {
const {appLanguage} = useLanguagePrefs()
useEffect(() => {
dynamicActivate(appLanguage)
}, [appLanguage])
}

View File

@ -0,0 +1,9 @@
import React from 'react'
import {I18nProvider as DefaultI18nProvider} from '@lingui/react'
import {i18n} from '@lingui/core'
import {useLocaleLanguage} from './i18n'
export default function I18nProvider({children}: {children: React.ReactNode}) {
useLocaleLanguage()
return <DefaultI18nProvider i18n={i18n}>{children}</DefaultI18nProvider>
}

View File

@ -4,6 +4,16 @@ interface Language {
name: string
}
interface AppLanguage {
code2: string
name: string
}
export const APP_LANGUAGES: AppLanguage[] = [
{code2: 'en', name: 'English'},
{code2: 'hi', name: 'हिंदी'},
]
export const LANGUAGES: Language[] = [
{code3: 'aar', code2: 'aa', name: 'Afar'},
{code3: 'abk', code2: 'ab', name: 'Abkhazian'},

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

@ -94,6 +94,8 @@ export function transform(legacy: Partial<LegacySchema>): Schema {
postLanguageHistory:
legacy.preferences?.postLanguageHistory ||
defaults.languagePrefs.postLanguageHistory,
appLanguage:
legacy.preferences?.postLanguage || defaults.languagePrefs.appLanguage,
},
requireAltTextEnabled:
legacy.preferences?.requireAltTextEnabled ||

View File

@ -30,6 +30,7 @@ export const schema = z.object({
contentLanguages: z.array(z.string()), // should move to server
postLanguage: z.string(), // should move to server
postLanguageHistory: z.array(z.string()),
appLanguage: z.string(),
}),
requireAltTextEnabled: z.boolean(), // should move to server
mutedThreads: z.array(z.string()), // should move to server
@ -58,6 +59,7 @@ export const defaults: Schema = {
postLanguageHistory: (deviceLocales || [])
.concat(['en', 'ja', 'pt', 'de'])
.slice(0, 6),
appLanguage: deviceLocales[0] || 'en',
},
requireAltTextEnabled: false,
mutedThreads: [],

View File

@ -11,6 +11,7 @@ type ApiContext = {
toggleContentLanguage: (code2: string) => void
togglePostLanguage: (code2: string) => void
savePostLanguageToHistory: () => void
setAppLanguage: (code2: string) => void
}
const stateContext = React.createContext<StateContext>(
@ -22,6 +23,7 @@ const apiContext = React.createContext<ApiContext>({
toggleContentLanguage: (_: string) => {},
togglePostLanguage: (_: string) => {},
savePostLanguageToHistory: () => {},
setAppLanguage: (_: string) => {},
})
export function Provider({children}: React.PropsWithChildren<{}>) {
@ -104,6 +106,9 @@ export function Provider({children}: React.PropsWithChildren<{}>) {
.slice(0, 6),
}))
},
setAppLanguage(code2: string) {
setStateWrapped(s => ({...s, appLanguage: code2}))
},
}),
[state, setStateWrapped],
)

View File

@ -10,6 +10,8 @@ import {RecommendedFeedsItem} from './RecommendedFeedsItem'
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
import {usePalette} from 'lib/hooks/usePalette'
import {ErrorMessage} from 'view/com/util/error/ErrorMessage'
import {Trans, msg} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {useSuggestedFeedsQuery} from '#/state/queries/suggested-feeds'
type Props = {
@ -17,40 +19,45 @@ type Props = {
}
export function RecommendedFeeds({next}: Props) {
const pal = usePalette('default')
const {_} = useLingui()
const {isTabletOrMobile} = useWebMediaQueries()
const {isLoading, data} = useSuggestedFeedsQuery()
const hasFeeds = data && data?.pages?.[0]?.feeds?.length
const hasFeeds = data && data.pages[0].feeds.length
const title = (
<>
<Text
style={[
pal.textLight,
tdStyles.title1,
isTabletOrMobile && tdStyles.title1Small,
]}>
Choose your
</Text>
<Text
style={[
pal.link,
tdStyles.title2,
isTabletOrMobile && tdStyles.title2Small,
]}>
Recommended
</Text>
<Text
style={[
pal.link,
tdStyles.title2,
isTabletOrMobile && tdStyles.title2Small,
]}>
Feeds
</Text>
<Trans>
<Text
style={[
pal.textLight,
tdStyles.title1,
isTabletOrMobile && tdStyles.title1Small,
]}>
Choose your
</Text>
<Text
style={[
pal.link,
tdStyles.title2,
isTabletOrMobile && tdStyles.title2Small,
]}>
Recommended
</Text>
<Text
style={[
pal.link,
tdStyles.title2,
isTabletOrMobile && tdStyles.title2Small,
]}>
Feeds
</Text>
</Trans>
<Text type="2xl-medium" style={[pal.textLight, tdStyles.description]}>
Feeds are created by users to curate content. Choose some feeds that you
find interesting.
<Trans>
Feeds are created by users to curate content. Choose some feeds that
you find interesting.
</Trans>
</Text>
<View
style={{
@ -69,7 +76,7 @@ export function RecommendedFeeds({next}: Props) {
<Text
type="2xl-medium"
style={{color: '#fff', position: 'relative', top: -1}}>
Next
<Trans>Next</Trans>
</Text>
<FontAwesomeIcon icon="angle-right" color="#fff" size={14} />
</View>
@ -99,20 +106,22 @@ export function RecommendedFeeds({next}: Props) {
<ActivityIndicator size="large" />
</View>
) : (
<ErrorMessage message="Failed to load recommended feeds" />
<ErrorMessage message={_(msg`Failed to load recommended feeds`)} />
)}
</TitleColumnLayout>
</TabletOrDesktop>
<Mobile>
<View style={[mStyles.container]} testID="recommendedFeedsOnboarding">
<ViewHeader
title="Recommended Feeds"
title={_(msg`Recommended Feeds`)}
showBackButton={false}
showOnDesktop
/>
<Text type="lg-medium" style={[pal.text, mStyles.header]}>
Check out some recommended feeds. Tap + to add them to your list of
pinned feeds.
<Trans>
Check out some recommended feeds. Tap + to add them to your list
of pinned feeds.
</Trans>
</Text>
{hasFeeds ? (
@ -128,13 +137,15 @@ export function RecommendedFeeds({next}: Props) {
</View>
) : (
<View style={{flex: 1}}>
<ErrorMessage message="Failed to load recommended feeds" />
<ErrorMessage
message={_(msg`Failed to load recommended feeds`)}
/>
</View>
)}
<Button
onPress={next}
label="Continue"
label={_(msg`Continue`)}
testID="continueBtn"
style={mStyles.button}
labelStyle={mStyles.buttonText}

View File

@ -14,12 +14,15 @@ import {useSuggestedFollowsQuery} from '#/state/queries/suggested-follows'
import {useGetSuggestedFollowersByActor} from '#/state/queries/suggested-follows'
import {useModerationOpts} from '#/state/queries/preferences'
import {logger} from '#/logger'
import {Trans, msg} from '@lingui/macro'
import {useLingui} from '@lingui/react'
type Props = {
next: () => void
}
export function RecommendedFollows({next}: Props) {
const pal = usePalette('default')
const {_} = useLingui()
const {isTabletOrMobile} = useWebMediaQueries()
const {data: suggestedFollows, dataUpdatedAt} = useSuggestedFollowsQuery()
const getSuggestedFollowsByActor = useGetSuggestedFollowersByActor()
@ -31,33 +34,37 @@ export function RecommendedFollows({next}: Props) {
const title = (
<>
<Text
style={[
pal.textLight,
tdStyles.title1,
isTabletOrMobile && tdStyles.title1Small,
]}>
Follow some
</Text>
<Text
style={[
pal.link,
tdStyles.title2,
isTabletOrMobile && tdStyles.title2Small,
]}>
Recommended
</Text>
<Text
style={[
pal.link,
tdStyles.title2,
isTabletOrMobile && tdStyles.title2Small,
]}>
Users
</Text>
<Trans>
<Text
style={[
pal.textLight,
tdStyles.title1,
isTabletOrMobile && tdStyles.title1Small,
]}>
Follow some
</Text>
<Text
style={[
pal.link,
tdStyles.title2,
isTabletOrMobile && tdStyles.title2Small,
]}>
Recommended
</Text>
<Text
style={[
pal.link,
tdStyles.title2,
isTabletOrMobile && tdStyles.title2Small,
]}>
Users
</Text>
</Trans>
<Text type="2xl-medium" style={[pal.textLight, tdStyles.description]}>
Follow some users to get started. We can recommend you more users based
on who you find interesting.
<Trans>
Follow some users to get started. We can recommend you more users
based on who you find interesting.
</Trans>
</Text>
<View
style={{
@ -76,7 +83,7 @@ export function RecommendedFollows({next}: Props) {
<Text
type="2xl-medium"
style={{color: '#fff', position: 'relative', top: -1}}>
Done
<Trans>Done</Trans>
</Text>
<FontAwesomeIcon icon="angle-right" color="#fff" size={14} />
</View>
@ -171,13 +178,15 @@ export function RecommendedFollows({next}: Props) {
<View style={[mStyles.container]} testID="recommendedFollowsOnboarding">
<View>
<ViewHeader
title="Recommended Follows"
title={_(msg`Recommended Users`)}
showBackButton={false}
showOnDesktop
/>
<Text type="lg-medium" style={[pal.text, mStyles.header]}>
Check out some recommended users. Follow them to see similar
users.
<Trans>
Check out some recommended users. Follow them to see similar
users.
</Trans>
</Text>
</View>
{!suggestedFollows || !moderationOpts ? (
@ -199,7 +208,7 @@ export function RecommendedFollows({next}: Props) {
)}
<Button
onPress={next}
label="Continue"
label={_(msg`Continue`)}
testID="continueBtn"
style={mStyles.button}
labelStyle={mStyles.buttonText}

View File

@ -43,10 +43,10 @@ export function WelcomeMobile({next, skip}: Props) {
/>
<View>
<Text style={[pal.text, styles.title]}>
Welcome to{' '}
<Text style={[pal.text, pal.link, styles.title]}>
<Trans>Bluesky</Trans>
</Text>
<Trans>
Welcome to{' '}
<Text style={[pal.text, pal.link, styles.title]}>Bluesky</Text>
</Trans>
</Text>
<View style={styles.spacer} />
<View style={[styles.row]}>

View File

@ -129,19 +129,19 @@ export const ComposePost = observer(function ComposePost({
}
openModal({
name: 'confirm',
title: 'Discard draft',
title: _(msg`Discard draft`),
onPressConfirm: onClose,
onPressCancel: () => {
closeModal()
},
message: "Are you sure you'd like to discard this draft?",
confirmBtnText: 'Discard',
message: _(msg`Are you sure you'd like to discard this draft?`),
confirmBtnText: _(msg`Discard`),
confirmBtnStyle: {backgroundColor: colors.red4},
})
} else {
onClose()
}
}, [openModal, closeModal, activeModals, onClose, graphemeLength, gallery])
}, [openModal, closeModal, activeModals, onClose, graphemeLength, gallery, _])
// android back button
useEffect(() => {
if (!isAndroid) {

View File

@ -14,6 +14,8 @@ 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 {useLingui} from '@lingui/react'
import {
UsePreferencesQueryResponse,
usePreferencesQuery,
@ -68,6 +70,7 @@ export function FeedSourceCardLoaded({
showLikes?: boolean
}) {
const pal = usePalette('default')
const {_} = useLingui()
const navigation = useNavigation<NavigationProp>()
const {openModal} = useModalControls()
@ -85,8 +88,8 @@ export function FeedSourceCardLoaded({
if (isSaved) {
openModal({
name: 'confirm',
title: 'Remove from my feeds',
message: `Remove ${feed?.displayName} from my feeds?`,
title: _(msg`Remove from my feeds`),
message: _(msg`Remove ${feed.displayName} from my feeds?`),
onPressConfirm: async () => {
try {
await removeFeed({uri: feed.uri})
@ -107,7 +110,7 @@ export function FeedSourceCardLoaded({
logger.error('Failed to save feed', {error: e})
}
}
}, [isSaved, openModal, feed, removeFeed, saveFeed])
}, [isSaved, openModal, feed, removeFeed, saveFeed, _])
if (!feed || !preferences) return null

View File

@ -75,7 +75,7 @@ function SwitchAccountCard({account}: {account: SessionAccount}) {
did: currentAccount.did,
handle: currentAccount.handle,
})}
title="Your profile"
title={_(msg`Your profile`)}
noFeedback>
{contents}
</Link>

View File

@ -235,7 +235,8 @@ let FeedItem = ({
{authors.length > 1 ? (
<>
<Text style={[pal.text, s.mr5, s.ml5]}>
<Trans>and</Trans>
{' '}
<Trans>and</Trans>{' '}
</Text>
<Text style={[pal.text, s.bold]}>
{formatCount(authors.length - 1)}{' '}

View File

@ -220,7 +220,7 @@ function PostThreadLoaded({
const renderItem = React.useCallback(
({item, index}: {item: YieldedItem; index: number}) => {
if (item === TOP_COMPONENT) {
return isTablet ? <ViewHeader title="Post" /> : null
return isTablet ? <ViewHeader title={_(msg`Post`)} /> : null
} else if (item === PARENT_SPINNER) {
return (
<View style={styles.parentSpinner}>

View File

@ -35,7 +35,8 @@ import {TimeElapsed} from 'view/com/util/TimeElapsed'
import {makeProfileLink} from 'lib/routes/links'
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
import {MAX_POST_LINES} from 'lib/constants'
import {Trans} from '@lingui/macro'
import {Trans, msg} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {useLanguagePrefs} from '#/state/preferences'
import {useComposerControls} from '#/state/shell/composer'
import {useModerationOpts} from '#/state/queries/preferences'
@ -637,13 +638,14 @@ function ExpandedPostDetails({
translatorUrl: string
}) {
const pal = usePalette('default')
const {_} = useLingui()
return (
<View style={[s.flexRow, s.mt2, s.mb10]}>
<Text style={pal.textLight}>{niceDate(post.indexedAt)}</Text>
{needsTranslation && (
<>
<Text style={[pal.textLight, s.ml5, s.mr5]}></Text>
<Link href={translatorUrl} title="Translate">
<Link href={translatorUrl} title={_(msg`Translate`)}>
<Text style={pal.link}>
<Trans>Translate</Trans>
</Text>

View File

@ -10,6 +10,8 @@ import {useNavigation} from '@react-navigation/native'
import {NavigationProp} from 'lib/routes/types'
import {logger} from '#/logger'
import {useModalControls} from '#/state/modals'
import {msg as msgLingui} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {FeedDescriptor} from '#/state/queries/post-feed'
import {EmptyState} from '../util/EmptyState'
import {cleanError} from '#/lib/strings/errors'
@ -86,6 +88,7 @@ function FeedgenErrorMessage({
knownError: KnownError
}) {
const pal = usePalette('default')
const {_: _l} = useLingui()
const navigation = useNavigation<NavigationProp>()
const msg = MESSAGES[knownError]
const [_, uri] = feedDesc.split('|')
@ -100,8 +103,8 @@ function FeedgenErrorMessage({
const onRemoveFeed = React.useCallback(async () => {
openModal({
name: 'confirm',
title: 'Remove feed',
message: 'Remove this feed from your saved feeds?',
title: _l(msgLingui`Remove feed`),
message: _l(msgLingui`Remove this feed from your saved feeds?`),
async onPressConfirm() {
try {
await removeFeed({uri})
@ -116,7 +119,7 @@ function FeedgenErrorMessage({
closeModal()
},
})
}, [openModal, closeModal, uri, removeFeed])
}, [openModal, closeModal, uri, removeFeed, _l])
return (
<View

View File

@ -236,9 +236,10 @@ let ProfileHeaderLoaded = ({
track('ProfileHeader:BlockAccountButtonClicked')
openModal({
name: 'confirm',
title: 'Block Account',
message:
'Blocked accounts cannot reply in your threads, mention you, or otherwise interact with you.',
title: _(msg`Block Account`),
message: _(
msg`Blocked accounts cannot reply in your threads, mention you, or otherwise interact with you.`,
),
onPressConfirm: async () => {
try {
await queueBlock()
@ -251,15 +252,16 @@ let ProfileHeaderLoaded = ({
}
},
})
}, [track, queueBlock, openModal])
}, [track, queueBlock, openModal, _])
const onPressUnblockAccount = React.useCallback(async () => {
track('ProfileHeader:UnblockAccountButtonClicked')
openModal({
name: 'confirm',
title: 'Unblock Account',
message:
'The account will be able to interact with you after unblocking.',
title: _(msg`Unblock Account`),
message: _(
msg`The account will be able to interact with you after unblocking.`,
),
onPressConfirm: async () => {
try {
await queueUnblock()
@ -272,7 +274,7 @@ let ProfileHeaderLoaded = ({
}
},
})
}, [track, queueUnblock, openModal])
}, [track, queueUnblock, openModal, _])
const onPressReportAccount = React.useCallback(() => {
track('ProfileHeader:ReportAccountButtonClicked')
@ -290,7 +292,7 @@ let ProfileHeaderLoaded = ({
let items: DropdownItem[] = [
{
testID: 'profileHeaderDropdownShareBtn',
label: 'Share',
label: _(msg`Share`),
onPress: onPressShare,
icon: {
ios: {
@ -304,7 +306,7 @@ let ProfileHeaderLoaded = ({
items.push({label: 'separator'})
items.push({
testID: 'profileHeaderDropdownListAddRemoveBtn',
label: 'Add to Lists',
label: _(msg`Add to Lists`),
onPress: onPressAddRemoveLists,
icon: {
ios: {
@ -318,7 +320,9 @@ let ProfileHeaderLoaded = ({
if (!profile.viewer?.blocking) {
items.push({
testID: 'profileHeaderDropdownMuteBtn',
label: profile.viewer?.muted ? 'Unmute Account' : 'Mute Account',
label: profile.viewer?.muted
? _(msg`Unmute Account`)
: _(msg`Mute Account`),
onPress: profile.viewer?.muted
? onPressUnmuteAccount
: onPressMuteAccount,
@ -334,7 +338,9 @@ let ProfileHeaderLoaded = ({
if (!profile.viewer?.blockingByList) {
items.push({
testID: 'profileHeaderDropdownBlockBtn',
label: profile.viewer?.blocking ? 'Unblock Account' : 'Block Account',
label: profile.viewer?.blocking
? _(msg`Unblock Account`)
: _(msg`Block Account`),
onPress: profile.viewer?.blocking
? onPressUnblockAccount
: onPressBlockAccount,
@ -349,7 +355,7 @@ let ProfileHeaderLoaded = ({
}
items.push({
testID: 'profileHeaderDropdownReportBtn',
label: 'Report Account',
label: _(msg`Report Account`),
onPress: onPressReportAccount,
icon: {
ios: {
@ -373,6 +379,7 @@ let ProfileHeaderLoaded = ({
onPressBlockAccount,
onPressReportAccount,
onPressAddRemoveLists,
_,
])
const blockHide =

View File

@ -19,7 +19,7 @@ export function AccountDropdownBtn({account}: {account: SessionAccount}) {
const items: DropdownItem[] = [
{
label: 'Remove account',
label: _(msg`Remove account`),
onPress: () => {
removeAccount(account)
Toast.show('Account removed from quick access')

View File

@ -1,6 +1,7 @@
import React, {Component, ErrorInfo, ReactNode} from 'react'
import {ErrorScreen} from './error/ErrorScreen'
import {CenteredView} from './Views'
import {t} from '@lingui/macro'
interface Props {
children?: ReactNode
@ -30,8 +31,8 @@ export class ErrorBoundary extends Component<Props, State> {
return (
<CenteredView style={{height: '100%', flex: 1}}>
<ErrorScreen
title="Oh no!"
message="There was an unexpected issue in the application. Please let us know if this happened to you!"
title={t`Oh no!`}
message={t`There was an unexpected issue in the application. Please let us know if this happened to you!`}
details={this.state.error.toString()}
/>
</CenteredView>

View File

@ -3,7 +3,6 @@ import {ago} from 'lib/strings/time'
import {useTickEveryMinute} from '#/state/shell'
// FIXME(dan): Figure out why the false positives
/* eslint-disable react/prop-types */
export function TimeElapsed({
timestamp,

View File

@ -208,7 +208,7 @@ export function EditableUserAvatar({
[
!isWeb && {
testID: 'changeAvatarCameraBtn',
label: 'Camera',
label: _(msg`Camera`),
icon: {
ios: {
name: 'camera',
@ -232,7 +232,7 @@ export function EditableUserAvatar({
},
{
testID: 'changeAvatarLibraryBtn',
label: 'Library',
label: _(msg`Library`),
icon: {
ios: {
name: 'photo.on.rectangle.angled',
@ -269,7 +269,7 @@ export function EditableUserAvatar({
},
!!avatar && {
testID: 'changeAvatarRemoveBtn',
label: 'Remove',
label: _(msg`Remove`),
icon: {
ios: {
name: 'trash',
@ -287,6 +287,7 @@ export function EditableUserAvatar({
onSelectNewAvatar,
requestCameraAccessIfNeeded,
requestPhotoAccessIfNeeded,
_,
],
)

View File

@ -35,7 +35,7 @@ export function UserBanner({
[
!isWeb && {
testID: 'changeBannerCameraBtn',
label: 'Camera',
label: _(msg`Camera`),
icon: {
ios: {
name: 'camera',
@ -57,7 +57,7 @@ export function UserBanner({
},
{
testID: 'changeBannerLibraryBtn',
label: 'Library',
label: _(msg`Library`),
icon: {
ios: {
name: 'photo.on.rectangle.angled',
@ -86,7 +86,7 @@ export function UserBanner({
},
!!banner && {
testID: 'changeBannerRemoveBtn',
label: 'Remove',
label: _(msg`Remove`),
icon: {
ios: {
name: 'trash',
@ -104,6 +104,7 @@ export function UserBanner({
onSelectNewBanner,
requestCameraAccessIfNeeded,
requestPhotoAccessIfNeeded,
_,
],
)

View File

@ -20,6 +20,8 @@ import {useMutedThreads, useToggleThreadMute} from '#/state/muted-threads'
import {useLanguagePrefs} from '#/state/preferences'
import {logger} from '#/logger'
import {Shadow} from '#/state/cache/types'
import {msg} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {useSession} from '#/state/session'
export function PostDropdownBtn({
@ -35,6 +37,7 @@ export function PostDropdownBtn({
}) {
const {currentAccount} = useSession()
const theme = useTheme()
const {_} = useLingui()
const defaultCtrlColor = theme.palette.default.postCtrl
const {openModal} = useModalControls()
const langPrefs = useLanguagePrefs()
@ -91,7 +94,7 @@ export function PostDropdownBtn({
const dropdownItems: NativeDropdownItem[] = [
{
label: 'Translate',
label: _(msg`Translate`),
onPress() {
onOpenTranslate()
},
@ -105,7 +108,7 @@ export function PostDropdownBtn({
},
},
{
label: 'Copy post text',
label: _(msg`Copy post text`),
onPress() {
onCopyPostText()
},
@ -119,7 +122,7 @@ export function PostDropdownBtn({
},
},
{
label: 'Share',
label: _(msg`Share`),
onPress() {
const url = toShareUrl(href)
shareUrl(url)
@ -137,7 +140,7 @@ export function PostDropdownBtn({
label: 'separator',
},
{
label: isThreadMuted ? 'Unmute thread' : 'Mute thread',
label: isThreadMuted ? _(msg`Unmute thread`) : _(msg`Mute thread`),
onPress() {
onToggleThreadMute()
},
@ -154,7 +157,7 @@ export function PostDropdownBtn({
label: 'separator',
},
!isAuthor && {
label: 'Report post',
label: _(msg`Report post`),
onPress() {
openModal({
name: 'report',
@ -175,12 +178,12 @@ export function PostDropdownBtn({
label: 'separator',
},
isAuthor && {
label: 'Delete post',
label: _(msg`Delete post`),
onPress() {
openModal({
name: 'confirm',
title: 'Delete this post?',
message: 'Are you sure? This can not be undone.',
title: _(msg`Delete this post?`),
message: _(msg`Are you sure? This cannot be undone.`),
onPressConfirm: onDeletePost,
})
},

View File

@ -41,7 +41,7 @@ export const RepostButton = ({
const dropdownItems: NativeDropdownItem[] = [
{
label: isReposted ? 'Undo repost' : 'Repost',
label: isReposted ? _(msg`Undo repost`) : _(msg`Repost`),
testID: 'repostDropdownRepostBtn',
icon: {
ios: {name: 'repeat'},
@ -51,7 +51,7 @@ export const RepostButton = ({
onPress: onRepost,
},
{
label: 'Quote post',
label: _(msg`Quote post`),
testID: 'repostDropdownQuoteBtn',
icon: {
ios: {name: 'quote.bubble'},

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"

View File

@ -247,7 +247,7 @@ export function DrawerContent() {
/>
)
}
label="Search"
label={_(msg`Search`)}
accessibilityLabel={_(msg`Search`)}
accessibilityHint=""
bold={isAtSearch}
@ -269,7 +269,7 @@ export function DrawerContent() {
/>
)
}
label="Home"
label={_(msg`Home`)}
accessibilityLabel={_(msg`Home`)}
accessibilityHint=""
bold={isAtHome}
@ -291,7 +291,7 @@ export function DrawerContent() {
/>
)
}
label="Notifications"
label={_(msg`Notifications`)}
accessibilityLabel={_(msg`Notifications`)}
accessibilityHint={
numUnreadNotifications === ''
@ -318,7 +318,7 @@ export function DrawerContent() {
/>
)
}
label="Feeds"
label={_(msg`Feeds`)}
accessibilityLabel={_(msg`Feeds`)}
accessibilityHint=""
bold={isAtFeeds}
@ -326,14 +326,14 @@ export function DrawerContent() {
/>
<MenuItem
icon={<ListIcon strokeWidth={2} style={pal.text} size={26} />}
label="Lists"
label={_(msg`Lists`)}
accessibilityLabel={_(msg`Lists`)}
accessibilityHint=""
onPress={onPressLists}
/>
<MenuItem
icon={<HandIcon strokeWidth={5} style={pal.text} size={24} />}
label="Moderation"
label={_(msg`Moderation`)}
accessibilityLabel={_(msg`Moderation`)}
accessibilityHint=""
onPress={onPressModeration}
@ -354,7 +354,7 @@ export function DrawerContent() {
/>
)
}
label="Profile"
label={_(msg`Profile`)}
accessibilityLabel={_(msg`Profile`)}
accessibilityHint=""
onPress={onPressProfile}
@ -367,7 +367,7 @@ export function DrawerContent() {
strokeWidth={1.75}
/>
}
label="Settings"
label={_(msg`Settings`)}
accessibilityLabel={_(msg`Settings`)}
accessibilityHint=""
onPress={onPressSettings}

View File

@ -4,10 +4,13 @@ import {useNavigationState} from '@react-navigation/native'
import {usePalette} from 'lib/hooks/usePalette'
import {TextLink} from 'view/com/util/Link'
import {getCurrentRoute} from 'lib/routes/helpers'
import {useLingui} from '@lingui/react'
import {msg} from '@lingui/macro'
import {usePinnedFeedsInfos} from '#/state/queries/feed'
export function DesktopFeeds() {
const pal = usePalette('default')
const {_} = useLingui()
const feeds = usePinnedFeedsInfos()
const route = useNavigationState(state => {
@ -47,7 +50,7 @@ export function DesktopFeeds() {
<TextLink
type="lg"
href="/feeds"
text="More feeds"
text={_(msg`More feeds`)}
style={[pal.link]}
/>
</View>

View File

@ -52,6 +52,7 @@ function ProfileCard() {
const {currentAccount} = useSession()
const {isLoading, data: profile} = useProfileQuery({did: currentAccount!.did})
const {isDesktop} = useWebMediaQueries()
const {_} = useLingui()
const size = 48
return !isLoading && profile ? (
@ -61,7 +62,7 @@ function ProfileCard() {
handle: currentAccount!.handle,
})}
style={[styles.profileCard, !isDesktop && styles.profileCardTablet]}
title="My Profile"
title={_(msg`My Profile`)}
asAnchor>
<UserAvatar avatar={profile.avatar} size={size} />
</Link>
@ -269,6 +270,7 @@ function ComposeBtn() {
export function DesktopLeftNav() {
const {currentAccount} = useSession()
const pal = usePalette('default')
const {_} = useLingui()
const {isDesktop, isTablet} = useWebMediaQueries()
const numUnread = useUnreadNotifications()
@ -292,7 +294,7 @@ export function DesktopLeftNav() {
style={pal.text}
/>
}
label="Home"
label={_(msg`Home`)}
/>
<NavItem
href="/search"
@ -310,7 +312,7 @@ export function DesktopLeftNav() {
style={pal.text}
/>
}
label="Search"
label={_(msg`Search`)}
/>
<NavItem
href="/feeds"
@ -328,7 +330,7 @@ export function DesktopLeftNav() {
size={isDesktop ? 24 : 28}
/>
}
label="Feeds"
label={_(msg`Feeds`)}
/>
<NavItem
href="/notifications"
@ -347,7 +349,7 @@ export function DesktopLeftNav() {
style={pal.text}
/>
}
label="Notifications"
label={_(msg`Notifications`)}
/>
<NavItem
href="/lists"
@ -365,7 +367,7 @@ export function DesktopLeftNav() {
strokeWidth={3}
/>
}
label="Lists"
label={_(msg`Lists`)}
/>
<NavItem
href="/moderation"
@ -383,7 +385,7 @@ export function DesktopLeftNav() {
size={isDesktop ? 20 : 26}
/>
}
label="Moderation"
label={_(msg`Moderation`)}
/>
<NavItem
href={currentAccount ? makeProfileLink(currentAccount) : '/'}
@ -419,7 +421,7 @@ export function DesktopLeftNav() {
style={pal.text}
/>
}
label="Settings"
label={_(msg`Settings`)}
/>
<ComposeBtn />
</View>

View File

@ -12,12 +12,15 @@ import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
import {pluralize} from 'lib/strings/helpers'
import {formatCount} from 'view/com/util/numeric/format'
import {useModalControls} from '#/state/modals'
import {useLingui} from '@lingui/react'
import {msg} from '@lingui/macro'
import {useSession} from '#/state/session'
import {useInviteCodesQuery} from '#/state/queries/invites'
export function DesktopRightNav() {
const pal = usePalette('default')
const palError = usePalette('error')
const {_} = useLingui()
const {isSandbox, hasSession, currentAccount} = useSession()
const {isTablet} = useWebMediaQueries()
@ -45,7 +48,7 @@ export function DesktopRightNav() {
email: currentAccount!.email,
handle: currentAccount!.handle,
})}
text="Send feedback"
text={_(msg`Feedback`)}
/>
<Text type="md" style={pal.textLight}>
&nbsp;&middot;&nbsp;
@ -54,7 +57,7 @@ export function DesktopRightNav() {
type="md"
style={pal.link}
href="https://blueskyweb.xyz/support/privacy-policy"
text="Privacy"
text={_(msg`Privacy`)}
/>
<Text type="md" style={pal.textLight}>
&nbsp;&middot;&nbsp;
@ -63,7 +66,7 @@ export function DesktopRightNav() {
type="md"
style={pal.link}
href="https://blueskyweb.xyz/support/tos"
text="Terms"
text={_(msg`Terms`)}
/>
<Text type="md" style={pal.textLight}>
&nbsp;&middot;&nbsp;
@ -72,7 +75,7 @@ export function DesktopRightNav() {
type="md"
style={pal.link}
href={HELP_DESK_URL}
text="Help"
text={_(msg`Help`)}
/>
</View>
</View>