diff --git a/services/web/.eslintrc b/services/web/.eslintrc index 6638f6f2b5..09898181af 100644 --- a/services/web/.eslintrc +++ b/services/web/.eslintrc @@ -114,7 +114,7 @@ // "files": ["**/frontend/js/**/components/**/*.{js,jsx,ts,tsx}", "**/frontend/js/**/hooks/**/*.{js,jsx,ts,tsx}"], "rules": { - "@overleaf/no-empty-trans": "off", + "@overleaf/no-empty-trans": "error", "@overleaf/should-unescape-trans": "error", // https://astexplorer.net/ diff --git a/services/web/frontend/extracted-translations.json b/services/web/frontend/extracted-translations.json index 8608aa7c4d..624fa7e7b4 100644 --- a/services/web/frontend/extracted-translations.json +++ b/services/web/frontend/extracted-translations.json @@ -92,7 +92,7 @@ "back_to_subscription": "", "back_to_your_projects": "", "beta_program_already_participating": "", - "beta_program_benefits": "<0>", + "beta_program_benefits": "", "beta_program_not_participating": "", "binary_history_error": "", "blank_project": "", @@ -288,7 +288,7 @@ "dropbox_sync_now_rate_limited": "", "dropbox_sync_now_running": "", "dropbox_sync_out": "", - "dropbox_sync_troubleshoot": "<0>", + "dropbox_sync_troubleshoot": "", "dropbox_synced": "", "dropbox_unlinked_premium_feature": "", "duplicate_file": "", @@ -305,7 +305,7 @@ "edit_tag": "", "editing": "", "editing_captions": "", - "editor_and_pdf": "&", + "editor_and_pdf": "", "editor_disconected_click_to_reconnect": "", "editor_only_hide_pdf": "", "editor_theme": "", @@ -567,7 +567,7 @@ "labels_help_you_to_easily_reference_your_figures": "", "labels_help_you_to_reference_your_tables": "", "labs_program_already_participating": "", - "labs_program_benefits": "<0>", + "labs_program_benefits": "", "labs_program_not_participating": "", "large_or_high-resolution_images_taking_too_long": "", "last_active": "", @@ -1050,7 +1050,7 @@ "something_went_wrong_loading_pdf_viewer": "", "something_went_wrong_processing_the_request": "", "something_went_wrong_rendering_pdf": "", - "something_went_wrong_rendering_pdf_expected": "<0>", + "something_went_wrong_rendering_pdf_expected": "", "something_went_wrong_server": "", "somthing_went_wrong_compiling": "", "sorry_your_table_cant_be_displayed_at_the_moment": "", diff --git a/services/web/frontend/js/features/editor-navigation-toolbar/components/layout-dropdown-button.jsx b/services/web/frontend/js/features/editor-navigation-toolbar/components/layout-dropdown-button.jsx index 1e24a50ae8..e253c15c74 100644 --- a/services/web/frontend/js/features/editor-navigation-toolbar/components/layout-dropdown-button.jsx +++ b/services/web/frontend/js/features/editor-navigation-toolbar/components/layout-dropdown-button.jsx @@ -171,7 +171,7 @@ function LayoutDropdownButton() { /> } icon={} - text={&} + text={t('editor_and_pdf')} /> - + {t('files_cannot_include_invalid_characters')} )} @@ -77,6 +77,8 @@ FileTreeCreateNameInput.propTypes = { } function ErrorMessage({ error }) { + const { t } = useTranslation() + // if (typeof error === 'string') { // return error // } @@ -85,21 +87,21 @@ function ErrorMessage({ error }) { case DuplicateFilenameError: return ( - + {t('file_already_exists')} ) case InvalidFilenameError: return ( - + {t('files_cannot_include_invalid_characters')} ) case BlockedFilenameError: return ( - + {t('blocked_filename')} ) diff --git a/services/web/frontend/js/features/pdf-preview/components/compile-timeout-messages.tsx b/services/web/frontend/js/features/pdf-preview/components/compile-timeout-messages.tsx index 9735c3a34a..331ec0bf65 100644 --- a/services/web/frontend/js/features/pdf-preview/components/compile-timeout-messages.tsx +++ b/services/web/frontend/js/features/pdf-preview/components/compile-timeout-messages.tsx @@ -154,21 +154,15 @@ function CompileTimeoutMessages() { content={
- - - + {t('your_project_near_compile_timeout_limit')}
{showNewCompileTimeoutUI === 'active' ? ( <> - - - + {t('upgrade_for_12x_more_compile_time')} {'. '} ) : ( - - - + {t('upgrade_for_plenty_more_compile_time')} )}
} @@ -204,7 +198,7 @@ function CompileTimeoutMessages() { shouldUnescape tOptions={{ interpolation: { escapeValue: true } }} />{' '} - + {t('and_you_can_upgrade_for_plenty_more_compile_time')}

} @@ -229,7 +223,9 @@ function CompileTimeoutMessages() { />

- + {t( + 'tell_the_project_owner_to_upgrade_plan_for_more_compile_time' + )}

} diff --git a/services/web/frontend/js/features/pdf-preview/components/pdf-preview-error.jsx b/services/web/frontend/js/features/pdf-preview/components/pdf-preview-error.jsx index e41e8b1489..9a0dcd4064 100644 --- a/services/web/frontend/js/features/pdf-preview/components/pdf-preview-error.jsx +++ b/services/web/frontend/js/features/pdf-preview/components/pdf-preview-error.jsx @@ -19,13 +19,17 @@ function PdfPreviewError({ error }) { headerTitle={t('pdf_rendering_error')} formattedContent={ <> - - )}

