chore: bump version to 2.23.12 and update billing localization (#449)
* chore: bump version to 2.23.12 and update billing localization - Updated application version from 2.23.11 to 2.23.12 in package.json. - Added new localization key for billing: "perUnitShort" to enhance user clarity in billing plans. - Improved unit label handling in LineItemDetails and Tiers components for better internationalization support. - Adjusted pricing table to conditionally display unit labels based on item type. * fix(billing): update pluralization in billing localization and component logic - Adjusted the localization key for "fromPreviousTierUpTo" to include pluralization support for unit labels. - Enhanced the LineItemDetails component to calculate and display the range count between previous and current tiers, improving clarity in billing information.
This commit is contained in:
committed by
GitHub
parent
9355c0a614
commit
58f08c5f39
@@ -17,12 +17,41 @@ export function LineItemDetails(
|
||||
lineItems: z.infer<typeof LineItemSchema>[];
|
||||
currency: string;
|
||||
selectedInterval?: string | undefined;
|
||||
alwaysDisplayMonthlyPrice?: boolean;
|
||||
}>,
|
||||
) {
|
||||
const { t, i18n } = useTranslation();
|
||||
const locale = i18n.language;
|
||||
const currencyCode = props?.currency.toLowerCase();
|
||||
|
||||
const shouldDisplayMonthlyPrice =
|
||||
props.alwaysDisplayMonthlyPrice && props.selectedInterval === 'year';
|
||||
|
||||
const getUnitLabel = (unit: string | undefined, count: number) => {
|
||||
if (!unit) {
|
||||
return '';
|
||||
}
|
||||
|
||||
const i18nKey = `billing:units.${unit}`;
|
||||
|
||||
if (!i18n.exists(i18nKey)) {
|
||||
return unit;
|
||||
}
|
||||
|
||||
return t(i18nKey, {
|
||||
count,
|
||||
defaultValue: unit,
|
||||
});
|
||||
};
|
||||
|
||||
const getDisplayCost = (cost: number, hasTiers: boolean) => {
|
||||
if (shouldDisplayMonthlyPrice && !hasTiers) {
|
||||
return cost / 12;
|
||||
}
|
||||
|
||||
return cost;
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={'flex flex-col space-y-1'}>
|
||||
{props.lineItems.map((item, index) => {
|
||||
@@ -68,6 +97,12 @@ export function LineItemDetails(
|
||||
</If>
|
||||
);
|
||||
|
||||
const unit =
|
||||
item.unit ?? (item.type === 'per_seat' ? 'member' : undefined);
|
||||
|
||||
const hasTiers = Boolean(item.tiers?.length);
|
||||
const isDefaultSeatUnit = unit === 'member';
|
||||
|
||||
const FlatFee = () => (
|
||||
<div className={'flex flex-col'}>
|
||||
<div className={cn(className, 'space-x-1')}>
|
||||
@@ -99,7 +134,7 @@ export function LineItemDetails(
|
||||
<span className={'text-xs font-semibold'}>
|
||||
{formatCurrency({
|
||||
currencyCode,
|
||||
value: item.cost,
|
||||
value: getDisplayCost(item.cost, hasTiers),
|
||||
locale,
|
||||
})}
|
||||
</span>
|
||||
@@ -116,14 +151,14 @@ export function LineItemDetails(
|
||||
<Trans
|
||||
i18nKey={'billing:perUnit'}
|
||||
values={{
|
||||
unit: t(`billing:units.${item.unit}`, { count: 1 }),
|
||||
unit: getUnitLabel(unit, 1),
|
||||
}}
|
||||
/>
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
|
||||
<Tiers item={item} currency={props.currency} />
|
||||
<Tiers item={item} currency={props.currency} unit={unit} />
|
||||
</If>
|
||||
</div>
|
||||
);
|
||||
@@ -135,16 +170,27 @@ export function LineItemDetails(
|
||||
<PlusSquare className={'w-3'} />
|
||||
|
||||
<span>
|
||||
<Trans i18nKey={'billing:perTeamMember'} />
|
||||
<If
|
||||
condition={Boolean(unit) && !isDefaultSeatUnit}
|
||||
fallback={<Trans i18nKey={'billing:perTeamMember'} />}
|
||||
>
|
||||
<Trans
|
||||
i18nKey={'billing:perUnitShort'}
|
||||
values={{
|
||||
unit: getUnitLabel(unit, 1),
|
||||
}}
|
||||
/>
|
||||
</If>
|
||||
</span>
|
||||
|
||||
<span>-</span>
|
||||
|
||||
<If condition={!item.tiers?.length}>
|
||||
<span>-</span>
|
||||
|
||||
<span className={'font-semibold'}>
|
||||
{formatCurrency({
|
||||
currencyCode,
|
||||
value: item.cost,
|
||||
value: getDisplayCost(item.cost, hasTiers),
|
||||
locale,
|
||||
})}
|
||||
</span>
|
||||
@@ -155,7 +201,7 @@ export function LineItemDetails(
|
||||
<SetupFee />
|
||||
|
||||
<If condition={item.tiers?.length}>
|
||||
<Tiers item={item} currency={props.currency} />
|
||||
<Tiers item={item} currency={props.currency} unit={unit} />
|
||||
</If>
|
||||
</div>
|
||||
);
|
||||
@@ -172,7 +218,7 @@ export function LineItemDetails(
|
||||
<Trans
|
||||
i18nKey={'billing:perUnit'}
|
||||
values={{
|
||||
unit: t(`billing:units.${item.unit}`, { count: 1 }),
|
||||
unit: getUnitLabel(unit, 1),
|
||||
}}
|
||||
/>
|
||||
</span>
|
||||
@@ -185,7 +231,7 @@ export function LineItemDetails(
|
||||
<span className={'font-semibold'}>
|
||||
{formatCurrency({
|
||||
currencyCode,
|
||||
value: item.cost,
|
||||
value: getDisplayCost(item.cost, hasTiers),
|
||||
locale,
|
||||
})}
|
||||
</span>
|
||||
@@ -196,7 +242,7 @@ export function LineItemDetails(
|
||||
|
||||
{/* If there are tiers, we render them as a list */}
|
||||
<If condition={item.tiers?.length}>
|
||||
<Tiers item={item} currency={props.currency} />
|
||||
<Tiers item={item} currency={props.currency} unit={unit} />
|
||||
</If>
|
||||
</div>
|
||||
);
|
||||
@@ -220,11 +266,12 @@ export function LineItemDetails(
|
||||
function Tiers({
|
||||
currency,
|
||||
item,
|
||||
unit,
|
||||
}: {
|
||||
currency: string;
|
||||
item: z.infer<typeof LineItemSchema>;
|
||||
unit?: string;
|
||||
}) {
|
||||
const unitKey = `billing:units.${item.unit}`;
|
||||
const { t, i18n } = useTranslation();
|
||||
const locale = i18n.language;
|
||||
|
||||
@@ -236,6 +283,15 @@ function Tiers({
|
||||
return Number.isNaN(num) ? 2 : num;
|
||||
};
|
||||
|
||||
const getUnitLabel = (count: number) => {
|
||||
if (!unit) return '';
|
||||
|
||||
return t(`billing:units.${unit}`, {
|
||||
count,
|
||||
defaultValue: unit,
|
||||
});
|
||||
};
|
||||
|
||||
const tiers = item.tiers?.map((tier, index) => {
|
||||
const tiersLength = item.tiers?.length ?? 0;
|
||||
const previousTier = item.tiers?.[index - 1];
|
||||
@@ -249,6 +305,13 @@ function Tiers({
|
||||
: previousTier.upTo + 1 || 0;
|
||||
|
||||
const upTo = tier.upTo;
|
||||
const previousTierUpTo =
|
||||
typeof previousTier?.upTo === 'number' ? previousTier.upTo : undefined;
|
||||
const currentTierUpTo = typeof upTo === 'number' ? upTo : undefined;
|
||||
const rangeCount =
|
||||
previousTierUpTo !== undefined && currentTierUpTo !== undefined
|
||||
? currentTierUpTo - previousTierUpTo
|
||||
: undefined;
|
||||
const isIncluded = tier.cost === 0;
|
||||
|
||||
return (
|
||||
@@ -267,9 +330,7 @@ function Tiers({
|
||||
<Trans
|
||||
i18nKey={'billing:andAbove'}
|
||||
values={{
|
||||
unit: t(unitKey, {
|
||||
count: getSafeCount(previousTierFrom) - 1,
|
||||
}),
|
||||
unit: getUnitLabel(getSafeCount(previousTierFrom) - 1),
|
||||
previousTier: getSafeCount(previousTierFrom) - 1,
|
||||
}}
|
||||
/>
|
||||
@@ -280,7 +341,7 @@ function Tiers({
|
||||
<Trans
|
||||
i18nKey={'billing:forEveryUnit'}
|
||||
values={{
|
||||
unit: t(unitKey, { count: 1 }),
|
||||
unit: getUnitLabel(1),
|
||||
}}
|
||||
/>
|
||||
</span>
|
||||
@@ -292,7 +353,7 @@ function Tiers({
|
||||
<Trans
|
||||
i18nKey={'billing:includedUpTo'}
|
||||
values={{
|
||||
unit: t(unitKey, { count: getSafeCount(upTo) }),
|
||||
unit: getUnitLabel(getSafeCount(upTo)),
|
||||
upTo,
|
||||
}}
|
||||
/>
|
||||
@@ -311,8 +372,9 @@ function Tiers({
|
||||
i18nKey={'billing:fromPreviousTierUpTo'}
|
||||
values={{
|
||||
previousTierFrom,
|
||||
unit: t(unitKey, { count: getSafeCount(previousTierFrom) }),
|
||||
upTo,
|
||||
unit: getUnitLabel(1),
|
||||
unitPlural: getUnitLabel(getSafeCount(rangeCount ?? upTo)),
|
||||
upTo: rangeCount ?? upTo,
|
||||
}}
|
||||
/>
|
||||
</span>
|
||||
|
||||
@@ -154,10 +154,22 @@ function PricingItem(
|
||||
};
|
||||
}>,
|
||||
) {
|
||||
const { t, i18n } = useTranslation();
|
||||
const highlighted = props.product.highlighted ?? false;
|
||||
const lineItem = props.primaryLineItem!;
|
||||
const isCustom = props.plan.custom ?? false;
|
||||
|
||||
const i18nKey = `billing:units.${lineItem.unit}`;
|
||||
|
||||
const unitLabel = lineItem?.unit
|
||||
? i18n.exists(i18nKey) ? t(i18nKey, {
|
||||
count: 1,
|
||||
defaultValue: lineItem.unit,
|
||||
}) : lineItem.unit
|
||||
: '';
|
||||
|
||||
const isDefaultSeatUnit = lineItem?.unit === 'member';
|
||||
|
||||
// we exclude flat line items from the details since
|
||||
// it doesn't need further explanation
|
||||
const lineItemsToDisplay = props.plan.lineItems.filter((item) => {
|
||||
@@ -259,14 +271,26 @@ function PricingItem(
|
||||
|
||||
<span
|
||||
className={cn(
|
||||
`animate-in slide-in-from-left-4 fade-in text-sm capitalize`,
|
||||
`animate-in slide-in-from-left-4 fade-in text-xs capitalize`,
|
||||
)}
|
||||
>
|
||||
<If condition={lineItem?.type === 'per_seat'}>
|
||||
<Trans i18nKey={'billing:perTeamMember'} />
|
||||
<If
|
||||
condition={Boolean(lineItem?.unit) && !isDefaultSeatUnit}
|
||||
fallback={<Trans i18nKey={'billing:perTeamMember'} />}
|
||||
>
|
||||
<Trans
|
||||
i18nKey={'billing:perUnitShort'}
|
||||
values={{
|
||||
unit: unitLabel,
|
||||
}}
|
||||
/>
|
||||
</If>
|
||||
</If>
|
||||
|
||||
<If condition={lineItem?.unit}>
|
||||
<If
|
||||
condition={lineItem?.type !== 'per_seat' && lineItem?.unit}
|
||||
>
|
||||
<Trans
|
||||
i18nKey={'billing:perUnit'}
|
||||
values={{
|
||||
@@ -324,6 +348,7 @@ function PricingItem(
|
||||
selectedInterval={props.plan.interval}
|
||||
currency={props.product.currency}
|
||||
lineItems={lineItemsToDisplay}
|
||||
alwaysDisplayMonthlyPrice={props.alwaysDisplayMonthlyPrice}
|
||||
/>
|
||||
</div>
|
||||
</If>
|
||||
|
||||
Reference in New Issue
Block a user