fix(frontend): improve performance by caching translated texts

Signed-off-by: Tilman Vatteroth <git@tilmanvatteroth.de>
This commit is contained in:
Tilman Vatteroth 2023-06-26 22:32:19 +02:00
parent ced4cd953c
commit 76242330fd
81 changed files with 341 additions and 292 deletions

View file

@ -3,6 +3,7 @@
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { useTranslatedText } from '../../../../hooks/common/use-translated-text'
import type { PropsWithDataCypressId } from '../../../../utils/cypress-attribute'
import { cypressId } from '../../../../utils/cypress-attribute'
import { UiIcon } from '../../icons/ui-icon'
@ -11,7 +12,6 @@ import React, { Fragment, useRef } from 'react'
import { Button } from 'react-bootstrap'
import { Files as IconFiles } from 'react-bootstrap-icons'
import type { Variant } from 'react-bootstrap/types'
import { useTranslation } from 'react-i18next'
export interface CopyToClipboardButtonProps extends PropsWithDataCypressId {
content: string
@ -33,10 +33,9 @@ export const CopyToClipboardButton: React.FC<CopyToClipboardButtonProps> = ({
variant = 'dark',
...props
}) => {
const { t } = useTranslation()
const button = useRef<HTMLButtonElement>(null)
const [copyToClipboard, overlayElement] = useCopyOverlay(button, content)
const buttonTitle = useTranslatedText('renderer.highlightCode.copyCode')
return (
<Fragment>
@ -44,7 +43,7 @@ export const CopyToClipboardButton: React.FC<CopyToClipboardButtonProps> = ({
ref={button}
size={size}
variant={variant}
title={t('renderer.highlightCode.copyCode') ?? undefined}
title={buttonTitle}
onClick={copyToClipboard}
{...cypressId(props)}>
<UiIcon icon={IconFiles} />

View file

@ -3,10 +3,11 @@
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { useTranslatedText } from '../../../hooks/common/use-translated-text'
import type { CommonFieldProps } from './fields'
import React from 'react'
import { Form } from 'react-bootstrap'
import { Trans, useTranslation } from 'react-i18next'
import { Trans } from 'react-i18next'
/**
* Renders an input field for the current password when changing passwords.
@ -14,7 +15,7 @@ import { Trans, useTranslation } from 'react-i18next'
* @param onChange Hook that is called when the entered password changes.
*/
export const CurrentPasswordField: React.FC<CommonFieldProps> = ({ onChange }) => {
const { t } = useTranslation()
const placeholderText = useTranslatedText('login.auth.password')
return (
<Form.Group>
@ -25,7 +26,7 @@ export const CurrentPasswordField: React.FC<CommonFieldProps> = ({ onChange }) =
type='password'
size='sm'
onChange={onChange}
placeholder={t('login.auth.password') ?? undefined}
placeholder={placeholderText}
autoComplete='current-password'
required
/>

View file

@ -3,10 +3,11 @@
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { useTranslatedText } from '../../../hooks/common/use-translated-text'
import type { CommonFieldProps } from './fields'
import React, { useMemo } from 'react'
import { Form } from 'react-bootstrap'
import { Trans, useTranslation } from 'react-i18next'
import { Trans } from 'react-i18next'
interface DisplayNameFieldProps extends CommonFieldProps {
initialValue?: string
@ -20,11 +21,8 @@ interface DisplayNameFieldProps extends CommonFieldProps {
* @param initialValue The initial input field value.
*/
export const DisplayNameField: React.FC<DisplayNameFieldProps> = ({ onChange, value, initialValue }) => {
const { t } = useTranslation()
const isValid = useMemo(() => {
return value.trim() !== '' && value !== initialValue
}, [value, initialValue])
const isValid = useMemo(() => value.trim() !== '' && value !== initialValue, [value, initialValue])
const placeholderText = useTranslatedText('profile.displayName')
return (
<Form.Group>
@ -37,7 +35,7 @@ export const DisplayNameField: React.FC<DisplayNameFieldProps> = ({ onChange, va
value={value}
isValid={isValid}
onChange={onChange}
placeholder={t('profile.displayName') ?? undefined}
placeholder={placeholderText}
autoComplete='name'
required
/>

View file

@ -3,10 +3,11 @@
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { useTranslatedText } from '../../../hooks/common/use-translated-text'
import type { CommonFieldProps } from './fields'
import React, { useMemo } from 'react'
import { Form } from 'react-bootstrap'
import { Trans, useTranslation } from 'react-i18next'
import { Trans } from 'react-i18next'
/**
* Renders an input field for the new password when registering.
@ -15,11 +16,9 @@ import { Trans, useTranslation } from 'react-i18next'
* @param value The currently entered password.
*/
export const NewPasswordField: React.FC<CommonFieldProps> = ({ onChange, value, hasError = false }) => {
const { t } = useTranslation()
const isValid = useMemo(() => value.trim() !== '', [value])
const isValid = useMemo(() => {
return value.trim() !== ''
}, [value])
const placeholderText = useTranslatedText('login.auth.password')
return (
<Form.Group>
@ -32,7 +31,7 @@ export const NewPasswordField: React.FC<CommonFieldProps> = ({ onChange, value,
isValid={isValid}
isInvalid={hasError}
onChange={onChange}
placeholder={t('login.auth.password') ?? undefined}
placeholder={placeholderText}
autoComplete='new-password'
required
/>

View file

@ -3,10 +3,11 @@
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { useTranslatedText } from '../../../hooks/common/use-translated-text'
import type { CommonFieldProps } from './fields'
import React, { useMemo } from 'react'
import { Form } from 'react-bootstrap'
import { Trans, useTranslation } from 'react-i18next'
import { Trans } from 'react-i18next'
interface PasswordAgainFieldProps extends CommonFieldProps {
password: string
@ -15,9 +16,10 @@ interface PasswordAgainFieldProps extends CommonFieldProps {
/**
* Renders an input field for typing the new password again when registering.
*
* @param onChange Hook that is called when the entered retype of the password changes.
* @param value The currently entered retype of the password.
* @param password The password entered into the password input field.
* @param onChange Hook that is called when the entered retype of the password changes
* @param value The currently entered retype of the password
* @param password The password entered into the password input field
* @param hasError Defines if the password should be shown as invalid
*/
export const PasswordAgainField: React.FC<PasswordAgainFieldProps> = ({
onChange,
@ -25,15 +27,9 @@ export const PasswordAgainField: React.FC<PasswordAgainFieldProps> = ({
password,
hasError = false
}) => {
const { t } = useTranslation()
const isInvalid = useMemo(() => {
return value !== '' && password !== value && hasError
}, [password, value, hasError])
const isValid = useMemo(() => {
return password !== '' && password === value && !hasError
}, [password, value, hasError])
const isInvalid = useMemo(() => value !== '' && password !== value && hasError, [password, value, hasError])
const isValid = useMemo(() => password !== '' && password === value && !hasError, [password, value, hasError])
const placeholderText = useTranslatedText('login.register.passwordAgain')
return (
<Form.Group>
@ -46,7 +42,7 @@ export const PasswordAgainField: React.FC<PasswordAgainFieldProps> = ({
isInvalid={isInvalid}
isValid={isValid}
onChange={onChange}
placeholder={t('login.register.passwordAgain') ?? undefined}
placeholder={placeholderText}
autoComplete='new-password'
required
/>

View file

@ -3,10 +3,10 @@
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { useTranslatedText } from '../../../hooks/common/use-translated-text'
import type { CommonFieldProps } from './fields'
import React from 'react'
import { Form } from 'react-bootstrap'
import { useTranslation } from 'react-i18next'
export interface UsernameFieldProps extends CommonFieldProps {
isInvalid?: boolean
@ -22,7 +22,7 @@ export interface UsernameFieldProps extends CommonFieldProps {
* @param isInvalid Adds error style to label
*/
export const UsernameField: React.FC<UsernameFieldProps> = ({ onChange, value, isValid, isInvalid }) => {
const { t } = useTranslation()
const placeholderText = useTranslatedText('login.auth.username')
return (
<Form.Control
@ -32,7 +32,7 @@ export const UsernameField: React.FC<UsernameFieldProps> = ({ onChange, value, i
isValid={isValid}
isInvalid={isInvalid}
onChange={onChange}
placeholder={t('login.auth.username') ?? undefined}
placeholder={placeholderText}
autoComplete='username'
autoFocus={true}
required

View file

@ -3,8 +3,8 @@
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { UsernameField } from './username-field'
import type { UsernameFieldProps } from './username-field'
import { UsernameField } from './username-field'
import React from 'react'
import { Form } from 'react-bootstrap'
import { Trans } from 'react-i18next'

View file

@ -4,8 +4,8 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { testId } from '../../../utils/test-id'
import { BootstrapLazyIcons } from './bootstrap-icons'
import type { BootstrapIconName } from './bootstrap-icons'
import { BootstrapLazyIcons } from './bootstrap-icons'
import React, { Suspense, useMemo } from 'react'
export interface LazyBootstrapIconProps {

View file

@ -3,10 +3,10 @@
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { useTranslatedText } from '../../../hooks/common/use-translated-text'
import { ExternalLink } from './external-link'
import type { TranslatedLinkProps } from './types'
import React, { useMemo } from 'react'
import { useTranslation } from 'react-i18next'
import React from 'react'
/**
* An {@link ExternalLink external link} with translated text.
@ -16,7 +16,6 @@ import { useTranslation } from 'react-i18next'
* @param props Additional props directly given to the {@link ExternalLink}
*/
export const TranslatedExternalLink: React.FC<TranslatedLinkProps> = ({ i18nKey, i18nOption, ...props }) => {
const { t } = useTranslation()
const text = useMemo(() => (i18nOption ? t(i18nKey, i18nOption) : t(i18nKey)), [i18nKey, i18nOption, t])
const text = useTranslatedText(i18nKey, i18nOption)
return <ExternalLink text={text} {...props} />
}

View file

@ -3,10 +3,10 @@
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { useTranslatedText } from '../../../hooks/common/use-translated-text'
import { InternalLink } from './internal-link'
import type { TranslatedLinkProps } from './types'
import React, { useMemo } from 'react'
import { useTranslation } from 'react-i18next'
import React from 'react'
/**
* An {@link InternalLink internal link} with translated text.
@ -16,7 +16,7 @@ import { useTranslation } from 'react-i18next'
* @param props Additional props directly given to the {@link InternalLink}
*/
export const TranslatedInternalLink: React.FC<TranslatedLinkProps> = ({ i18nKey, i18nOption, ...props }) => {
const { t } = useTranslation()
const text = useMemo(() => (i18nOption ? t(i18nKey, i18nOption) : t(i18nKey)), [i18nKey, i18nOption, t])
const text = useTranslatedText(i18nKey, i18nOption)
return <InternalLink text={text} {...props} />
}

View file

@ -11,7 +11,7 @@ import { RendererType } from '../../render-page/window-post-message-communicator
import { CommonModal } from '../modals/common-modal'
import { RendererIframe } from '../renderer-iframe/renderer-iframe'
import { fetchMotd, MOTD_LOCAL_STORAGE_KEY } from './fetch-motd'
import React, { useCallback, useMemo, useEffect, useState } from 'react'
import React, { useCallback, useEffect, useMemo, useState } from 'react'
import { Button, Modal } from 'react-bootstrap'
import { Trans, useTranslation } from 'react-i18next'
import { useAsync } from 'react-use'

View file

@ -10,9 +10,11 @@ import { UiIcon } from '../icons/ui-icon'
import { ShowIf } from '../show-if/show-if'
import React, { useCallback, useEffect } from 'react'
import { Alert, Button } from 'react-bootstrap'
import { ArrowRepeat as IconArrowRepeat } from 'react-bootstrap-icons'
import { CheckCircle as IconCheckCircle } from 'react-bootstrap-icons'
import { ExclamationTriangle as IconExclamationTriangle } from 'react-bootstrap-icons'
import {
ArrowRepeat as IconArrowRepeat,
CheckCircle as IconCheckCircle,
ExclamationTriangle as IconExclamationTriangle
} from 'react-bootstrap-icons'
import { Trans, useTranslation } from 'react-i18next'
import { useAsyncFn } from 'react-use'

View file

@ -15,9 +15,9 @@ import { useEditorReceiveHandler } from '../../render-page/window-post-message-c
import type {
ExtensionEvent,
OnHeightChangeMessage,
RendererType,
SetScrollStateMessage
} from '../../render-page/window-post-message-communicator/rendering-message'
import type { RendererType } from '../../render-page/window-post-message-communicator/rendering-message'
import { CommunicationMessageType } from '../../render-page/window-post-message-communicator/rendering-message'
import { ShowIf } from '../show-if/show-if'
import { WaitSpinner } from '../wait-spinner/wait-spinner'

View file

@ -3,13 +3,13 @@
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { useTranslatedText } from '../../../hooks/common/use-translated-text'
import { ShowIf } from '../show-if/show-if'
import defaultAvatar from './default-avatar.png'
import styles from './user-avatar.module.scss'
import React, { useCallback, useMemo } from 'react'
import { OverlayTrigger, Tooltip } from 'react-bootstrap'
import type { OverlayInjectedProps } from 'react-bootstrap/Overlay'
import { useTranslation } from 'react-i18next'
export interface UserAvatarProps {
size?: 'sm' | 'lg'
@ -34,8 +34,6 @@ export const UserAvatar: React.FC<UserAvatarProps> = ({
additionalClasses = '',
showName = true
}) => {
const { t } = useTranslation()
const imageSize = useMemo(() => {
switch (size) {
case 'sm':
@ -51,7 +49,13 @@ export const UserAvatar: React.FC<UserAvatarProps> = ({
return photoUrl || defaultAvatar.src
}, [photoUrl])
const imgDescription = useMemo(() => t('common.avatarOf', { name: displayName }), [t, displayName])
const imageTranslateOptions = useMemo(
() => ({
name: displayName
}),
[displayName]
)
const imgDescription = useTranslatedText('common.avatarOf', imageTranslateOptions)
const tooltip = useCallback(
(overlayInjectedProps: OverlayInjectedProps) => (