{startedFreeTrial && ( -

- -

+

{t('refresh_page_after_starting_free_trial')}

)} ) diff --git a/services/web/frontend/js/features/share-project-modal/components/add-collaborators.jsx b/services/web/frontend/js/features/share-project-modal/components/add-collaborators.jsx index b99859b5e6..822d2cf6f3 100644 --- a/services/web/frontend/js/features/share-project-modal/components/add-collaborators.jsx +++ b/services/web/frontend/js/features/share-project-modal/components/add-collaborators.jsx @@ -1,5 +1,5 @@ import { useState, useMemo } from 'react' -import { useTranslation, Trans } from 'react-i18next' +import { useTranslation } from 'react-i18next' import { Form, FormGroup, FormControl, Button } from 'react-bootstrap' import { useMultipleSelection } from 'downshift' import { useShareProjectContext } from './share-project-modal' @@ -152,7 +152,7 @@ export default function AddCollaborators() {    diff --git a/services/web/frontend/js/features/share-project-modal/components/edit-member.jsx b/services/web/frontend/js/features/share-project-modal/components/edit-member.jsx index 205d55c7a6..354db93ba0 100644 --- a/services/web/frontend/js/features/share-project-modal/components/edit-member.jsx +++ b/services/web/frontend/js/features/share-project-modal/components/edit-member.jsx @@ -1,6 +1,6 @@ import { useState, useEffect } from 'react' import PropTypes from 'prop-types' -import { Trans, useTranslation } from 'react-i18next' +import { useTranslation } from 'react-i18next' import { useShareProjectContext } from './share-project-modal' import TransferOwnershipModal from './transfer-ownership-modal' import { removeMemberFromProject, updateMember } from '../utils/api' @@ -137,7 +137,7 @@ function RemoveMemberAction({ member }) { } + description={t('remove_collaborator')} overlayProps={{ placement: 'bottom' }} >
- + {t('change_or_cancel-or')}  
diff --git a/services/web/frontend/js/features/share-project-modal/components/invite.jsx b/services/web/frontend/js/features/share-project-modal/components/invite.jsx index 42c1fc8a74..1304f19106 100644 --- a/services/web/frontend/js/features/share-project-modal/components/invite.jsx +++ b/services/web/frontend/js/features/share-project-modal/components/invite.jsx @@ -4,20 +4,21 @@ import { useShareProjectContext } from './share-project-modal' import Icon from '../../../shared/components/icon' import { Button, Col, Row } from 'react-bootstrap' import Tooltip from '../../../shared/components/tooltip' -import { Trans, useTranslation } from 'react-i18next' +import { useTranslation } from 'react-i18next' import MemberPrivileges from './member-privileges' import { resendInvite, revokeInvite } from '../utils/api' import { useProjectContext } from '../../../shared/context/project-context' import { sendMB } from '../../../infrastructure/event-tracking' export default function Invite({ invite, isProjectOwner }) { + const { t } = useTranslation() return (
{invite.email}
- + {t('invite_not_accepted')} .  {isProjectOwner && }
@@ -42,6 +43,7 @@ Invite.propTypes = { } function ResendInvite({ invite }) { + const { t } = useTranslation() const { monitorRequest } = useShareProjectContext() const { _id: projectId } = useProjectContext() @@ -66,7 +68,7 @@ function ResendInvite({ invite }) { onClick={handleClick} // ref={buttonRef} > - + {t('resend')} ) } @@ -99,7 +101,7 @@ function RevokeInvite({ invite }) { return ( } + description={t('revoke_invite')} overlayProps={{ placement: 'bottom' }} >    @@ -117,6 +118,7 @@ PrivateSharing.propTypes = { } function TokenBasedSharing({ setAccessLevel, inflight, canAddCollaborators }) { + const { t } = useTranslation() const { _id: projectId } = useProjectContext() const [tokens, setTokens] = useState(null) @@ -132,9 +134,7 @@ function TokenBasedSharing({ setAccessLevel, inflight, canAddCollaborators }) { return ( - - - + {t('link_sharing_is_on')}      
- - - + {t('anyone_with_link_can_edit')}
- - - + {t('anyone_with_link_can_view')} - {accessLevel === 'readAndWrite' && ( - - )} - {accessLevel === 'readOnly' && ( - - )} + {accessLevel === 'readAndWrite' && t('this_project_is_public')} + {accessLevel === 'readOnly' && t('this_project_is_public_read_only')}       @@ -215,6 +209,7 @@ LegacySharing.propTypes = { } export function ReadOnlyTokenLink() { + const { t } = useTranslation() const { _id: projectId } = useProjectContext() const [tokens, setTokens] = useState(null) @@ -231,9 +226,7 @@ export function ReadOnlyTokenLink() {
- - - + {t('anyone_with_link_can_view')} - - … - + {t('loading')}… ) } @@ -279,10 +271,12 @@ AccessToken.propTypes = { } function LinkSharingInfo() { + const { t } = useTranslation() + return ( } + description={t('learn_more_about_link_sharing')} > + return t('can_edit') case 'readOnly': - return + return t('read_only') default: return null diff --git a/services/web/frontend/js/features/share-project-modal/components/owner-info.jsx b/services/web/frontend/js/features/share-project-modal/components/owner-info.jsx index 13ac8b97e0..c84d0f9200 100644 --- a/services/web/frontend/js/features/share-project-modal/components/owner-info.jsx +++ b/services/web/frontend/js/features/share-project-modal/components/owner-info.jsx @@ -1,15 +1,16 @@ import { useProjectContext } from '../../../shared/context/project-context' import { Col, Row } from 'react-bootstrap' -import { Trans } from 'react-i18next' +import { useTranslation } from 'react-i18next' export default function OwnerInfo() { + const { t } = useTranslation() const { owner } = useProjectContext() return ( {owner?.email} - + {t('owner')} ) diff --git a/services/web/frontend/js/features/share-project-modal/components/select-collaborators.jsx b/services/web/frontend/js/features/share-project-modal/components/select-collaborators.jsx index 1ab008c576..b21546448a 100644 --- a/services/web/frontend/js/features/share-project-modal/components/select-collaborators.jsx +++ b/services/web/frontend/js/features/share-project-modal/components/select-collaborators.jsx @@ -1,6 +1,6 @@ import { useEffect, useMemo, useState, useRef, useCallback } from 'react' import PropTypes from 'prop-types' -import { Trans, useTranslation } from 'react-i18next' +import { useTranslation } from 'react-i18next' import { matchSorter } from 'match-sorter' import { useCombobox } from 'downshift' import classnames from 'classnames' @@ -19,6 +19,7 @@ export default function SelectCollaborators({ placeholder, multipleSelectionProps, }) { + const { t } = useTranslation() const { getSelectedItemProps, getDropdownProps, @@ -141,7 +142,7 @@ export default function SelectCollaborators({
{/* eslint-disable-next-line jsx-a11y/label-has-for */} diff --git a/services/web/frontend/js/features/share-project-modal/components/send-invites-notice.jsx b/services/web/frontend/js/features/share-project-modal/components/send-invites-notice.jsx index dad789e670..06abd8360e 100644 --- a/services/web/frontend/js/features/share-project-modal/components/send-invites-notice.jsx +++ b/services/web/frontend/js/features/share-project-modal/components/send-invites-notice.jsx @@ -1,6 +1,6 @@ import { Col, Row } from 'react-bootstrap' import PropTypes from 'prop-types' -import { Trans } from 'react-i18next' +import { useTranslation } from 'react-i18next' import { useProjectContext } from '../../../shared/context/project-context' export default function SendInvitesNotice() { @@ -16,12 +16,13 @@ export default function SendInvitesNotice() { } function AccessLevel({ level }) { + const { t } = useTranslation() switch (level) { case 'private': - return + return t('to_add_more_collaborators') case 'tokenBased': - return + return t('to_change_access_permissions') default: return null diff --git a/services/web/frontend/js/features/share-project-modal/components/share-project-modal-content.jsx b/services/web/frontend/js/features/share-project-modal/components/share-project-modal-content.jsx index 9e83e50dc5..118ddbd884 100644 --- a/services/web/frontend/js/features/share-project-modal/components/share-project-modal-content.jsx +++ b/services/web/frontend/js/features/share-project-modal/components/share-project-modal-content.jsx @@ -1,5 +1,5 @@ import { Button, Modal, Grid } from 'react-bootstrap' -import { Trans } from 'react-i18next' +import { useTranslation } from 'react-i18next' import Icon from '../../../shared/components/icon' import AccessibleModal from '../../../shared/components/accessible-modal' import PropTypes from 'prop-types' @@ -23,6 +23,8 @@ export default function ShareProjectModalContent({ inFlight, error, }) { + const { t } = useTranslation() + const { isRestrictedTokenMember } = useEditorContext({ isRestrictedTokenMember: PropTypes.bool, }) @@ -30,9 +32,7 @@ export default function ShareProjectModalContent({ return ( - - - + {t('share_project')} @@ -65,7 +65,7 @@ export default function ShareProjectModalContent({ className="btn-secondary" disabled={inFlight} > - + {t('close')}
@@ -81,24 +81,26 @@ ShareProjectModalContent.propTypes = { } function ErrorMessage({ error }) { + const { t } = useTranslation() + switch (error) { case 'cannot_invite_non_user': - return + return t('cannot_invite_non_user') case 'cannot_verify_user_not_robot': - return + return t('cannot_verify_user_not_robot') case 'cannot_invite_self': - return + return t('cannot_invite_self') case 'invalid_email': - return + return t('invalid_email') case 'too_many_requests': - return + return t('too_many_requests') default: - return + return t('generic_something_went_wrong') } } ErrorMessage.propTypes = { diff --git a/services/web/frontend/js/features/share-project-modal/components/transfer-ownership-modal.jsx b/services/web/frontend/js/features/share-project-modal/components/transfer-ownership-modal.jsx index 48cbf64830..2c1791cab9 100644 --- a/services/web/frontend/js/features/share-project-modal/components/transfer-ownership-modal.jsx +++ b/services/web/frontend/js/features/share-project-modal/components/transfer-ownership-modal.jsx @@ -1,6 +1,6 @@ import { useState } from 'react' import { Modal, Button } from 'react-bootstrap' -import { Trans } from 'react-i18next' +import { Trans, useTranslation } from 'react-i18next' import PropTypes from 'prop-types' import Icon from '../../../shared/components/icon' import { transferProjectOwnership } from '../utils/api' @@ -9,6 +9,8 @@ import { useProjectContext } from '../../../shared/context/project-context' import { useLocation } from '../../../shared/hooks/use-location' export default function TransferOwnershipModal({ member, cancel }) { + const { t } = useTranslation() + const [inflight, setInflight] = useState(false) const [error, setError] = useState(false) const location = useLocation() @@ -32,9 +34,7 @@ export default function TransferOwnershipModal({ member, cancel }) { return ( - - - + {t('change_project_owner')}

@@ -47,16 +47,14 @@ export default function TransferOwnershipModal({ member, cancel }) { tOptions={{ interpolation: { escapeValue: true } }} />

-

- -

+

{t('project_ownership_transfer_confirmation_2')}

{inflight && } {error && ( - + {t('generic_something_went_wrong')} )}
@@ -68,7 +66,7 @@ export default function TransferOwnershipModal({ member, cancel }) { onClick={cancel} disabled={inflight} > - + {t('cancel')}
diff --git a/services/web/frontend/js/features/source-editor/components/figure-modal/figure-modal-help.tsx b/services/web/frontend/js/features/source-editor/components/figure-modal/figure-modal-help.tsx index f434ddf1f5..e65e21b5ea 100644 --- a/services/web/frontend/js/features/source-editor/components/figure-modal/figure-modal-help.tsx +++ b/services/web/frontend/js/features/source-editor/components/figure-modal/figure-modal-help.tsx @@ -9,13 +9,9 @@ export const FigureModalHelp = () => { const { t } = useTranslation() return ( <> -

- -

+

{t('this_tool_helps_you_insert_figures')}

{t('editing_captions')} -

- -

+

{t('when_you_tick_the_include_caption_box')}

{t('understanding_labels')}

diff --git a/services/web/locales/da.json b/services/web/locales/da.json index c68d4d93da..b0538ed022 100644 --- a/services/web/locales/da.json +++ b/services/web/locales/da.json @@ -410,7 +410,7 @@ "edit_tag": "Redigér tag", "editing": "Redigering", "editing_captions": "Redigering af billedtekster", - "editor_and_pdf": "Skrivevindue <0> PDF", + "editor_and_pdf": "Skrivevindue & PDF", "editor_disconected_click_to_reconnect": "Skriveprogrammets forbindelse afbrudt, klik hvor som helst for at forbinde igen.", "editor_only_hide_pdf": "Kun skrivevindue <0>(gem PDF)", "editor_resources": "Læringsmidler til skriveprogrammet", diff --git a/services/web/locales/de.json b/services/web/locales/de.json index 730640f0b9..d346084300 100644 --- a/services/web/locales/de.json +++ b/services/web/locales/de.json @@ -412,7 +412,7 @@ "edit_tag": "Schlagwort bearbeiten", "editing": "Bearbeitung", "editing_captions": "Beschriftungen bearbeiten", - "editor_and_pdf": "Editor <0> PDF", + "editor_and_pdf": "Editor & PDF", "editor_disconected_click_to_reconnect": "Editor wurde getrennt", "editor_only_hide_pdf": "Nur Editor <0>(PDF ausblenden)", "editor_resources": "Editor-Literatur", diff --git a/services/web/locales/en.json b/services/web/locales/en.json index 3910d59f55..e17388649d 100644 --- a/services/web/locales/en.json +++ b/services/web/locales/en.json @@ -460,7 +460,7 @@ "edit_tag": "Edit Tag", "editing": "Editing", "editing_captions": "Editing captions", - "editor_and_pdf": "Editor <0> PDF", + "editor_and_pdf": "Editor & PDF", "editor_disconected_click_to_reconnect": "Editor disconnected, click anywhere to reconnect.", "editor_only_hide_pdf": "Editor only <0>(hide PDF)", "editor_resources": "Editor Resources", diff --git a/services/web/test/frontend/infrastructure/i18n.spec.tsx b/services/web/test/frontend/infrastructure/i18n.spec.tsx index d456882789..6e22bb302e 100644 --- a/services/web/test/frontend/infrastructure/i18n.spec.tsx +++ b/services/web/test/frontend/infrastructure/i18n.spec.tsx @@ -31,30 +31,6 @@ describe('i18n', function () { }) describe('Trans', function () { - it('translates a plain string', function () { - const Test = () => { - return ( -

- -
- ) - } - cy.mount() - cy.findByText('Accept') - }) - - it('uses defaultValues', function () { - const Test = () => { - return ( -
- -
- ) - } - cy.mount() - cy.findByText('Welcome to Overleaf!') - }) - it('uses values', function () { const Test = () => { return (