0
Fork 0
mirror of https://github.com/logto-io/logto.git synced 2025-04-14 23:11:31 -05:00

Merge pull request from logto-io/renovate/i18next-22.x

chore(deps): update i18next series packages
This commit is contained in:
Gao Sun 2023-04-23 17:47:58 +08:00 committed by GitHub
commit 9f14e065e8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
52 changed files with 187 additions and 137 deletions
packages
console
package.json
src
components
CopyToClipboard
Dropdown
DynamicT
FormCard
FormField
RadioGroup
Table
UserAccountInformation
consts
containers/ConsoleContent/Sidebar
components/Item
hook.tsx
hooks
include.d
onboarding
components/CardSelector/MultiCardSelector
pages/SignInExperience/components/PlatformTabs
pages
Connectors/components/ConnectorName
Dashboard/components
GetStarted/components/GetStartedProgress
Profile/components
CardContent
MainFlowLikeModal
SignInExperience
components/SignUpAndSignInChangePreview/SignUpAndSignInDiffSection
constants.ts
core
demo-app
ui
package.json
src
Layout
LandingPageLayout
SecondaryPageLayout
components
BrandingHeader
Button
ConfirmModal
Divider
DynamicT
ErrorMessage
InputFields/SmartInputField
Notification/InlineNotification
PageMeta
TextLink
containers
SetPassword
SocialLinkAccount
include.d
pages
Continue
IdentifierProfileForm
SetEmailOrPhone
SetUsername
ErrorPage
ForgotPassword/ForgotPasswordForm
Register/IdentifierRegisterForm
SocialLinkAccount
utils
pnpm-lock.yaml

View file

@ -65,8 +65,8 @@
"dnd-core": "^16.0.0",
"eslint": "^8.34.0",
"history": "^5.3.0",
"i18next": "^21.8.16",
"i18next-browser-languagedetector": "^6.1.4",
"i18next": "^22.4.15",
"i18next-browser-languagedetector": "^7.0.1",
"just-kebab-case": "^4.2.0",
"ky": "^0.33.0",
"lint-staged": "^13.0.0",
@ -89,7 +89,7 @@
"react-helmet": "^6.1.0",
"react-hook-form": "^7.34.0",
"react-hot-toast": "^2.2.0",
"react-i18next": "^11.18.3",
"react-i18next": "^12.2.0",
"react-markdown": "^8.0.0",
"react-modal": "^3.15.1",
"react-paginate": "^8.1.3",

View file

@ -1,7 +1,7 @@
import classNames from 'classnames';
import type { TFuncKey } from 'i18next';
import type { MouseEventHandler } from 'react';
import { useEffect, useMemo, useRef, useState } from 'react';
import type { TFuncKey } from 'react-i18next';
import { useTranslation } from 'react-i18next';
import Copy from '@/assets/images/copy.svg';

View file

