Fix: use pluralization correctly (#445)
* feat(billing): add i18n pluralization support for billing unit names Use i18next plural feature to properly translate and pluralize unit names in billing plans (e.g., "member" vs "members"). This ensures correct grammar for phrases like "Up to 4 members included in the plan" and enables proper translation of unit names in non-English locales. * fix(billing): handle 'unlimited' tier values in pluralization Add getSafeCount helper to safely convert tier values to numbers, preventing NaN when 'unlimited' values are passed to pluralization. Falls back to plural form for 'unlimited' or invalid values. --------- Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
committed by
GitHub
parent
f3ce70a5b6
commit
bebd56238b
@@ -1,4 +1,8 @@
|
|||||||
{
|
{
|
||||||
|
"units": {
|
||||||
|
"member_one": "member",
|
||||||
|
"member_other": "members"
|
||||||
|
},
|
||||||
"subscriptionTabSubheading": "Manage your Subscription and Billing",
|
"subscriptionTabSubheading": "Manage your Subscription and Billing",
|
||||||
"planCardTitle": "Your Plan",
|
"planCardTitle": "Your Plan",
|
||||||
"planCardDescription": "Below are the details of your current plan. You can change your plan or cancel your subscription at any time.",
|
"planCardDescription": "Below are the details of your current plan. You can change your plan or cancel your subscription at any time.",
|
||||||
|
|||||||
@@ -19,7 +19,8 @@ export function LineItemDetails(
|
|||||||
selectedInterval?: string | undefined;
|
selectedInterval?: string | undefined;
|
||||||
}>,
|
}>,
|
||||||
) {
|
) {
|
||||||
const locale = useTranslation().i18n.language;
|
const { t, i18n } = useTranslation();
|
||||||
|
const locale = i18n.language;
|
||||||
const currencyCode = props?.currency.toLowerCase();
|
const currencyCode = props?.currency.toLowerCase();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -115,7 +116,7 @@ export function LineItemDetails(
|
|||||||
<Trans
|
<Trans
|
||||||
i18nKey={'billing:perUnit'}
|
i18nKey={'billing:perUnit'}
|
||||||
values={{
|
values={{
|
||||||
unit: item.unit,
|
unit: t(`billing:units.${item.unit}`, { count: 1 }),
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</span>
|
</span>
|
||||||
@@ -171,7 +172,7 @@ export function LineItemDetails(
|
|||||||
<Trans
|
<Trans
|
||||||
i18nKey={'billing:perUnit'}
|
i18nKey={'billing:perUnit'}
|
||||||
values={{
|
values={{
|
||||||
unit: item.unit,
|
unit: t(`billing:units.${item.unit}`, { count: 1 }),
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</span>
|
</span>
|
||||||
@@ -223,8 +224,17 @@ function Tiers({
|
|||||||
currency: string;
|
currency: string;
|
||||||
item: z.infer<typeof LineItemSchema>;
|
item: z.infer<typeof LineItemSchema>;
|
||||||
}) {
|
}) {
|
||||||
const unit = item.unit;
|
const unitKey = `billing:units.${item.unit}`;
|
||||||
const locale = useTranslation().i18n.language;
|
const { t, i18n } = useTranslation();
|
||||||
|
const locale = i18n.language;
|
||||||
|
|
||||||
|
// Helper to safely convert tier values to numbers for pluralization
|
||||||
|
// Falls back to plural form (2) for 'unlimited' values
|
||||||
|
const getSafeCount = (value: number | 'unlimited' | string): number => {
|
||||||
|
if (value === 'unlimited') return 2;
|
||||||
|
const num = typeof value === 'number' ? value : Number(value);
|
||||||
|
return Number.isNaN(num) ? 2 : num;
|
||||||
|
};
|
||||||
|
|
||||||
const tiers = item.tiers?.map((tier, index) => {
|
const tiers = item.tiers?.map((tier, index) => {
|
||||||
const tiersLength = item.tiers?.length ?? 0;
|
const tiersLength = item.tiers?.length ?? 0;
|
||||||
@@ -257,8 +267,10 @@ function Tiers({
|
|||||||
<Trans
|
<Trans
|
||||||
i18nKey={'billing:andAbove'}
|
i18nKey={'billing:andAbove'}
|
||||||
values={{
|
values={{
|
||||||
unit,
|
unit: t(unitKey, {
|
||||||
previousTier: (Number(previousTierFrom) as number) - 1,
|
count: getSafeCount(previousTierFrom) - 1,
|
||||||
|
}),
|
||||||
|
previousTier: getSafeCount(previousTierFrom) - 1,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</span>
|
</span>
|
||||||
@@ -268,7 +280,7 @@ function Tiers({
|
|||||||
<Trans
|
<Trans
|
||||||
i18nKey={'billing:forEveryUnit'}
|
i18nKey={'billing:forEveryUnit'}
|
||||||
values={{
|
values={{
|
||||||
unit,
|
unit: t(unitKey, { count: 1 }),
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</span>
|
</span>
|
||||||
@@ -277,7 +289,13 @@ function Tiers({
|
|||||||
<If condition={!isLastTier}>
|
<If condition={!isLastTier}>
|
||||||
<If condition={isIncluded}>
|
<If condition={isIncluded}>
|
||||||
<span>
|
<span>
|
||||||
<Trans i18nKey={'billing:includedUpTo'} values={{ unit, upTo }} />
|
<Trans
|
||||||
|
i18nKey={'billing:includedUpTo'}
|
||||||
|
values={{
|
||||||
|
unit: t(unitKey, { count: getSafeCount(upTo) }),
|
||||||
|
upTo,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
</span>
|
</span>
|
||||||
</If>{' '}
|
</If>{' '}
|
||||||
<If condition={!isIncluded}>
|
<If condition={!isIncluded}>
|
||||||
@@ -291,7 +309,11 @@ function Tiers({
|
|||||||
<span>
|
<span>
|
||||||
<Trans
|
<Trans
|
||||||
i18nKey={'billing:fromPreviousTierUpTo'}
|
i18nKey={'billing:fromPreviousTierUpTo'}
|
||||||
values={{ previousTierFrom, unit, upTo }}
|
values={{
|
||||||
|
previousTierFrom,
|
||||||
|
unit: t(unitKey, { count: getSafeCount(previousTierFrom) }),
|
||||||
|
upTo,
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</span>
|
</span>
|
||||||
</If>
|
</If>
|
||||||
|
|||||||
Reference in New Issue
Block a user