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>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>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>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>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={
<>
-
-
+ startCompile()}
+ />,
+ ]}
+ />
) : (
<>
-
-
- {' '}
-
+ {t('upgrade_for_12x_more_compile_time')}{' '}
+ {t(
+ 'plus_additional_collaborators_document_history_track_changes_and_more'
+ )}
>
)}
diff --git a/services/web/frontend/js/features/project-list/components/notifications/ads/inr-banner.tsx b/services/web/frontend/js/features/project-list/components/notifications/ads/inr-banner.tsx
index 840d94f772..35d6ad34ed 100644
--- a/services/web/frontend/js/features/project-list/components/notifications/ads/inr-banner.tsx
+++ b/services/web/frontend/js/features/project-list/components/notifications/ads/inr-banner.tsx
@@ -154,9 +154,7 @@ export default function INRBanner({ variant, splitTestName }: INRBannerProps) {
}}
/>
-
-
-
+ {t('inr_discount_modal_info')}
)}
{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' }}
>
)
}
@@ -99,7 +101,7 @@ function RevokeInvite({ invite }) {
return (
}
+ description={t('revoke_invite')}
overlayProps={{ placement: 'bottom' }}
>
-
+ {t('link_sharing_is_off')}
-
+ {t('turn_on_link_sharing')}
@@ -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')}
setAccessLevel('private')}
disabled={inflight}
>
-
+ {t('turn_off_link_sharing')}
-
-
-
+
{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')}
setAccessLevel('private')}
disabled={inflight}
>
-
+ {t('make_private')}
@@ -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')}
-
+ {t('change_owner')}
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>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)0>",
"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>0> PDF",
+ "editor_and_pdf": "Editor & PDF",
"editor_disconected_click_to_reconnect": "Editor wurde getrennt",
"editor_only_hide_pdf": "Nur Editor <0>(PDF ausblenden)0>",
"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>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)0>",
"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 (