Disable transSupportBasicHtmlNodes in react-i18next config (#15430)

* Set transSupportBasicHtmlNodes to false
* Update ESLint rule
* Convert Trans to t
* Convert shouldUnescape={true}
* Convert some arrays to objects
* Update translations

GitOrigin-RevId: 64a50318388abcada408f72d949de148129a9f63
This commit is contained in:
Alf Eaton 2023-10-30 10:29:56 +00:00 committed by Copybot
parent 1314f9082c
commit 221d16f4e4
21 changed files with 97 additions and 176 deletions

View file

@ -114,7 +114,7 @@
//
"files": ["**/frontend/js/**/components/**/*.{js,jsx,ts,tsx}", "**/frontend/js/**/hooks/**/*.{js,jsx,ts,tsx}"],
"rules": {
"@overleaf/no-empty-trans": "error",
"@overleaf/no-unnecessary-trans": "error",
"@overleaf/should-unescape-trans": "error",
// https://astexplorer.net/

View file

@ -1,5 +1,5 @@
import PropTypes from 'prop-types'
import { useTranslation, Trans } from 'react-i18next'
import { useTranslation } from 'react-i18next'
import { FetchError } from '../../../../infrastructure/fetch-json'
import RedirectToLogin from './redirect-to-login'
import {
@ -29,14 +29,9 @@ export default function ErrorMessage({ error }) {
case 'invalid_filename':
return (
<DangerMessage>
<Trans
i18nKey="invalid_filename"
values={{
{t('invalid_filename', {
nameLimit: fileNameLimit,
}}
shouldUnescape
tOptions={{ interpolation: { escapeValue: true } }}
/>
})}
</DangerMessage>
)

View file

@ -1,4 +1,4 @@
import { Trans, useTranslation } from 'react-i18next'
import { useTranslation } from 'react-i18next'
import { Button } from 'react-bootstrap'
import { useCallback, useEffect, useState } from 'react'
import PropTypes from 'prop-types'
@ -209,16 +209,12 @@ const projectContextPropTypes = {
}
function UploadErrorMessage({ error, maxNumberOfFiles }) {
const { t } = useTranslation()
switch (error) {
case 'too-many-files':
return (
<Trans
i18nKey="maximum_files_uploaded_together"
values={{ max: maxNumberOfFiles }}
shouldUnescape
tOptions={{ interpolation: { escapeValue: true } }}
/>
)
return t('maximum_files_uploaded_together', {
max: maxNumberOfFiles,
})
default:
return <ErrorMessage error={error} />

View file

@ -1,11 +1,12 @@
import { useState, useEffect } from 'react'
import PropTypes from 'prop-types'
import { Trans } from 'react-i18next'
import { useTranslation } from 'react-i18next'
import { useProjectContext } from '../../../../shared/context/project-context'
import { useLocation } from '../../../../shared/hooks/use-location'
// handle "not-logged-in" errors by redirecting to the login page
export default function RedirectToLogin() {
const { t } = useTranslation()
const { _id: projectId } = useProjectContext(projectContextPropTypes)
const [secondsToRedirect, setSecondsToRedirect] = useState(10)
const location = useLocation()
@ -30,14 +31,9 @@ export default function RedirectToLogin() {
}
}, [projectId, location])
return (
<Trans
i18nKey="session_expired_redirecting_to_login"
values={{ seconds: secondsToRedirect }}
shouldUnescape
tOptions={{ interpolation: { escapeValue: true } }}
/>
)
return t('session_expired_redirecting_to_login', {
seconds: secondsToRedirect,
})
}
const projectContextPropTypes = {

View file

@ -90,8 +90,9 @@ function CompileTimeWarning() {
<div className="warning-text">
<Trans
i18nKey="approaching_compile_timeout_limit_upgrade_for_more_compile_time"
// eslint-disable-next-line react/jsx-key
components={[<strong style={{ display: 'inline-block' }} />]}
components={{
strong: <strong style={{ display: 'inline-block' }} />,
}}
/>
</div>
<div className="upgrade-prompt">

View file

@ -18,7 +18,7 @@ export const CompileTimeoutChangingSoon: FC<{
const { t } = useTranslation()
const compileTimeoutBlogLinks = [
/* eslint-disable-next-line jsx-a11y/anchor-has-content, react/jsx-key */
/* eslint-disable-next-line jsx-a11y/anchor-has-content */
<a
aria-label={t('read_more_about_free_compile_timeouts_servers')}
href="/blog/changes-to-free-compile-timeouts-and-servers"
@ -27,7 +27,7 @@ export const CompileTimeoutChangingSoon: FC<{
target="_blank"
onClick={sendInfoClickEvent}
/>,
/* eslint-disable-next-line jsx-a11y/anchor-has-content, react/jsx-key */
/* eslint-disable-next-line jsx-a11y/anchor-has-content */
<a
aria-label={t('read_more_about_fix_prevent_timeout')}
href="/learn/how-to/Fixing_and_preventing_compile_timeouts"

View file

@ -1,5 +1,5 @@
import { Button } from 'react-bootstrap'
import { Trans, useTranslation } from 'react-i18next'
import { useTranslation } from 'react-i18next'
import type { Dispatch, SetStateAction } from 'react'
import Notification from '../../notification'
import { GroupInvitationStatus } from './hooks/use-group-invitation-notification'
@ -26,14 +26,7 @@ export default function GroupInvitationCancelIndividualSubscriptionNotification(
return (
<Notification bsStyle="info" onDismiss={dismissGroupInviteNotification}>
<Notification.Body>
<Trans
i18nKey="invited_to_group_have_individual_subcription"
values={{
inviterName,
}}
shouldUnescape
tOptions={{ interpolation: { escapeValue: true } }}
/>
{t('invited_to_group_have_individual_subcription', { inviterName })}
</Notification.Body>
<Notification.Action className="group-invitation-cancel-subscription-notification-buttons">
<Button

View file

@ -1,5 +1,5 @@
import { Button } from 'react-bootstrap'
import { Trans, useTranslation } from 'react-i18next'
import { useTranslation } from 'react-i18next'
import Notification from '../../notification'
import type { NotificationGroupInvitation } from '../../../../../../../../types/project/dashboard/notification'
@ -24,14 +24,7 @@ export default function GroupInvitationNotificationJoin({
return (
<Notification bsStyle="info" onDismiss={dismissGroupInviteNotification}>
<Notification.Body>
<Trans
i18nKey="invited_to_group"
values={{
inviterName,
}}
shouldUnescape
tOptions={{ interpolation: { escapeValue: true } }}
/>
{t('invited_to_group', { inviterName })}
</Notification.Body>
<Notification.Action>
<Button

View file

@ -1,4 +1,4 @@
import { Trans } from 'react-i18next'
import { useTranslation } from 'react-i18next'
import { formatDate, fromNowDate } from '../../../../../utils/dates'
import { Project } from '../../../../../../../types/project/dashboard/api'
import Tooltip from '../../../../../shared/components/tooltip'
@ -9,20 +9,14 @@ type LastUpdatedCellProps = {
}
export default function LastUpdatedCell({ project }: LastUpdatedCellProps) {
const displayText = project.lastUpdatedBy ? (
<Trans
i18nKey="last_updated_date_by_x"
values={{
const { t } = useTranslation()
const displayText = project.lastUpdatedBy
? t('last_updated_date_by_x', {
lastUpdatedDate: fromNowDate(project.lastUpdated),
person: getUserName(project.lastUpdatedBy),
}}
// eslint-disable-next-line react/jsx-boolean-value
shouldUnescape={true}
tOptions={{ interpolation: { escapeValue: true } }}
/>
) : (
fromNowDate(project.lastUpdated)
)
})
: fromNowDate(project.lastUpdated)
const tooltipText = formatDate(project.lastUpdated)
return (

View file

@ -34,8 +34,7 @@ function SsoLinkingInfo({ domainInfo, email }: SSOLinkingInfoProps) {
i18nKey="to_add_email_accounts_need_to_be_linked_2"
components={[<strong />]} // eslint-disable-line react/jsx-key
values={{ institutionName: domainInfo.university.name }}
// eslint-disable-next-line react/jsx-boolean-value
shouldUnescape={true}
shouldUnescape
tOptions={{ interpolation: { escapeValue: true } }}
/>
</p>
@ -44,8 +43,7 @@ function SsoLinkingInfo({ domainInfo, email }: SSOLinkingInfoProps) {
i18nKey="doing_this_will_verify_affiliation_and_allow_log_in_2"
components={[<strong />]} // eslint-disable-line react/jsx-key
values={{ institutionName: domainInfo.university.name }}
// eslint-disable-next-line react/jsx-boolean-value
shouldUnescape={true}
shouldUnescape
tOptions={{ interpolation: { escapeValue: true } }}
/>{' '}
<a

View file

@ -57,7 +57,7 @@ function LeaveModalContent({
<p>
<Trans
i18nKey="delete_account_warning_message_3"
components={[<strong />]} // eslint-disable-line react/jsx-key
components={{ strong: <strong /> }}
/>
</p>
<LeaveModalContentBlock

View file

@ -1,7 +1,8 @@
import { Trans } from 'react-i18next'
import { Trans, useTranslation } from 'react-i18next'
import getMeta from '../../../utils/meta'
export default function ManagedAccountAlert() {
const { t } = useTranslation()
const isManaged = getMeta('ol-isManagedAccount', false)
const currentManagedUserAdminEmail: string = getMeta(
'ol-currentManagedUserAdminEmail',
@ -20,14 +21,9 @@ export default function ManagedAccountAlert() {
<div>
<div>
<strong>
<Trans
i18nKey="account_managed_by_group_administrator"
values={{
{t('account_managed_by_group_administrator', {
admin: currentManagedUserAdminEmail,
}}
shouldUnescape
tOptions={{ interpolation: { escapeValue: true } }}
/>
})}
</strong>
</div>
<div>

View file

@ -1,5 +1,5 @@
import Icon from '../../../shared/components/icon'
import { Trans, useTranslation } from 'react-i18next'
import { useTranslation } from 'react-i18next'
export default function AddCollaboratorsUpgradeContentDefault() {
const { t } = useTranslation()
@ -18,12 +18,9 @@ export default function AddCollaboratorsUpgradeContentDefault() {
<li>
<Icon type="check" />
&nbsp;
<Trans
i18nKey="collabs_per_proj"
values={{ collabcount: 'Multiple' }}
shouldUnescape
tOptions={{ interpolation: { escapeValue: true } }}
/>
{t('collabs_per_proj', {
collabcount: 'Multiple',
})}
</li>
<li>
<Icon type="check" />

View file

@ -1,5 +1,5 @@
import Icon from '../../../shared/components/icon'
import { Trans, useTranslation } from 'react-i18next'
import { useTranslation } from 'react-i18next'
export default function AddCollaboratorsUpgradeContentVariant() {
const { t } = useTranslation()
@ -23,12 +23,9 @@ export default function AddCollaboratorsUpgradeContentVariant() {
<li>
<Icon type="check" />
&nbsp;
<Trans
i18nKey="collabs_per_proj"
values={{ collabcount: 'Multiple' }}
shouldUnescape
tOptions={{ interpolation: { escapeValue: true } }}
/>
{t('collabs_per_proj', {
collabcount: 'Multiple',
})}
</li>
<li>
<Icon type="check" />

View file

@ -42,8 +42,7 @@ export default function TransferOwnershipModal({ member, cancel }) {
i18nKey="project_ownership_transfer_confirmation_1"
values={{ user: member.email, project: projectName }}
components={[<strong key="strong-1" />, <strong key="strong-2" />]}
// eslint-disable-next-line react/jsx-boolean-value
shouldUnescape={true}
shouldUnescape
tOptions={{ interpolation: { escapeValue: true } }}
/>
</p>

View file

@ -16,8 +16,10 @@ function ToggleWidget() {
})}
onClick={toggleReviewPanel}
>
{/* eslint-disable-next-line react/jsx-key */}
<Trans i18nKey="track_changes_is_on" components={[<strong />]} />
<Trans
i18nKey="track_changes_is_on"
components={{ strong: <strong /> }}
/>
</button>
)
}

View file

@ -68,11 +68,15 @@ function ToggleMenu() {
onClick={handleToggleFullTCStateCollapse}
>
{wantTrackChanges ? (
// eslint-disable-next-line react/jsx-key
<Trans i18nKey="track_changes_is_on" components={[<strong />]} />
<Trans
i18nKey="track_changes_is_on"
components={{ strong: <strong /> }}
/>
) : (
// eslint-disable-next-line react/jsx-key
<Trans i18nKey="track_changes_is_off" components={[<strong />]} />
<Trans
i18nKey="track_changes_is_off"
components={{ strong: <strong /> }}
/>
)}
<span
className={classnames('rp-tc-state-collapse', {

View file

@ -80,10 +80,7 @@ function NotCancelOption({
}}
shouldUnescape
tOptions={{ interpolation: { escapeValue: true } }}
components={[
// eslint-disable-next-line react/jsx-key
<strong />,
]}
components={{ strong: <strong /> }}
/>
</p>
<p>

View file

@ -22,14 +22,9 @@ function GroupPlanCollaboratorCount({ planCode }: { planCode: string }) {
if (planCode === 'collaborator') {
return (
<>
<Trans
i18nKey="collabs_per_proj"
values={{
{t('collabs_per_proj', {
collabcount: 10,
}}
shouldUnescape
tOptions={{ interpolation: { escapeValue: true } }}
/>
})}
</>
)
} else if (planCode === 'professional') {
@ -39,28 +34,23 @@ function GroupPlanCollaboratorCount({ planCode }: { planCode: string }) {
}
function EducationDiscountAppliedOrNot({ groupSize }: { groupSize: string }) {
const { t } = useTranslation()
const size = parseInt(groupSize)
if (size >= groupSizeForEducationalDiscount) {
return (
<p className="applied">
<Trans
i18nKey="educational_percent_discount_applied"
values={{ percent: educationalPercentDiscount }}
shouldUnescape
tOptions={{ interpolation: { escapeValue: true } }}
/>
{t('educational_percent_discount_applied', {
percent: educationalPercentDiscount,
})}
</p>
)
}
return (
<p className="ineligible">
<Trans
i18nKey="educational_discount_for_groups_of_x_or_more"
values={{ size: groupSizeForEducationalDiscount }}
shouldUnescape
tOptions={{ interpolation: { escapeValue: true } }}
/>
{t('educational_discount_for_groups_of_x_or_more', {
size: groupSizeForEducationalDiscount,
})}
</p>
)
}
@ -92,44 +82,27 @@ function GroupPrice({
{totalPrice} <span className="small">/ {t('year')}</span>
</span>
<span className="sr-only">
{queryingGroupPlanToChangeToPrice ? (
t('loading_prices')
) : (
<Trans
i18nKey="x_price_per_year"
values={{ price: groupPlanToChangeToPrice?.totalForDisplay }}
shouldUnescape
tOptions={{ interpolation: { escapeValue: true } }}
/>
)}
{queryingGroupPlanToChangeToPrice
? t('loading_prices')
: t('x_price_per_year', {
price: groupPlanToChangeToPrice?.totalForDisplay,
})}
</span>
<br />
<span className="circle-subtext">
<span aria-hidden>
<Trans
i18nKey="x_price_per_user"
values={{
{t('x_price_per_user', {
price: perUserPrice,
}}
shouldUnescape
tOptions={{ interpolation: { escapeValue: true } }}
/>
})}
</span>
<span className="sr-only">
{queryingGroupPlanToChangeToPrice ? (
t('loading_prices')
) : (
<Trans
i18nKey="x_price_per_user"
values={{
{queryingGroupPlanToChangeToPrice
? t('loading_prices')
: t('x_price_per_user', {
price: perUserPrice,
}}
shouldUnescape
tOptions={{ interpolation: { escapeValue: true } }}
/>
)}
})}
</span>
</span>
</>
@ -215,14 +188,9 @@ export function ChangeToGroupModal() {
<div className="modal-title">
<h2>{t('customize_your_group_subscription')}</h2>
<h3>
<Trans
i18nKey="save_x_percent_or_more"
values={{
{t('save_x_percent_or_more', {
percent: '30',
}}
shouldUnescape
tOptions={{ interpolation: { escapeValue: true } }}
/>
})}
</h3>
</div>
</Modal.Header>
@ -304,15 +272,10 @@ export function ChangeToGroupModal() {
<div className="form-group">
<strong>
<Trans
i18nKey="percent_discount_for_groups"
values={{
{t('percent_discount_for_groups', {
percent: educationalPercentDiscount,
size: groupSizeForEducationalDiscount,
}}
shouldUnescape
tOptions={{ interpolation: { escapeValue: true } }}
/>
})}
</strong>
</div>
@ -393,12 +356,9 @@ export function ChangeToGroupModal() {
</button>
<hr className="thin" />
<button className="btn-inline-link" onClick={handleGetInTouchButton}>
<Trans
i18nKey="need_more_than_x_licenses"
values={{ x: 50 }}
shouldUnescape
tOptions={{ interpolation: { escapeValue: true } }}
/>{' '}
{t('need_more_than_x_licenses', {
x: 50,
})}{' '}
{t('please_get_in_touch')}
</button>
</div>

View file

@ -23,6 +23,9 @@ i18n.use(initReactI18next).init({
// translation strings asynchronously, we need to trigger a re-render once
// they've loaded
bindI18nStore: 'added',
// Disable automatic conversion of basic markup to React components
transSupportBasicHtmlNodes: false,
},
interpolation: {

View file

@ -371,7 +371,7 @@
"delete": "Delete",
"delete_account": "Delete Account",
"delete_account_confirmation_label": "I understand this will delete all projects in my __appName__ account with email address <0>__userDefaultEmail__</0>",
"delete_account_warning_message_3": "You are about to permanently <0>delete all of your account data</0>, including your projects and settings. Please type your account email address and password in the boxes below to proceed.",
"delete_account_warning_message_3": "You are about to permanently <strong>delete all of your account data</strong>, including your projects and settings. Please type your account email address and password in the boxes below to proceed.",
"delete_acct_no_existing_pw": "Please use the password reset form to set a password before deleting your account",
"delete_and_leave": "Delete / Leave",
"delete_and_leave_projects": "Delete and Leave Projects",