Files
Verso/services/web/frontend/js/shared/components/upgrade-prompt.tsx
T
0e4fe4090a [web] Migrate manual plurals to i18next _plural convention (#33989)
* 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
2026-06-02 08:07:17 +00:00

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>
)
}