mirror of
https://github.com/overleaf/overleaf.git
synced 2024-11-21 20:47:08 -05:00
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:
parent
1314f9082c
commit
221d16f4e4
21 changed files with 97 additions and 176 deletions
|
@ -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/
|
||||
|
|
|
@ -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={{
|
||||
nameLimit: fileNameLimit,
|
||||
}}
|
||||
shouldUnescape
|
||||
tOptions={{ interpolation: { escapeValue: true } }}
|
||||
/>
|
||||
{t('invalid_filename', {
|
||||
nameLimit: fileNameLimit,
|
||||
})}
|
||||
</DangerMessage>
|
||||
)
|
||||
|
||||
|
|
|
@ -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} />
|
||||
|
|
|
@ -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 = {
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 (
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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={{
|
||||
admin: currentManagedUserAdminEmail,
|
||||
}}
|
||||
shouldUnescape
|
||||
tOptions={{ interpolation: { escapeValue: true } }}
|
||||
/>
|
||||
{t('account_managed_by_group_administrator', {
|
||||
admin: currentManagedUserAdminEmail,
|
||||
})}
|
||||
</strong>
|
||||
</div>
|
||||
<div>
|
||||
|
|
|
@ -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" />
|
||||
|
||||
<Trans
|
||||
i18nKey="collabs_per_proj"
|
||||
values={{ collabcount: 'Multiple' }}
|
||||
shouldUnescape
|
||||
tOptions={{ interpolation: { escapeValue: true } }}
|
||||
/>
|
||||
{t('collabs_per_proj', {
|
||||
collabcount: 'Multiple',
|
||||
})}
|
||||
</li>
|
||||
<li>
|
||||
<Icon type="check" />
|
||||
|
|
|
@ -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" />
|
||||
|
||||
<Trans
|
||||
i18nKey="collabs_per_proj"
|
||||
values={{ collabcount: 'Multiple' }}
|
||||
shouldUnescape
|
||||
tOptions={{ interpolation: { escapeValue: true } }}
|
||||
/>
|
||||
{t('collabs_per_proj', {
|
||||
collabcount: 'Multiple',
|
||||
})}
|
||||
</li>
|
||||
<li>
|
||||
<Icon type="check" />
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -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', {
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -22,14 +22,9 @@ function GroupPlanCollaboratorCount({ planCode }: { planCode: string }) {
|
|||
if (planCode === 'collaborator') {
|
||||
return (
|
||||
<>
|
||||
<Trans
|
||||
i18nKey="collabs_per_proj"
|
||||
values={{
|
||||
collabcount: 10,
|
||||
}}
|
||||
shouldUnescape
|
||||
tOptions={{ interpolation: { escapeValue: true } }}
|
||||
/>
|
||||
{t('collabs_per_proj', {
|
||||
collabcount: 10,
|
||||
})}
|
||||
</>
|
||||
)
|
||||
} 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={{
|
||||
price: perUserPrice,
|
||||
}}
|
||||
shouldUnescape
|
||||
tOptions={{ interpolation: { escapeValue: true } }}
|
||||
/>
|
||||
{t('x_price_per_user', {
|
||||
price: perUserPrice,
|
||||
})}
|
||||
</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={{
|
||||
percent: '30',
|
||||
}}
|
||||
shouldUnescape
|
||||
tOptions={{ interpolation: { escapeValue: true } }}
|
||||
/>
|
||||
{t('save_x_percent_or_more', {
|
||||
percent: '30',
|
||||
})}
|
||||
</h3>
|
||||
</div>
|
||||
</Modal.Header>
|
||||
|
@ -304,15 +272,10 @@ export function ChangeToGroupModal() {
|
|||
|
||||
<div className="form-group">
|
||||
<strong>
|
||||
<Trans
|
||||
i18nKey="percent_discount_for_groups"
|
||||
values={{
|
||||
percent: educationalPercentDiscount,
|
||||
size: groupSizeForEducationalDiscount,
|
||||
}}
|
||||
shouldUnescape
|
||||
tOptions={{ interpolation: { escapeValue: true } }}
|
||||
/>
|
||||
{t('percent_discount_for_groups', {
|
||||
percent: educationalPercentDiscount,
|
||||
size: groupSizeForEducationalDiscount,
|
||||
})}
|
||||
</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>
|
||||
|
|
|
@ -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: {
|
||||
|
|
|
@ -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",
|
||||
|
|
Loading…
Reference in a new issue