diff --git a/packages/desktop-client/src/components/FatalError.tsx b/packages/desktop-client/src/components/FatalError.tsx index 663fd6abb4d47162ea2fbe256b1bc786f005ede2..682ba97aa242cdd73c840fa071c980363154131a 100644 --- a/packages/desktop-client/src/components/FatalError.tsx +++ b/packages/desktop-client/src/components/FatalError.tsx @@ -1,4 +1,5 @@ import React, { useState, type ReactNode } from 'react'; +import { useTranslation, Trans } from 'react-i18next'; import { LazyLoadFailedError } from 'loot-core/src/shared/errors'; @@ -32,10 +33,12 @@ function RenderSimple({ error }: RenderSimpleProps) { // IndexedDB wasn't able to open the database msg = ( <Text> - Your browser doesn’t support IndexedDB in this environment, a feature - that Actual requires to run. This might happen if you are in private - browsing mode. Please try a different browser or turn off private - browsing. + <Trans> + Your browser doesn’t support IndexedDB in this environment, a feature + that Actual requires to run. This might happen if you are in private + browsing mode. Please try a different browser or turn off private + browsing. + </Trans> </Text> ); } else if ( @@ -45,18 +48,20 @@ function RenderSimple({ error }: RenderSimpleProps) { // SharedArrayBuffer isn't available msg = ( <Text> - Actual requires access to <code>SharedArrayBuffer</code> in order to - function properly. If you’re seeing this error, either your browser does - not support <code>SharedArrayBuffer</code>, or your server is not - sending the appropriate headers, or you are not using HTTPS. See{' '} - <Link - variant="external" - linkColor="muted" - to="https://actualbudget.org/docs/troubleshooting/shared-array-buffer" - > - our troubleshooting documentation - </Link>{' '} - to learn more. <SharedArrayBufferOverride /> + <Trans> + Actual requires access to <code>SharedArrayBuffer</code> in order to + function properly. If you’re seeing this error, either your browser + does not support <code>SharedArrayBuffer</code>, or your server is not + sending the appropriate headers, or you are not using HTTPS. See{' '} + <Link + variant="external" + linkColor="muted" + to="https://actualbudget.org/docs/troubleshooting/shared-array-buffer" + > + our troubleshooting documentation + </Link>{' '} + to learn more. <SharedArrayBufferOverride /> + </Trans> </Text> ); } else { @@ -64,7 +69,11 @@ function RenderSimple({ error }: RenderSimpleProps) { // user something at least so they aren't looking at a blank // screen msg = ( - <Text>There was a problem loading the app in this browser version.</Text> + <Text> + <Trans> + There was a problem loading the app in this browser version. + </Trans> + </Text> ); } @@ -78,15 +87,17 @@ function RenderSimple({ error }: RenderSimpleProps) { > <Text>{msg}</Text> <Text> - Please get{' '} - <Link - variant="external" - linkColor="muted" - to="https://actualbudget.org/contact" - > - in touch - </Link>{' '} - for support + <Trans> + Please get{' '} + <Link + variant="external" + linkColor="muted" + to="https://actualbudget.org/contact" + > + in touch + </Link>{' '} + for support + </Trans> </Text> </Stack> ); @@ -102,10 +113,12 @@ function RenderLazyLoadError() { }} > <Text> - There was a problem loading one of the chunks of the application. Please - reload the page and try again. If the issue persists - there might be an - issue with either your internet connection and/or the server where the - app is hosted. + <Trans> + There was a problem loading one of the chunks of the application. + Please reload the page and try again. If the issue persists - there + might be an issue with either your internet connection and/or the + server where the app is hosted. + </Trans> </Text> </Stack> ); @@ -114,13 +127,17 @@ function RenderLazyLoadError() { function RenderUIError() { return ( <> - <Paragraph>There was an unrecoverable error in the UI. Sorry!</Paragraph> <Paragraph> - If this error persists, please get{' '} - <Link variant="external" to="https://actualbudget.org/contact"> - in touch - </Link>{' '} - so it can be investigated. + <Trans>There was an unrecoverable error in the UI. Sorry!</Trans> + </Paragraph> + <Paragraph> + <Trans> + If this error persists, please get{' '} + <Link variant="external" to="https://actualbudget.org/contact"> + in touch + </Link>{' '} + so it can be investigated. + </Trans> </Paragraph> </> ); @@ -133,11 +150,13 @@ function SharedArrayBufferOverride() { return expanded ? ( <> <Paragraph style={{ marginTop: 10 }}> - Actual uses <code>SharedArrayBuffer</code> to allow usage from multiple - tabs at once and to ensure correct behavior when switching files. While - it can run without access to <code>SharedArrayBuffer</code>, you may - encounter data loss or notice multiple budget files being merged with - each other. + <Trans> + Actual uses <code>SharedArrayBuffer</code> to allow usage from + multiple tabs at once and to ensure correct behavior when switching + files. While it can run without access to + <code>SharedArrayBuffer</code>, you may encounter data loss or notice + multiple budget files being merged with each other. + </Trans> </Paragraph> <label style={{ display: 'flex', alignItems: 'center', marginBottom: 10 }} @@ -146,7 +165,9 @@ function SharedArrayBufferOverride() { checked={understand} onChange={() => setUnderstand(!understand)} />{' '} - I understand the risks, run Actual in the unsupported fallback mode + <Trans> + I understand the risks, run Actual in the unsupported fallback mode + </Trans> </label> <Button isDisabled={!understand} @@ -155,7 +176,7 @@ function SharedArrayBufferOverride() { window.location.reload(); }} > - Open Actual + <Trans>Open Actual</Trans> </Button> </> ) : ( @@ -164,12 +185,14 @@ function SharedArrayBufferOverride() { onClick={() => setExpanded(true)} style={{ marginLeft: 5 }} > - Advanced options + <Trans>Advanced options</Trans> </Link> ); } export function FatalError({ error }: FatalErrorProps) { + const { t } = useTranslation(); + const [showError, setShowError] = useState(false); const showSimpleRender = 'type' in error && error.type === 'app-init-failure'; @@ -177,7 +200,9 @@ export function FatalError({ error }: FatalErrorProps) { return ( <Modal name="fatal-error" isDismissable={false}> - <ModalHeader title={isLazyLoadError ? 'Loading Error' : 'Fatal Error'} /> + <ModalHeader + title={isLazyLoadError ? t('Loading Error') : t('Fatal Error')} + /> <View style={{ maxWidth: 500, @@ -192,11 +217,13 @@ export function FatalError({ error }: FatalErrorProps) { )} <Paragraph> - <Button onPress={() => window.Actual?.relaunch()}>Restart app</Button> + <Button onPress={() => window.Actual?.relaunch()}> + <Trans>Restart app</Trans> + </Button> </Paragraph> <Paragraph isLast={true} style={{ fontSize: 11 }}> <Link variant="text" onClick={() => setShowError(state => !state)}> - Show Error + <Trans>Show Error</Trans> </Link> {showError && ( <Block diff --git a/packages/desktop-client/src/components/LoggedInUser.tsx b/packages/desktop-client/src/components/LoggedInUser.tsx index 0df0d616f082e7f692c75d45e0210e3e487c1b22..0db60ec4c5ba0a02541140fee746e8089bd6bae2 100644 --- a/packages/desktop-client/src/components/LoggedInUser.tsx +++ b/packages/desktop-client/src/components/LoggedInUser.tsx @@ -1,5 +1,6 @@ // @ts-strict-ignore import React, { useState, useEffect, useRef } from 'react'; +import { Trans, useTranslation } from 'react-i18next'; import { useSelector } from 'react-redux'; import { type State } from 'loot-core/src/client/state-types'; @@ -24,6 +25,8 @@ export function LoggedInUser({ style, color, }: LoggedInUserProps) { + const { t } = useTranslation(); + const userData = useSelector((state: State) => state.user.data); const { getUserData, signOut, closeBudget } = useActions(); const [loading, setLoading] = useState(true); @@ -64,14 +67,14 @@ export function LoggedInUser({ function serverMessage() { if (!serverUrl) { - return 'No server'; + return t('No server'); } if (userData?.offline) { - return 'Server offline'; + return t('Server offline'); } - return 'Server online'; + return t('Server online'); } if (hideIfNoServer && !serverUrl) { @@ -88,7 +91,7 @@ export function LoggedInUser({ ...style, }} > - Connecting... + <Trans>Connecting...</Trans> </Text> ); } @@ -115,12 +118,14 @@ export function LoggedInUser({ serverUrl && !userData?.offline && { name: 'change-password', - text: 'Change password', + text: t('Change password'), }, - serverUrl && { name: 'sign-out', text: 'Sign out' }, + serverUrl && { name: 'sign-out', text: t('Sign out') }, { name: 'config-server', - text: serverUrl ? 'Change server URL' : 'Start using a server', + text: serverUrl + ? t('Change server URL') + : t('Start using a server'), }, ]} /> diff --git a/packages/desktop-client/src/components/gocardless/GoCardlessLink.tsx b/packages/desktop-client/src/components/gocardless/GoCardlessLink.tsx index a40f31ef01f1be8b941e3711637d755334a9c24e..2a3312242e90336115bb9faea06c0f3866fc32e2 100644 --- a/packages/desktop-client/src/components/gocardless/GoCardlessLink.tsx +++ b/packages/desktop-client/src/components/gocardless/GoCardlessLink.tsx @@ -1,4 +1,5 @@ import React from 'react'; +import { Trans } from 'react-i18next'; import { Modal, ModalHeader } from '../common/Modal'; import { Paragraph } from '../common/Paragraph'; @@ -10,10 +11,14 @@ export function GoCardlessLink() { <Modal name="gocardless-link" isDismissable={false}> <ModalHeader title="Account sync" /> <View style={{ maxWidth: 500 }}> - <Paragraph>Please wait...</Paragraph> <Paragraph> - The window should close automatically. If nothing happened you can - close this window or tab. + <Trans>Please wait...</Trans> + </Paragraph> + <Paragraph> + <Trans> + The window should close automatically. If nothing happened you can + close this window or tab. + </Trans> </Paragraph> </View> </Modal> diff --git a/packages/desktop-client/src/components/manager/BudgetList.tsx b/packages/desktop-client/src/components/manager/BudgetList.tsx index d9e7a3fd1a9d3d739a02612bc61d191c18985d4c..c12700fa9a4894420a8ea6e63cb23875b18b36a3 100644 --- a/packages/desktop-client/src/components/manager/BudgetList.tsx +++ b/packages/desktop-client/src/components/manager/BudgetList.tsx @@ -1,4 +1,5 @@ import React, { useState, useRef, type CSSProperties } from 'react'; +import { Trans, useTranslation } from 'react-i18next'; import { useDispatch, useSelector } from 'react-redux'; import { @@ -38,19 +39,19 @@ import { Popover } from '../common/Popover'; import { Text } from '../common/Text'; import { View } from '../common/View'; -function getFileDescription(file: File) { +function getFileDescription(file: File, t: (key: string) => string) { if (file.state === 'unknown') { - return ( + return t( 'This is a cloud-based file but its state is unknown because you ' + - 'are offline.' + 'are offline.', ); } if (file.encryptKeyId) { if (file.hasKey) { - return 'This file is encrypted and you have key to access it.'; + return t('This file is encrypted and you have key to access it.'); } - return 'This file is encrypted and you do not have the key for it.'; + return t('This file is encrypted and you do not have the key for it.'); } return null; @@ -74,7 +75,9 @@ function FileMenu({ } } - const items = [{ name: 'delete', text: 'Delete' }]; + const { t } = useTranslation(); + + const items = [{ name: 'delete', text: t('Delete') }]; const { isNarrowWidth } = useResponsive(); const defaultMenuItemStyle = isNarrowWidth @@ -124,6 +127,8 @@ function FileMenuButton({ onDelete }: { onDelete: () => void }) { } function FileState({ file }: { file: File }) { + const { t } = useTranslation(); + let Icon; let status; let color; @@ -131,21 +136,21 @@ function FileState({ file }: { file: File }) { switch (file.state) { case 'unknown': Icon = SvgCloudUnknown; - status = 'Network unavailable'; + status = t('Network unavailable'); color = theme.buttonNormalDisabledText; break; case 'remote': Icon = SvgCloudDownload; - status = 'Available for download'; + status = t('Available for download'); break; case 'local': case 'broken': Icon = SvgFileDouble; - status = 'Local'; + status = t('Local'); break; default: Icon = SvgCloudCheck; - status = 'Syncing'; + status = t('Syncing'); break; } @@ -182,6 +187,8 @@ function FileItem({ onSelect: (file: File) => void; onDelete: (file: File) => void; }) { + const { t } = useTranslation(); + const selecting = useRef(false); async function _onSelect(file: File) { @@ -197,7 +204,7 @@ function FileItem({ return ( <View onClick={() => _onSelect(file)} - title={getFileDescription(file) || ''} + title={getFileDescription(file, t) || ''} style={{ flexDirection: 'row', justifyContent: 'space-between', @@ -277,7 +284,7 @@ function BudgetFiles({ color: theme.pageTextSubdued, }} > - No budget files + <Trans>No budget files</Trans> </Text> ) : ( files.map(file => ( @@ -343,7 +350,7 @@ function BudgetListHeader({ ...styles.veryLargeText, }} > - Files + <Trans>Files</Trans> </Text> {!quickSwitchMode && <RefreshButton onRefresh={onRefresh} />} </View> @@ -452,7 +459,7 @@ export function BudgetList({ showHeader = true, quickSwitchMode = false }) { dispatch(pushModal('import')); }} > - Import file + <Trans>Import file</Trans> </Button> <Button @@ -463,7 +470,7 @@ export function BudgetList({ showHeader = true, quickSwitchMode = false }) { marginLeft: 10, }} > - Create new file + <Trans>Create new file</Trans> </Button> {isNonProductionEnvironment() && ( @@ -475,7 +482,7 @@ export function BudgetList({ showHeader = true, quickSwitchMode = false }) { marginLeft: 10, }} > - Create test file + <Trans>Create test file</Trans> </Button> )} </View> diff --git a/packages/desktop-client/src/components/modals/GoCardlessExternalMsgModal.tsx b/packages/desktop-client/src/components/modals/GoCardlessExternalMsgModal.tsx index 51b2c4af16ff1eacc7941ca19a19d174d895540f..37d4913f224e7e04ec6da470cfeea07ae441dba2 100644 --- a/packages/desktop-client/src/components/modals/GoCardlessExternalMsgModal.tsx +++ b/packages/desktop-client/src/components/modals/GoCardlessExternalMsgModal.tsx @@ -1,5 +1,6 @@ // @ts-strict-ignore import React, { useEffect, useState, useRef } from 'react'; +import { Trans, useTranslation } from 'react-i18next'; import { useDispatch } from 'react-redux'; import { pushModal } from 'loot-core/src/client/actions/modals'; @@ -61,12 +62,12 @@ function useAvailableBanks(country: string) { }; } -function renderError(error: 'unknown' | 'timeout') { +function renderError(error: 'unknown' | 'timeout', t: (key: string) => string) { return ( <Error style={{ alignSelf: 'center' }}> {error === 'timeout' - ? 'Timed out. Please try again.' - : 'An error occurred while linking your account, sorry!'} + ? t('Timed out. Please try again.') + : t('An error occurred while linking your account, sorry!')} </Error> ); } @@ -84,6 +85,8 @@ export function GoCardlessExternalMsgModal({ onSuccess, onClose, }: GoCardlessExternalMsgProps) { + const { t } = useTranslation(); + const dispatch = useDispatch(); const [waiting, setWaiting] = useState<string | null>(null); @@ -140,37 +143,45 @@ export function GoCardlessExternalMsgModal({ return ( <View style={{ gap: 10 }}> <FormField> - <FormLabel title="Choose your country:" htmlFor="country-field" /> + <FormLabel + title={t('Choose your country:')} + htmlFor="country-field" + /> <Autocomplete strict highlightFirst suggestions={COUNTRY_OPTIONS} onSelect={setCountry} value={country} - inputProps={{ id: 'country-field', placeholder: '(please select)' }} + inputProps={{ + id: 'country-field', + placeholder: t('(please select)'), + }} /> </FormField> {isBankOptionError ? ( <Error> - Failed loading available banks: GoCardless access credentials might - be misconfigured. Please{' '} - <Link - variant="text" - onClick={onGoCardlessInit} - style={{ color: theme.formLabelText, display: 'inline' }} - > - set them up - </Link>{' '} - again. + <Trans> + Failed loading available banks: GoCardless access credentials + might be misconfigured. Please{' '} + <Link + variant="text" + onClick={onGoCardlessInit} + style={{ color: theme.formLabelText, display: 'inline' }} + > + set them up + </Link>{' '} + again. + </Trans> </Error> ) : ( country && (isBankOptionsLoading ? ( - 'Loading banks...' + t('Loading banks...') ) : ( <FormField> - <FormLabel title="Choose your bank:" htmlFor="bank-field" /> + <FormLabel title={t('Choose your bank:')} htmlFor="bank-field" /> <Autocomplete focused strict @@ -180,7 +191,7 @@ export function GoCardlessExternalMsgModal({ value={institutionId} inputProps={{ id: 'bank-field', - placeholder: '(please select)', + placeholder: t('(please select)'), }} /> </FormField> @@ -188,18 +199,20 @@ export function GoCardlessExternalMsgModal({ )} <Warning> - By enabling bank-sync, you will be granting GoCardless (a third party - service) read-only access to your entire account’s transaction - history. This service is not affiliated with Actual in any way. Make - sure you’ve read and understand GoCardless’s{' '} - <Link - variant="external" - to="https://gocardless.com/privacy/" - linkColor="purple" - > - Privacy Policy - </Link>{' '} - before proceeding. + <Trans> + By enabling bank-sync, you will be granting GoCardless (a third + party service) read-only access to your entire account’s transaction + history. This service is not affiliated with Actual in any way. Make + sure you’ve read and understand GoCardless’s{' '} + <Link + variant="external" + to="https://gocardless.com/privacy/" + linkColor="purple" + > + Privacy Policy + </Link>{' '} + before proceeding. + </Trans> </Warning> <View style={{ flexDirection: 'row', gap: 10, alignItems: 'center' }}> @@ -215,7 +228,7 @@ export function GoCardlessExternalMsgModal({ onPress={onJump} isDisabled={!institutionId || !country} > - Link bank in browser → + <Trans>Link bank in browser →</Trans> </Button> </View> </View> @@ -231,17 +244,19 @@ export function GoCardlessExternalMsgModal({ {({ state: { close } }) => ( <> <ModalHeader - title="Link Your Bank" + title={t('Link Your Bank')} rightContent={<ModalCloseButton onPress={close} />} /> <View> <Paragraph style={{ fontSize: 15 }}> - To link your bank account, you will be redirected to a new page - where GoCardless will ask to connect to your bank. GoCardless will - not be able to withdraw funds from your accounts. + <Trans> + To link your bank account, you will be redirected to a new page + where GoCardless will ask to connect to your bank. GoCardless + will not be able to withdraw funds from your accounts. + </Trans> </Paragraph> - {error && renderError(error)} + {error && renderError(error, t)} {waiting || isConfigurationLoading ? ( <View style={{ alignItems: 'center', marginTop: 15 }}> @@ -251,11 +266,11 @@ export function GoCardlessExternalMsgModal({ /> <View style={{ marginTop: 10, color: theme.pageText }}> {isConfigurationLoading - ? 'Checking GoCardless configuration..' + ? t('Checking GoCardless configuration..') : waiting === 'browser' - ? 'Waiting on GoCardless...' + ? t('Waiting on GoCardless...') : waiting === 'accounts' - ? 'Loading accounts...' + ? t('Loading accounts...') : null} </View> @@ -265,7 +280,9 @@ export function GoCardlessExternalMsgModal({ onClick={onJump} style={{ marginTop: 10 }} > - (Account linking not opening in a new tab? Click here) + <Trans> + (Account linking not opening in a new tab? Click here) + </Trans> </Link> )} </View> @@ -281,17 +298,19 @@ export function GoCardlessExternalMsgModal({ }} onPress={onContinue} > - Success! Click to continue → + <Trans>Success! Click to continue →</Trans> </Button> ) : isConfigured || isGoCardlessSetupComplete ? ( renderLinkButton() ) : ( <> <Paragraph style={{ color: theme.errorText }}> - GoCardless integration has not yet been configured. + <Trans> + GoCardless integration has not yet been configured. + </Trans> </Paragraph> <Button variant="primary" onPress={onGoCardlessInit}> - Configure GoCardless integration + <Trans>Configure GoCardless integration</Trans> </Button> </> )} diff --git a/upcoming-release-notes/3430.md b/upcoming-release-notes/3430.md new file mode 100644 index 0000000000000000000000000000000000000000..941288195368c3645d2a970a9384fe3fc2c666c2 --- /dev/null +++ b/upcoming-release-notes/3430.md @@ -0,0 +1,6 @@ +--- +category: Maintenance +authors: [a-gradina] +--- + +Support translations for component files