diff --git a/src/components/ExpensifyWordmark.tsx b/src/components/ExpensifyWordmark.tsx index a26bebe3f0d8..fb0f30ddf56e 100644 --- a/src/components/ExpensifyWordmark.tsx +++ b/src/components/ExpensifyWordmark.tsx @@ -6,6 +6,7 @@ import DevLogo from '@assets/images/expensify-logo--dev.svg'; import ProductionLogo from '@assets/images/expensify-logo--prod.svg'; import StagingLogo from '@assets/images/expensify-logo--staging.svg'; import useEnvironment from '@hooks/useEnvironment'; +import useLocalize from '@hooks/useLocalize'; import useResponsiveLayout from '@hooks/useResponsiveLayout'; import useStyleUtils from '@hooks/useStyleUtils'; import useThemeStyles from '@hooks/useThemeStyles'; @@ -28,6 +29,7 @@ const logoComponents = { function ExpensifyWordmark({style}: ExpensifyWordmarkProps) { const styles = useThemeStyles(); const StyleUtils = useStyleUtils(); + const {translate} = useLocalize(); const {environment} = useEnvironment(); // PascalCase is required for React components, so capitalize the const here const LogoComponent = logoComponents[environment]; @@ -36,6 +38,9 @@ function ExpensifyWordmark({style}: ExpensifyWordmarkProps) { return ( diff --git a/src/components/QRCode.tsx b/src/components/QRCode.tsx index 712d24112a5f..3e30492799c2 100644 --- a/src/components/QRCode.tsx +++ b/src/components/QRCode.tsx @@ -1,5 +1,6 @@ import React from 'react'; import type {ImageSourcePropType} from 'react-native'; +import {View} from 'react-native'; import QRCodeLibrary from 'react-native-qrcode-svg'; import type {Svg, SvgProps} from 'react-native-svg'; import useTheme from '@hooks/useTheme'; @@ -55,6 +56,9 @@ type QRCodeProps = { * methods */ getRef?: (ref: Svg) => Svg; + + /** Accessibility label for the QR code image */ + accessibilityLabel?: string; }; function QRCode({ @@ -69,24 +73,32 @@ function QRCode({ backgroundColor, logoRatio = CONST.QR.DEFAULT_LOGO_SIZE_RATIO, logoMarginRatio = CONST.QR.DEFAULT_LOGO_MARGIN_RATIO, + accessibilityLabel, }: QRCodeProps) { const theme = useTheme(); return ( - + + + ); } diff --git a/src/components/QRShare/index.tsx b/src/components/QRShare/index.tsx index 20779450cc83..cfd681940485 100644 --- a/src/components/QRShare/index.tsx +++ b/src/components/QRShare/index.tsx @@ -36,7 +36,7 @@ function QRShare({ const qrCodeContainerWidth = shouldUseNarrowLayout ? windowWidth : variables.sideBarWidth; const icons = useMemoizedLazyExpensifyIcons(['ExpensifyWordmark']); - const {formatPhoneNumber} = useLocalize(); + const {formatPhoneNumber, translate} = useLocalize(); const [qrCodeSize, setQrCodeSize] = useState(qrCodeContainerWidth - styles.ph5.paddingHorizontal * 2 - variables.qrShareHorizontalPadding * 2); const svgRef = useRef(undefined); @@ -79,6 +79,7 @@ function QRShare({ size={size ?? qrCodeSize} logoRatio={logoRatio} logoMarginRatio={logoMarginRatio} + accessibilityLabel={translate('qrCodes.qrCode')} /> {!!title && ( diff --git a/src/components/TextLink.tsx b/src/components/TextLink.tsx index ac6e04e9e521..c923117fb2e5 100644 --- a/src/components/TextLink.tsx +++ b/src/components/TextLink.tsx @@ -72,6 +72,7 @@ function TextLink({href, onPress, children, style, onMouseDown = (event) => even onMouseDown={onMouseDown} ref={ref} suppressHighlighting + accessible // eslint-disable-next-line react/jsx-props-no-spreading {...rest} > diff --git a/src/languages/de.ts b/src/languages/de.ts index e6e196879b7f..89c6ea64d1bd 100644 --- a/src/languages/de.ts +++ b/src/languages/de.ts @@ -576,6 +576,14 @@ const translations: TranslationDeepObject = { week: 'Woche', year: 'Jahr', quarter: 'Quartal', + expensifyLogo: 'Expensify-Logo', + }, + socials: { + podcast: 'Folgen Sie uns auf Podcast', + twitter: 'Folgen Sie uns auf Twitter', + instagram: 'Folgen Sie uns auf Instagram', + facebook: 'Folgen Sie uns auf Facebook', + linkedin: 'Folgen Sie uns auf LinkedIn', }, supportalNoAccess: { title: 'Nicht so schnell', @@ -7446,6 +7454,7 @@ Fordern Sie Spesendetails wie Belege und Beschreibungen an, legen Sie Limits und parentNavigationSummary: ({reportName, workspaceName}: ParentNavigationSummaryParams) => `Von ${reportName}${workspaceName ? `in ${workspaceName}` : ''}`, }, qrCodes: { + qrCode: 'QR-Code', copy: 'URL kopieren', copied: 'Kopiert!', }, diff --git a/src/languages/en.ts b/src/languages/en.ts index 63b67e3d50a9..8202f52af093 100644 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -596,6 +596,14 @@ const translations = { week: 'Week', year: 'Year', quarter: 'Quarter', + expensifyLogo: 'Expensify logo', + }, + socials: { + podcast: 'Follow us on Podcast', + twitter: 'Follow us on Twitter', + instagram: 'Follow us on Instagram', + facebook: 'Follow us on Facebook', + linkedin: 'Follow us on LinkedIn', }, supportalNoAccess: { title: 'Not so fast', @@ -7386,6 +7394,7 @@ const translations = { parentNavigationSummary: ({reportName, workspaceName}: ParentNavigationSummaryParams) => `From ${reportName}${workspaceName ? ` in ${workspaceName}` : ''}`, }, qrCodes: { + qrCode: 'QR code', copy: 'Copy URL', copied: 'Copied!', }, diff --git a/src/languages/es.ts b/src/languages/es.ts index c43fc1e37533..3f88a1925833 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -400,6 +400,14 @@ const translations: TranslationDeepObject = { week: 'Semana', year: 'Año', quarter: 'Trimestre', + expensifyLogo: 'Logo de Expensify', + }, + socials: { + podcast: 'Síguenos en Podcast', + twitter: 'Síguenos en Twitter', + instagram: 'Síguenos en Instagram', + facebook: 'Síguenos en Facebook', + linkedin: 'Síguenos en LinkedIn', }, supportalNoAccess: { title: 'No tan rápido', @@ -7656,6 +7664,7 @@ ${amount} para ${merchant} - ${date}`, parentNavigationSummary: ({reportName, workspaceName}) => `De ${reportName}${workspaceName ? ` en ${workspaceName}` : ''}`, }, qrCodes: { + qrCode: 'Código QR', copy: 'Copiar URL', copied: '¡Copiado!', }, diff --git a/src/languages/fr.ts b/src/languages/fr.ts index de8ee551b715..06bc5955808a 100644 --- a/src/languages/fr.ts +++ b/src/languages/fr.ts @@ -577,6 +577,14 @@ const translations: TranslationDeepObject = { week: 'Semaine', year: 'Année', quarter: 'Trimestre', + expensifyLogo: 'Logo Expensify', + }, + socials: { + podcast: 'Suivez-nous sur Podcast', + twitter: 'Suivez-nous sur Twitter', + instagram: 'Suivez-nous sur Instagram', + facebook: 'Suivez-nous sur Facebook', + linkedin: 'Suivez-nous sur LinkedIn', }, supportalNoAccess: { title: 'Pas si vite', @@ -1767,7 +1775,7 @@ const translations: TranslationDeepObject = { featureRequiresValidate: 'Cette fonctionnalité exige que vous validiez votre compte.', validateAccount: 'Validez votre compte', helpText: ({email}: {email: string}) => - `Ajoutez d’autres moyens de vous connecter et d’envoyer des reçus à Expensify.

Ajoutez une adresse e-mail pour transférer des reçus à ${email} ou ajoutez un numéro de téléphone pour envoyer des reçus par SMS au 47777 (numéros américains uniquement).`, + `Ajoutez d'autres moyens de vous connecter et d'envoyer des reçus à Expensify.

Ajoutez une adresse e-mail pour transférer des reçus à ${email} ou ajoutez un numéro de téléphone pour envoyer des reçus par SMS au 47777 (numéros américains uniquement).`, pleaseVerify: 'Veuillez vérifier cette méthode de contact.', getInTouch: 'Nous utiliserons cette méthode pour vous contacter.', enterMagicCode: (contactMethod: string) => `Veuillez saisir le code magique envoyé à ${contactMethod}. Il devrait arriver d'ici une à deux minutes.`, @@ -7470,6 +7478,7 @@ Rendez obligatoires des informations de dépense comme les reçus et les descrip parentNavigationSummary: ({reportName, workspaceName}: ParentNavigationSummaryParams) => `De ${reportName}${workspaceName ? `dans ${workspaceName}` : ''}`, }, qrCodes: { + qrCode: 'Code QR', copy: 'Copier l’URL', copied: 'Copié !', }, diff --git a/src/languages/it.ts b/src/languages/it.ts index 7500619ebca8..15e4b4c9ad0a 100644 --- a/src/languages/it.ts +++ b/src/languages/it.ts @@ -576,6 +576,14 @@ const translations: TranslationDeepObject = { week: 'Settimana', year: 'Anno', quarter: 'Trimestre', + expensifyLogo: 'Logo Expensify', + }, + socials: { + podcast: 'Seguici su Podcast', + twitter: 'Seguici su Twitter', + instagram: 'Seguici su Instagram', + facebook: 'Seguici su Facebook', + linkedin: 'Seguici su LinkedIn', }, supportalNoAccess: { title: 'Non così in fretta', @@ -7432,6 +7440,7 @@ Richiedi dettagli sulle spese come ricevute e descrizioni, imposta limiti e valo parentNavigationSummary: ({reportName, workspaceName}: ParentNavigationSummaryParams) => `Da ${reportName}${workspaceName ? `in ${workspaceName}` : ''}`, }, qrCodes: { + qrCode: 'Codice QR', copy: 'Copia URL', copied: 'Copiato!', }, diff --git a/src/languages/ja.ts b/src/languages/ja.ts index cf18bfec0d42..e764e7623594 100644 --- a/src/languages/ja.ts +++ b/src/languages/ja.ts @@ -576,6 +576,14 @@ const translations: TranslationDeepObject = { week: '週', year: '年', quarter: '四半期', + expensifyLogo: 'Expensifyロゴ', + }, + socials: { + podcast: 'ポッドキャストでフォロー', + twitter: 'Twitterでフォロー', + instagram: 'Instagramでフォロー', + facebook: 'Facebookでフォロー', + linkedin: 'LinkedInでフォロー', }, supportalNoAccess: { title: 'ちょっと待ってください', @@ -7361,6 +7369,7 @@ ${reportName} parentNavigationSummary: ({reportName, workspaceName}: ParentNavigationSummaryParams) => `${reportName}${workspaceName ? `${workspaceName} の中` : ''} から`, }, qrCodes: { + qrCode: 'QRコード', copy: 'URLをコピー', copied: 'コピーしました!', }, diff --git a/src/languages/nl.ts b/src/languages/nl.ts index bd446b207cef..c64bd21863bf 100644 --- a/src/languages/nl.ts +++ b/src/languages/nl.ts @@ -576,6 +576,14 @@ const translations: TranslationDeepObject = { week: 'Week', year: 'Jaar', quarter: 'Kwartaal', + expensifyLogo: 'Expensify-logo', + }, + socials: { + podcast: 'Volg ons op Podcast', + twitter: 'Volg ons op Twitter', + instagram: 'Volg ons op Instagram', + facebook: 'Volg ons op Facebook', + linkedin: 'Volg ons op LinkedIn', }, supportalNoAccess: { title: 'Niet zo snel', @@ -1756,7 +1764,7 @@ const translations: TranslationDeepObject = { featureRequiresValidate: 'Voor deze functie moet je je account verifiëren.', validateAccount: 'Valideer je account', helpText: ({email}: {email: string}) => - `Voeg meer manieren toe om in te loggen en bonnetjes naar Expensify te sturen.

Voeg een e-mailadres toe om bonnetjes door te sturen naar ${email} of voeg een telefoonnummer toe om bonnetjes te sms’en naar 47777 (alleen voor Amerikaanse nummers).`, + `Voeg meer manieren toe om in te loggen en bonnetjes naar Expensify te sturen.

Voeg een e-mailadres toe om bonnetjes door te sturen naar ${email} of voeg een telefoonnummer toe om bonnetjes te sms'en naar 47777 (alleen voor Amerikaanse nummers).`, pleaseVerify: 'Verifieer deze contactmethode.', getInTouch: 'We gebruiken deze methode om contact met je op te nemen.', enterMagicCode: (contactMethod: string) => `Voer de magische code in die naar ${contactMethod} is verzonden. Deze zou binnen een minuut of twee moeten aankomen.`, @@ -7417,6 +7425,7 @@ Vereis onkostendetails zoals bonnen en beschrijvingen, stel limieten en standaar parentNavigationSummary: ({reportName, workspaceName}: ParentNavigationSummaryParams) => `Van ${reportName}${workspaceName ? `in ${workspaceName}` : ''}`, }, qrCodes: { + qrCode: 'QR-code', copy: 'URL kopiëren', copied: 'Gekopieerd!', }, diff --git a/src/languages/pl.ts b/src/languages/pl.ts index 4cc16d308508..4e151057299e 100644 --- a/src/languages/pl.ts +++ b/src/languages/pl.ts @@ -576,6 +576,14 @@ const translations: TranslationDeepObject = { week: 'Tydzień', year: 'Rok', quarter: 'Kwartał', + expensifyLogo: 'Logo Expensify', + }, + socials: { + podcast: 'Śledź nas na Podcast', + twitter: 'Śledź nas na Twitter', + instagram: 'Śledź nas na Instagram', + facebook: 'Śledź nas na Facebook', + linkedin: 'Śledź nas na LinkedIn', }, supportalNoAccess: { title: 'Nie tak szybko', @@ -7399,6 +7407,7 @@ Wymagaj szczegółów wydatków, takich jak paragony i opisy, ustawiaj limity i parentNavigationSummary: ({reportName, workspaceName}: ParentNavigationSummaryParams) => `Z raportu ${reportName}${workspaceName ? `w ${workspaceName}` : ''}`, }, qrCodes: { + qrCode: 'Kod QR', copy: 'Skopiuj URL', copied: 'Skopiowano!', }, diff --git a/src/languages/pt-BR.ts b/src/languages/pt-BR.ts index 03c08e51ee5e..cdff517ddf27 100644 --- a/src/languages/pt-BR.ts +++ b/src/languages/pt-BR.ts @@ -575,6 +575,14 @@ const translations: TranslationDeepObject = { week: 'Semana', year: 'Ano', quarter: 'Trimestre', + expensifyLogo: 'Logo da Expensify', + }, + socials: { + podcast: 'Siga-nos no Podcast', + twitter: 'Siga-nos no Twitter', + instagram: 'Siga-nos no Instagram', + facebook: 'Siga-nos no Facebook', + linkedin: 'Siga-nos no LinkedIn', }, supportalNoAccess: { title: 'Calma aí', @@ -7402,6 +7410,7 @@ Exija dados de despesas como recibos e descrições, defina limites e padrões e parentNavigationSummary: ({reportName, workspaceName}: ParentNavigationSummaryParams) => `De ${reportName}${workspaceName ? `em ${workspaceName}` : ''}`, }, qrCodes: { + qrCode: 'Código QR', copy: 'Copiar URL', copied: 'Copiado!', }, diff --git a/src/languages/zh-hans.ts b/src/languages/zh-hans.ts index d465f940236f..08535dfd7ce8 100644 --- a/src/languages/zh-hans.ts +++ b/src/languages/zh-hans.ts @@ -572,6 +572,14 @@ const translations: TranslationDeepObject = { week: '周', year: '年份', quarter: '季度', + expensifyLogo: 'Expensify徽标', + }, + socials: { + podcast: '在播客上关注我们', + twitter: '在Twitter上关注我们', + instagram: '在Instagram上关注我们', + facebook: '在Facebook上关注我们', + linkedin: '在LinkedIn上关注我们', }, supportalNoAccess: { title: '先别急', @@ -7256,6 +7264,7 @@ ${reportName} parentNavigationSummary: ({reportName, workspaceName}: ParentNavigationSummaryParams) => `来自${reportName}${workspaceName ? `在 ${workspaceName} 中` : ''}`, }, qrCodes: { + qrCode: '二维码', copy: '复制 URL', copied: '已复制!', }, diff --git a/src/pages/signin/Socials.tsx b/src/pages/signin/Socials.tsx index 2ccfbfb72a23..7b09a6fd9a6a 100644 --- a/src/pages/signin/Socials.tsx +++ b/src/pages/signin/Socials.tsx @@ -4,43 +4,52 @@ import Icon from '@components/Icon'; import type {ExpensifyIconName} from '@components/Icon/ExpensifyIconLoader'; import PressableWithoutFeedback from '@components/Pressable/PressableWithoutFeedback'; import {useMemoizedLazyExpensifyIcons} from '@hooks/useLazyAsset'; +import useLocalize from '@hooks/useLocalize'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import {openExternalLink} from '@libs/actions/Link'; import variables from '@styles/variables'; import CONST from '@src/CONST'; +import type {TranslationPaths} from '@src/languages/types'; type Social = { iconURL: Extract; link: string; + label: TranslationPaths; }; const socialList: Social[] = [ { iconURL: 'Podcast', link: CONST.SOCIALS.PODCAST, + label: 'socials.podcast', }, { iconURL: 'Twitter', link: CONST.SOCIALS.TWITTER, + label: 'socials.twitter', }, { iconURL: 'Instagram', link: CONST.SOCIALS.INSTAGRAM, + label: 'socials.instagram', }, { iconURL: 'Facebook', link: CONST.SOCIALS.FACEBOOK, + label: 'socials.facebook', }, { iconURL: 'Linkedin', link: CONST.SOCIALS.LINKEDIN, + label: 'socials.linkedin', }, ]; function Socials() { const theme = useTheme(); const styles = useThemeStyles(); + const {translate} = useLocalize(); const icons = useMemoizedLazyExpensifyIcons(['Podcast', 'Twitter', 'Instagram', 'Facebook', 'Linkedin']); return ( @@ -53,7 +62,9 @@ function Socials() { e?.preventDefault(); openExternalLink(social.link); }} - accessibilityLabel={social.iconURL} + accessible + accessibilityRole={CONST.ROLE.LINK} + accessibilityLabel={translate(social.label)} style={[styles.mr1, styles.mt1]} shouldUseAutoHitSlop={false} sentryLabel={`${CONST.SENTRY_LABEL.SOCIALS.LINK}-${social.iconURL}`} diff --git a/src/pages/workspace/WorkspacesListPage.tsx b/src/pages/workspace/WorkspacesListPage.tsx index 74c7b9ad8481..001afefa82d4 100755 --- a/src/pages/workspace/WorkspacesListPage.tsx +++ b/src/pages/workspace/WorkspacesListPage.tsx @@ -55,7 +55,6 @@ import { getConnectionExporters, getPolicyBrickRoadIndicatorStatus, getUberConnectionErrorDirectlyFromPolicy, - getUserFriendlyWorkspaceType, isPendingDeletePolicy, isPolicyAdmin, isPolicyAuditor, @@ -439,17 +438,6 @@ function WorkspacesListPage() { }); } - const ownerDisplayName = personalDetails?.[item.ownerAccountID ?? CONST.DEFAULT_NUMBER_ID]?.displayName ?? ''; - const workspaceType = item.type ? getUserFriendlyWorkspaceType(item.type, translate) : ''; - const accessibilityLabel = [ - `${translate('workspace.common.workspace')}: ${item.title}`, - isDefault ? translate('common.default') : '', - `${translate('workspace.common.workspaceOwner')}: ${ownerDisplayName}`, - `${translate('workspace.common.workspaceType')}: ${workspaceType}`, - ] - .filter(Boolean) - .join(', '); - return ( + {/* accessible={false} allows child elements (workspace row and 3-dot menu) to be individually focusable. + onPress is handled by WorkspacesListRow to prevent double-firing on web due to event bubbling. */} {({hovered}) => ( @@ -488,6 +476,7 @@ function WorkspacesListPage() { isLoadingBill={isLoadingBill} resetLoadingSpinnerIconIndex={resetLoadingSpinnerIconIndex} isHovered={hovered} + onPress={item.action} /> )} diff --git a/src/pages/workspace/WorkspacesListRow.tsx b/src/pages/workspace/WorkspacesListRow.tsx index 30bf3330ea74..853bb20ed4a4 100644 --- a/src/pages/workspace/WorkspacesListRow.tsx +++ b/src/pages/workspace/WorkspacesListRow.tsx @@ -10,6 +10,7 @@ import Badge from '@components/Badge'; import Icon from '@components/Icon'; import * as Expensicons from '@components/Icon/Expensicons'; import type {PopoverMenuItem} from '@components/PopoverMenu'; +import PressableWithoutFeedback from '@components/Pressable/PressableWithoutFeedback'; import Text from '@components/Text'; import TextWithTooltip from '@components/TextWithTooltip'; import ThreeDotsMenu from '@components/ThreeDotsMenu'; @@ -20,7 +21,6 @@ import WorkspacesListRowDisplayName from '@components/WorkspacesListRowDisplayNa import useAnimatedHighlightStyle from '@hooks/useAnimatedHighlightStyle'; import {useMemoizedLazyExpensifyIcons, useMemoizedLazyIllustrations} from '@hooks/useLazyAsset'; import useLocalize from '@hooks/useLocalize'; -import useResponsiveLayout from '@hooks/useResponsiveLayout'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import {getDisplayNameOrDefault, getPersonalDetailsByIDs} from '@libs/PersonalDetailsUtils'; @@ -85,6 +85,9 @@ type WorkspacesListRowProps = WithCurrentUserPersonalDetailsProps & { /** Whether the list item is hovered */ isHovered?: boolean; + + /** Callback when the row is pressed */ + onPress?: () => void; }; type BrickRoadIndicatorIconProps = { @@ -122,10 +125,10 @@ function WorkspacesListRow({ isLoadingBill, resetLoadingSpinnerIconIndex, isHovered, + onPress, }: WorkspacesListRowProps) { const styles = useThemeStyles(); const {translate} = useLocalize(); - const {shouldUseNarrowLayout} = useResponsiveLayout(); const theme = useTheme(); const isFocused = useIsFocused(); const isNarrow = layoutWidth === CONST.LAYOUT_WIDTH.NARROW; @@ -177,17 +180,15 @@ function WorkspacesListRow({ const isDeleted = style && Array.isArray(style) ? style.includes(styles.offlineFeedbackDeleted) : false; - const ThreeDotMenuOrPendingIcon = ( - + const WorkspaceBadges = ( + {!!isJoinRequestPending && ( - - - + )} {!!isDefault && ( - + )} {!isJoinRequestPending && ( - - - - - + + )} ); + const ownerName = ownerDetails ? getDisplayNameOrDefault(ownerDetails) : ''; + const workspaceTypeName = workspaceType ? getUserFriendlyWorkspaceType(workspaceType, translate) : ''; + const defaultLabel = isDefault ? `, ${translate('common.default')}` : ''; + const pendingLabel = isJoinRequestPending ? `, ${translate('workspace.common.requested')}` : ''; + const accessibilityLabel = `${title}${ownerName ? `, ${ownerName}` : ''}${workspaceTypeName ? `, ${workspaceTypeName}` : ''}${defaultLabel}${pendingLabel}`; + return ( - + - {isNarrow && ThreeDotMenuOrPendingIcon} + {isNarrow && WorkspaceBadges} {!!ownerDetails && ( @@ -260,7 +262,7 @@ function WorkspacesListRow({ - {getUserFriendlyWorkspaceType(workspaceType, translate)} + {workspaceTypeName} )} - - - - {!isNarrow && ThreeDotMenuOrPendingIcon} + {!isNarrow && WorkspaceBadges} + + + + {!isJoinRequestPending && ( + + )} {!isNarrow && (