@ -8,7 +8,7 @@ import * as styles from './DropdownItem.module.scss';
type Props = {
onClick?: (event: MouseEvent<HTMLDivElement> | KeyboardEvent<HTMLDivElement>) => void;
className?: string;
children: ReactNode | Record<string, unknown>;
children: ReactNode;
icon?: ReactNode;
iconClassName?: string;
type?: 'default' | 'danger';

View file

@ -0,0 +1,18 @@
import { type AdminConsoleKey } from '@logto/phrases';
import { useTranslation } from 'react-i18next';
type Props = {
forKey: AdminConsoleKey;
};
/**
* A component to render a dynamic translation key.
* Since `ReactNode` does not include vanilla objects while `JSX.Element` does. It's strange but no better way for now.
*
* @see https://github.com/i18next/i18next/issues/1852
*/
export default function DynamicT({ forKey }: Props) {
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
return <>{t(forKey)}</>;
}

View file

@ -3,6 +3,7 @@ import type { ReactNode } from 'react';
import { useTranslation } from 'react-i18next';
import Card from '../Card';
import DynamicT from '../DynamicT';
import TextLink from '../TextLink';
import * as styles from './index.module.scss';
@ -23,7 +24,7 @@ function FormCard({ title, description, learnMoreLink, children }: Props) {
<div className={styles.title}>{t(title)}</div>
{description && (
<div className={styles.description}>
{t(description)}
<DynamicT forKey={description} />
{learnMoreLink && (
<>
{' '}

View file

@ -6,6 +6,7 @@ import { useTranslation } from 'react-i18next';
import Tip from '@/assets/images/tip.svg';
import type DangerousRaw from '../DangerousRaw';
import DynamicT from '../DynamicT';
import IconButton from '../IconButton';
import Spacer from '../Spacer';
import { ToggleTip } from '../Tip';
@ -38,7 +39,7 @@ function FormField({
<div className={classNames(styles.field, className)}>
<div className={classNames(styles.headline, headlineClassName)}>
<div className={styles.title}>
{typeof title === 'string' ? t(title) : title}
{typeof title === 'string' ? <DynamicT forKey={title} /> : title}
{isMultiple && (
<span className={styles.multiple}>{t('general.multiple_form_field')}</span>
)}

View file

@ -5,6 +5,7 @@ import { useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import type DangerousRaw from '../DangerousRaw';
import DynamicT from '../DynamicT';
import * as styles from './Radio.module.scss';
@ -89,7 +90,7 @@ function Radio({
{children}
{type === 'plain' && <div className={styles.indicator} />}
{icon && <span className={styles.icon}>{icon}</span>}
{title && (typeof title === 'string' ? t(title) : title)}
{title && (typeof title === 'string' ? <DynamicT forKey={title} /> : title)}
{isDisabled && disabledLabel && (
<div className={classNames(styles.indicator, styles.disabledLabel)}>
{t(disabledLabel)}

View file

@ -5,6 +5,7 @@ import { useTranslation } from 'react-i18next';
import useTheme from '@/hooks/use-theme';
import DynamicT from '../DynamicT';
import TextLink from '../TextLink';
import * as styles from './TablePlaceholder.module.scss';
@ -27,7 +28,7 @@ function TablePlaceholder({ image, imageDark, title, description, learnMoreLink,
<div className={styles.image}>{theme === Theme.Light ? image : imageDark}</div>
<div className={styles.title}>{t(title)}</div>
<div className={styles.description}>
{t(description)}
<DynamicT forKey={description} />
{learnMoreLink && (
<>
{' '}

View file

@ -46,7 +46,7 @@ function UserAccountInformation({
primaryEmail && `${t('user_details.created_email')} ${primaryEmail}`,
primaryPhone && `${t('user_details.created_phone')} ${primaryPhone}`,
username && `${t('user_details.created_username')} ${username}`,
`${passwordLabel ?? t('user_details.created_password')} ${password}`
`${passwordLabel ?? t('user_details.created_username')} ${password}`
).join('\n');
await navigator.clipboard.writeText(content);

View file

@ -9,21 +9,21 @@ type TitlePlaceHolder = {
[key in ConnectorType]: AdminConsoleKey;
};
export const connectorTitlePlaceHolder: TitlePlaceHolder = Object.freeze({
export const connectorTitlePlaceHolder = Object.freeze({
[ConnectorType.Sms]: 'connectors.type.sms',
[ConnectorType.Email]: 'connectors.type.email',
[ConnectorType.Social]: 'connectors.type.social',
});
}) satisfies TitlePlaceHolder;
type ConnectorPlatformLabel = {
[key in ConnectorPlatform]: AdminConsoleKey;
};
export const connectorPlatformLabel: ConnectorPlatformLabel = Object.freeze({
export const connectorPlatformLabel = Object.freeze({
[ConnectorPlatform.Native]: 'connectors.platform.native',
[ConnectorPlatform.Universal]: 'connectors.platform.universal',
[ConnectorPlatform.Web]: 'connectors.platform.web',
});
}) satisfies ConnectorPlatformLabel;
type ConnectorPlaceholderIcon = {
[key in ConnectorType]?: SvgComponent;
@ -32,7 +32,7 @@ type ConnectorPlaceholderIcon = {
export const connectorPlaceholderIcon: ConnectorPlaceholderIcon = Object.freeze({
[ConnectorType.Sms]: SmsConnectorIcon,
[ConnectorType.Email]: EmailConnector,
} as const);
});
export const defaultSmsConnectorGroup: ConnectorGroup = {
id: 'default-sms-connector',

View file

@ -1,7 +1,7 @@
import classNames from 'classnames';
import type { ReactChild, ReactNode } from 'react';
import type { TFuncKey } from 'i18next';
import type { ReactNode } from 'react';
import { useMemo, useState } from 'react';
import type { TFuncKey } from 'react-i18next';
import { useTranslation } from 'react-i18next';
import { Link } from 'react-router-dom';
@ -10,7 +10,7 @@ import { getPath } from '../../utils';
import * as styles from './index.module.scss';
type Props = {
icon?: ReactChild;
icon?: ReactNode;
titleKey: TFuncKey<'translation', 'admin_console.tabs'>;
isActive?: boolean;
modal?: (isOpen: boolean, onCancel: () => void) => ReactNode;

View file

@ -1,6 +1,6 @@
import type { Optional } from '@silverhand/essentials';
import type { TFuncKey } from 'i18next';
import type { FC, ReactNode } from 'react';
import type { TFuncKey } from 'react-i18next';
import Role from '@/assets/images/role.svg';
import useDocumentationUrl from '@/hooks/use-documentation-url';

View file

@ -1,7 +1,7 @@
import { useLogto } from '@logto/react';
import { t } from 'i18next';
import { useCallback } from 'react';
import { toast } from 'react-hot-toast';
import { useTranslation } from 'react-i18next';
import useSWR from 'swr';
import { adminTenantEndpoint, meApi } from '@/consts';
@ -13,6 +13,7 @@ import useSwrFetcher from './use-swr-fetcher';
const useMeCustomData = () => {
const { isAuthenticated, error: authError } = useLogto();
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
const userId = useLogtoUserId();
const shouldFetch = isAuthenticated && !authError && userId;
const api = useStaticApi({ prefixUrl: adminTenantEndpoint, resourceIndicator: meApi.indicator });
@ -38,7 +39,7 @@ const useMeCustomData = () => {
.json();
await mutate(updated);
},
[api, mutate, userId]
[api, mutate, t, userId]
);
return {

View file

@ -2,8 +2,9 @@
import type { LocalePhrase } from '@logto/phrases';
declare module 'react-i18next' {
declare module 'i18next' {
interface CustomTypeOptions {
returnNull: false;
allowObjectInHTMLChildren: true;
resources: LocalePhrase;
}

View file

@ -2,6 +2,7 @@ import { conditional } from '@silverhand/essentials';
import classNames from 'classnames';
import { useTranslation } from 'react-i18next';
import DynamicT from '@/components/DynamicT';
import { Tooltip } from '@/components/Tip';
import { onKeyDownHandler } from '@/utils/a11y';
@ -52,7 +53,7 @@ function CardItem({
{icon && <span className={styles.icon}>{icon}</span>}
<div className={styles.content}>
<div>
{typeof title === 'string' ? t(title) : title}
{typeof title === 'string' ? <DynamicT forKey={title} /> : title}
{trailingTag && (
<span className={classNames(styles.tag, styles.trailingTag)}>{t(trailingTag)}</span>
)}

View file

@ -3,6 +3,7 @@ import classNames from 'classnames';
import type { ReactNode } from 'react';
import { useTranslation } from 'react-i18next';
import DynamicT from '@/components/DynamicT';
import type { PreviewPlatform } from '@/components/SignInExperiencePreview/types';
import { onKeyDownHandler } from '@/utils/a11y';
@ -32,7 +33,7 @@ function PlatformTab({ isSelected, icon, title, tab, onClick }: Props) {
})}
>
<span className={styles.icon}>{icon}</span>
{t(title)}
<DynamicT forKey={title} />
</div>
);
}

View file

@ -73,7 +73,7 @@ function ConnectorName({ connectorGroup, isDemo = false }: Props) {
platform && (
<div key={id} className={styles.platform}>
<ConnectorPlatformIcon platform={platform} />
{t(`${connectorPlatformLabel[platform]}`)}
{t(connectorPlatformLabel[platform])}
</div>
)
)}

View file

@ -7,6 +7,7 @@ import ArrowDown from '@/assets/images/arrow-down.svg';
import ArrowUp from '@/assets/images/arrow-up.svg';
import Tip from '@/assets/images/tip.svg';
import Card from '@/components/Card';
import DynamicT from '@/components/DynamicT';
import IconButton from '@/components/IconButton';
import { ToggleTip } from '@/components/Tip';
import type { Props as ToggleTipProps } from '@/components/Tip/ToggleTip';
@ -30,7 +31,7 @@ function Block({ variant = 'default', count, delta, title, tip }: Props) {
return (
<Card className={classNames(styles.block, styles[variant])}>
<div className={styles.title}>
{t(title)}
<DynamicT forKey={title} />
{tip && (
<ToggleTip anchorClassName={styles.toggleTipButton} content={tip}>
<IconButton size="small">

View file

@ -6,6 +6,7 @@ import { useTranslation } from 'react-i18next';
import TadaDark from '@/assets/images/tada-dark.svg';
import Tada from '@/assets/images/tada.svg';
import Dropdown, { DropdownItem } from '@/components/Dropdown';
import DynamicT from '@/components/DynamicT';
import Index from '@/components/Index';
import useTheme from '@/hooks/use-theme';
import useUserPreferences from '@/hooks/use-user-preferences';
@ -77,7 +78,7 @@ function GetStartedProgress() {
icon={<Index className={styles.index} index={index + 1} isComplete={isComplete} />}
onClick={onClick}
>
{t(title)}
<DynamicT forKey={title} />
</DropdownItem>
))}
</div>

View file

@ -5,6 +5,7 @@ import { cloneElement } from 'react';
import { useTranslation } from 'react-i18next';
import Button from '@/components/Button';
import DynamicT from '@/components/DynamicT';
import NotSet from '../NotSet';
@ -56,7 +57,7 @@ function CardContent<T extends Nullable<boolean | string | Record<string, unknow
cloneElement(icon, {
className: styles.icon,
})}
{typeof label === 'string' ? t(label) : label}
{typeof label === 'string' ? <DynamicT forKey={label} /> : label}
</div>
</td>
<td>{renderer(value)}</td>

View file

@ -44,7 +44,7 @@ function MainFlowLikeModal({ title, subtitle, subtitleProps, children, onClose,
{subtitle && (
<span className={styles.subtitle}>
<Trans components={{ strong: <span className={styles.strong} /> }}>
{t(subtitle, subtitleProps)}
{t(subtitle, subtitleProps ?? {})}
</Trans>
</span>
)}

View file

@ -3,6 +3,7 @@ import { getSafe } from '@silverhand/essentials';
import { detailedDiff } from 'deep-object-diff';
import { useTranslation } from 'react-i18next';
import DynamicT from '@/components/DynamicT';
import { signInIdentifierPhrase } from '@/pages/SignInExperience/constants';
import type { SignInMethod, SignInMethodsObject } from '@/pages/SignInExperience/types';
@ -55,7 +56,7 @@ function SignInDiffSection({ before, after, isAfter = false }: Props) {
return (
<li key={identifierKey}>
<DiffSegment hasChanged={hasIdentifierChanged(identifierKey)} isAfter={isAfter}>
{String(t(signInIdentifierPhrase[identifierKey]))}
<DynamicT forKey={signInIdentifierPhrase[identifierKey]} />
{hasAuthentication && ' ('}
{password && (
<DiffSegment
@ -65,7 +66,7 @@ function SignInDiffSection({ before, after, isAfter = false }: Props) {
{t('sign_in_exp.sign_up_and_sign_in.sign_in.password_auth')}
</DiffSegment>
)}
{needDisjunction && ` ${String(t('sign_in_exp.sign_up_and_sign_in.or'))} `}
{needDisjunction && ` ${t('sign_in_exp.sign_up_and_sign_in.or')} `}
{verificationCode && (
<DiffSegment
hasChanged={hasAuthenticationChanged(identifierKey, 'verificationCode')}

View file

@ -3,6 +3,7 @@ import { getSafe } from '@silverhand/essentials';
import { diff } from 'deep-object-diff';
import { useTranslation } from 'react-i18next';
import DynamicT from '@/components/DynamicT';
import { signUpIdentifierPhrase } from '@/pages/SignInExperience/constants';
import type { SignUpForm } from '@/pages/SignInExperience/types';
import { signInExperienceParser } from '@/pages/SignInExperience/utils/form';
@ -34,7 +35,7 @@ function SignUpDiffSection({ before, after, isAfter = false }: Props) {
<ul className={styles.list}>
<li>
<DiffSegment hasChanged={hasChanged('identifier')} isAfter={isAfter}>
{String(t(signUpIdentifierPhrase[identifier]))}
<DynamicT forKey={signUpIdentifierPhrase[identifier]} />
</DiffSegment>
{hasAuthentication && ' ('}
{password && (
@ -42,7 +43,7 @@ function SignUpDiffSection({ before, after, isAfter = false }: Props) {
{t('sign_in_exp.sign_up_and_sign_in.sign_up.set_a_password_option')}
</DiffSegment>
)}
{needConjunction && ` ${String(t('sign_in_exp.sign_up_and_sign_in.and'))} `}
{needConjunction && ` ${t('sign_in_exp.sign_up_and_sign_in.and')} `}
{verify && (
<DiffSegment hasChanged={hasChanged('verify')} isAfter={isAfter}>
{needConjunction

View file

@ -26,30 +26,30 @@ type SignInIdentifierPhrase = {
[key in SignInIdentifier]: AdminConsoleKey;
};
export const signInIdentifierPhrase: SignInIdentifierPhrase = Object.freeze({
export const signInIdentifierPhrase = Object.freeze({
[SignInIdentifier.Email]: 'sign_in_exp.sign_up_and_sign_in.identifiers_email',
[SignInIdentifier.Phone]: 'sign_in_exp.sign_up_and_sign_in.identifiers_phone',
[SignInIdentifier.Username]: 'sign_in_exp.sign_up_and_sign_in.identifiers_username',
} as const);
}) satisfies SignInIdentifierPhrase;
type SignUpIdentifierPhrase = {
[key in SignUpIdentifier]: AdminConsoleKey;
};
export const signUpIdentifierPhrase: SignUpIdentifierPhrase = Object.freeze({
export const signUpIdentifierPhrase = Object.freeze({
[SignUpIdentifier.Email]: 'sign_in_exp.sign_up_and_sign_in.identifiers_email',
[SignUpIdentifier.Phone]: 'sign_in_exp.sign_up_and_sign_in.identifiers_phone',
[SignUpIdentifier.Username]: 'sign_in_exp.sign_up_and_sign_in.identifiers_username',
[SignUpIdentifier.EmailOrSms]: 'sign_in_exp.sign_up_and_sign_in.identifiers_email_or_sms',
[SignUpIdentifier.None]: 'sign_in_exp.sign_up_and_sign_in.identifiers_none',
} as const);
}) satisfies SignUpIdentifierPhrase;
type NoConnectorWarningPhrase = {
[key in ConnectorType]: AdminConsoleKey;
};
export const noConnectorWarningPhrase: NoConnectorWarningPhrase = Object.freeze({
export const noConnectorWarningPhrase = Object.freeze({
[ConnectorType.Email]: 'sign_in_exp.setup_warning.no_connector_email',
[ConnectorType.Sms]: 'sign_in_exp.setup_warning.no_connector_sms',
[ConnectorType.Social]: 'sign_in_exp.setup_warning.no_connector_social',
} as const);
}) satisfies NoConnectorWarningPhrase;

View file

@ -52,7 +52,7 @@
"got": "^12.5.3",
"hash-wasm": "^4.9.0",
"helmet": "^6.0.1",
"i18next": "^21.8.16",
"i18next": "^22.4.15",
"iconv-lite": "0.6.3",
"jose": "^4.11.0",
"js-yaml": "^4.1.0",

View file

@ -35,15 +35,15 @@
"buffer": "^5.7.1",
"cross-env": "^7.0.3",
"eslint": "^8.34.0",
"i18next": "^21.8.16",
"i18next-browser-languagedetector": "^6.1.4",
"i18next": "^22.4.15",
"i18next-browser-languagedetector": "^7.0.1",
"lint-staged": "^13.0.0",
"parcel": "2.8.3",
"postcss": "^8.4.6",
"prettier": "^2.8.2",
"react": "^18.0.0",
"react-dom": "^18.0.0",
"react-i18next": "^11.18.3",
"react-i18next": "^12.2.0",
"stylelint": "^15.0.0",
"typescript": "^5.0.0",
"zod": "^3.20.2"

View file

@ -55,8 +55,8 @@
"color": "^4.2.3",
"cross-env": "^7.0.3",
"eslint": "^8.34.0",
"i18next": "^21.8.16",
"i18next-browser-languagedetector": "^6.1.4",
"i18next": "^22.4.15",
"i18next-browser-languagedetector": "^7.0.1",
"identity-obj-proxy": "^3.0.0",
"jest": "^29.5.0",
"jest-environment-jsdom": "^29.0.0",
@ -76,7 +76,7 @@
"react-dom": "^18.0.0",
"react-helmet": "^6.1.0",
"react-hook-form": "^7.34.0",
"react-i18next": "^11.18.3",
"react-i18next": "^12.2.0",
"react-modal": "^3.15.1",
"react-router-dom": "^6.10.0",
"react-string-replace": "^1.0.0",

View file

@ -1,7 +1,7 @@
import classNames from 'classnames';
import type { TFuncKey } from 'i18next';
import type { ReactNode } from 'react';
import { useContext } from 'react';
import type { TFuncKey } from 'react-i18next';
import PageContext from '@/Providers/PageContextProvider/PageContext';
import BrandingHeader from '@/components/BrandingHeader';

View file

@ -1,5 +1,5 @@
import type { TFuncKey } from 'i18next';
import { useTranslation } from 'react-i18next';
import type { TFuncKey } from 'react-i18next';
import NavBar from '@/components/NavBar';
import PageMeta from '@/components/PageMeta';
@ -38,9 +38,9 @@ const SecondaryPageLayout = ({
)}
<div className={styles.container}>
<div className={styles.header}>
<div className={styles.title}>{t(title, titleProps)}</div>
<div className={styles.title}>{t(title, titleProps ?? {})}</div>
{description && (
<div className={styles.description}>{t(description, descriptionProps)}</div>
<div className={styles.description}>{t(description, descriptionProps ?? {})}</div>
)}
</div>

View file

@ -1,7 +1,7 @@
import type { Nullable } from '@silverhand/essentials';
import classNames from 'classnames';
import type { TFuncKey } from 'i18next';
import { useTranslation } from 'react-i18next';
import type { TFuncKey } from 'react-i18next';
import * as styles from './index.module.scss';

View file

@ -1,6 +1,6 @@
import classNames from 'classnames';
import type { TFuncKey } from 'i18next';
import type { HTMLProps } from 'react';
import type { TFuncKey } from 'react-i18next';
import { useTranslation } from 'react-i18next';
import * as styles from './index.module.scss';

View file

@ -1,5 +1,5 @@
import type { TFuncKey } from 'i18next';
import type { ReactNode } from 'react';
import type { TFuncKey } from 'react-i18next';
export type ModalProps = {
className?: string;

View file

@ -1,6 +1,7 @@
import classNames from 'classnames';
import type { TFuncKey } from 'react-i18next';
import { useTranslation } from 'react-i18next';
import type { TFuncKey } from 'i18next';
import DynamicT from '../DynamicT';
import * as styles from './index.module.scss';
@ -10,12 +11,10 @@ type Props = {
};
const Divider = ({ className, label }: Props) => {
const { t } = useTranslation();
return (
<div className={classNames(styles.divider, className)}>
<i className={styles.line} />
{label && t(label)}
{label && <DynamicT forKey={label} />}
<i className={styles.line} />
</div>
);

View file

@ -0,0 +1,23 @@
import { type TFuncKey } from 'i18next';
import { useTranslation } from 'react-i18next';
type Props = {
forKey?: TFuncKey;
};
/**
* A component to render a dynamic translation key.
* Since `ReactNode` does not include vanilla objects while `JSX.Element` does. It's strange but no better way for now.
*
* @see https://github.com/i18next/i18next/issues/1852
*/
const DynamicT = ({ forKey }: Props) => {
const { t } = useTranslation();
if (!forKey) {
return null;
}
return <>{t(forKey)}</>;
};
export default DynamicT;

View file

@ -1,6 +1,6 @@
import classNames from 'classnames';
import type { TFuncKey } from 'i18next';
import type { ReactNode } from 'react';
import type { TFuncKey } from 'react-i18next';
import { useTranslation } from 'react-i18next';
import * as styles from './index.module.scss';

View file

@ -1,7 +1,6 @@
import { SignInIdentifier } from '@logto/schemas';
import i18next from 'i18next';
import type { HTMLProps } from 'react';
import type { TFuncKey } from 'react-i18next';
import { identifierInputPlaceholderMap } from '@/utils/form';
@ -20,7 +19,7 @@ export const getInputHtmlProps = (
pattern: '[0-9]*',
inputMode: 'numeric',
autoComplete: 'tel',
placeholder: i18next.t<'translation', TFuncKey>('input.phone_number'),
placeholder: i18next.t('input.phone_number'),
};
}
@ -29,7 +28,7 @@ export const getInputHtmlProps = (
type: 'email',
inputMode: 'email',
autoComplete: 'email',
placeholder: i18next.t<'translation', TFuncKey>('input.email'),
placeholder: i18next.t('input.email'),
};
}
@ -39,7 +38,7 @@ export const getInputHtmlProps = (
.map((type) => (type === SignInIdentifier.Phone ? 'tel' : type))
.join(' '),
placeholder: enabledTypes
.map((type) => i18next.t<'translation', TFuncKey>(identifierInputPlaceholderMap[type]))
.map((type) => i18next.t(identifierInputPlaceholderMap[type]))
.join(' / '),
};
};

View file

@ -1,5 +1,5 @@
import classNames from 'classnames';
import type { TFuncKey } from 'react-i18next';
import type { TFuncKey } from 'i18next';
import { useTranslation } from 'react-i18next';
import * as styles from './index.module.scss';

View file

@ -1,7 +1,8 @@
import { useAppInsights } from '@logto/app-insights/react';
import { type TFuncKey } from 'i18next';
import { useEffect, useState } from 'react';
import { Helmet } from 'react-helmet';
import { type TFuncKey, useTranslation } from 'react-i18next';
import { useTranslation } from 'react-i18next';
import { shouldTrack } from '@/utils/cookies';

View file

@ -1,7 +1,7 @@
import classNames from 'classnames';
import type { TFuncKey } from 'i18next';
import { useMemo } from 'react';
import type { ReactNode, AnchorHTMLAttributes } from 'react';
import type { TFuncKey } from 'react-i18next';
import { useTranslation } from 'react-i18next';
import type { LinkProps } from 'react-router-dom';
import { Link } from 'react-router-dom';
@ -9,6 +9,8 @@ import { Link } from 'react-router-dom';
import { useIframeModal } from '@/Providers/IframeModalProvider';
import usePlatform from '@/hooks/use-platform';
import DynamicT from '../DynamicT';
import * as styles from './index.module.scss';
export type Props = AnchorHTMLAttributes<HTMLAnchorElement> & {
@ -54,7 +56,7 @@ const TextLink = ({ className, children, text, icon, type = 'primary', to, ...re
return (
<Link className={classNames(styles.link, styles[type], className)} to={to} {...rest}>
{icon}
{children ?? (text ? t(text) : '')}
{children ?? <DynamicT forKey={text} />}
</Link>
);
}
@ -66,7 +68,7 @@ const TextLink = ({ className, children, text, icon, type = 'primary', to, ...re
rel="noopener"
>
{icon}
{children ?? (text ? t(text) : '')}
{children ?? <DynamicT forKey={text} />}
</a>
);
};

View file

@ -70,7 +70,7 @@ const Lite = ({ className, autoFocus, onSubmit, errorMessage, clearErrorMessage
if (errorMessage) {
return typeof errorMessage === 'string'
? t(`error.${errorMessage}`)
: t(`error.${errorMessage.code}`, errorMessage.data);
: t(`error.${errorMessage.code}`, errorMessage.data ?? {});
}
return true;

View file

@ -85,7 +85,7 @@ const SetPassword = ({
if (errorMessage) {
return typeof errorMessage === 'string'
? t(`error.${errorMessage}`)
: t(`error.${errorMessage.code}`, errorMessage.data);
: t(`error.${errorMessage.code}`, errorMessage.data ?? {});
}
return true;

View file

@ -1,7 +1,7 @@
import { SignInIdentifier } from '@logto/schemas';
import classNames from 'classnames';
import type { TFuncKey } from 'i18next';
import { useTranslation } from 'react-i18next';
import type { TFuncKey } from 'react-i18next';
import Button from '@/components/Button';
import Divider from '@/components/Divider';

View file

@ -2,7 +2,7 @@
import type { LocalePhrase } from '@logto/phrases-ui';
declare module 'react-i18next' {
declare module 'i18next' {
interface CustomTypeOptions {
allowObjectInHTMLChildren: true;
resources: LocalePhrase;

View file

@ -93,7 +93,7 @@ const IdentifierProfileForm = ({
if (errorMessage) {
return typeof errorMessage === 'string'
? t(`error.${errorMessage}`)
: t(`error.${errorMessage.code}`, errorMessage.data);
: t(`error.${errorMessage.code}`, errorMessage.data ?? {});
}
return true;

View file

@ -1,6 +1,6 @@
import type { MissingProfile } from '@logto/schemas';
import { SignInIdentifier } from '@logto/schemas';
import type { TFuncKey } from 'react-i18next';
import type { TFuncKey } from 'i18next';
import SecondaryPageLayout from '@/Layout/SecondaryPageLayout';
import useSendVerificationCode from '@/hooks/use-send-verification-code';

View file

@ -1,5 +1,5 @@
import { SignInIdentifier } from '@logto/schemas';
import type { TFuncKey } from 'react-i18next';
import type { TFuncKey } from 'i18next';
import SecondaryPageLayout from '@/Layout/SecondaryPageLayout';

View file

@ -1,6 +1,6 @@
import { Theme } from '@logto/schemas';
import type { TFuncKey } from 'i18next';
import { useContext } from 'react';
import type { TFuncKey } from 'react-i18next';
import { useTranslation } from 'react-i18next';
import StaticPageLayout from '@/Layout/StaticPageLayout';

View file

@ -94,7 +94,7 @@ const ForgotPasswordForm = ({
if (errorMessage) {
return typeof errorMessage === 'string'
? t(`error.${errorMessage}`)
: t(`error.${errorMessage.code}`, errorMessage.data);
: t(`error.${errorMessage.code}`, errorMessage.data ?? {});
}
return true;

View file

@ -81,7 +81,7 @@ const IdentifierRegisterForm = ({ className, autoFocus, signUpMethods }: Props)
if (errorMessage) {
return typeof errorMessage === 'string'
? t(`error.${errorMessage}`)
: t(`error.${errorMessage.code}`, errorMessage.data);
: t(`error.${errorMessage.code}`, errorMessage.data ?? {});
}
return true;

View file

@ -1,5 +1,5 @@
import { SignInIdentifier } from '@logto/schemas';
import type { TFuncKey } from 'react-i18next';
import type { TFuncKey } from 'i18next';
import { useParams, useLocation } from 'react-router-dom';
import { is } from 'superstruct';

View file

@ -1,8 +1,8 @@
import { usernameRegEx, emailRegEx } from '@logto/core-kit';
import { SignInIdentifier } from '@logto/schemas';
import i18next from 'i18next';
import type { TFuncKey } from 'i18next';
import { parsePhoneNumberWithError, ParseError } from 'libphonenumber-js/mobile';
import type { TFuncKey } from 'react-i18next';
import type { ErrorType } from '@/components/ErrorMessage';
import type { IdentifierInputType } from '@/components/InputFields/SmartInputField';
@ -108,14 +108,12 @@ export const getGeneralIdentifierErrorMessage = (
type: 'required' | 'invalid'
) => {
const data = {
types: enabledFields.map((field) =>
t<'translation', TFuncKey>(identifierInputDescriptionMap[field])
),
types: enabledFields.map((field) => t(identifierInputDescriptionMap[field])),
};
const code = type === 'required' ? 'error.general_required' : 'error.general_invalid';
return t<'translation', TFuncKey>(code, data);
return t(code, data);
};
export const parseIdentifierValue = (type?: IdentifierInputType, value?: string) => {

103
pnpm-lock.yaml generated
View file

@ -2895,11 +2895,11 @@ importers:
specifier: ^5.3.0
version: 5.3.0
i18next:
specifier: ^21.8.16
version: 21.8.16
specifier: ^22.4.15
version: 22.4.15
i18next-browser-languagedetector:
specifier: ^6.1.4
version: 6.1.4
specifier: ^7.0.1
version: 7.0.1
just-kebab-case:
specifier: ^4.2.0
version: 4.2.0
@ -2967,8 +2967,8 @@ importers:
specifier: ^2.2.0
version: 2.2.0(csstype@3.0.11)(react-dom@18.2.0)(react@18.2.0)
react-i18next:
specifier: ^11.18.3
version: 11.18.3(i18next@21.8.16)(react-dom@18.2.0)(react@18.2.0)
specifier: ^12.2.0
version: 12.2.0(i18next@22.4.15)(react-dom@18.2.0)(react@18.2.0)
react-markdown:
specifier: ^8.0.0
version: 8.0.0(@types/react@18.0.31)(react@18.2.0)
@ -3096,8 +3096,8 @@ importers:
specifier: ^6.0.1
version: 6.0.1
i18next:
specifier: ^21.8.16
version: 21.8.16
specifier: ^22.4.15
version: 22.4.15
iconv-lite:
specifier: 0.6.3
version: 0.6.3
@ -3331,11 +3331,11 @@ importers:
specifier: ^8.34.0
version: 8.34.0
i18next:
specifier: ^21.8.16
version: 21.8.16
specifier: ^22.4.15
version: 22.4.15
i18next-browser-languagedetector:
specifier: ^6.1.4
version: 6.1.4
specifier: ^7.0.1
version: 7.0.1
lint-staged:
specifier: ^13.0.0
version: 13.0.0
@ -3355,8 +3355,8 @@ importers:
specifier: ^18.0.0
version: 18.2.0(react@18.2.0)
react-i18next:
specifier: ^11.18.3
version: 11.18.3(i18next@21.8.16)(react-dom@18.2.0)(react@18.2.0)
specifier: ^12.2.0
version: 12.2.0(i18next@22.4.15)(react-dom@18.2.0)(react@18.2.0)
stylelint:
specifier: ^15.0.0
version: 15.0.0
@ -3892,11 +3892,11 @@ importers:
specifier: ^8.34.0
version: 8.34.0
i18next:
specifier: ^21.8.16
version: 21.8.16
specifier: ^22.4.15
version: 22.4.15
i18next-browser-languagedetector:
specifier: ^6.1.4
version: 6.1.4
specifier: ^7.0.1
version: 7.0.1
identity-obj-proxy:
specifier: ^3.0.0
version: 3.0.0
@ -3955,8 +3955,8 @@ importers:
specifier: ^7.34.0
version: 7.34.0(react@18.2.0)
react-i18next:
specifier: ^11.18.3
version: 11.18.3(i18next@21.8.16)(react-dom@18.2.0)(react@18.2.0)
specifier: ^12.2.0
version: 12.2.0(i18next@22.4.15)(react-dom@18.2.0)(react@18.2.0)
react-modal:
specifier: ^3.15.1
version: 3.15.1(react-dom@18.2.0)(react@18.2.0)
@ -6153,18 +6153,18 @@ packages:
regenerator-runtime: 0.13.11
dev: true
/@babel/runtime@7.18.3:
resolution: {integrity: sha512-38Y8f7YUhce/K7RMwTp7m0uCumpv9hZkitCbBClqQIow1qSbCvGkcegKOXpEWCQLfWmevgRiWokZ1GkpfhbZug==}
engines: {node: '>=6.9.0'}
dependencies:
regenerator-runtime: 0.13.9
/@babel/runtime@7.19.4:
resolution: {integrity: sha512-EXpLCrk55f+cYqmHsSR+yD/0gAIMxxA9QK9lnQWzhMCvt+YmoBN7Zx94s++Kv0+unHk39vxNO8t+CMA2WSS3wA==}
engines: {node: '>=6.9.0'}
dependencies:
regenerator-runtime: 0.13.11
/@babel/runtime@7.21.0:
resolution: {integrity: sha512-xwII0//EObnq89Ji5AKYQaRYiW/nZ3llSv29d49IuxPhKbtJoLP+9QUUZ4nVragQVtaVGeZrpB+ZtG/Pdy/POw==}
engines: {node: '>=6.9.0'}
dependencies:
regenerator-runtime: 0.13.11
/@babel/template@7.18.10:
resolution: {integrity: sha512-TI+rCtooWHr3QJ27kJxfjutghu44DLnasDMwpDqCXVTal9RLp3RSYNh4NdBrRP2cQAoG9A8juOQl6P6oZG4JxA==}
engines: {node: '>=6.9.0'}
@ -6207,7 +6207,7 @@ packages:
/@changesets/apply-release-plan@6.1.1:
resolution: {integrity: sha512-LaQiP/Wf0zMVR0HNrLQAjz3rsNsr0d/RlnP6Ef4oi8VafOwnY1EoWdK4kssuUJGgNgDyHpomS50dm8CU3D7k7g==}
dependencies:
'@babel/runtime': 7.19.4
'@babel/runtime': 7.21.0
'@changesets/config': 2.2.0
'@changesets/get-version-range-type': 0.3.2
'@changesets/git': 1.5.0
@ -6225,7 +6225,7 @@ packages:
/@changesets/assemble-release-plan@5.2.2:
resolution: {integrity: sha512-B1qxErQd85AeZgZFZw2bDKyOfdXHhG+X5S+W3Da2yCem8l/pRy4G/S7iOpEcMwg6lH8q2ZhgbZZwZ817D+aLuQ==}
dependencies:
'@babel/runtime': 7.19.4
'@babel/runtime': 7.21.0
'@changesets/errors': 0.1.4
'@changesets/get-dependents-graph': 1.3.4
'@changesets/types': 5.2.0
@ -6309,7 +6309,7 @@ packages:
/@changesets/get-release-plan@3.0.15:
resolution: {integrity: sha512-W1tFwxE178/en+zSj/Nqbc3mvz88mcdqUMJhRzN1jDYqN3QI4ifVaRF9mcWUU+KI0gyYEtYR65tour690PqTcA==}
dependencies:
'@babel/runtime': 7.19.4
'@babel/runtime': 7.21.0
'@changesets/assemble-release-plan': 5.2.2
'@changesets/config': 2.2.0
'@changesets/pre': 1.0.13
@ -6325,7 +6325,7 @@ packages:
/@changesets/git@1.5.0:
resolution: {integrity: sha512-Xo8AT2G7rQJSwV87c8PwMm6BAc98BnufRMsML7m7Iw8Or18WFvFmxqG5aOL5PBvhgq9KrKvaeIBNIymracSuHg==}
dependencies:
'@babel/runtime': 7.19.4
'@babel/runtime': 7.21.0
'@changesets/errors': 0.1.4
'@changesets/types': 5.2.0
'@manypkg/get-packages': 1.1.3
@ -6349,7 +6349,7 @@ packages:
/@changesets/pre@1.0.13:
resolution: {integrity: sha512-jrZc766+kGZHDukjKhpBXhBJjVQMied4Fu076y9guY1D3H622NOw8AQaLV3oQsDtKBTrT2AUFjt9Z2Y9Qx+GfA==}
dependencies:
'@babel/runtime': 7.19.4
'@babel/runtime': 7.21.0
'@changesets/errors': 0.1.4
'@changesets/types': 5.2.0
'@manypkg/get-packages': 1.1.3
@ -6359,7 +6359,7 @@ packages:
/@changesets/read@0.5.8:
resolution: {integrity: sha512-eYaNfxemgX7f7ELC58e7yqQICW5FB7V+bd1lKt7g57mxUrTveYME+JPaBPpYx02nP53XI6CQp6YxnR9NfmFPKw==}
dependencies:
'@babel/runtime': 7.19.4
'@babel/runtime': 7.21.0
'@changesets/git': 1.5.0
'@changesets/logger': 0.0.5
'@changesets/parse': 0.3.15
@ -6380,7 +6380,7 @@ packages:
/@changesets/write@0.2.1:
resolution: {integrity: sha512-KUd49nt2fnYdGixIqTi1yVE1nAoZYUMdtB3jBfp77IMqjZ65hrmZE5HdccDlTeClZN0420ffpnfET3zzeY8pdw==}
dependencies:
'@babel/runtime': 7.19.4
'@babel/runtime': 7.21.0
'@changesets/types': 5.2.0
fs-extra: 7.0.1
human-id: 1.0.2
@ -7191,7 +7191,7 @@ packages:
/@manypkg/find-root@1.1.0:
resolution: {integrity: sha512-mki5uBvhHzO8kYYix/WRy2WX8S3B5wdVSc9D6KcU5lQNglP2yt58/VfLuAK49glRXChosY8ap2oJ1qgma3GUVA==}
dependencies:
'@babel/runtime': 7.19.4
'@babel/runtime': 7.21.0
'@types/node': 12.20.55
find-up: 4.1.0
fs-extra: 8.1.0
@ -7200,7 +7200,7 @@ packages:
/@manypkg/get-packages@1.1.3:
resolution: {integrity: sha512-fo+QhuU3qE/2TQMQmbVMqaQ6EWbMhi4ABWP+O4AM1NqPBuy0OrApV5LO6BrrgnhtAHS2NH6RrVk9OL181tTi8A==}
dependencies:
'@babel/runtime': 7.19.4
'@babel/runtime': 7.21.0
'@changesets/types': 4.1.0
'@manypkg/find-root': 1.1.0
fs-extra: 8.1.0
@ -9001,7 +9001,7 @@ packages:
engines: {node: '>=14'}
dependencies:
'@babel/code-frame': 7.18.6
'@babel/runtime': 7.19.4
'@babel/runtime': 7.21.0
'@types/aria-query': 5.0.1
aria-query: 5.0.0
chalk: 4.1.2
@ -9975,7 +9975,7 @@ packages:
resolution: {integrity: sha512-o/HelwhuKpTj/frsOsbNLNgnNGVIFsVP/SW2BSF14gVl7kAfMOJ6/8wUAUvG1R1NHKrfG+2sHZTu0yauT1qBrA==}
engines: {node: '>=6.0'}
dependencies:
'@babel/runtime': 7.19.4
'@babel/runtime': 7.21.0
'@babel/runtime-corejs3': 7.19.4
dev: true
@ -10177,7 +10177,7 @@ packages:
/babel-plugin-macros@2.8.0:
resolution: {integrity: sha512-SEP5kJpfGYqYKpBrj5XU3ahw5p5GOHJ0U5ssOSQ/WBVdwkD2Dzlce95exQTs3jOVWPPKLBN2rlEWkCK7dSmLvg==}
dependencies:
'@babel/runtime': 7.19.4
'@babel/runtime': 7.21.0
cosmiconfig: 6.0.0
resolve: 1.22.1
dev: false
@ -11437,7 +11437,7 @@ packages:
/dom-helpers@3.4.0:
resolution: {integrity: sha512-LnuPJ+dwqKDIyotW1VzmOZ5TONUN7CwkCR5hrgawTUbkBGYdeoNLZo6nNfGkCrjtE1nXXaj7iMMpDa8/d9WoIA==}
dependencies:
'@babel/runtime': 7.19.4
'@babel/runtime': 7.21.0
dev: true
/dom-serializer@1.4.1:
@ -13293,16 +13293,16 @@ packages:
uuid: 8.3.2
uuid-parse: 1.1.0
/i18next-browser-languagedetector@6.1.4:
resolution: {integrity: sha512-wukWnFeU7rKIWT66VU5i8I+3Zc4wReGcuDK2+kuFhtoxBRGWGdvYI9UQmqNL/yQH1KogWwh+xGEaIPH8V/i2Zg==}
/i18next-browser-languagedetector@7.0.1:
resolution: {integrity: sha512-Pa5kFwaczXJAeHE56CHG2aWzFBMJNUNghf0Pm4SwSrEMps/PTKqW90EYWlIvhuYStf3Sn1K0vw+gH3+TLdkH1g==}
dependencies:
'@babel/runtime': 7.18.3
'@babel/runtime': 7.21.0
dev: true
/i18next@21.8.16:
resolution: {integrity: sha512-acJLCk38YMfEPjBR/1vS13SFY7rBQLs9E5m1tSRnWc9UW3f+SZszgH+NP1fZRA1+O+CdG2eLGGmuUMJW52EwzQ==}
/i18next@22.4.15:
resolution: {integrity: sha512-yYudtbFrrmWKLEhl6jvKUYyYunj4bTBCe2qIUYAxbXoPusY7YmdwPvOE6fx6UIfWvmlbCWDItr7wIs8KEBZ5Zg==}
dependencies:
'@babel/runtime': 7.18.3
'@babel/runtime': 7.21.0
/iconv-lite@0.4.24:
resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==}
@ -17439,8 +17439,8 @@ packages:
- csstype
dev: true
/react-i18next@11.18.3(i18next@21.8.16)(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-EttTX31HbqzZymUM3SIrMPuvamfSXFZVsDHm/ZAqoDfTLjhzlwyxqfbDNxcKNAGOi2mjZaXfR7hSNMlvLNpB/g==}
/react-i18next@12.2.0(i18next@22.4.15)(react-dom@18.2.0)(react@18.2.0):
resolution: {integrity: sha512-5XeVgSygaGfyFmDd2WcXvINRw2WEC1XviW1LXY/xLOEMzsCFRwKqfnHN+hUjla8ZipbVJR27GCMSuTr0BhBBBQ==}
peerDependencies:
i18next: '>= 19.0.0'
react: '>= 16.8.0 || ^18.0.0'
@ -17452,9 +17452,9 @@ packages:
react-native:
optional: true
dependencies:
'@babel/runtime': 7.18.3
'@babel/runtime': 7.21.0
html-parse-stringify: 3.0.1
i18next: 21.8.16
i18next: 22.4.15
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
dev: true
@ -17776,7 +17776,7 @@ packages:
/redux@4.1.2:
resolution: {integrity: sha512-SH8PglcebESbd/shgf6mii6EIoRM0zrQyjcuQ+ojmfxjTtE0z9Y8pa62iA/OJ58qjP6j27uyW4kUF4jl/jd6sw==}
dependencies:
'@babel/runtime': 7.19.4
'@babel/runtime': 7.21.0
dev: true
/refractor@3.6.0:
@ -17790,9 +17790,6 @@ packages:
/regenerator-runtime@0.13.11:
resolution: {integrity: sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==}
/regenerator-runtime@0.13.9:
resolution: {integrity: sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==}
/regexp-tree@0.1.24:
resolution: {integrity: sha512-s2aEVuLhvnVJW6s/iPgEGK6R+/xngd2jNQ+xy4bXNDKxZKJH6jpPHY6kVeVv1IeLCHgswRj+Kl3ELaDjG6V1iw==}
hasBin: true