From 58f08c5f39a8452af9d1689ae7628cb7a432cb13 Mon Sep 17 00:00:00 2001 From: Giancarlo Buomprisco Date: Tue, 3 Feb 2026 13:26:52 +0100 Subject: [PATCH] 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. --- apps/web/public/locales/en/billing.json | 3 +- package.json | 2 +- .../src/components/line-item-details.tsx | 98 +++++++++++++++---- .../gateway/src/components/pricing-table.tsx | 31 +++++- 4 files changed, 111 insertions(+), 23 deletions(-) diff --git a/apps/web/public/locales/en/billing.json b/apps/web/public/locales/en/billing.json index 8438b5319..be79d61e0 100644 --- a/apps/web/public/locales/en/billing.json +++ b/apps/web/public/locales/en/billing.json @@ -40,10 +40,11 @@ "proceedToPayment": "Proceed to Payment", "startTrial": "Start Trial", "perTeamMember": "Per team member", + "perUnitShort": "Per {{unit}}", "perUnit": "Per {{unit}} usage", "teamMembers": "Team Members", "includedUpTo": "Up to {{upTo}} {{unit}} included in the plan", - "fromPreviousTierUpTo": "for each {{unit}} for the next {{ upTo }} {{ unit }}", + "fromPreviousTierUpTo": "for each {{unit}} for the next {{ upTo }} {{ unitPlural }}", "andAbove": "above {{ previousTier }} {{ unit }}", "startingAtPriceUnit": "Starting at {{price}}/{{unit}}", "priceUnit": "{{price}}/{{unit}}", diff --git a/package.json b/package.json index 8a03d29cf..cc3773d27 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "next-supabase-saas-kit-turbo", - "version": "2.23.11", + "version": "2.23.12", "private": true, "sideEffects": false, "engines": { diff --git a/packages/billing/gateway/src/components/line-item-details.tsx b/packages/billing/gateway/src/components/line-item-details.tsx index 64532041c..77481bee0 100644 --- a/packages/billing/gateway/src/components/line-item-details.tsx +++ b/packages/billing/gateway/src/components/line-item-details.tsx @@ -17,12 +17,41 @@ export function LineItemDetails( lineItems: z.infer[]; 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 (
{props.lineItems.map((item, index) => { @@ -68,6 +97,12 @@ export function LineItemDetails( ); + const unit = + item.unit ?? (item.type === 'per_seat' ? 'member' : undefined); + + const hasTiers = Boolean(item.tiers?.length); + const isDefaultSeatUnit = unit === 'member'; + const FlatFee = () => (
@@ -99,7 +134,7 @@ export function LineItemDetails( {formatCurrency({ currencyCode, - value: item.cost, + value: getDisplayCost(item.cost, hasTiers), locale, })} @@ -116,14 +151,14 @@ export function LineItemDetails( - +
); @@ -135,16 +170,27 @@ export function LineItemDetails( - + } + > + + - - + - + {formatCurrency({ currencyCode, - value: item.cost, + value: getDisplayCost(item.cost, hasTiers), locale, })} @@ -155,7 +201,7 @@ export function LineItemDetails( - +
); @@ -172,7 +218,7 @@ export function LineItemDetails( @@ -185,7 +231,7 @@ export function LineItemDetails( {formatCurrency({ currencyCode, - value: item.cost, + value: getDisplayCost(item.cost, hasTiers), locale, })} @@ -196,7 +242,7 @@ export function LineItemDetails( {/* If there are tiers, we render them as a list */} - +
); @@ -220,11 +266,12 @@ export function LineItemDetails( function Tiers({ currency, item, + unit, }: { currency: string; item: z.infer; + 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({ @@ -280,7 +341,7 @@ function Tiers({ @@ -292,7 +353,7 @@ function Tiers({ @@ -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, }} /> diff --git a/packages/billing/gateway/src/components/pricing-table.tsx b/packages/billing/gateway/src/components/pricing-table.tsx index c227ce829..ed3673cea 100644 --- a/packages/billing/gateway/src/components/pricing-table.tsx +++ b/packages/billing/gateway/src/components/pricing-table.tsx @@ -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( - + } + > + + - +