* Add tests on plurals * Update `collabs_per_proj` and its pluralisations * Update `n_user` and its pluralisations * Update `showing_x_results` and its pluralisations * Update `show_x_more_projects` and its pluralisations * `bin/run web npm run extract-translations` * Populate `_plural` keys in non-en locales For 2-form languages (da, de, es, fi, fr, it, nl, no, pt, sv, tr), copy the existing bare-key value into the new `_plural` sibling to prevent i18next from falling back to English for count!=1. Also remove orphan singular keys (`collabs_per_proj_single`, `showing_1_result*`) left over from the previous commits. Bare-key values remain in their original plural form pending translator review — count=1 will still render the plural form in non-en until translators flip those to singular. Multi-form (cs, pl, ru) and single-form (ja, ko, zh-CN, zh-TW) locales are unchanged. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * Flip non-en bare-key values to singular form Per review, the i18next v3 plural convention uses the bare key for count=1 (singular) and `_plural` for count!=1. The non-en bare-key values were left as the original plural form by the previous commit so the `_plural` siblings could be copied from them; this commit flips the bare values to the singular form per language. Languages where singular and plural noun forms coincide (Finnish, Swedish, Turkish) are unchanged. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * Apply suggestions from code review Co-authored-by: Olzhas Askar <olzhas.askar@gmail.com> --------- Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com> Co-authored-by: Olzhas Askar <olzhas.askar@gmail.com> GitOrigin-RevId: 628513ca792c2dcce023247e52b7320e2741cc54
178 lines
5.7 KiB
TypeScript
178 lines
5.7 KiB
TypeScript
import OLBadge from '@/shared/components/ol/ol-badge'
|
|
import OLButton from '@/shared/components/ol/ol-button'
|
|
import OLCloseButton from '@/shared/components/ol/ol-close-button'
|
|
import OLCol from '@/shared/components/ol/ol-col'
|
|
import OLRow from '@/shared/components/ol/ol-row'
|
|
import MaterialIcon from '@/shared/components/material-icon'
|
|
import { PropsWithChildren } from 'react'
|
|
import { Container } from 'react-bootstrap'
|
|
import { Trans, useTranslation } from 'react-i18next'
|
|
|
|
type IconListItemProps = PropsWithChildren<{
|
|
icon: string
|
|
}>
|
|
|
|
function IconListItem({ icon, children }: IconListItemProps) {
|
|
return (
|
|
<li className="d-flex align-items-center">
|
|
<div className="flex-shrink-0">
|
|
<MaterialIcon type={icon} />
|
|
</div>
|
|
<div className="flex-grow-1 ms-2">{children}</div>
|
|
</li>
|
|
)
|
|
}
|
|
|
|
type PlansLinkProps = {
|
|
itmCampaign: string
|
|
onClick?: React.MouseEventHandler<HTMLAnchorElement>
|
|
}
|
|
function PlansLink({
|
|
children,
|
|
itmCampaign,
|
|
onClick,
|
|
}: PropsWithChildren<PlansLinkProps>) {
|
|
return (
|
|
<a
|
|
key="compare_plans_link"
|
|
href={`/user/subscription/choose-your-plan?itm-campaign=${itmCampaign}`}
|
|
target="_blank"
|
|
rel="noreferrer"
|
|
onClick={onClick}
|
|
>
|
|
{children}
|
|
<MaterialIcon type="open_in_new" />
|
|
</a>
|
|
)
|
|
}
|
|
|
|
type UpgradePromptProps = {
|
|
title: string
|
|
summary: string
|
|
onClose: () => void
|
|
planPricing: { student: string; standard: string }
|
|
itmCampaign: string
|
|
isStudent?: boolean
|
|
onClickInfoLink?: React.MouseEventHandler<HTMLAnchorElement>
|
|
onClickPaywall?: React.MouseEventHandler<HTMLAnchorElement>
|
|
}
|
|
|
|
export function UpgradePrompt({
|
|
title,
|
|
summary,
|
|
onClose,
|
|
planPricing,
|
|
itmCampaign,
|
|
isStudent = false,
|
|
onClickInfoLink,
|
|
onClickPaywall,
|
|
}: UpgradePromptProps) {
|
|
const { t } = useTranslation()
|
|
const planPrice = isStudent ? planPricing.student : planPricing.standard
|
|
const planCode = isStudent
|
|
? 'student_free_trial_7_days'
|
|
: 'collaborator_free_trial_7_days'
|
|
|
|
return (
|
|
<Container className="upgrade-prompt">
|
|
<OLRow className="justify-content-end">
|
|
<OLCloseButton onClick={() => onClose()} />
|
|
</OLRow>
|
|
<OLRow className="text-center">
|
|
<h2 className="my-0 upgrade-prompt-title">{title}</h2>
|
|
<p className="upgrade-prompt-summary">{summary}</p>
|
|
</OLRow>
|
|
<OLRow className="g-3">
|
|
<OLCol md={6} className="upgrade-prompt-card-container">
|
|
<div className="g-0 upgrade-prompt-card upgrade-prompt-card-premium">
|
|
<OLRow className="justify-content-between">
|
|
<OLCol>
|
|
<h3>{isStudent ? t('student') : t('standard')}</h3>
|
|
</OLCol>
|
|
<OLCol xs="auto">
|
|
<OLBadge className="badge-premium-gradient">
|
|
{t('recommended')}
|
|
</OLBadge>
|
|
</OLCol>
|
|
</OLRow>
|
|
<OLRow>
|
|
<p className="upgrade-prompt-price">
|
|
<span className="upgrade-prompt-price-number">{planPrice}</span>{' '}
|
|
{t('per_month')}
|
|
</p>
|
|
</OLRow>
|
|
<OLRow>
|
|
<ul className="upgrade-prompt-list">
|
|
<IconListItem icon="hourglass_top">
|
|
{t('24x_more_compile_time')}
|
|
</IconListItem>
|
|
<IconListItem icon="group_add">
|
|
{t('collabs_per_proj', { count: 10 })}
|
|
</IconListItem>
|
|
<IconListItem icon="history">
|
|
{t('unlimited_document_history')}
|
|
</IconListItem>
|
|
</ul>
|
|
</OLRow>
|
|
<OLRow className="mt-auto">
|
|
<a
|
|
className="btn btn-premium"
|
|
href={`/user/subscription/new?planCode=${planCode}&itm-campaign=${itmCampaign}`}
|
|
onClick={onClickPaywall}
|
|
target="_blank"
|
|
rel="noreferrer"
|
|
>
|
|
{t('try_for_free')}
|
|
</a>
|
|
</OLRow>
|
|
</div>
|
|
</OLCol>
|
|
<OLCol md={6} className="upgrade-prompt-card-container">
|
|
<div className="g-0 upgrade-prompt-card upgrade-prompt-card-free">
|
|
<OLRow>
|
|
<h3>{t('free')}</h3>
|
|
</OLRow>
|
|
<OLRow>
|
|
<p className="upgrade-prompt-price">
|
|
{/* Invisible span here to hold the correct height to match a card with a price */}
|
|
<span className="upgrade-prompt-price-number invisible" />
|
|
{t('your_current_plan')}
|
|
</p>
|
|
</OLRow>
|
|
<OLRow>
|
|
<ul className="upgrade-prompt-list">
|
|
<IconListItem icon="hourglass_bottom">
|
|
{t('basic_compile_time')}
|
|
</IconListItem>
|
|
<IconListItem icon="person">
|
|
{t('collabs_per_proj', { count: 1 })}
|
|
</IconListItem>
|
|
<IconListItem icon="history_off">
|
|
{t('limited_document_history')}
|
|
</IconListItem>
|
|
</ul>
|
|
</OLRow>
|
|
<OLRow className="mt-auto">
|
|
<OLButton variant="secondary" onClick={() => onClose()}>
|
|
{t('continue_with_free_plan')}
|
|
</OLButton>
|
|
</OLRow>
|
|
</div>
|
|
</OLCol>
|
|
</OLRow>
|
|
<OLRow className="text-center">
|
|
<p className="upgrade-prompt-all-plans">
|
|
{/* eslint-disable react/jsx-key */}
|
|
<Trans
|
|
i18nKey="compare_all_plans"
|
|
components={[
|
|
<PlansLink onClick={onClickInfoLink} itmCampaign={itmCampaign} />,
|
|
]}
|
|
/>
|
|
{/* eslint-disable react/jsx-key */}
|
|
</p>
|
|
</OLRow>
|
|
</Container>
|
|
)
|
|
}
|