0
Fork 0
mirror of https://github.com/logto-io/logto.git synced 2025-03-31 22:51:25 -05:00

refactor: add allowObjectInHTMLChildren prop to resolve i18n type issues

This commit is contained in:
Charles Zhao 2022-08-08 12:05:14 +08:00
parent e4629f2a5f
commit fd9b8435ac
No known key found for this signature in database
GPG key ID: 4858774754C92DF2
21 changed files with 42 additions and 37 deletions

View file

@ -31,8 +31,8 @@ const Contact = ({ isOpen, onCancel }: Props) => {
<ContactIcon />
</div>
<div className={styles.text}>
<div className={styles.title}>{String(t(title))}</div>
<div className={styles.description}>{String(t(description))}</div>
<div className={styles.title}>{t(title)}</div>
<div className={styles.description}>{t(description)}</div>
</div>
<div>
<Button

View file

@ -82,7 +82,7 @@ const Button = ({
>
{showSpinner && <Spinner className={styles.spinner} />}
{icon && <span className={styles.icon}>{icon}</span>}
{title && (typeof title === 'string' ? <span>{String(t(title))}</span> : title)}
{title && (typeof title === 'string' ? <span>{t(title)}</span> : title)}
</button>
);
};

View file

@ -20,10 +20,10 @@ const CardTitle = ({ title, subtitle, size = 'large' }: Props) => {
return (
<div className={classNames(styles.container, styles[size])}>
<div className={styles.title}>{typeof title === 'string' ? String(t(title)) : title}</div>
<div className={styles.title}>{typeof title === 'string' ? t(title) : title}</div>
{subtitle && (
<div className={styles.subtitle}>
{typeof subtitle === 'string' ? String(t(subtitle)) : subtitle}
{typeof subtitle === 'string' ? t(subtitle) : subtitle}
</div>
)}
</div>

View file

@ -1,5 +1,5 @@
import classNames from 'classnames';
import React, { MouseEvent, ReactNode } from 'react';
import { MouseEvent, ReactNode } from 'react';
import * as styles from './DropdownItem.module.scss';
@ -22,7 +22,7 @@ const DropdownItem = ({
}: Props) => (
<li className={classNames(styles.item, styles[type], className)} onClick={onClick}>
{icon && <span className={classNames(styles.icon, iconClassName)}>{icon}</span>}
{React.isValidElement(children) ? children : String(children)}
{children}
</li>
);

View file

@ -25,7 +25,7 @@ const FormField = ({ title, children, isRequired, className, tooltip }: Props) =
return (
<div className={classNames(styles.field, className)}>
<div className={styles.headline}>
<div className={styles.title}>{typeof title === 'string' ? String(t(title)) : title}</div>
<div className={styles.title}>{typeof title === 'string' ? t(title) : title}</div>
{tooltip && (
<div ref={tipRef} className={styles.icon}>
<Tip />

View file

@ -20,7 +20,7 @@ const LinkButton = ({ to, title, icon, className }: Props) => {
return (
<Link to={to} className={classNames(styles.linkButton, className)}>
{icon}
{typeof title === 'string' ? <span>{String(t(title))}</span> : title}
{typeof title === 'string' ? <span>{t(title)}</span> : title}
</Link>
);
};

View file

@ -76,9 +76,9 @@ const Radio = ({
{type === 'card' && <Check />}
{children}
{type === 'plain' && <div className={styles.indicator} />}
{title && String(t(title))}
{title && t(title)}
{isDisabled && disabledLabel && (
<div className={styles.disabledLabel}>{String(t(disabledLabel))}</div>
<div className={styles.disabledLabel}>{t(disabledLabel)}</div>
)}
</div>
);

View file

@ -1,5 +1,5 @@
import classNames from 'classnames';
import React, { ReactNode, RefObject, useEffect, useLayoutEffect, useRef, useState } from 'react';
import { ReactNode, RefObject, useEffect, useLayoutEffect, useRef, useState } from 'react';
import { createPortal } from 'react-dom';
import usePosition, { HorizontalAlignment } from '@/hooks/use-position';
@ -131,9 +131,7 @@ const Tooltip = ({
)}
style={{ ...position }}
>
<div className={styles.content}>
{React.isValidElement(content) ? content : String(content)}
</div>
<div className={styles.content}>{content}</div>
</div>,
tooltipDom
);

View file

@ -1,11 +1,14 @@
// https://react.i18next.com/latest/typescript#create-a-declaration-file
// eslint-disable-next-line import/no-unassigned-import
import 'react-i18next';
import en from '@logto/phrases/lib/locales/en.js';
import { Translation, Errors } from '@logto/phrases';
import { CustomTypeOptions } from 'react-i18next';
declare module 'react-i18next' {
interface CustomTypeOptions {
resources: typeof en;
allowObjectInHTMLChildren: true;
resources: {
translation: Translation;
errors: Errors;
};
}
}

View file

@ -48,7 +48,7 @@ const ConnectorTabs = ({ target, connectorId }: Props) => {
<ConnectorPlatformIcon platform={connector.platform} />
</div>
)}
{connector.platform && String(t(connectorPlatformLabel[connector.platform]))}
{connector.platform && t(connectorPlatformLabel[connector.platform])}
</Link>
))}
</div>

View file

@ -34,7 +34,7 @@ const ConnectorName = ({ type, connectors, onClickSetup }: Props) => {
<ItemPreview
title={
<div className={styles.previewTitle}>
<div>{String(t(connectorTitlePlaceHolder[type]))}</div>
<div>{t(connectorTitlePlaceHolder[type])}</div>
{type !== ConnectorType.Social && (
<Button title="general.set_up" onClick={onClickSetup} />
)}
@ -63,7 +63,7 @@ const ConnectorName = ({ type, connectors, onClickSetup }: Props) => {
platform && (
<div key={id} className={styles.platform}>
<ConnectorPlatformIcon platform={platform} />
{String(t(`${connectorPlatformLabel[platform]}`))}
{t(`${connectorPlatformLabel[platform]}`)}
</div>
)
)}

View file

@ -36,7 +36,7 @@ const ConnectorRow = ({ type, connectors, onClickSetup }: Props) => {
<td>
<ConnectorName type={type} connectors={connectors} onClickSetup={onClickSetup} />
</td>
<td>{String(t(connectorTitlePlaceHolder[type]))}</td>
<td>{t(connectorTitlePlaceHolder[type])}</td>
<td>
{inUse !== undefined && (
<Status status={inUse ? 'enabled' : 'disabled'}>

View file

@ -29,7 +29,7 @@ const Block = ({ variant = 'default', count, delta, title, tooltip }: Props) =>
return (
<Card className={classNames(styles.block, styles[variant])}>
<div className={styles.title}>
{String(t(title))}
{t(title)}
{tooltip && (
<div ref={tipRef} className={styles.icon}>
<Tip />

View file

@ -53,8 +53,8 @@ const GetStarted = () => {
{!isComplete && <CardIcon className={styles.icon} />}
{isComplete && <CompleteIndicator className={styles.icon} />}
<div className={styles.wrapper}>
<div className={styles.title}>{String(t(title))}</div>
<div className={styles.subtitle}>{String(t(subtitle))}</div>
<div className={styles.title}>{t(title)}</div>
<div className={styles.subtitle}>{t(subtitle)}</div>
</div>
<Button
className={styles.button}

View file

@ -62,17 +62,17 @@ const Main = () => {
<div className={styles.app}>
<div className={styles.card}>
{congratsIcon && <img src={congratsIcon} alt="Congrats" />}
<div className={styles.title}>{String(t('title'))}</div>
<div className={styles.text}>{String(t('subtitle'))}</div>
<div className={styles.title}>{t('title')}</div>
<div className={styles.text}>{t('subtitle')}</div>
<div className={styles.infoCard}>
{user.username && (
<div>
{String(t('username'))}
{t('username')}
<span>{user.username}</span>
</div>
)}
<div>
{String(t('user_id'))}
{t('user_id')}
<span>{user.sub}</span>
</div>
</div>
@ -80,7 +80,7 @@ const Main = () => {
className={styles.button}
onClick={async () => signOut(`${window.location.origin}/demo-app`)}
>
{String(t('sign_out'))}
{t('sign_out')}
</div>
</div>
</div>

View file

@ -1,8 +1,11 @@
// https://react.i18next.com/latest/typescript#create-a-declaration-file
import { Translation, Errors } from '@logto/phrases';
import { CustomTypeOptions } from 'react-i18next';
declare module 'react-i18next' {
interface CustomTypeOptions {
allowObjectInHTMLChildren: true;
resources: {
translation: Translation;
errors: Errors;

View file

@ -1,5 +1,5 @@
import classNames from 'classnames';
import React, { ReactNode } from 'react';
import { ReactNode } from 'react';
import * as styles from './index.module.scss';
@ -34,7 +34,7 @@ const Button = ({
type={htmlType}
onClick={onClick}
>
{React.isValidElement(children) ? children : String(children)}
{children}
</button>
);

View file

@ -14,7 +14,7 @@ const Divider = ({ className, label }: Props) => {
return (
<div className={classNames(styles.divider, className)}>
<i className={styles.line} />
{label && String(t(label))}
{label && t(label)}
<i className={styles.line} />
</div>
);

View file

@ -19,14 +19,14 @@ const TextLink = ({ className, children, text, type = 'primary', to, ...rest }:
if (to) {
return (
<Link className={classNames(styles.link, styles[type], className)} to={to}>
{children ?? (text ? String(t(text)) : '')}
{children ?? (text ? t(text) : '')}
</Link>
);
}
return (
<a className={classNames(styles.link, styles[type], className)} {...rest} rel="noreferrer">
{children ?? (text ? String(t(text)) : '')}
{children ?? (text ? t(text) : '')}
</a>
);
};

View file

@ -6,6 +6,7 @@ import en from '@logto/phrases-ui/lib/locales/en.js';
declare module 'react-i18next' {
interface CustomTypeOptions {
allowObjectInHTMLChildren: true;
resources: typeof en;
}
}

View file

@ -24,7 +24,7 @@ const ErrorPage = ({ title = 'description.not_found', message, rawMessage }: Pro
<NavBar />
<div className={styles.container}>
<ErrorImage />
<div className={styles.title}>{String(t(title))}</div>
<div className={styles.title}>{t(title)}</div>
{errorMessage && <div className={styles.message}>{String(errorMessage)}</div>}
</div>
<Button