mirror of
https://github.com/langgenius/dify.git
synced 2026-05-06 10:28:10 +08:00
refactor(i18n): use JSON with flattened key and namespace (#30114)
Co-authored-by: yyh <yuanyouhuilyz@gmail.com> Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
This commit is contained in:
@ -18,7 +18,12 @@ vi.mock('react-i18next', async (importOriginal) => {
|
||||
return {
|
||||
...actual,
|
||||
useTranslation: () => ({
|
||||
t: (key: string) => mockTranslations[key] ?? key,
|
||||
t: (key: string, options?: { ns?: string }) => {
|
||||
if (mockTranslations[key])
|
||||
return mockTranslations[key]
|
||||
const prefix = options?.ns ? `${options.ns}.` : ''
|
||||
return `${prefix}${key}`
|
||||
},
|
||||
}),
|
||||
}
|
||||
})
|
||||
|
||||
@ -22,8 +22,8 @@ const Footer = ({
|
||||
<div className={cn('flex max-w-[1680px] grow border-x border-divider-accent p-6', currentCategory === CategoryEnum.CLOUD ? 'justify-between' : 'justify-end')}>
|
||||
{currentCategory === CategoryEnum.CLOUD && (
|
||||
<div className="flex flex-col text-text-tertiary">
|
||||
<span className="system-xs-regular">{t('billing.plansCommon.taxTip')}</span>
|
||||
<span className="system-xs-regular">{t('billing.plansCommon.taxTipSecond')}</span>
|
||||
<span className="system-xs-regular">{t('plansCommon.taxTip', { ns: 'billing' })}</span>
|
||||
<span className="system-xs-regular">{t('plansCommon.taxTipSecond', { ns: 'billing' })}</span>
|
||||
</div>
|
||||
)}
|
||||
<span className="flex h-fit items-center gap-x-1 text-saas-dify-blue-accessible">
|
||||
@ -32,7 +32,7 @@ const Footer = ({
|
||||
className="system-md-regular"
|
||||
target="_blank"
|
||||
>
|
||||
{t('billing.plansCommon.comparePlanAndFeatures')}
|
||||
{t('plansCommon.comparePlanAndFeatures', { ns: 'billing' })}
|
||||
</Link>
|
||||
<RiArrowRightUpLine className="size-4" />
|
||||
</span>
|
||||
|
||||
@ -9,7 +9,12 @@ vi.mock('react-i18next', async (importOriginal) => {
|
||||
return {
|
||||
...actual,
|
||||
useTranslation: () => ({
|
||||
t: (key: string) => mockTranslations[key] ?? key,
|
||||
t: (key: string, options?: { ns?: string }) => {
|
||||
if (mockTranslations[key])
|
||||
return mockTranslations[key]
|
||||
const prefix = options?.ns ? `${options.ns}.` : ''
|
||||
return `${prefix}${key}`
|
||||
},
|
||||
}),
|
||||
}
|
||||
})
|
||||
|
||||
@ -21,11 +21,11 @@ const Header = ({
|
||||
<DifyLogo className="h-[27px] w-[60px]" />
|
||||
</div>
|
||||
<span className="bg-billing-plan-title-bg bg-clip-text px-1.5 font-instrument text-[37px] italic leading-[1.2] text-transparent">
|
||||
{t('billing.plansCommon.title.plans')}
|
||||
{t('plansCommon.title.plans', { ns: 'billing' })}
|
||||
</span>
|
||||
</div>
|
||||
<p className="system-sm-regular text-text-tertiary">
|
||||
{t('billing.plansCommon.title.description')}
|
||||
{t('plansCommon.title.description', { ns: 'billing' })}
|
||||
</p>
|
||||
<Button
|
||||
variant="secondary"
|
||||
|
||||
@ -41,10 +41,13 @@ vi.mock('react-i18next', async (importOriginal) => {
|
||||
return {
|
||||
...actual,
|
||||
useTranslation: () => ({
|
||||
t: (key: string, options?: { returnObjects?: boolean }) => {
|
||||
t: (key: string, options?: { returnObjects?: boolean, ns?: string }) => {
|
||||
if (options?.returnObjects)
|
||||
return mockTranslations[key] ?? []
|
||||
return mockTranslations[key] ?? key
|
||||
if (mockTranslations[key])
|
||||
return mockTranslations[key]
|
||||
const prefix = options?.ns ? `${options.ns}.` : ''
|
||||
return `${prefix}${key}`
|
||||
},
|
||||
}),
|
||||
Trans: ({ i18nKey }: { i18nKey: string }) => <span>{i18nKey}</span>,
|
||||
|
||||
@ -11,7 +11,12 @@ vi.mock('react-i18next', async (importOriginal) => {
|
||||
return {
|
||||
...actual,
|
||||
useTranslation: () => ({
|
||||
t: (key: string) => mockTranslations[key] ?? key,
|
||||
t: (key: string, options?: { ns?: string }) => {
|
||||
if (key in mockTranslations)
|
||||
return mockTranslations[key]
|
||||
const prefix = options?.ns ? `${options.ns}.` : ''
|
||||
return `${prefix}${key}`
|
||||
},
|
||||
}),
|
||||
}
|
||||
})
|
||||
@ -85,8 +90,8 @@ describe('PlanSwitcher', () => {
|
||||
it('should render tabs when translation strings are empty', () => {
|
||||
// Arrange
|
||||
mockTranslations = {
|
||||
'billing.plansCommon.cloud': '',
|
||||
'billing.plansCommon.self': '',
|
||||
'plansCommon.cloud': '',
|
||||
'plansCommon.self': '',
|
||||
}
|
||||
|
||||
// Act
|
||||
|
||||
@ -27,12 +27,12 @@ const PlanSwitcher: FC<PlanSwitcherProps> = ({
|
||||
const tabs = {
|
||||
cloud: {
|
||||
value: 'cloud' as Category,
|
||||
label: t('billing.plansCommon.cloud'),
|
||||
label: t('plansCommon.cloud', { ns: 'billing' }),
|
||||
Icon: Cloud,
|
||||
},
|
||||
self: {
|
||||
value: 'self' as Category,
|
||||
label: t('billing.plansCommon.self'),
|
||||
label: t('plansCommon.self', { ns: 'billing' }),
|
||||
Icon: SelfHosted,
|
||||
},
|
||||
}
|
||||
|
||||
@ -9,7 +9,12 @@ vi.mock('react-i18next', async (importOriginal) => {
|
||||
return {
|
||||
...actual,
|
||||
useTranslation: () => ({
|
||||
t: (key: string) => mockTranslations[key] ?? key,
|
||||
t: (key: string, options?: { ns?: string }) => {
|
||||
if (mockTranslations[key])
|
||||
return mockTranslations[key]
|
||||
const prefix = options?.ns ? `${options.ns}.` : ''
|
||||
return `${prefix}${key}`
|
||||
},
|
||||
}),
|
||||
}
|
||||
})
|
||||
|
||||
@ -30,7 +30,7 @@ const PlanRangeSwitcher: FC<PlanRangeSwitcherProps> = ({
|
||||
}}
|
||||
/>
|
||||
<span className="system-md-regular text-text-tertiary">
|
||||
{t('billing.plansCommon.annualBilling', { percent: 17 })}
|
||||
{t('plansCommon.annualBilling', { ns: 'billing', percent: 17 })}
|
||||
</span>
|
||||
</div>
|
||||
)
|
||||
|
||||
@ -35,7 +35,7 @@ const CloudPlanItem: FC<CloudPlanItemProps> = ({
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const [loading, setLoading] = React.useState(false)
|
||||
const i18nPrefix = `billing.plans.${plan}`
|
||||
const i18nPrefix = `plans.${plan}` as const
|
||||
const isFreePlan = plan === Plan.sandbox
|
||||
const isMostPopularPlan = plan === Plan.professional
|
||||
const planInfo = ALL_PLANS[plan]
|
||||
@ -48,12 +48,12 @@ const CloudPlanItem: FC<CloudPlanItemProps> = ({
|
||||
|
||||
const btnText = useMemo(() => {
|
||||
if (isCurrent)
|
||||
return t('billing.plansCommon.currentPlan')
|
||||
return t('plansCommon.currentPlan', { ns: 'billing' })
|
||||
|
||||
return ({
|
||||
[Plan.sandbox]: t('billing.plansCommon.startForFree'),
|
||||
[Plan.professional]: t('billing.plansCommon.startBuilding'),
|
||||
[Plan.team]: t('billing.plansCommon.getStarted'),
|
||||
[Plan.sandbox]: t('plansCommon.startForFree', { ns: 'billing' }),
|
||||
[Plan.professional]: t('plansCommon.startBuilding', { ns: 'billing' }),
|
||||
[Plan.team]: t('plansCommon.getStarted', { ns: 'billing' }),
|
||||
})[plan]
|
||||
}, [isCurrent, plan, t])
|
||||
|
||||
@ -67,7 +67,7 @@ const CloudPlanItem: FC<CloudPlanItemProps> = ({
|
||||
if (!isCurrentWorkspaceManager) {
|
||||
Toast.notify({
|
||||
type: 'error',
|
||||
message: t('billing.buyPermissionDeniedTip'),
|
||||
message: t('buyPermissionDeniedTip', { ns: 'billing' }),
|
||||
className: 'z-[1001]',
|
||||
})
|
||||
return
|
||||
@ -106,24 +106,24 @@ const CloudPlanItem: FC<CloudPlanItemProps> = ({
|
||||
{ICON_MAP[plan]}
|
||||
<div className="flex min-h-[104px] flex-col gap-y-2">
|
||||
<div className="flex items-center gap-x-2.5">
|
||||
<div className="text-[30px] font-medium leading-[1.2] text-text-primary">{t(`${i18nPrefix}.name` as any) as string}</div>
|
||||
<div className="text-[30px] font-medium leading-[1.2] text-text-primary">{t(`${i18nPrefix}.name`, { ns: 'billing' })}</div>
|
||||
{
|
||||
isMostPopularPlan && (
|
||||
<div className="flex items-center justify-center bg-saas-dify-blue-static px-1.5 py-1">
|
||||
<span className="system-2xs-semibold-uppercase text-text-primary-on-surface">
|
||||
{t('billing.plansCommon.mostPopular')}
|
||||
{t('plansCommon.mostPopular', { ns: 'billing' })}
|
||||
</span>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
<div className="system-sm-regular text-text-secondary">{t(`${i18nPrefix}.description` as any) as string}</div>
|
||||
<div className="system-sm-regular text-text-secondary">{t(`${i18nPrefix}.description`, { ns: 'billing' })}</div>
|
||||
</div>
|
||||
</div>
|
||||
{/* Price */}
|
||||
<div className="flex items-end gap-x-2 px-1 pb-8 pt-4">
|
||||
{isFreePlan && (
|
||||
<span className="title-4xl-semi-bold text-text-primary">{t('billing.plansCommon.free')}</span>
|
||||
<span className="title-4xl-semi-bold text-text-primary">{t('plansCommon.free', { ns: 'billing' })}</span>
|
||||
)}
|
||||
{!isFreePlan && (
|
||||
<>
|
||||
@ -138,8 +138,8 @@ const CloudPlanItem: FC<CloudPlanItemProps> = ({
|
||||
{isYear ? planInfo.price * 10 : planInfo.price}
|
||||
</span>
|
||||
<span className="system-md-regular pb-0.5 text-text-tertiary">
|
||||
{t('billing.plansCommon.priceTip')}
|
||||
{t(`billing.plansCommon.${!isYear ? 'month' : 'year'}`)}
|
||||
{t('plansCommon.priceTip', { ns: 'billing' })}
|
||||
{t(`plansCommon.${!isYear ? 'month' : 'year'}`, { ns: 'billing' })}
|
||||
</span>
|
||||
</>
|
||||
)}
|
||||
|
||||
@ -21,82 +21,82 @@ const List = ({
|
||||
<div className="flex flex-col gap-y-2.5 p-6">
|
||||
<Item
|
||||
label={isFreePlan
|
||||
? t('billing.plansCommon.messageRequest.title', { count: planInfo.messageRequest })
|
||||
: t('billing.plansCommon.messageRequest.titlePerMonth', { count: planInfo.messageRequest })}
|
||||
tooltip={t('billing.plansCommon.messageRequest.tooltip') as string}
|
||||
? t('plansCommon.messageRequest.title', { ns: 'billing', count: planInfo.messageRequest })
|
||||
: t('plansCommon.messageRequest.titlePerMonth', { ns: 'billing', count: planInfo.messageRequest })}
|
||||
tooltip={t('plansCommon.messageRequest.tooltip', { ns: 'billing' }) as string}
|
||||
/>
|
||||
<Item
|
||||
label={t('billing.plansCommon.teamWorkspace', { count: planInfo.teamWorkspace })}
|
||||
label={t('plansCommon.teamWorkspace', { ns: 'billing', count: planInfo.teamWorkspace })}
|
||||
/>
|
||||
<Item
|
||||
label={t('billing.plansCommon.teamMember', { count: planInfo.teamMembers })}
|
||||
label={t('plansCommon.teamMember', { ns: 'billing', count: planInfo.teamMembers })}
|
||||
/>
|
||||
<Item
|
||||
label={t('billing.plansCommon.buildApps', { count: planInfo.buildApps })}
|
||||
label={t('plansCommon.buildApps', { ns: 'billing', count: planInfo.buildApps })}
|
||||
/>
|
||||
<Divider bgStyle="gradient" />
|
||||
<Item
|
||||
label={t('billing.plansCommon.documents', { count: planInfo.documents })}
|
||||
tooltip={t('billing.plansCommon.documentsTooltip') as string}
|
||||
label={t('plansCommon.documents', { ns: 'billing', count: planInfo.documents })}
|
||||
tooltip={t('plansCommon.documentsTooltip', { ns: 'billing' }) as string}
|
||||
/>
|
||||
<Item
|
||||
label={t('billing.plansCommon.vectorSpace', { size: planInfo.vectorSpace })}
|
||||
tooltip={t('billing.plansCommon.vectorSpaceTooltip') as string}
|
||||
label={t('plansCommon.vectorSpace', { ns: 'billing', size: planInfo.vectorSpace })}
|
||||
tooltip={t('plansCommon.vectorSpaceTooltip', { ns: 'billing' }) as string}
|
||||
/>
|
||||
<Item
|
||||
label={t('billing.plansCommon.documentsRequestQuota', { count: planInfo.documentsRequestQuota })}
|
||||
tooltip={t('billing.plansCommon.documentsRequestQuotaTooltip')}
|
||||
label={t('plansCommon.documentsRequestQuota', { ns: 'billing', count: planInfo.documentsRequestQuota })}
|
||||
tooltip={t('plansCommon.documentsRequestQuotaTooltip', { ns: 'billing' })}
|
||||
/>
|
||||
<Item
|
||||
label={[t(`billing.plansCommon.priority.${planInfo.documentProcessingPriority}`), t('billing.plansCommon.documentProcessingPriority')].join('')}
|
||||
label={[t(`plansCommon.priority.${planInfo.documentProcessingPriority}`, { ns: 'billing' }), t('plansCommon.documentProcessingPriority', { ns: 'billing' })].join('')}
|
||||
/>
|
||||
<Divider bgStyle="gradient" />
|
||||
<Item
|
||||
label={
|
||||
planInfo.triggerEvents === NUM_INFINITE
|
||||
? t('billing.plansCommon.triggerEvents.unlimited')
|
||||
? t('plansCommon.triggerEvents.unlimited', { ns: 'billing' })
|
||||
: plan === Plan.sandbox
|
||||
? t('billing.plansCommon.triggerEvents.sandbox', { count: planInfo.triggerEvents })
|
||||
: t('billing.plansCommon.triggerEvents.professional', { count: planInfo.triggerEvents })
|
||||
? t('plansCommon.triggerEvents.sandbox', { ns: 'billing', count: planInfo.triggerEvents })
|
||||
: t('plansCommon.triggerEvents.professional', { ns: 'billing', count: planInfo.triggerEvents })
|
||||
}
|
||||
tooltip={t('billing.plansCommon.triggerEvents.tooltip') as string}
|
||||
tooltip={t('plansCommon.triggerEvents.tooltip', { ns: 'billing' }) as string}
|
||||
/>
|
||||
<Item
|
||||
label={
|
||||
plan === Plan.sandbox
|
||||
? t('billing.plansCommon.startNodes.limited', { count: 2 })
|
||||
: t('billing.plansCommon.startNodes.unlimited')
|
||||
? t('plansCommon.startNodes.limited', { ns: 'billing', count: 2 })
|
||||
: t('plansCommon.startNodes.unlimited', { ns: 'billing' })
|
||||
}
|
||||
/>
|
||||
<Item
|
||||
label={
|
||||
plan === Plan.sandbox
|
||||
? t('billing.plansCommon.workflowExecution.standard')
|
||||
? t('plansCommon.workflowExecution.standard', { ns: 'billing' })
|
||||
: plan === Plan.professional
|
||||
? t('billing.plansCommon.workflowExecution.faster')
|
||||
: t('billing.plansCommon.workflowExecution.priority')
|
||||
? t('plansCommon.workflowExecution.faster', { ns: 'billing' })
|
||||
: t('plansCommon.workflowExecution.priority', { ns: 'billing' })
|
||||
}
|
||||
tooltip={t('billing.plansCommon.workflowExecution.tooltip') as string}
|
||||
tooltip={t('plansCommon.workflowExecution.tooltip', { ns: 'billing' }) as string}
|
||||
/>
|
||||
<Divider bgStyle="gradient" />
|
||||
<Item
|
||||
label={t('billing.plansCommon.annotatedResponse.title', { count: planInfo.annotatedResponse })}
|
||||
tooltip={t('billing.plansCommon.annotatedResponse.tooltip') as string}
|
||||
label={t('plansCommon.annotatedResponse.title', { ns: 'billing', count: planInfo.annotatedResponse })}
|
||||
tooltip={t('plansCommon.annotatedResponse.tooltip', { ns: 'billing' }) as string}
|
||||
/>
|
||||
<Item
|
||||
label={t('billing.plansCommon.logsHistory', { days: planInfo.logHistory === NUM_INFINITE ? t('billing.plansCommon.unlimited') as string : `${planInfo.logHistory} ${t('billing.plansCommon.days')}` })}
|
||||
label={t('plansCommon.logsHistory', { ns: 'billing', days: planInfo.logHistory === NUM_INFINITE ? t('plansCommon.unlimited', { ns: 'billing' }) as string : `${planInfo.logHistory} ${t('plansCommon.days', { ns: 'billing' })}` })}
|
||||
/>
|
||||
<Item
|
||||
label={
|
||||
planInfo.apiRateLimit === NUM_INFINITE
|
||||
? t('billing.plansCommon.unlimitedApiRate')
|
||||
: `${t('billing.plansCommon.apiRateLimitUnit', { count: planInfo.apiRateLimit })} ${t('billing.plansCommon.apiRateLimit')}/${t('billing.plansCommon.month')}`
|
||||
? t('plansCommon.unlimitedApiRate', { ns: 'billing' })
|
||||
: `${t('plansCommon.apiRateLimitUnit', { ns: 'billing', count: planInfo.apiRateLimit })} ${t('plansCommon.apiRateLimit', { ns: 'billing' })}/${t('plansCommon.month', { ns: 'billing' })}`
|
||||
}
|
||||
tooltip={planInfo.apiRateLimit === NUM_INFINITE ? undefined : t('billing.plansCommon.apiRateLimitTooltip') as string}
|
||||
tooltip={planInfo.apiRateLimit === NUM_INFINITE ? undefined : t('plansCommon.apiRateLimitTooltip', { ns: 'billing' }) as string}
|
||||
/>
|
||||
<Divider bgStyle="gradient" />
|
||||
<Item
|
||||
label={t('billing.plansCommon.modelProviders')}
|
||||
label={t('plansCommon.modelProviders', { ns: 'billing' })}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
|
||||
@ -25,7 +25,7 @@ const Button = ({
|
||||
}: ButtonProps) => {
|
||||
const { t } = useTranslation()
|
||||
const { theme } = useTheme()
|
||||
const i18nPrefix = `billing.plans.${plan}`
|
||||
const i18nPrefix = `plans.${plan}` as const
|
||||
const isPremiumPlan = plan === SelfHostedPlan.premium
|
||||
const AwsMarketplace = useMemo(() => {
|
||||
return theme === Theme.light ? AwsMarketplaceLight : AwsMarketplaceDark
|
||||
@ -42,7 +42,7 @@ const Button = ({
|
||||
onClick={handleGetPayUrl}
|
||||
>
|
||||
<div className="flex grow items-center gap-x-2">
|
||||
<span>{t(`${i18nPrefix}.btnText` as any) as string}</span>
|
||||
<span>{t(`${i18nPrefix}.btnText`, { ns: 'billing' })}</span>
|
||||
{isPremiumPlan && (
|
||||
<span className="pb-px pt-[7px]">
|
||||
<AwsMarketplace className="h-6" />
|
||||
|
||||
@ -16,12 +16,13 @@ const featuresTranslations: Record<string, string[]> = {
|
||||
vi.mock('react-i18next', () => ({
|
||||
useTranslation: () => ({
|
||||
t: (key: string, options?: Record<string, unknown>) => {
|
||||
const prefix = options?.ns ? `${options.ns}.` : ''
|
||||
if (options?.returnObjects)
|
||||
return featuresTranslations[key] || []
|
||||
return key
|
||||
return featuresTranslations[`${prefix}${key}`] || []
|
||||
return `${prefix}${key}`
|
||||
},
|
||||
}),
|
||||
Trans: ({ i18nKey }: { i18nKey: string }) => <span>{i18nKey}</span>,
|
||||
Trans: ({ i18nKey, ns }: { i18nKey: string, ns?: string }) => <span>{ns ? `${ns}.${i18nKey}` : i18nKey}</span>,
|
||||
}))
|
||||
|
||||
vi.mock('../../../../base/toast', () => ({
|
||||
|
||||
@ -47,7 +47,7 @@ const SelfHostedPlanItem: FC<SelfHostedPlanItemProps> = ({
|
||||
plan,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const i18nPrefix = `billing.plans.${plan}`
|
||||
const i18nPrefix = `plans.${plan}` as const
|
||||
const isFreePlan = plan === SelfHostedPlan.community
|
||||
const isPremiumPlan = plan === SelfHostedPlan.premium
|
||||
const isEnterprisePlan = plan === SelfHostedPlan.enterprise
|
||||
@ -58,7 +58,7 @@ const SelfHostedPlanItem: FC<SelfHostedPlanItemProps> = ({
|
||||
if (!isCurrentWorkspaceManager) {
|
||||
Toast.notify({
|
||||
type: 'error',
|
||||
message: t('billing.buyPermissionDeniedTip'),
|
||||
message: t('buyPermissionDeniedTip', { ns: 'billing' }),
|
||||
className: 'z-[1001]',
|
||||
})
|
||||
return
|
||||
@ -85,16 +85,16 @@ const SelfHostedPlanItem: FC<SelfHostedPlanItemProps> = ({
|
||||
<div className=" flex flex-col gap-y-6 px-1 pt-10">
|
||||
{STYLE_MAP[plan].icon}
|
||||
<div className="flex min-h-[104px] flex-col gap-y-2">
|
||||
<div className="text-[30px] font-medium leading-[1.2] text-text-primary">{t(`${i18nPrefix}.name` as any) as string}</div>
|
||||
<div className="system-md-regular line-clamp-2 text-text-secondary">{t(`${i18nPrefix}.description` as any) as string}</div>
|
||||
<div className="text-[30px] font-medium leading-[1.2] text-text-primary">{t(`${i18nPrefix}.name`, { ns: 'billing' })}</div>
|
||||
<div className="system-md-regular line-clamp-2 text-text-secondary">{t(`${i18nPrefix}.description`, { ns: 'billing' })}</div>
|
||||
</div>
|
||||
</div>
|
||||
{/* Price */}
|
||||
<div className="flex items-end gap-x-2 px-1 pb-8 pt-4">
|
||||
<div className="title-4xl-semi-bold shrink-0 text-text-primary">{t(`${i18nPrefix}.price` as any) as string}</div>
|
||||
<div className="title-4xl-semi-bold shrink-0 text-text-primary">{t(`${i18nPrefix}.price`, { ns: 'billing' })}</div>
|
||||
{!isFreePlan && (
|
||||
<span className="system-md-regular pb-0.5 text-text-tertiary">
|
||||
{t(`${i18nPrefix}.priceTip` as any) as string}
|
||||
{t(`${i18nPrefix}.priceTip`, { ns: 'billing' })}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
@ -115,7 +115,7 @@ const SelfHostedPlanItem: FC<SelfHostedPlanItemProps> = ({
|
||||
</div>
|
||||
</div>
|
||||
<span className="system-xs-regular text-text-tertiary">
|
||||
{t('billing.plans.premium.comingSoon')}
|
||||
{t('plans.premium.comingSoon', { ns: 'billing' })}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@ -8,10 +8,11 @@ vi.mock('react-i18next', () => ({
|
||||
t: (key: string, options?: Record<string, unknown>) => {
|
||||
if (options?.returnObjects)
|
||||
return ['Feature A', 'Feature B']
|
||||
return key
|
||||
const prefix = options?.ns ? `${options.ns}.` : ''
|
||||
return `${prefix}${key}`
|
||||
},
|
||||
}),
|
||||
Trans: ({ i18nKey }: { i18nKey: string }) => <span>{i18nKey}</span>,
|
||||
Trans: ({ i18nKey, ns }: { i18nKey: string, ns?: string }) => <span>{ns ? `${ns}.${i18nKey}` : i18nKey}</span>,
|
||||
}))
|
||||
|
||||
describe('SelfHostedPlanItem/List', () => {
|
||||
|
||||
@ -11,14 +11,15 @@ const List = ({
|
||||
plan,
|
||||
}: ListProps) => {
|
||||
const { t } = useTranslation()
|
||||
const i18nPrefix = `billing.plans.${plan}`
|
||||
const features = t(`${i18nPrefix}.features` as any, { returnObjects: true }) as unknown as string[]
|
||||
const i18nPrefix = `plans.${plan}` as const
|
||||
const features = t(`${i18nPrefix}.features`, { ns: 'billing', returnObjects: true }) as string[]
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-y-[10px] p-6">
|
||||
<div className="system-md-semibold text-text-secondary">
|
||||
<Trans
|
||||
i18nKey={t(`${i18nPrefix}.includesTitle` as any) as string}
|
||||
i18nKey={`${i18nPrefix}.includesTitle`}
|
||||
ns="billing"
|
||||
components={{ highlight: <span className="text-text-warning"></span> }}
|
||||
/>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user