Internationalization & localization (#1822)

* install and setup lingui

* setup dynamic locale activation and async loading

* first pass of automated replacement of text messages

* add some more documentaton

* fix nits

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

* make accessibilityLabel localized

* compile and extract new messages

* fix merge conflicts

* fix eslint warning

* change instructions from sending email to opening PR

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

View file

@ -13,6 +13,8 @@ import {
import Clipboard from '@react-native-clipboard/clipboard'
import * as Toast from '../util/Toast'
import {logger} from '#/logger'
import {Trans, msg} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {useModalControls} from '#/state/modals'
export const snapPoints = ['70%']
@ -55,6 +57,7 @@ const shadesOfBlue: string[] = [
export function Component({}: {}) {
const pal = usePalette('default')
const store = useStores()
const {_} = useLingui()
const {closeModal} = useModalControls()
const [name, setName] = useState(
shadesOfBlue[Math.floor(Math.random() * shadesOfBlue.length)],
@ -121,15 +124,19 @@ export function Component({}: {}) {
<View>
{!appPassword ? (
<Text type="lg" style={[pal.text]}>
Please enter a unique name for this App Password or use our randomly
generated one.
<Trans>
Please enter a unique name for this App Password or use our
randomly generated one.
</Trans>
</Text>
) : (
<Text type="lg" style={[pal.text]}>
<Text type="lg-bold" style={[pal.text]}>
Here is your app password.
</Text>{' '}
Use this to sign into the other app along with your handle.
<Text type="lg-bold" style={[pal.text, s.mr5]}>
<Trans>Here is your app password.</Trans>
</Text>
<Trans>
Use this to sign into the other app along with your handle.
</Trans>
</Text>
)}
{!appPassword ? (
@ -154,7 +161,7 @@ export function Component({}: {}) {
returnKeyType="done"
onEndEditing={createAppPassword}
accessible={true}
accessibilityLabel="Name"
accessibilityLabel={_(msg`Name`)}
accessibilityHint="Input name for app password"
/>
</View>
@ -163,13 +170,15 @@ export function Component({}: {}) {
style={[pal.border, styles.passwordContainer, pal.btn]}
onPress={onCopy}
accessibilityRole="button"
accessibilityLabel="Copy"
accessibilityLabel={_(msg`Copy`)}
accessibilityHint="Copies app password">
<Text type="2xl-bold" style={[pal.text]}>
{appPassword}
</Text>
{wasCopied ? (
<Text style={[pal.textLight]}>Copied</Text>
<Text style={[pal.textLight]}>
<Trans>Copied</Trans>
</Text>
) : (
<FontAwesomeIcon
icon={['far', 'clone']}
@ -182,14 +191,18 @@ export function Component({}: {}) {
</View>
{appPassword ? (
<Text type="lg" style={[pal.textLight, s.mb10]}>
For security reasons, you won't be able to view this again. If you
lose this password, you'll need to generate a new one.
<Trans>
For security reasons, you won't be able to view this again. If you
lose this password, you'll need to generate a new one.
</Trans>
</Text>
) : (
<Text type="xs" style={[pal.textLight, s.mb10, s.mt2]}>
Can only contain letters, numbers, spaces, dashes, and underscores.
Must be at least 4 characters long, but no more than 32 characters
long.
<Trans>
Can only contain letters, numbers, spaces, dashes, and underscores.
Must be at least 4 characters long, but no more than 32 characters
long.
</Trans>
</Text>
)}
<View style={styles.btnContainer}>

View file

@ -19,6 +19,8 @@ import {Text} from '../util/text/Text'
import LinearGradient from 'react-native-linear-gradient'
import {isAndroid, isWeb} from 'platform/detection'
import {ImageModel} from 'state/models/media/image'
import {useLingui} from '@lingui/react'
import {Trans, msg} from '@lingui/macro'
import {useModalControls} from '#/state/modals'
export const snapPoints = ['fullscreen']
@ -30,6 +32,7 @@ interface Props {
export function Component({image}: Props) {
const pal = usePalette('default')
const theme = useTheme()
const {_} = useLingui()
const [altText, setAltText] = useState(image.altText)
const windim = useWindowDimensions()
const {closeModal} = useModalControls()
@ -90,7 +93,7 @@ export function Component({image}: Props) {
placeholderTextColor={pal.colors.textLight}
value={altText}
onChangeText={text => setAltText(enforceLen(text, MAX_ALT_TEXT))}
accessibilityLabel="Image alt text"
accessibilityLabel={_(msg`Image alt text`)}
accessibilityHint=""
accessibilityLabelledBy="imageAltText"
autoFocus
@ -99,7 +102,7 @@ export function Component({image}: Props) {
<TouchableOpacity
testID="altTextImageSaveBtn"
onPress={onPressSave}
accessibilityLabel="Save alt text"
accessibilityLabel={_(msg`Save alt text`)}
accessibilityHint={`Saves alt text, which reads: ${altText}`}
accessibilityRole="button">
<LinearGradient
@ -108,7 +111,7 @@ export function Component({image}: Props) {
end={{x: 1, y: 1}}
style={[styles.button]}>
<Text type="button-lg" style={[s.white, s.bold]}>
Save
<Trans>Save</Trans>
</Text>
</LinearGradient>
</TouchableOpacity>
@ -116,12 +119,12 @@ export function Component({image}: Props) {
testID="altTextImageCancelBtn"
onPress={onPressCancel}
accessibilityRole="button"
accessibilityLabel="Cancel add image alt text"
accessibilityLabel={_(msg`Cancel add image alt text`)}
accessibilityHint=""
onAccessibilityEscape={onPressCancel}>
<View style={[styles.button]}>
<Text type="button-lg" style={[pal.textLight]}>
Cancel
<Trans>Cancel</Trans>
</Text>
</View>
</TouchableOpacity>

View file

@ -15,6 +15,8 @@ import {usePalette} from 'lib/hooks/usePalette'
import {isWeb} from 'platform/detection'
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
import {cleanError} from 'lib/strings/errors'
import {Trans, msg} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {useModalControls} from '#/state/modals'
export const snapPoints = ['50%']
@ -22,6 +24,7 @@ export const snapPoints = ['50%']
export const Component = observer(function Component({}: {}) {
const pal = usePalette('default')
const store = useStores()
const {_} = useLingui()
const {closeModal} = useModalControls()
const [date, setDate] = useState<Date>(
store.preferences.birthDate || new Date(),
@ -49,12 +52,12 @@ export const Component = observer(function Component({}: {}) {
style={[pal.view, styles.container, isMobile && {paddingHorizontal: 18}]}>
<View style={styles.titleSection}>
<Text type="title-lg" style={[pal.text, styles.title]}>
My Birthday
<Trans>My Birthday</Trans>
</Text>
</View>
<Text type="lg" style={[pal.textLight, {marginBottom: 10}]}>
This information is not shared with other users.
<Trans>This information is not shared with other users.</Trans>
</Text>
<View>
@ -65,7 +68,7 @@ export const Component = observer(function Component({}: {}) {
buttonType="default-light"
buttonStyle={[pal.border, styles.dateInputButton]}
buttonLabelType="lg"
accessibilityLabel="Birthday"
accessibilityLabel={_(msg`Birthday`)}
accessibilityHint="Enter your birth date"
accessibilityLabelledBy="birthDate"
/>
@ -86,9 +89,11 @@ export const Component = observer(function Component({}: {}) {
onPress={onSave}
style={styles.btn}
accessibilityRole="button"
accessibilityLabel="Save"
accessibilityLabel={_(msg`Save`)}
accessibilityHint="">
<Text style={[s.white, s.bold, s.f18]}>Save</Text>
<Text style={[s.white, s.bold, s.f18]}>
<Trans>Save</Trans>
</Text>
</TouchableOpacity>
)}
</View>

View file

@ -12,6 +12,8 @@ import {usePalette} from 'lib/hooks/usePalette'
import {isWeb} from 'platform/detection'
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
import {cleanError} from 'lib/strings/errors'
import {Trans, msg} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {useModalControls} from '#/state/modals'
enum Stages {
@ -25,6 +27,7 @@ export const snapPoints = ['90%']
export const Component = observer(function Component({}: {}) {
const pal = usePalette('default')
const store = useStores()
const {_} = useLingui()
const [stage, setStage] = useState<Stages>(Stages.InputEmail)
const [email, setEmail] = useState<string>(
store.session.currentSession?.email || '',
@ -62,7 +65,9 @@ export const Component = observer(function Component({}: {}) {
// you can remove this any time after Oct2023
// -prf
if (err === 'email must be confirmed (temporary)') {
err = `Please confirm your email before changing it. This is a temporary requirement while email-updating tools are added, and it will soon be removed.`
err = _(
msg`Please confirm your email before changing it. This is a temporary requirement while email-updating tools are added, and it will soon be removed.`,
)
}
setError(err)
} finally {
@ -103,26 +108,26 @@ export const Component = observer(function Component({}: {}) {
style={[s.flex1, isMobile && {paddingHorizontal: 18}]}>
<View style={styles.titleSection}>
<Text type="title-lg" style={[pal.text, styles.title]}>
{stage === Stages.InputEmail ? 'Change Your Email' : ''}
{stage === Stages.ConfirmCode ? 'Security Step Required' : ''}
{stage === Stages.Done ? 'Email Updated' : ''}
{stage === Stages.InputEmail ? _(msg`Change Your Email`) : ''}
{stage === Stages.ConfirmCode ? _(msg`Security Step Required`) : ''}
{stage === Stages.Done ? _(msg`Email Updated`) : ''}
</Text>
</View>
<Text type="lg" style={[pal.textLight, {marginBottom: 10}]}>
{stage === Stages.InputEmail ? (
<>Enter your new email address below.</>
<Trans>Enter your new email address below.</Trans>
) : stage === Stages.ConfirmCode ? (
<>
<Trans>
An email has been sent to your previous address,{' '}
{store.session.currentSession?.email || ''}. It includes a
confirmation code which you can enter below.
</>
</Trans>
) : (
<>
<Trans>
Your email has been updated but not verified. As a next step,
please verify your new email.
</>
</Trans>
)}
</Text>
@ -135,7 +140,7 @@ export const Component = observer(function Component({}: {}) {
value={email}
onChangeText={setEmail}
accessible={true}
accessibilityLabel="Email"
accessibilityLabel={_(msg`Email`)}
accessibilityHint=""
autoCapitalize="none"
autoComplete="email"
@ -151,7 +156,7 @@ export const Component = observer(function Component({}: {}) {
value={confirmationCode}
onChangeText={setConfirmationCode}
accessible={true}
accessibilityLabel="Confirmation code"
accessibilityLabel={_(msg`Confirmation code`)}
accessibilityHint=""
autoCapitalize="none"
autoComplete="off"
@ -175,9 +180,9 @@ export const Component = observer(function Component({}: {}) {
testID="requestChangeBtn"
type="primary"
onPress={onRequestChange}
accessibilityLabel="Request Change"
accessibilityLabel={_(msg`Request Change`)}
accessibilityHint=""
label="Request Change"
label={_(msg`Request Change`)}
labelContainerStyle={{justifyContent: 'center', padding: 4}}
labelStyle={[s.f18]}
/>
@ -187,9 +192,9 @@ export const Component = observer(function Component({}: {}) {
testID="confirmBtn"
type="primary"
onPress={onConfirm}
accessibilityLabel="Confirm Change"
accessibilityLabel={_(msg`Confirm Change`)}
accessibilityHint=""
label="Confirm Change"
label={_(msg`Confirm Change`)}
labelContainerStyle={{justifyContent: 'center', padding: 4}}
labelStyle={[s.f18]}
/>
@ -199,9 +204,9 @@ export const Component = observer(function Component({}: {}) {
testID="verifyBtn"
type="primary"
onPress={onVerify}
accessibilityLabel="Verify New Email"
accessibilityLabel={_(msg`Verify New Email`)}
accessibilityHint=""
label="Verify New Email"
label={_(msg`Verify New Email`)}
labelContainerStyle={{justifyContent: 'center', padding: 4}}
labelStyle={[s.f18]}
/>
@ -210,9 +215,9 @@ export const Component = observer(function Component({}: {}) {
testID="cancelBtn"
type="default"
onPress={() => closeModal()}
accessibilityLabel="Cancel"
accessibilityLabel={_(msg`Cancel`)}
accessibilityHint=""
label="Cancel"
label={_(msg`Cancel`)}
labelContainerStyle={{justifyContent: 'center', padding: 4}}
labelStyle={[s.f18]}
/>

View file

@ -22,6 +22,8 @@ import {useTheme} from 'lib/ThemeContext'
import {useAnalytics} from 'lib/analytics/analytics'
import {cleanError} from 'lib/strings/errors'
import {logger} from '#/logger'
import {Trans, msg} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {useModalControls} from '#/state/modals'
export const snapPoints = ['100%']
@ -31,6 +33,7 @@ export function Component({onChanged}: {onChanged: () => void}) {
const [error, setError] = useState<string>('')
const pal = usePalette('default')
const {track} = useAnalytics()
const {_} = useLingui()
const {closeModal} = useModalControls()
const [isProcessing, setProcessing] = useState<boolean>(false)
@ -141,7 +144,7 @@ export function Component({onChanged}: {onChanged: () => void}) {
<TouchableOpacity
onPress={onPressCancel}
accessibilityRole="button"
accessibilityLabel="Cancel change handle"
accessibilityLabel={_(msg`Cancel change handle`)}
accessibilityHint="Exits handle change process"
onAccessibilityEscape={onPressCancel}>
<Text type="lg" style={pal.textLight}>
@ -153,7 +156,7 @@ export function Component({onChanged}: {onChanged: () => void}) {
type="2xl-bold"
style={[styles.titleMiddle, pal.text]}
numberOfLines={1}>
Change Handle
<Trans>Change Handle</Trans>
</Text>
<View style={styles.titleRight}>
{isProcessing ? (
@ -163,7 +166,7 @@ export function Component({onChanged}: {onChanged: () => void}) {
testID="retryConnectButton"
onPress={onPressRetryConnect}
accessibilityRole="button"
accessibilityLabel="Retry change handle"
accessibilityLabel={_(msg`Retry change handle`)}
accessibilityHint={`Retries handle change to ${handle}`}>
<Text type="xl-bold" style={[pal.link, s.pr5]}>
Retry
@ -173,10 +176,10 @@ export function Component({onChanged}: {onChanged: () => void}) {
<TouchableOpacity
onPress={onPressSave}
accessibilityRole="button"
accessibilityLabel="Save handle change"
accessibilityLabel={_(msg`Save handle change`)}
accessibilityHint={`Saves handle change to ${handle}`}>
<Text type="2xl-medium" style={pal.link}>
Save
<Trans>Save</Trans>
</Text>
</TouchableOpacity>
) : undefined}
@ -234,6 +237,7 @@ function ProvidedHandleForm({
}) {
const pal = usePalette('default')
const theme = useTheme()
const {_} = useLingui()
// events
// =
@ -266,12 +270,12 @@ function ProvidedHandleForm({
onChangeText={onChangeHandle}
editable={!isProcessing}
accessible={true}
accessibilityLabel="Handle"
accessibilityLabel={_(msg`Handle`)}
accessibilityHint="Sets Bluesky username"
/>
</View>
<Text type="md" style={[pal.textLight, s.pl10, s.pt10]}>
Your full handle will be{' '}
<Trans>Your full handle will be </Trans>
<Text type="md-bold" style={pal.textLight}>
@{createFullHandle(handle, userDomain)}
</Text>
@ -280,9 +284,9 @@ function ProvidedHandleForm({
onPress={onToggleCustom}
accessibilityRole="button"
accessibilityHint="Hosting provider"
accessibilityLabel="Opens modal for using custom domain">
accessibilityLabel={_(msg`Opens modal for using custom domain`)}>
<Text type="md-medium" style={[pal.link, s.pl10, s.pt5]}>
I have my own domain
<Trans>I have my own domain</Trans>
</Text>
</TouchableOpacity>
</>
@ -314,6 +318,7 @@ function CustomHandleForm({
const palSecondary = usePalette('secondary')
const palError = usePalette('error')
const theme = useTheme()
const {_} = useLingui()
const [isVerifying, setIsVerifying] = React.useState(false)
const [error, setError] = React.useState<string>('')
const [isDNSForm, setDNSForm] = React.useState<boolean>(true)
@ -367,7 +372,7 @@ function CustomHandleForm({
return (
<>
<Text type="md" style={[pal.text, s.pb5, s.pl5]} nativeID="customDomain">
Enter the domain you want to use
<Trans>Enter the domain you want to use</Trans>
</Text>
<View style={[pal.btn, styles.textInputWrapper]}>
<FontAwesomeIcon
@ -385,7 +390,7 @@ function CustomHandleForm({
onChangeText={onChangeHandle}
editable={!isProcessing}
accessibilityLabelledBy="customDomain"
accessibilityLabel="Custom domain"
accessibilityLabel={_(msg`Custom domain`)}
accessibilityHint="Input your preferred hosting provider"
/>
</View>
@ -413,7 +418,7 @@ function CustomHandleForm({
{isDNSForm ? (
<>
<Text type="md" style={[pal.text, s.pb5, s.pl5]}>
Add the following DNS record to your domain:
<Trans>Add the following DNS record to your domain:</Trans>
</Text>
<View style={[styles.dnsTable, pal.btn]}>
<Text type="md-medium" style={[styles.dnsLabel, pal.text]}>
@ -451,7 +456,7 @@ function CustomHandleForm({
) : (
<>
<Text type="md" style={[pal.text, s.pb5, s.pl5]}>
Upload a text file to:
<Trans>Upload a text file to:</Trans>
</Text>
<View style={[styles.valueContainer, pal.btn]}>
<View style={[styles.dnsValue]}>
@ -483,7 +488,7 @@ function CustomHandleForm({
{canSave === true && (
<View style={[styles.message, palSecondary.view]}>
<Text type="md-medium" style={palSecondary.text}>
Domain verified!
<Trans>Domain verified!</Trans>
</Text>
</View>
)}
@ -511,7 +516,7 @@ function CustomHandleForm({
<View style={styles.spacer} />
<TouchableOpacity
onPress={onToggleCustom}
accessibilityLabel="Use default provider"
accessibilityLabel={_(msg`Use default provider`)}
accessibilityHint="Use bsky.social as hosting provider">
<Text type="md-medium" style={[pal.link, s.pl10, s.pt5]}>
Nevermind, create a handle for me

View file

@ -11,6 +11,8 @@ import {ErrorMessage} from '../util/error/ErrorMessage'
import {cleanError} from 'lib/strings/errors'
import {usePalette} from 'lib/hooks/usePalette'
import {isWeb} from 'platform/detection'
import {useLingui} from '@lingui/react'
import {msg} from '@lingui/macro'
import type {ConfirmModal} from '#/state/modals'
import {useModalControls} from '#/state/modals'
@ -26,6 +28,7 @@ export function Component({
cancelBtnText,
}: ConfirmModal) {
const pal = usePalette('default')
const {_} = useLingui()
const {closeModal} = useModalControls()
const [isProcessing, setIsProcessing] = useState<boolean>(false)
const [error, setError] = useState<string>('')
@ -69,7 +72,7 @@ export function Component({
onPress={onPress}
style={[styles.btn, confirmBtnStyle]}
accessibilityRole="button"
accessibilityLabel="Confirm"
accessibilityLabel={_(msg`Confirm`)}
accessibilityHint="">
<Text style={[s.white, s.bold, s.f18]}>
{confirmBtnText ?? 'Confirm'}
@ -82,7 +85,7 @@ export function Component({
onPress={onPressCancel}
style={[styles.btnCancel, s.mt10]}
accessibilityRole="button"
accessibilityLabel="Cancel"
accessibilityLabel={_(msg`Cancel`)}
accessibilityHint="">
<Text type="button-lg" style={pal.textLight}>
{cancelBtnText ?? 'Cancel'}

View file

@ -16,6 +16,8 @@ import {isIOS} from 'platform/detection'
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
import * as Toast from '../util/Toast'
import {logger} from '#/logger'
import {Trans, msg} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {useModalControls} from '#/state/modals'
export const snapPoints = ['90%']
@ -25,6 +27,7 @@ export const Component = observer(
const store = useStores()
const {isMobile} = useWebMediaQueries()
const pal = usePalette('default')
const {_} = useLingui()
const {closeModal} = useModalControls()
React.useEffect(() => {
@ -37,7 +40,9 @@ export const Component = observer(
return (
<View testID="contentFilteringModal" style={[pal.view, styles.container]}>
<Text style={[pal.text, styles.title]}>Content Filtering</Text>
<Text style={[pal.text, styles.title]}>
<Trans>Content Filtering</Trans>
</Text>
<ScrollView style={styles.scrollContainer}>
<AdultContentEnabledPref />
<ContentLabelPref
@ -71,14 +76,16 @@ export const Component = observer(
testID="sendReportBtn"
onPress={onPressDone}
accessibilityRole="button"
accessibilityLabel="Done"
accessibilityLabel={_(msg`Done`)}
accessibilityHint="">
<LinearGradient
colors={[gradients.blueLight.start, gradients.blueLight.end]}
start={{x: 0, y: 0}}
end={{x: 1, y: 1}}
style={[styles.btn]}>
<Text style={[s.white, s.bold, s.f18]}>Done</Text>
<Text style={[s.white, s.bold, s.f18]}>
<Trans>Done</Trans>
</Text>
</LinearGradient>
</Pressable>
</View>

View file

@ -24,6 +24,8 @@ import {useTheme} from 'lib/ThemeContext'
import {useAnalytics} from 'lib/analytics/analytics'
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
import {cleanError, isNetworkError} from 'lib/strings/errors'
import {Trans, msg} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {useModalControls} from '#/state/modals'
const MAX_NAME = 64 // todo
@ -47,6 +49,7 @@ export function Component({
const pal = usePalette('default')
const theme = useTheme()
const {track} = useAnalytics()
const {_} = useLingui()
const activePurpose = useMemo(() => {
if (list?.data?.purpose) {
@ -164,14 +167,18 @@ export function Component({
]}
testID="createOrEditListModal">
<Text style={[styles.title, pal.text]}>
{list ? 'Edit' : 'New'} {purposeLabel} List
<Trans>
{list ? 'Edit' : 'New'} {purposeLabel} List
</Trans>
</Text>
{error !== '' && (
<View style={styles.errorContainer}>
<ErrorMessage message={error} />
</View>
)}
<Text style={[styles.label, pal.text]}>List Avatar</Text>
<Text style={[styles.label, pal.text]}>
<Trans>List Avatar</Trans>
</Text>
<View style={[styles.avi, {borderColor: pal.colors.background}]}>
<EditableUserAvatar
type="list"
@ -183,7 +190,7 @@ export function Component({
<View style={styles.form}>
<View>
<Text style={[styles.label, pal.text]} nativeID="list-name">
List Name
<Trans>List Name</Trans>
</Text>
<TextInput
testID="editNameInput"
@ -195,14 +202,14 @@ export function Component({
value={name}
onChangeText={v => setName(enforceLen(v, MAX_NAME))}
accessible={true}
accessibilityLabel="Name"
accessibilityLabel={_(msg`Name`)}
accessibilityHint=""
accessibilityLabelledBy="list-name"
/>
</View>
<View style={s.pb10}>
<Text style={[styles.label, pal.text]} nativeID="list-description">
Description
<Trans>Description</Trans>
</Text>
<TextInput
testID="editDescriptionInput"
@ -218,7 +225,7 @@ export function Component({
value={description}
onChangeText={v => setDescription(enforceLen(v, MAX_DESCRIPTION))}
accessible={true}
accessibilityLabel="Description"
accessibilityLabel={_(msg`Description`)}
accessibilityHint=""
accessibilityLabelledBy="list-description"
/>
@ -233,14 +240,16 @@ export function Component({
style={s.mt10}
onPress={onPressSave}
accessibilityRole="button"
accessibilityLabel="Save"
accessibilityLabel={_(msg`Save`)}
accessibilityHint="">
<LinearGradient
colors={[gradients.blueLight.start, gradients.blueLight.end]}
start={{x: 0, y: 0}}
end={{x: 1, y: 1}}
style={[styles.btn]}>
<Text style={[s.white, s.bold]}>Save</Text>
<Text style={[s.white, s.bold]}>
<Trans>Save</Trans>
</Text>
</LinearGradient>
</TouchableOpacity>
)}
@ -249,11 +258,13 @@ export function Component({
style={s.mt5}
onPress={onPressCancel}
accessibilityRole="button"
accessibilityLabel="Cancel"
accessibilityLabel={_(msg`Cancel`)}
accessibilityHint=""
onAccessibilityEscape={onPressCancel}>
<View style={[styles.btn]}>
<Text style={[s.black, s.bold, pal.text]}>Cancel</Text>
<Text style={[s.black, s.bold, pal.text]}>
<Trans>Cancel</Trans>
</Text>
</View>
</TouchableOpacity>
</View>

View file

@ -17,6 +17,8 @@ import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
import {ErrorMessage} from '../util/error/ErrorMessage'
import {cleanError} from 'lib/strings/errors'
import {resetToTab} from '../../../Navigation'
import {Trans, msg} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {useModalControls} from '#/state/modals'
export const snapPoints = ['60%']
@ -25,6 +27,7 @@ export function Component({}: {}) {
const pal = usePalette('default')
const theme = useTheme()
const store = useStores()
const {_} = useLingui()
const {closeModal} = useModalControls()
const {isMobile} = useWebMediaQueries()
const [isEmailSent, setIsEmailSent] = React.useState<boolean>(false)
@ -71,7 +74,7 @@ export function Component({}: {}) {
<View style={[styles.innerContainer, pal.view]}>
<View style={[styles.titleContainer, pal.view]}>
<Text type="title-xl" style={[s.textCenter, pal.text]}>
Delete Account
<Trans>Delete Account</Trans>
</Text>
<View style={[pal.view, s.flexRow]}>
<Text type="title-xl" style={[pal.text, s.bold]}>
@ -95,8 +98,10 @@ export function Component({}: {}) {
{!isEmailSent ? (
<>
<Text type="lg" style={[styles.description, pal.text]}>
For security reasons, we'll need to send a confirmation code to
your email address.
<Trans>
For security reasons, we'll need to send a confirmation code to
your email address.
</Trans>
</Text>
{error ? (
<View style={s.mt10}>
@ -113,7 +118,7 @@ export function Component({}: {}) {
style={styles.mt20}
onPress={onPressSendEmail}
accessibilityRole="button"
accessibilityLabel="Send email"
accessibilityLabel={_(msg`Send email`)}
accessibilityHint="Sends email with confirmation code for account deletion">
<LinearGradient
colors={[
@ -124,7 +129,7 @@ export function Component({}: {}) {
end={{x: 1, y: 1}}
style={[styles.btn]}>
<Text type="button-lg" style={[s.white, s.bold]}>
Send Email
<Trans>Send Email</Trans>
</Text>
</LinearGradient>
</TouchableOpacity>
@ -132,11 +137,11 @@ export function Component({}: {}) {
style={[styles.btn, s.mt10]}
onPress={onCancel}
accessibilityRole="button"
accessibilityLabel="Cancel account deletion"
accessibilityLabel={_(msg`Cancel account deletion`)}
accessibilityHint=""
onAccessibilityEscape={onCancel}>
<Text type="button-lg" style={pal.textLight}>
Cancel
<Trans>Cancel</Trans>
</Text>
</TouchableOpacity>
</>
@ -149,8 +154,10 @@ export function Component({}: {}) {
type="lg"
style={styles.description}
nativeID="confirmationCode">
Check your inbox for an email with the confirmation code to enter
below:
<Trans>
Check your inbox for an email with the confirmation code to
enter below:
</Trans>
</Text>
<TextInput
style={[styles.textInput, pal.borderDark, pal.text, styles.mb20]}
@ -160,11 +167,11 @@ export function Component({}: {}) {
value={confirmCode}
onChangeText={setConfirmCode}
accessibilityLabelledBy="confirmationCode"
accessibilityLabel="Confirmation code"
accessibilityLabel={_(msg`Confirmation code`)}
accessibilityHint="Input confirmation code for account deletion"
/>
<Text type="lg" style={styles.description} nativeID="password">
Please enter your password as well:
<Trans>Please enter your password as well:</Trans>
</Text>
<TextInput
style={[styles.textInput, pal.borderDark, pal.text]}
@ -175,7 +182,7 @@ export function Component({}: {}) {
value={password}
onChangeText={setPassword}
accessibilityLabelledBy="password"
accessibilityLabel="Password"
accessibilityLabel={_(msg`Password`)}
accessibilityHint="Input password for account deletion"
/>
{error ? (
@ -193,21 +200,21 @@ export function Component({}: {}) {
style={[styles.btn, styles.evilBtn, styles.mt20]}
onPress={onPressConfirmDelete}
accessibilityRole="button"
accessibilityLabel="Confirm delete account"
accessibilityLabel={_(msg`Confirm delete account`)}
accessibilityHint="">
<Text type="button-lg" style={[s.white, s.bold]}>
Delete my account
<Trans>Delete my account</Trans>
</Text>
</TouchableOpacity>
<TouchableOpacity
style={[styles.btn, s.mt10]}
onPress={onCancel}
accessibilityRole="button"
accessibilityLabel="Cancel account deletion"
accessibilityLabel={_(msg`Cancel account deletion`)}
accessibilityHint="Exits account deletion process"
onAccessibilityEscape={onCancel}>
<Text type="button-lg" style={pal.textLight}>
Cancel
<Trans>Cancel</Trans>
</Text>
</TouchableOpacity>
</>

View file

@ -18,6 +18,8 @@ import {Slider} from '@miblanchard/react-native-slider'
import {MaterialIcons} from '@expo/vector-icons'
import {observer} from 'mobx-react-lite'
import {getKeys} from 'lib/type-assertions'
import {Trans, msg} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {useModalControls} from '#/state/modals'
export const snapPoints = ['80%']
@ -52,6 +54,7 @@ export const Component = observer(function EditImageImpl({
}: Props) {
const pal = usePalette('default')
const theme = useTheme()
const {_} = useLingui()
const windowDimensions = useWindowDimensions()
const {isMobile} = useWebMediaQueries()
const {closeModal} = useModalControls()
@ -200,7 +203,9 @@ export const Component = observer(function EditImageImpl({
paddingHorizontal: isMobile ? 16 : undefined,
},
]}>
<Text style={[styles.title, pal.text]}>Edit image</Text>
<Text style={[styles.title, pal.text]}>
<Trans>Edit image</Trans>
</Text>
<View style={[styles.gap18, s.flexRow]}>
<View>
<View
@ -228,7 +233,7 @@ export const Component = observer(function EditImageImpl({
<View>
{!isMobile ? (
<Text type="sm-bold" style={pal.text}>
Ratios
<Trans>Ratios</Trans>
</Text>
) : null}
<View style={imgControlStyles}>
@ -263,7 +268,7 @@ export const Component = observer(function EditImageImpl({
</View>
{!isMobile ? (
<Text type="sm-bold" style={[pal.text, styles.subsection]}>
Transformations
<Trans>Transformations</Trans>
</Text>
) : null}
<View style={imgControlStyles}>
@ -291,7 +296,7 @@ export const Component = observer(function EditImageImpl({
</View>
<View style={[styles.gap18, styles.bottomSection, pal.border]}>
<Text type="sm-bold" style={pal.text} nativeID="alt-text">
Accessibility
<Trans>Accessibility</Trans>
</Text>
<TextInput
testID="altTextImageInput"
@ -307,7 +312,7 @@ export const Component = observer(function EditImageImpl({
multiline
value={altText}
onChangeText={text => setAltText(enforceLen(text, MAX_ALT_TEXT))}
accessibilityLabel="Alt text"
accessibilityLabel={_(msg`Alt text`)}
accessibilityHint=""
accessibilityLabelledBy="alt-text"
/>
@ -315,7 +320,7 @@ export const Component = observer(function EditImageImpl({
<View style={styles.btns}>
<Pressable onPress={onPressCancel} accessibilityRole="button">
<Text type="xl" style={pal.link}>
Cancel
<Trans>Cancel</Trans>
</Text>
</Pressable>
<Pressable onPress={onPressSave} accessibilityRole="button">
@ -325,7 +330,7 @@ export const Component = observer(function EditImageImpl({
end={{x: 1, y: 1}}
style={[styles.btn]}>
<Text type="xl-medium" style={s.white}>
Done
<Trans>Done</Trans>
</Text>
</LinearGradient>
</Pressable>

View file

@ -26,6 +26,8 @@ import {useAnalytics} from 'lib/analytics/analytics'
import {cleanError, isNetworkError} from 'lib/strings/errors'
import Animated, {FadeOut} from 'react-native-reanimated'
import {isWeb} from 'platform/detection'
import {Trans, msg} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {useModalControls} from '#/state/modals'
const AnimatedTouchableOpacity =
@ -44,6 +46,7 @@ export function Component({
const pal = usePalette('default')
const theme = useTheme()
const {track} = useAnalytics()
const {_} = useLingui()
const {closeModal} = useModalControls()
const [isProcessing, setProcessing] = useState<boolean>(false)
@ -151,7 +154,9 @@ export function Component({
return (
<KeyboardAvoidingView style={s.flex1} behavior="height">
<ScrollView style={[pal.view]} testID="editProfileModal">
<Text style={[styles.title, pal.text]}>Edit my profile</Text>
<Text style={[styles.title, pal.text]}>
<Trans>Edit my profile</Trans>
</Text>
<View style={styles.photos}>
<UserBanner
banner={userBanner}
@ -172,7 +177,9 @@ export function Component({
)}
<View style={styles.form}>
<View>
<Text style={[styles.label, pal.text]}>Display Name</Text>
<Text style={[styles.label, pal.text]}>
<Trans>Display Name</Trans>
</Text>
<TextInput
testID="editProfileDisplayNameInput"
style={[styles.textInput, pal.border, pal.text]}
@ -183,12 +190,14 @@ export function Component({
setDisplayName(enforceLen(v, MAX_DISPLAY_NAME))
}
accessible={true}
accessibilityLabel="Display name"
accessibilityLabel={_(msg`Display name`)}
accessibilityHint="Edit your display name"
/>
</View>
<View style={s.pb10}>
<Text style={[styles.label, pal.text]}>Description</Text>
<Text style={[styles.label, pal.text]}>
<Trans>Description</Trans>
</Text>
<TextInput
testID="editProfileDescriptionInput"
style={[styles.textArea, pal.border, pal.text]}
@ -199,7 +208,7 @@ export function Component({
value={description}
onChangeText={v => setDescription(enforceLen(v, MAX_DESCRIPTION))}
accessible={true}
accessibilityLabel="Description"
accessibilityLabel={_(msg`Description`)}
accessibilityHint="Edit your profile description"
/>
</View>
@ -213,14 +222,16 @@ export function Component({
style={s.mt10}
onPress={onPressSave}
accessibilityRole="button"
accessibilityLabel="Save"
accessibilityLabel={_(msg`Save`)}
accessibilityHint="Saves any changes to your profile">
<LinearGradient
colors={[gradients.blueLight.start, gradients.blueLight.end]}
start={{x: 0, y: 0}}
end={{x: 1, y: 1}}
style={[styles.btn]}>
<Text style={[s.white, s.bold]}>Save Changes</Text>
<Text style={[s.white, s.bold]}>
<Trans>Save Changes</Trans>
</Text>
</LinearGradient>
</TouchableOpacity>
)}
@ -231,11 +242,13 @@ export function Component({
style={s.mt5}
onPress={onPressCancel}
accessibilityRole="button"
accessibilityLabel="Cancel profile editing"
accessibilityLabel={_(msg`Cancel profile editing`)}
accessibilityHint=""
onAccessibilityEscape={onPressCancel}>
<View style={[styles.btn]}>
<Text style={[s.black, s.bold, pal.text]}>Cancel</Text>
<Text style={[s.black, s.bold, pal.text]}>
<Trans>Cancel</Trans>
</Text>
</View>
</AnimatedTouchableOpacity>
)}

View file

@ -15,6 +15,7 @@ import {ScrollView} from './util'
import {usePalette} from 'lib/hooks/usePalette'
import {isWeb} from 'platform/detection'
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
import {Trans} from '@lingui/macro'
import {useModalControls} from '#/state/modals'
import {useInvitesState, useInvitesAPI} from '#/state/invites'
import {UserInfoText} from '../util/UserInfoText'
@ -38,8 +39,10 @@ export function Component({}: {}) {
<View style={[styles.container, pal.view]} testID="inviteCodesModal">
<View style={[styles.empty, pal.viewLight]}>
<Text type="lg" style={[pal.text, styles.emptyText]}>
You don't have any invite codes yet! We'll send you some when you've
been on Bluesky for a little longer.
<Trans>
You don't have any invite codes yet! We'll send you some when
you've been on Bluesky for a little longer.
</Trans>
</Text>
</View>
<View style={styles.flex1} />
@ -63,10 +66,12 @@ export function Component({}: {}) {
return (
<View style={[styles.container, pal.view]} testID="inviteCodesModal">
<Text type="title-xl" style={[styles.title, pal.text]}>
Invite a Friend
<Trans>Invite a Friend</Trans>
</Text>
<Text type="lg" style={[styles.description, pal.text]}>
Each code works once. You'll receive more invite codes periodically.
<Trans>
Each code works once. You'll receive more invite codes periodically.
</Trans>
</Text>
<ScrollView style={[styles.scrollContainer, pal.border]}>
{store.me.invites.map((invite, i) => (
@ -138,7 +143,9 @@ const InviteCode = observer(function InviteCodeImpl({
</Text>
<View style={styles.flex1} />
{!used && invitesState.copiedInvites.includes(invite.code) && (
<Text style={[pal.textLight, styles.codeCopied]}>Copied</Text>
<Text style={[pal.textLight, styles.codeCopied]}>
<Trans>Copied</Trans>
</Text>
)}
{!used && (
<FontAwesomeIcon
@ -154,7 +161,9 @@ const InviteCode = observer(function InviteCodeImpl({
gap: 8,
paddingTop: 6,
}}>
<Text style={pal.text}>Used by:</Text>
<Text style={pal.text}>
<Trans>Used by:</Trans>
</Text>
{invite.uses.map(use => (
<Link
key={use.usedBy}

View file

@ -10,6 +10,8 @@ import {usePalette} from 'lib/hooks/usePalette'
import {isWeb} from 'platform/detection'
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
import {isPossiblyAUrl, splitApexDomain} from 'lib/strings/url-helpers'
import {Trans, msg} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {useModalControls} from '#/state/modals'
export const snapPoints = ['50%']
@ -24,6 +26,7 @@ export const Component = observer(function Component({
const pal = usePalette('default')
const {closeModal} = useModalControls()
const {isMobile} = useWebMediaQueries()
const {_} = useLingui()
const potentiallyMisleading = isPossiblyAUrl(text)
const onPressVisit = () => {
@ -45,26 +48,26 @@ export const Component = observer(function Component({
size={18}
/>
<Text type="title-lg" style={[pal.text, styles.title]}>
Potentially Misleading Link
<Trans>Potentially Misleading Link</Trans>
</Text>
</>
) : (
<Text type="title-lg" style={[pal.text, styles.title]}>
Leaving Bluesky
<Trans>Leaving Bluesky</Trans>
</Text>
)}
</View>
<View style={{gap: 10}}>
<Text type="lg" style={pal.text}>
This link is taking you to the following website:
<Trans>This link is taking you to the following website:</Trans>
</Text>
<LinkBox href={href} />
{potentiallyMisleading && (
<Text type="lg" style={pal.text}>
Make sure this is where you intend to go!
<Trans>Make sure this is where you intend to go!</Trans>
</Text>
)}
</View>
@ -74,7 +77,7 @@ export const Component = observer(function Component({
testID="confirmBtn"
type="primary"
onPress={onPressVisit}
accessibilityLabel="Visit Site"
accessibilityLabel={_(msg`Visit Site`)}
accessibilityHint=""
label="Visit Site"
labelContainerStyle={{justifyContent: 'center', padding: 4}}
@ -84,7 +87,7 @@ export const Component = observer(function Component({
testID="cancelBtn"
type="default"
onPress={() => closeModal()}
accessibilityLabel="Cancel"
accessibilityLabel={_(msg`Cancel`)}
accessibilityHint=""
label="Cancel"
labelContainerStyle={{justifyContent: 'center', padding: 4}}

View file

@ -26,6 +26,8 @@ import {cleanError} from 'lib/strings/errors'
import {sanitizeDisplayName} from 'lib/strings/display-names'
import {sanitizeHandle} from 'lib/strings/handles'
import {HITSLOP_20} from '#/lib/constants'
import {Trans, msg} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {useModalControls} from '#/state/modals'
export const snapPoints = ['90%']
@ -39,6 +41,7 @@ export const Component = observer(function Component({
}) {
const pal = usePalette('default')
const store = useStores()
const {_} = useLingui()
const {closeModal} = useModalControls()
const {isMobile} = useWebMediaQueries()
const [query, setQuery] = useState('')
@ -85,7 +88,7 @@ export const Component = observer(function Component({
value={query}
onChangeText={onChangeQuery}
accessible={true}
accessibilityLabel="Search"
accessibilityLabel={_(msg`Search`)}
accessibilityHint=""
autoFocus
autoCapitalize="none"
@ -97,7 +100,7 @@ export const Component = observer(function Component({
<Pressable
onPress={onPressCancelSearch}
accessibilityRole="button"
accessibilityLabel="Cancel search"
accessibilityLabel={_(msg`Cancel search`)}
accessibilityHint="Exits inputting search query"
onAccessibilityEscape={onPressCancelSearch}
hitSlop={HITSLOP_20}>
@ -136,7 +139,7 @@ export const Component = observer(function Component({
pal.textLight,
{paddingHorizontal: 12, paddingVertical: 16},
]}>
No results found for {autocompleteView.prefix}
<Trans>No results found for {autocompleteView.prefix}</Trans>
</Text>
)}
</ScrollView>
@ -149,7 +152,7 @@ export const Component = observer(function Component({
testID="doneBtn"
type="default"
onPress={() => closeModal()}
accessibilityLabel="Done"
accessibilityLabel={_(msg`Done`)}
accessibilityHint=""
label="Done"
labelContainerStyle={{justifyContent: 'center', padding: 4}}

View file

@ -6,6 +6,8 @@ import {Text} from '../util/text/Text'
import {usePalette} from 'lib/hooks/usePalette'
import {RepostIcon} from 'lib/icons'
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
import {Trans, msg} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {useModalControls} from '#/state/modals'
export const snapPoints = [250]
@ -21,6 +23,7 @@ export function Component({
// TODO: Add author into component
}) {
const pal = usePalette('default')
const {_} = useLingui()
const {closeModal} = useModalControls()
const onPress = async () => {
closeModal()
@ -38,7 +41,7 @@ export function Component({
accessibilityHint={isReposted ? 'Remove repost' : 'Repost '}>
<RepostIcon strokeWidth={2} size={24} style={s.blue3} />
<Text type="title-lg" style={[styles.actionBtnLabel, pal.text]}>
{!isReposted ? 'Repost' : 'Undo repost'}
<Trans>{!isReposted ? 'Repost' : 'Undo repost'}</Trans>
</Text>
</TouchableOpacity>
<TouchableOpacity
@ -46,11 +49,11 @@ export function Component({
style={[styles.actionBtn]}
onPress={onQuote}
accessibilityRole="button"
accessibilityLabel="Quote post"
accessibilityLabel={_(msg`Quote post`)}
accessibilityHint="">
<FontAwesomeIcon icon="quote-left" size={24} style={s.blue3} />
<Text type="title-lg" style={[styles.actionBtnLabel, pal.text]}>
Quote Post
<Trans>Quote Post</Trans>
</Text>
</TouchableOpacity>
</View>
@ -58,7 +61,7 @@ export function Component({
testID="cancelBtn"
onPress={onPress}
accessibilityRole="button"
accessibilityLabel="Cancel quote post"
accessibilityLabel={_(msg`Cancel quote post`)}
accessibilityHint=""
onAccessibilityEscape={onPress}>
<LinearGradient
@ -66,7 +69,9 @@ export function Component({
start={{x: 0, y: 0}}
end={{x: 1, y: 1}}
style={[styles.btn]}>
<Text style={[s.white, s.bold, s.f18]}>Cancel</Text>
<Text style={[s.white, s.bold, s.f18]}>
<Trans>Cancel</Trans>
</Text>
</LinearGradient>
</TouchableOpacity>
</View>

View file

@ -9,6 +9,8 @@ import {isWeb} from 'platform/detection'
import {Button} from '../util/forms/Button'
import {SelectableBtn} from '../util/forms/SelectableBtn'
import {ScrollView} from 'view/com/modals/util'
import {Trans, msg} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {useModalControls} from '#/state/modals'
const ADULT_CONTENT_LABELS = ['sexual', 'nudity', 'porn']
@ -28,6 +30,7 @@ export const Component = observer(function Component({
const {closeModal} = useModalControls()
const {isMobile} = useWebMediaQueries()
const [selected, setSelected] = useState(labels)
const {_} = useLingui()
const toggleAdultLabel = (label: string) => {
const hadLabel = selected.includes(label)
@ -51,7 +54,7 @@ export const Component = observer(function Component({
<View testID="selfLabelModal" style={[pal.view, styles.container]}>
<View style={styles.titleSection}>
<Text type="title-lg" style={[pal.text, styles.title]}>
Add a content warning
<Trans>Add a content warning</Trans>
</Text>
</View>
@ -70,7 +73,7 @@ export const Component = observer(function Component({
paddingBottom: 8,
}}>
<Text type="title" style={pal.text}>
Adult Content
<Trans>Adult Content</Trans>
</Text>
{hasAdultSelection ? (
<Button
@ -78,7 +81,7 @@ export const Component = observer(function Component({
onPress={removeAdultLabel}
style={{paddingTop: 0, paddingBottom: 0, paddingRight: 0}}>
<Text type="md" style={pal.link}>
Remove
<Trans>Remove</Trans>
</Text>
</Button>
) : null}
@ -116,23 +119,25 @@ export const Component = observer(function Component({
<Text style={[pal.text, styles.adultExplainer]}>
{selected.includes('sexual') ? (
<>Pictures meant for adults.</>
<Trans>Pictures meant for adults.</Trans>
) : selected.includes('nudity') ? (
<>Artistic or non-erotic nudity.</>
<Trans>Artistic or non-erotic nudity.</Trans>
) : selected.includes('porn') ? (
<>Sexual activity or erotic nudity.</>
<Trans>Sexual activity or erotic nudity.</Trans>
) : (
<>If none are selected, suitable for all ages.</>
<Trans>If none are selected, suitable for all ages.</Trans>
)}
</Text>
</>
) : (
<View>
<Text style={[pal.textLight]}>
<Text type="md-bold" style={[pal.textLight]}>
Not Applicable
<Text type="md-bold" style={[pal.textLight, s.mr5]}>
<Trans>Not Applicable.</Trans>
</Text>
. This warning is only available for posts with media attached.
<Trans>
This warning is only available for posts with media attached.
</Trans>
</Text>
</View>
)}
@ -147,9 +152,11 @@ export const Component = observer(function Component({
}}
style={styles.btn}
accessibilityRole="button"
accessibilityLabel="Confirm"
accessibilityLabel={_(msg`Confirm`)}
accessibilityHint="">
<Text style={[s.white, s.bold, s.f18]}>Done</Text>
<Text style={[s.white, s.bold, s.f18]}>
<Trans>Done</Trans>
</Text>
</TouchableOpacity>
</View>
</View>

View file

@ -11,6 +11,8 @@ import {usePalette} from 'lib/hooks/usePalette'
import {useTheme} from 'lib/ThemeContext'
import {LOCAL_DEV_SERVICE, STAGING_SERVICE, PROD_SERVICE} from 'state/index'
import {LOGIN_INCLUDE_DEV_SERVERS} from 'lib/build-flags'
import {Trans, msg} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {useModalControls} from '#/state/modals'
export const snapPoints = ['80%']
@ -19,6 +21,7 @@ export function Component({onSelect}: {onSelect: (url: string) => void}) {
const theme = useTheme()
const pal = usePalette('default')
const [customUrl, setCustomUrl] = useState<string>('')
const {_} = useLingui()
const {closeModal} = useModalControls()
const doSelect = (url: string) => {
@ -32,7 +35,7 @@ export function Component({onSelect}: {onSelect: (url: string) => void}) {
return (
<View style={[pal.view, s.flex1]} testID="serverInputModal">
<Text type="2xl-bold" style={[pal.text, s.textCenter]}>
Choose Service
<Trans>Choose Service</Trans>
</Text>
<ScrollView style={styles.inner}>
<View style={styles.group}>
@ -43,7 +46,9 @@ export function Component({onSelect}: {onSelect: (url: string) => void}) {
style={styles.btn}
onPress={() => doSelect(LOCAL_DEV_SERVICE)}
accessibilityRole="button">
<Text style={styles.btnText}>Local dev server</Text>
<Text style={styles.btnText}>
<Trans>Local dev server</Trans>
</Text>
<FontAwesomeIcon
icon="arrow-right"
style={s.white as FontAwesomeIconStyle}
@ -53,7 +58,9 @@ export function Component({onSelect}: {onSelect: (url: string) => void}) {
style={styles.btn}
onPress={() => doSelect(STAGING_SERVICE)}
accessibilityRole="button">
<Text style={styles.btnText}>Staging</Text>
<Text style={styles.btnText}>
<Trans>Staging</Trans>
</Text>
<FontAwesomeIcon
icon="arrow-right"
style={s.white as FontAwesomeIconStyle}
@ -65,9 +72,11 @@ export function Component({onSelect}: {onSelect: (url: string) => void}) {
style={styles.btn}
onPress={() => doSelect(PROD_SERVICE)}
accessibilityRole="button"
accessibilityLabel="Select Bluesky Social"
accessibilityLabel={_(msg`Select Bluesky Social`)}
accessibilityHint="Sets Bluesky Social as your service provider">
<Text style={styles.btnText}>Bluesky.Social</Text>
<Text style={styles.btnText}>
<Trans>Bluesky.Social</Trans>
</Text>
<FontAwesomeIcon
icon="arrow-right"
style={s.white as FontAwesomeIconStyle}
@ -75,7 +84,9 @@ export function Component({onSelect}: {onSelect: (url: string) => void}) {
</TouchableOpacity>
</View>
<View style={styles.group}>
<Text style={[pal.text, styles.label]}>Other service</Text>
<Text style={[pal.text, styles.label]}>
<Trans>Other service</Trans>
</Text>
<View style={s.flexRow}>
<TextInput
testID="customServerTextInput"
@ -88,7 +99,7 @@ export function Component({onSelect}: {onSelect: (url: string) => void}) {
keyboardAppearance={theme.colorScheme}
value={customUrl}
onChangeText={setCustomUrl}
accessibilityLabel="Custom domain"
accessibilityLabel={_(msg`Custom domain`)}
// TODO: Simplify this wording further to be understandable by everyone
accessibilityHint="Use your domain as your Bluesky client service provider"
/>

View file

@ -17,12 +17,15 @@ import {Link} from '../util/Link'
import {makeProfileLink} from 'lib/routes/links'
import {BottomSheetScrollView} from '@gorhom/bottom-sheet'
import {Haptics} from 'lib/haptics'
import {Trans, msg} from '@lingui/macro'
import {useLingui} from '@lingui/react'
export const snapPoints = ['40%', '90%']
export function Component({}: {}) {
const pal = usePalette('default')
const {track} = useAnalytics()
const {_: _lingui} = useLingui()
const store = useStores()
const [isSwitching, _, onPressSwitchAccount] = useAccountSwitcher()
@ -41,7 +44,7 @@ export function Component({}: {}) {
style={[styles.container, pal.view]}
contentContainerStyle={[styles.innerContainer, pal.view]}>
<Text type="title-xl" style={[styles.title, pal.text]}>
Switch Account
<Trans>Switch Account</Trans>
</Text>
{isSwitching ? (
<View style={[pal.view, styles.linkCard]}>
@ -65,10 +68,10 @@ export function Component({}: {}) {
testID="signOutBtn"
onPress={isSwitching ? undefined : onPressSignout}
accessibilityRole="button"
accessibilityLabel="Sign out"
accessibilityLabel={_lingui(msg`Sign out`)}
accessibilityHint={`Signs ${store.me.displayName} out of Bluesky`}>
<Text type="lg" style={pal.link}>
Sign out
<Trans>Sign out</Trans>
</Text>
</TouchableOpacity>
</View>

View file

@ -21,6 +21,8 @@ import {usePalette} from 'lib/hooks/usePalette'
import {isWeb, isAndroid} from 'platform/detection'
import isEqual from 'lodash.isequal'
import {logger} from '#/logger'
import {Trans, msg} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {useModalControls} from '#/state/modals'
export const snapPoints = ['fullscreen']
@ -39,6 +41,7 @@ export const Component = observer(function UserAddRemoveListsImpl({
const store = useStores()
const {closeModal} = useModalControls()
const pal = usePalette('default')
const {_} = useLingui()
const palPrimary = usePalette('primary')
const palInverted = usePalette('inverted')
const [originalSelections, setOriginalSelections] = React.useState<string[]>(
@ -181,7 +184,7 @@ export const Component = observer(function UserAddRemoveListsImpl({
return (
<View testID="userAddRemoveListsModal" style={s.hContentRegion}>
<Text style={[styles.title, pal.text]}>
Update {displayName} in Lists
<Trans>Update {displayName} in Lists</Trans>
</Text>
<ListsList
listsList={listsList}
@ -195,7 +198,7 @@ export const Component = observer(function UserAddRemoveListsImpl({
type="default"
onPress={onPressCancel}
style={styles.footerBtn}
accessibilityLabel="Cancel"
accessibilityLabel={_(msg`Cancel`)}
accessibilityHint=""
onAccessibilityEscape={onPressCancel}
label="Cancel"
@ -206,7 +209,7 @@ export const Component = observer(function UserAddRemoveListsImpl({
type="primary"
onPress={onPressSave}
style={styles.footerBtn}
accessibilityLabel="Save changes"
accessibilityLabel={_(msg`Save changes`)}
accessibilityHint=""
onAccessibilityEscape={onPressSave}
label="Save Changes"

View file

@ -20,6 +20,8 @@ import {usePalette} from 'lib/hooks/usePalette'
import {isWeb} from 'platform/detection'
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
import {cleanError} from 'lib/strings/errors'
import {Trans, msg} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {useModalControls} from '#/state/modals'
export const snapPoints = ['90%']
@ -37,6 +39,7 @@ export const Component = observer(function Component({
}) {
const pal = usePalette('default')
const store = useStores()
const {_} = useLingui()
const [stage, setStage] = useState<Stages>(
showReminder ? Stages.Reminder : Stages.Email,
)
@ -98,21 +101,21 @@ export const Component = observer(function Component({
<Text type="lg" style={[pal.textLight, {marginBottom: 10}]}>
{stage === Stages.Reminder ? (
<>
<Trans>
Your email has not yet been verified. This is an important
security step which we recommend.
</>
</Trans>
) : stage === Stages.Email ? (
<>
<Trans>
This is important in case you ever need to change your email or
reset your password.
</>
</Trans>
) : stage === Stages.ConfirmCode ? (
<>
<Trans>
An email has been sent to{' '}
{store.session.currentSession?.email || ''}. It includes a
confirmation code which you can enter below.
</>
</Trans>
) : (
''
)}
@ -132,7 +135,7 @@ export const Component = observer(function Component({
</View>
<Pressable
accessibilityRole="link"
accessibilityLabel="Change my email"
accessibilityLabel={_(msg`Change my email`)}
accessibilityHint=""
onPress={onEmailIncorrect}
style={styles.changeEmailLink}>
@ -150,7 +153,7 @@ export const Component = observer(function Component({
value={confirmationCode}
onChangeText={setConfirmationCode}
accessible={true}
accessibilityLabel="Confirmation code"
accessibilityLabel={_(msg`Confirmation code`)}
accessibilityHint=""
autoCapitalize="none"
autoComplete="off"
@ -174,7 +177,7 @@ export const Component = observer(function Component({
testID="getStartedBtn"
type="primary"
onPress={() => setStage(Stages.Email)}
accessibilityLabel="Get Started"
accessibilityLabel={_(msg`Get Started`)}
accessibilityHint=""
label="Get Started"
labelContainerStyle={{justifyContent: 'center', padding: 4}}
@ -187,7 +190,7 @@ export const Component = observer(function Component({
testID="sendEmailBtn"
type="primary"
onPress={onSendEmail}
accessibilityLabel="Send Confirmation Email"
accessibilityLabel={_(msg`Send Confirmation Email`)}
accessibilityHint=""
label="Send Confirmation Email"
labelContainerStyle={{
@ -199,7 +202,7 @@ export const Component = observer(function Component({
<Button
testID="haveCodeBtn"
type="default"
accessibilityLabel="I have a code"
accessibilityLabel={_(msg`I have a code`)}
accessibilityHint=""
label="I have a confirmation code"
labelContainerStyle={{
@ -216,7 +219,7 @@ export const Component = observer(function Component({
testID="confirmBtn"
type="primary"
onPress={onConfirm}
accessibilityLabel="Confirm"
accessibilityLabel={_(msg`Confirm`)}
accessibilityHint=""
label="Confirm"
labelContainerStyle={{justifyContent: 'center', padding: 4}}

View file

@ -17,6 +17,8 @@ import {usePalette} from 'lib/hooks/usePalette'
import {useTheme} from 'lib/ThemeContext'
import {ErrorMessage} from '../util/error/ErrorMessage'
import {cleanError} from 'lib/strings/errors'
import {Trans, msg} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {useModalControls} from '#/state/modals'
export const snapPoints = ['80%']
@ -24,6 +26,7 @@ export const snapPoints = ['80%']
export function Component({}: {}) {
const pal = usePalette('default')
const theme = useTheme()
const {_} = useLingui()
const {closeModal} = useModalControls()
const [email, setEmail] = React.useState<string>('')
const [isEmailSent, setIsEmailSent] = React.useState<boolean>(false)
@ -61,12 +64,14 @@ export function Component({}: {}) {
<View style={[styles.container, pal.view]}>
<View style={[styles.innerContainer, pal.view]}>
<Text type="title-xl" style={[styles.title, pal.text]}>
Join the waitlist
<Trans>Join the waitlist</Trans>
</Text>
<Text type="lg" style={[styles.description, pal.text]}>
Bluesky uses invites to build a healthier community. If you don't know
anybody with an invite, you can sign up for the waitlist and we'll
send one soon.
<Trans>
Bluesky uses invites to build a healthier community. If you don't
know anybody with an invite, you can sign up for the waitlist and
we'll send one soon.
</Trans>
</Text>
<TextInput
style={[styles.textInput, pal.borderDark, pal.text, s.mb10, s.mt10]}
@ -80,7 +85,7 @@ export function Component({}: {}) {
onSubmitEditing={onPressSignup}
enterKeyHint="done"
accessible={true}
accessibilityLabel="Email"
accessibilityLabel={_(msg`Email`)}
accessibilityHint="Input your email to get on the Bluesky waitlist"
/>
{error ? (
@ -99,7 +104,9 @@ export function Component({}: {}) {
style={pal.text as FontAwesomeIconStyle}
/>
<Text style={[s.ml10, pal.text]}>
Your email has been saved! We&apos;ll be in touch soon.
<Trans>
Your email has been saved! We&apos;ll be in touch soon.
</Trans>
</Text>
</View>
) : (
@ -114,7 +121,7 @@ export function Component({}: {}) {
end={{x: 1, y: 1}}
style={[styles.btn]}>
<Text type="button-lg" style={[s.white, s.bold]}>
Join Waitlist
<Trans>Join Waitlist</Trans>
</Text>
</LinearGradient>
</TouchableOpacity>
@ -122,11 +129,11 @@ export function Component({}: {}) {
style={[styles.btn, s.mt10]}
onPress={onCancel}
accessibilityRole="button"
accessibilityLabel="Cancel waitlist signup"
accessibilityLabel={_(msg`Cancel waitlist signup`)}
accessibilityHint={`Exits signing up for waitlist with ${email}`}
onAccessibilityEscape={onCancel}>
<Text type="button-lg" style={pal.textLight}>
Cancel
<Trans>Cancel</Trans>
</Text>
</TouchableOpacity>
</>

View file

@ -10,6 +10,8 @@ import {s, gradients} from 'lib/styles'
import {usePalette} from 'lib/hooks/usePalette'
import {SquareIcon, RectWideIcon, RectTallIcon} from 'lib/icons'
import {Image as RNImage} from 'react-native-image-crop-picker'
import {Trans, msg} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {useModalControls} from '#/state/modals'
enum AspectRatio {
@ -35,6 +37,7 @@ export function Component({
}) {
const {closeModal} = useModalControls()
const pal = usePalette('default')
const {_} = useLingui()
const [as, setAs] = React.useState<AspectRatio>(AspectRatio.Square)
const [scale, setScale] = React.useState<number>(1)
const editorRef = React.useRef<ImageEditor>(null)
@ -96,7 +99,7 @@ export function Component({
<TouchableOpacity
onPress={doSetAs(AspectRatio.Wide)}
accessibilityRole="button"
accessibilityLabel="Wide"
accessibilityLabel={_(msg`Wide`)}
accessibilityHint="Sets image aspect ratio to wide">
<RectWideIcon
size={24}
@ -106,7 +109,7 @@ export function Component({
<TouchableOpacity
onPress={doSetAs(AspectRatio.Tall)}
accessibilityRole="button"
accessibilityLabel="Tall"
accessibilityLabel={_(msg`Tall`)}
accessibilityHint="Sets image aspect ratio to tall">
<RectTallIcon
size={24}
@ -116,7 +119,7 @@ export function Component({
<TouchableOpacity
onPress={doSetAs(AspectRatio.Square)}
accessibilityRole="button"
accessibilityLabel="Square"
accessibilityLabel={_(msg`Square`)}
accessibilityHint="Sets image aspect ratio to square">
<SquareIcon
size={24}
@ -128,7 +131,7 @@ export function Component({
<TouchableOpacity
onPress={onPressCancel}
accessibilityRole="button"
accessibilityLabel="Cancel image crop"
accessibilityLabel={_(msg`Cancel image crop`)}
accessibilityHint="Exits image cropping process">
<Text type="xl" style={pal.link}>
Cancel
@ -138,7 +141,7 @@ export function Component({
<TouchableOpacity
onPress={onPressDone}
accessibilityRole="button"
accessibilityLabel="Save image crop"
accessibilityLabel={_(msg`Save image crop`)}
accessibilityHint="Saves image crop settings">
<LinearGradient
colors={[gradients.blueLight.start, gradients.blueLight.end]}
@ -146,7 +149,7 @@ export function Component({
end={{x: 1, y: 1}}
style={[styles.btn]}>
<Text type="xl-medium" style={s.white}>
Done
<Trans>Done</Trans>
</Text>
</LinearGradient>
</TouchableOpacity>

View file

@ -4,6 +4,8 @@ import LinearGradient from 'react-native-linear-gradient'
import {s, colors, gradients} from 'lib/styles'
import {usePalette} from 'lib/hooks/usePalette'
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
import {Trans, msg} from '@lingui/macro'
import {useLingui} from '@lingui/react'
export const ConfirmLanguagesButton = ({
onPress,
@ -13,6 +15,7 @@ export const ConfirmLanguagesButton = ({
extraText?: string
}) => {
const pal = usePalette('default')
const {_} = useLingui()
const {isMobile} = useWebMediaQueries()
return (
<View
@ -28,14 +31,16 @@ export const ConfirmLanguagesButton = ({
testID="confirmContentLanguagesBtn"
onPress={onPress}
accessibilityRole="button"
accessibilityLabel="Confirm content language settings"
accessibilityLabel={_(msg`Confirm content language settings`)}
accessibilityHint="">
<LinearGradient
colors={[gradients.blueLight.start, gradients.blueLight.end]}
start={{x: 0, y: 0}}
end={{x: 1, y: 1}}
style={[styles.btn]}>
<Text style={[s.white, s.bold, s.f18]}>Done{extraText}</Text>
<Text style={[s.white, s.bold, s.f18]}>
<Trans>Done{extraText}</Trans>
</Text>
</LinearGradient>
</Pressable>
</View>

View file

@ -8,6 +8,7 @@ import {deviceLocales} from 'platform/detection'
import {LANGUAGES, LANGUAGES_MAP_CODE2} from '../../../../locale/languages'
import {LanguageToggle} from './LanguageToggle'
import {ConfirmLanguagesButton} from './ConfirmLanguagesButton'
import {Trans} from '@lingui/macro'
import {useModalControls} from '#/state/modals'
import {
useLanguagePrefs,
@ -69,12 +70,16 @@ export function Component({}: {}) {
maxHeight: '90vh',
},
]}>
<Text style={[pal.text, styles.title]}>Content Languages</Text>
<Text style={[pal.text, styles.title]}>
<Trans>Content Languages</Trans>
</Text>
<Text style={[pal.text, styles.description]}>
Which languages would you like to see in your algorithmic feeds?
<Trans>
Which languages would you like to see in your algorithmic feeds?
</Trans>
</Text>
<Text style={[pal.textLight, styles.description]}>
Leave them all unchecked to see any language.
<Trans>Leave them all unchecked to see any language.</Trans>
</Text>
<ScrollView style={styles.scrollContainer}>
{languages.map(lang => (

View file

@ -9,6 +9,7 @@ import {deviceLocales} from 'platform/detection'
import {LANGUAGES, LANGUAGES_MAP_CODE2} from '../../../../locale/languages'
import {ConfirmLanguagesButton} from './ConfirmLanguagesButton'
import {ToggleButton} from 'view/com/util/forms/ToggleButton'
import {Trans} from '@lingui/macro'
import {useModalControls} from '#/state/modals'
import {
useLanguagePrefs,
@ -71,9 +72,11 @@ export const Component = observer(function PostLanguagesSettingsImpl() {
maxHeight: '90vh',
},
]}>
<Text style={[pal.text, styles.title]}>Post Languages</Text>
<Text style={[pal.text, styles.title]}>
<Trans>Post Languages</Trans>
</Text>
<Text style={[pal.text, styles.description]}>
Which languages are used in this post?
<Trans>Which languages are used in this post?</Trans>
</Text>
<ScrollView style={styles.scrollContainer}>
{languages.map(lang => {

View file

@ -8,6 +8,8 @@ import {usePalette} from 'lib/hooks/usePalette'
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
import {s} from 'lib/styles'
import {SendReportButton} from './SendReportButton'
import {Trans, msg} from '@lingui/macro'
import {useLingui} from '@lingui/react'
export function InputIssueDetails({
details,
@ -23,6 +25,7 @@ export function InputIssueDetails({
isProcessing: boolean
}) {
const pal = usePalette('default')
const {_} = useLingui()
const {isMobile} = useWebMediaQueries()
return (
@ -35,14 +38,16 @@ export function InputIssueDetails({
style={[s.mb10, styles.backBtn]}
onPress={goBack}
accessibilityRole="button"
accessibilityLabel="Add details"
accessibilityLabel={_(msg`Add details`)}
accessibilityHint="Add more details to your report">
<FontAwesomeIcon size={18} icon="angle-left" style={[pal.link]} />
<Text style={[pal.text, s.f18, pal.link]}> Back</Text>
<Text style={[pal.text, s.f18, pal.link]}>
<Trans> Back</Trans>
</Text>
</TouchableOpacity>
<View style={[pal.btn, styles.detailsInputContainer]}>
<TextInput
accessibilityLabel="Text input field"
accessibilityLabel={_(msg`Text input field`)}
accessibilityHint="Enter a reason for reporting this post."
placeholder="Enter a reason or any other details here."
placeholderTextColor={pal.textLight.color}

View file

@ -14,6 +14,8 @@ import {SendReportButton} from './SendReportButton'
import {InputIssueDetails} from './InputIssueDetails'
import {ReportReasonOptions} from './ReasonOptions'
import {CollectionId} from './types'
import {Trans, msg} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {useModalControls} from '#/state/modals'
const DMCA_LINK = 'https://blueskyweb.xyz/support/copyright'
@ -148,6 +150,7 @@ const SelectIssue = ({
atUri: AtUri | null
}) => {
const pal = usePalette('default')
const {_} = useLingui()
const collectionName = getCollectionNameForReport(atUri)
const onSelectIssue = (v: string) => setIssue(v)
const goToDetails = () => {
@ -160,9 +163,11 @@ const SelectIssue = ({
return (
<>
<Text style={[pal.text, styles.title]}>Report {collectionName}</Text>
<Text style={[pal.text, styles.title]}>
<Trans>Report {collectionName}</Trans>
</Text>
<Text style={[pal.textLight, styles.description]}>
What is the issue with this {collectionName}?
<Trans>What is the issue with this {collectionName}?</Trans>
</Text>
<View style={{marginBottom: 10}}>
<ReportReasonOptions
@ -184,9 +189,11 @@ const SelectIssue = ({
style={styles.addDetailsBtn}
onPress={goToDetails}
accessibilityRole="button"
accessibilityLabel="Add details"
accessibilityLabel={_(msg`Add details`)}
accessibilityHint="Add more details to your report">
<Text style={[s.f18, pal.link]}>Add details to report</Text>
<Text style={[s.f18, pal.link]}>
<Trans>Add details to report</Trans>
</Text>
</TouchableOpacity>
</>
) : undefined}

View file

@ -8,6 +8,8 @@ import {
} from 'react-native'
import {Text} from '../../util/text/Text'
import {s, gradients, colors} from 'lib/styles'
import {Trans, msg} from '@lingui/macro'
import {useLingui} from '@lingui/react'
export function SendReportButton({
onPress,
@ -16,6 +18,7 @@ export function SendReportButton({
onPress: () => void
isProcessing: boolean
}) {
const {_} = useLingui()
// loading state
// =
if (isProcessing) {
@ -31,14 +34,16 @@ export function SendReportButton({
style={s.mt10}
onPress={onPress}
accessibilityRole="button"
accessibilityLabel="Report post"
accessibilityLabel={_(msg`Report post`)}
accessibilityHint={`Reports post with reason and details`}>
<LinearGradient
colors={[gradients.blueLight.start, gradients.blueLight.end]}
start={{x: 0, y: 0}}
end={{x: 1, y: 1}}
style={[styles.btn]}>
<Text style={[s.white, s.bold, s.f18]}>Send Report</Text>
<Text style={[s.white, s.bold, s.f18]}>
<Trans>Send Report</Trans>
</Text>
</LinearGradient>
</TouchableOpacity>
)