0
Fork 0
mirror of https://github.com/logto-io/logto.git synced 2025-03-24 22:41:28 -05:00

refactor(console,phrases): update charge notification for add-on features (#5146)

This commit is contained in:
Xiao Yijun 2023-12-22 16:32:24 +08:00 committed by GitHub
parent 39b915aa23
commit d4996c3f57
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
26 changed files with 320 additions and 182 deletions

View file

@ -1,16 +1,20 @@
import { ReservedPlanId } from '@logto/schemas';
import { cond } from '@silverhand/essentials';
import { type TFuncKey } from 'i18next';
import { useContext } from 'react';
import { Trans, useTranslation } from 'react-i18next';
import { pricingLink } from '@/consts';
import { isDevFeaturesEnabled } from '@/consts/env';
import { TenantsContext } from '@/contexts/TenantsProvider';
import DynamicT from '@/ds-components/DynamicT';
import InlineNotification from '@/ds-components/InlineNotification';
import TextLink from '@/ds-components/TextLink';
import useSubscriptionPlan from '@/hooks/use-subscription-plan';
type Props = {
hasReachedLimit: boolean;
notification?: TFuncKey<'translation', 'admin_console.upsell'>;
hasSurpassedLimit: boolean;
quotaItem: TFuncKey<'translation', 'admin_console.upsell.add_on_quota_item'>;
quotaLimit?: number;
className?: string;
};
@ -20,26 +24,36 @@ type Props = {
* CAUTION: This notification will be rendered only when the tenant's subscription plan is a paid plan.
* We won't render it for free plan since we will not charge for free plan.
*/
function ChargeNotification({
hasReachedLimit,
notification = 'charge_notification_for_quota_limit',
className,
}: Props) {
function ChargeNotification({ hasSurpassedLimit, quotaItem, quotaLimit, className }: Props) {
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console.upsell' });
const { currentTenantId } = useContext(TenantsContext);
const { data: currentPlan } = useSubscriptionPlan(currentTenantId);
// Todo @xiaoyijun [Pricing] Remove feature flag
if (!isDevFeaturesEnabled) {
return null;
}
if (
// Todo @xiaoyijun [Pricing] Remove feature flag
isDevFeaturesEnabled &&
!hasSurpassedLimit ||
// No charge notification for free plan
(!hasReachedLimit || currentPlan?.id === ReservedPlanId.Free)
currentPlan?.id === ReservedPlanId.Free
) {
return null;
}
return (
<InlineNotification severity="error" className={className}>
<DynamicT forKey={`upsell.${notification}`} />
<InlineNotification className={className}>
<Trans components={{ a: <TextLink href={pricingLink} targetBlank="noopener" /> }}>
{t('charge_notification_for_quota_limit', {
item: t(`add_on_quota_item.${quotaItem}`, {
...cond(
// Note: tokens use 'M' as unit
quotaLimit && { limit: quotaItem === 'tokens' ? quotaLimit / 1_000_000 : quotaLimit }
),
}),
})}
</Trans>
</InlineNotification>
);
}

View file

@ -51,17 +51,21 @@ function Footer({
[existingConnectors]
);
const isStandardConnectorsReachLimit = hasReachedQuotaLimit({
quotaKey: 'standardConnectorsLimit',
plan: currentPlan,
usage: standardConnectorCount,
});
const isStandardConnectorsReachLimit =
currentPlan &&
hasReachedQuotaLimit({
quotaKey: 'standardConnectorsLimit',
plan: currentPlan,
usage: standardConnectorCount,
});
const isSocialConnectorsReachLimit = hasReachedQuotaLimit({
quotaKey: 'socialConnectorsLimit',
plan: currentPlan,
usage: socialConnectorCount,
});
const isSocialConnectorsReachLimit =
currentPlan &&
hasReachedQuotaLimit({
quotaKey: 'socialConnectorsLimit',
plan: currentPlan,
usage: socialConnectorCount,
});
if (isCreatingSocialConnector && currentPlan && selectedConnectorGroup) {
const { id: planId, name: planName, quota } = currentPlan;

View file

@ -5,7 +5,7 @@ import useSWR from 'swr';
import { type ApiResource } from '@/consts';
import { isCloud } from '@/consts/env';
import { TenantsContext } from '@/contexts/TenantsProvider';
import { hasReachedQuotaLimit } from '@/utils/quota';
import { hasReachedQuotaLimit, hasSurpassedQuotaLimit } from '@/utils/quota';
import useSubscriptionPlan from './use-subscription-plan';
@ -18,19 +18,40 @@ const useApiResourcesUsage = () => {
*/
const { data: allResources } = useSWR<ApiResource[]>(isCloud && 'api/resources');
const hasReachedLimit = useMemo(() => {
const resourceCount =
allResources?.filter(({ indicator }) => !isManagementApi(indicator)).length ?? 0;
const resourceCount = useMemo(
() => allResources?.filter(({ indicator }) => !isManagementApi(indicator)).length ?? 0,
[allResources]
);
return hasReachedQuotaLimit({
quotaKey: 'resourcesLimit',
plan: currentPlan,
usage: resourceCount,
});
}, [allResources, currentPlan]);
const hasReachedLimit = useMemo(
() =>
Boolean(
currentPlan &&
hasReachedQuotaLimit({
quotaKey: 'resourcesLimit',
plan: currentPlan,
usage: resourceCount,
})
),
[currentPlan, resourceCount]
);
const hasSurpassedLimit = useMemo(
() =>
Boolean(
currentPlan &&
hasSurpassedQuotaLimit({
quotaKey: 'resourcesLimit',
plan: currentPlan,
usage: resourceCount,
})
),
[currentPlan, resourceCount]
);
return {
hasReachedLimit,
hasSurpassedLimit,
};
};

View file

@ -17,29 +17,54 @@ const useApplicationsUsage = () => {
*/
const { data: allApplications } = useSWR<Application[]>(isCloud && 'api/applications');
const hasMachineToMachineAppsReachedLimit = useMemo(() => {
const m2mAppCount =
allApplications?.filter(({ type }) => type === ApplicationType.MachineToMachine).length ?? 0;
const m2mAppCount = useMemo(
() =>
allApplications?.filter(({ type }) => type === ApplicationType.MachineToMachine).length ?? 0,
[allApplications]
);
return hasReachedQuotaLimit({
quotaKey: 'machineToMachineLimit',
plan: currentPlan,
usage: m2mAppCount,
});
}, [allApplications, currentPlan]);
const hasMachineToMachineAppsReachedLimit = useMemo(
() =>
Boolean(
currentPlan &&
hasReachedQuotaLimit({
quotaKey: 'machineToMachineLimit',
plan: currentPlan,
usage: m2mAppCount,
})
),
[currentPlan, m2mAppCount]
);
const hasMachineToMachineAppsSurpassedLimit = useMemo(
() =>
Boolean(
currentPlan &&
hasReachedQuotaLimit({
quotaKey: 'machineToMachineLimit',
plan: currentPlan,
usage: m2mAppCount,
})
),
[currentPlan, m2mAppCount]
);
const hasAppsReachedLimit = useMemo(
() =>
hasReachedQuotaLimit({
quotaKey: 'applicationsLimit',
plan: currentPlan,
usage: allApplications?.length ?? 0,
}),
Boolean(
currentPlan &&
hasReachedQuotaLimit({
quotaKey: 'applicationsLimit',
plan: currentPlan,
usage: allApplications?.length ?? 0,
})
),
[allApplications?.length, currentPlan]
);
return {
hasMachineToMachineAppsReachedLimit,
hasMachineToMachineAppsSurpassedLimit,
hasAppsReachedLimit,
};
};

View file

@ -53,11 +53,13 @@ function CreatePermissionModal({ resourceId, totalResourceCount, onClose }: Prop
})
);
const isScopesPerResourceReachLimit = hasReachedQuotaLimit({
quotaKey: 'scopesPerResourceLimit',
plan: currentPlan,
usage: totalResourceCount,
});
const isScopesPerResourceReachLimit =
currentPlan &&
hasReachedQuotaLimit({
quotaKey: 'scopesPerResourceLimit',
plan: currentPlan,
usage: totalResourceCount,
});
return (
<ReactModal
@ -77,7 +79,7 @@ function CreatePermissionModal({ resourceId, totalResourceCount, onClose }: Prop
targetBlank: 'noopener',
}}
footer={
isScopesPerResourceReachLimit && currentPlan ? (
isScopesPerResourceReachLimit ? (
<QuotaGuardFooter>
<Trans
components={{

View file

@ -41,7 +41,7 @@ const icons = {
function ApiResources() {
const { search } = useLocation();
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
const { hasReachedLimit } = useApiResourcesUsage();
const { hasSurpassedLimit } = useApiResourcesUsage();
const [{ page }, updateSearchParameters] = useSearchParametersWatcher({
page: 1,
});
@ -78,7 +78,9 @@ function ApiResources() {
});
},
}}
subHeader={<ChargeNotification hasReachedLimit={hasReachedLimit} />}
subHeader={
<ChargeNotification hasSurpassedLimit={hasSurpassedLimit} quotaItem="api_resource" />
}
table={{
rowGroups: [{ key: 'apiResources', data: apiResources }],
rowIndexKey: 'id',

View file

@ -37,7 +37,7 @@ function Applications() {
const { match, navigate } = useTenantPathname();
const isCreating = match(createApplicationPathname);
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
const { hasMachineToMachineAppsReachedLimit } = useApplicationsUsage();
const { hasMachineToMachineAppsSurpassedLimit } = useApplicationsUsage();
const [{ page }, updateSearchParameters] = useSearchParametersWatcher({
page: 1,
});
@ -73,8 +73,8 @@ function Applications() {
)}
</div>
<ChargeNotification
hasReachedLimit={hasMachineToMachineAppsReachedLimit}
notification="charge_notification_for_m2m_app_limit"
hasSurpassedLimit={hasMachineToMachineAppsSurpassedLimit}
quotaItem="machine_to_machine"
className={styles.chargeNotification}
/>
{!isLoading && !applications?.length && (

View file

@ -15,7 +15,7 @@ import ModalLayout from '@/ds-components/ModalLayout';
import useApi from '@/hooks/use-api';
import useSubscriptionPlan from '@/hooks/use-subscription-plan';
import * as modalStyles from '@/scss/modal.module.scss';
import { hasReachedQuotaLimit } from '@/utils/quota';
import { hasSurpassedQuotaLimit } from '@/utils/quota';
type Props = {
roleId: string;
@ -51,17 +51,13 @@ function AssignPermissionsModal({ roleId, roleType, totalRoleScopeCount, onClose
}
};
const shouldBlockScopeAssignment = hasReachedQuotaLimit({
quotaKey: 'scopesPerRoleLimit',
plan: currentPlan,
/**
* If usage is equal to the limit, it means the current role has reached the maximum allowed scope.
* Therefore, we should not assign any more scopes at this point.
* However, the currently selected scopes haven't been assigned yet, so we subtract 1
* to allow the assignment when the scope count is equal to the limit.
*/
usage: totalRoleScopeCount + scopes.length - 1,
});
const shouldBlockScopeAssignment =
currentPlan &&
hasSurpassedQuotaLimit({
quotaKey: 'scopesPerRoleLimit',
plan: currentPlan,
usage: totalRoleScopeCount + scopes.length,
});
return (
<ReactModal
@ -82,7 +78,7 @@ function AssignPermissionsModal({ roleId, roleType, totalRoleScopeCount, onClose
}}
size="large"
footer={
shouldBlockScopeAssignment && currentPlan ? (
shouldBlockScopeAssignment ? (
<QuotaGuardFooter>
<Trans
components={{

View file

@ -10,7 +10,7 @@ import { isCloud, isDevFeaturesEnabled } from '@/consts/env';
import { TenantsContext } from '@/contexts/TenantsProvider';
import Button from '@/ds-components/Button';
import useSubscriptionPlan from '@/hooks/use-subscription-plan';
import { hasReachedQuotaLimit } from '@/utils/quota';
import { hasReachedQuotaLimit, hasSurpassedQuotaLimit } from '@/utils/quota';
import { buildUrl } from '@/utils/url';
type Props = {
@ -33,25 +33,24 @@ function Footer({ roleType, selectedScopesCount, isCreating, onClickCreate }: Pr
})
);
const hasRoleReachedLimit = hasReachedQuotaLimit({
quotaKey: roleType === RoleType.User ? 'rolesLimit' : 'machineToMachineRolesLimit',
plan: currentPlan,
usage: roleCount ?? 0,
});
const hasRoleReachedLimit =
currentPlan &&
hasReachedQuotaLimit({
quotaKey: roleType === RoleType.User ? 'rolesLimit' : 'machineToMachineRolesLimit',
plan: currentPlan,
usage: roleCount ?? 0,
});
const hasBeyondScopesPerRoleLimit = hasReachedQuotaLimit({
quotaKey: 'scopesPerRoleLimit',
plan: currentPlan,
/**
* If usage equals to the limit, it means the current role has reached the maximum allowed scope.
* Therefore, we should not assign any more scopes at this point.
* However, the currently selected scopes haven't been assigned yet, so we subtract 1
* to allow the assignment when the scope count equals to the limit.
*/
usage: selectedScopesCount - 1,
});
const hasScopesPerRoleSurpassedLimit =
currentPlan &&
hasSurpassedQuotaLimit({
quotaKey: 'scopesPerRoleLimit',
plan: currentPlan,
usage: selectedScopesCount,
});
if (currentPlan && (hasRoleReachedLimit || hasBeyondScopesPerRoleLimit)) {
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
if (currentPlan && (hasRoleReachedLimit || hasScopesPerRoleSurpassedLimit)) {
return (
<QuotaGuardFooter>
<Trans
@ -74,7 +73,7 @@ function Footer({ roleType, selectedScopesCount, isCreating, onClickCreate }: Pr
}))}
{/* Role scopes limit paywall */}
{!hasRoleReachedLimit &&
hasBeyondScopesPerRoleLimit &&
hasScopesPerRoleSurpassedLimit &&
t('upsell.paywall.scopes_per_role', {
count: currentPlan.quota.scopesPerRoleLimit ?? 0,
})}

View file

@ -32,11 +32,13 @@ function CreateForm({ totalWebhookCount, onClose }: Props) {
const { data: currentPlan } = useSubscriptionPlan(currentTenantId);
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
const shouldBlockCreation = hasReachedQuotaLimit({
quotaKey: 'hooksLimit',
usage: totalWebhookCount,
plan: currentPlan,
});
const shouldBlockCreation =
currentPlan &&
hasReachedQuotaLimit({
quotaKey: 'hooksLimit',
usage: totalWebhookCount,
plan: currentPlan,
});
const formMethods = useForm<BasicWebhookFormType>();
const {
@ -67,7 +69,7 @@ function CreateForm({ totalWebhookCount, onClose }: Props) {
title="webhooks.create_form.title"
subtitle="webhooks.create_form.subtitle"
footer={
shouldBlockCreation && currentPlan ? (
shouldBlockCreation ? (
<QuotaGuardFooter>
<Trans
components={{

View file

@ -1,28 +1,32 @@
import { isCloud } from '@/consts/env';
import { type SubscriptionPlan, type SubscriptionPlanQuota } from '@/types/subscriptions';
type HasReachedQuotaLimitParameters = {
type UsageOptions = {
quotaKey: keyof SubscriptionPlanQuota;
usage: number;
plan?: SubscriptionPlan;
plan: SubscriptionPlan;
};
export const hasReachedQuotaLimit = ({ quotaKey, usage, plan }: HasReachedQuotaLimitParameters) => {
// If the plan is not loaded, guarded by backend APIs
if (!isCloud || !plan) {
return false;
const isUsageWithInLimit = ({ quotaKey, usage, plan }: UsageOptions, inclusive = true) => {
// No limitations for OSS version
if (!isCloud) {
return true;
}
const quotaValue = plan.quota[quotaKey];
// Unlimited
if (quotaValue === null) {
return false;
return true;
}
if (typeof quotaValue === 'boolean') {
return !quotaValue;
return quotaValue;
}
return usage >= quotaValue;
return inclusive ? usage <= quotaValue : usage < quotaValue;
};
export const hasSurpassedQuotaLimit = (options: UsageOptions) => !isUsageWithInLimit(options);
export const hasReachedQuotaLimit = (options: UsageOptions) => !isUsageWithInLimit(options, false);

View file

@ -31,12 +31,17 @@ const upsell = {
unpaid_bills: 'Ausstehende Rechnungen',
update_payment: 'Zahlung aktualisieren',
},
add_on_quota_item: {
/** UNTRANSLATED */
api_resource: 'API resource',
/** UNTRANSLATED */
machine_to_machine: 'machine-to-machine application',
/** UNTRANSLATED */
tokens: '{{limit}}M tokens',
},
/** UNTRANSLATED */
charge_notification_for_quota_limit:
'Sie haben Ihr Kontingentlimit erreicht. Möglicherweise berechnen wir Gebühren für Funktionen, die Ihr Kontingentlimit überschreiten, sobald wir die Preise festgelegt haben.',
charge_notification_for_token_limit:
'Sie haben Ihr Kontingentlimit von {{value}}M Token erreicht. Möglicherweise berechnen wir Gebühren für Funktionen, die Ihr Kontingentlimit überschreiten, sobald wir die Preise festgelegt haben.',
charge_notification_for_m2m_app_limit:
'Sie haben Ihr Kontingentlimit für Machine-to-Machine erreicht. Möglicherweise berechnen wir Gebühren für Funktionen, die Ihr Kontingentlimit überschreiten, sobald wir die Preise festgelegt haben.',
'You have surpassed your {{item}} quota limit. Logto will add charges for the usage beyond your quota limit. Charging will commence on the day the new add-on pricing design is released. <a>Learn more</a>',
paywall,
};

View file

@ -31,12 +31,13 @@ const upsell = {
unpaid_bills: 'Unpaid bills',
update_payment: 'Update Payment',
},
add_on_quota_item: {
api_resource: 'API resource',
machine_to_machine: 'machine-to-machine application',
tokens: '{{limit}}M tokens',
},
charge_notification_for_quota_limit:
'You have reached your quota limit. We may add charges for features that go beyond your quota limit as add-ons, once we finalize the prices.',
charge_notification_for_token_limit:
'You have reached your {{value}}M token quota limit. We may add charges for features that go beyond your quota limit as add-ons, once we finalize the prices.',
charge_notification_for_m2m_app_limit:
'You have reached your machine-to-machine quota limit. We may add charges for features that go beyond your quota limit as add-ons, once we finalize the prices.',
'You have surpassed your {{item}} quota limit. Logto will add charges for the usage beyond your quota limit. Charging will commence on the day the new add-on pricing design is released. <a>Learn more</a>',
paywall,
};

View file

@ -31,12 +31,17 @@ const upsell = {
unpaid_bills: 'Facturas impagas',
update_payment: 'Actualizar pago',
},
add_on_quota_item: {
/** UNTRANSLATED */
api_resource: 'API resource',
/** UNTRANSLATED */
machine_to_machine: 'machine-to-machine application',
/** UNTRANSLATED */
tokens: '{{limit}}M tokens',
},
/** UNTRANSLATED */
charge_notification_for_quota_limit:
'Has alcanzado tu límite de cuota. Es posible que añadamos cargos por funciones que superen tu límite de cuota como complementos, una vez que finalicemos los precios.',
charge_notification_for_token_limit:
'Has alcanzado tu límite de cuota de token de {{value}}M. Es posible que añadamos cargos por funciones que superen tu límite de cuota como complementos, una vez que finalicemos los precios.',
charge_notification_for_m2m_app_limit:
'Has alcanzado tu límite de cuota para aplicaciones de máquina a máquina. Es posible que añadamos cargos por funciones que superen tu límite de cuota como complementos, una vez que finalicemos los precios.',
'You have surpassed your {{item}} quota limit. Logto will add charges for the usage beyond your quota limit. Charging will commence on the day the new add-on pricing design is released. <a>Learn more</a>',
paywall,
};

View file

@ -31,12 +31,17 @@ const upsell = {
unpaid_bills: 'Factures impayées',
update_payment: 'Mettre à jour le paiement',
},
add_on_quota_item: {
/** UNTRANSLATED */
api_resource: 'API resource',
/** UNTRANSLATED */
machine_to_machine: 'machine-to-machine application',
/** UNTRANSLATED */
tokens: '{{limit}}M tokens',
},
/** UNTRANSLATED */
charge_notification_for_quota_limit:
'Vous avez atteint votre limite de quota. Nous pouvons facturer des fonctionnalités qui dépassent votre limite de quota en tant que modules complémentaires, une fois que nous aurons finalisé les prix.',
charge_notification_for_token_limit:
'Vous avez atteint votre limite de quota de jetons de {{value}}M. Nous pourrions facturer les fonctionnalités qui dépassent votre limite de quota en tant que modules complémentaires, une fois que nous aurons finalisé les prix.',
charge_notification_for_m2m_app_limit:
'Vous avez atteint votre limite de quota pour les connexions machine à machine. Nous pourrions facturer les fonctionnalités qui dépassent votre limite de quota en tant que modules complémentaires, une fois que nous aurons finalisé les prix.',
'You have surpassed your {{item}} quota limit. Logto will add charges for the usage beyond your quota limit. Charging will commence on the day the new add-on pricing design is released. <a>Learn more</a>',
paywall,
};

View file

@ -31,12 +31,17 @@ const upsell = {
unpaid_bills: 'Fatture non pagate',
update_payment: 'Aggiorna pagamento',
},
add_on_quota_item: {
/** UNTRANSLATED */
api_resource: 'API resource',
/** UNTRANSLATED */
machine_to_machine: 'machine-to-machine application',
/** UNTRANSLATED */
tokens: '{{limit}}M tokens',
},
/** UNTRANSLATED */
charge_notification_for_quota_limit:
'Hai raggiunto il tuo limite di quota. Possiamo aggiungere addebiti per funzionalità che superano il limite di quota come componenti aggiuntivi, una volta che finalizziamo i prezzi.',
charge_notification_for_token_limit:
'Hai raggiunto il limite di quota di {{value}}M token. Possiamo aggiungere addebiti per funzionalità che superano il limite di quota come componenti aggiuntivi, una volta che finalizziamo i prezzi.',
charge_notification_for_m2m_app_limit:
'Hai raggiunto il tuo limite di quota machine-to-machine. Possiamo aggiungere addebiti per funzionalità che superano il limite di quota come componenti aggiuntivi, una volta che finalizziamo i prezzi.',
'You have surpassed your {{item}} quota limit. Logto will add charges for the usage beyond your quota limit. Charging will commence on the day the new add-on pricing design is released. <a>Learn more</a>',
paywall,
};

View file

@ -31,12 +31,17 @@ const upsell = {
unpaid_bills: '未払いの請求書',
update_payment: '支払いを更新',
},
add_on_quota_item: {
/** UNTRANSLATED */
api_resource: 'API resource',
/** UNTRANSLATED */
machine_to_machine: 'machine-to-machine application',
/** UNTRANSLATED */
tokens: '{{limit}}M tokens',
},
/** UNTRANSLATED */
charge_notification_for_quota_limit:
'あなたのクォータ上限に達しました。価格が確定次第、クォータ上限を超える機能に関して追加料金が発生する可能性があります。',
charge_notification_for_token_limit:
'あなたの{{value}}M トークンクォータ上限に達しました。価格が確定次第、クォータ上限を超える機能に関して追加料金が発生する可能性があります。',
charge_notification_for_m2m_app_limit:
'あなたの機械間通信M2Mクォータ上限に達しました。価格が確定次第、クォータ上限を超える機能に関して追加料金が発生する可能性があります。',
'You have surpassed your {{item}} quota limit. Logto will add charges for the usage beyond your quota limit. Charging will commence on the day the new add-on pricing design is released. <a>Learn more</a>',
paywall,
};

View file

@ -31,12 +31,17 @@ const upsell = {
unpaid_bills: '미납 청구서',
update_payment: '지불 업데이트',
},
add_on_quota_item: {
/** UNTRANSLATED */
api_resource: 'API resource',
/** UNTRANSLATED */
machine_to_machine: 'machine-to-machine application',
/** UNTRANSLATED */
tokens: '{{limit}}M tokens',
},
/** UNTRANSLATED */
charge_notification_for_quota_limit:
'귀하의 할당량 한도에 도달했습니다. 최종 가격이 확정되면 할당량을 초과하는 기능에 대한 추가 요금이 부과될 수 있습니다.',
charge_notification_for_token_limit:
'귀하의 {{value}}M 토큰 한도에 도달했습니다. 최종 가격이 확정되면 할당량을 초과하는 기능에 대한 추가 요금이 부과될 수 있습니다.',
charge_notification_for_m2m_app_limit:
'기계 간 통신 할당량 한도에 도달했습니다. 최종 가격이 확정되면 할당량을 초과하는 기능에 대한 추가 요금이 부과될 수 있습니다.',
'You have surpassed your {{item}} quota limit. Logto will add charges for the usage beyond your quota limit. Charging will commence on the day the new add-on pricing design is released. <a>Learn more</a>',
paywall,
};

View file

@ -31,12 +31,17 @@ const upsell = {
unpaid_bills: 'Nieuregulowane faktury',
update_payment: 'Zaktualizuj płatność',
},
add_on_quota_item: {
/** UNTRANSLATED */
api_resource: 'API resource',
/** UNTRANSLATED */
machine_to_machine: 'machine-to-machine application',
/** UNTRANSLATED */
tokens: '{{limit}}M tokens',
},
/** UNTRANSLATED */
charge_notification_for_quota_limit:
'Osiągnąłeś swój limit przypisany. Możemy dodać opłaty za funkcje, które przekraczają Twój limit przypisany jako dodatki, gdy ustalimy ceny.',
charge_notification_for_token_limit:
'Osiągnąłeś swój limit łącznego przypisanego tokenów {{value}}M. Możemy dodać opłaty za funkcje, które przekraczają Twój limit przypisany jako dodatki, gdy ustalimy ceny.',
charge_notification_for_m2m_app_limit:
'Osiągnąłeś swój limit przypisany dla aplikacji typu machine-to-machine. Możemy dodać opłaty za funkcje, które przekraczają Twój limit przypisany jako dodatki, gdy ustalimy ceny.',
'You have surpassed your {{item}} quota limit. Logto will add charges for the usage beyond your quota limit. Charging will commence on the day the new add-on pricing design is released. <a>Learn more</a>',
paywall,
};

View file

@ -31,14 +31,17 @@ const upsell = {
unpaid_bills: 'Faturas não pagas',
update_payment: 'Atualizar pagamento',
},
add_on_quota_item: {
/** UNTRANSLATED */
api_resource: 'API resource',
/** UNTRANSLATED */
machine_to_machine: 'machine-to-machine application',
/** UNTRANSLATED */
tokens: '{{limit}}M tokens',
},
/** UNTRANSLATED */
charge_notification_for_quota_limit:
'Você atingiu o seu limite de cota. Podemos adicionar cobranças para recursos que ultrapassem seu limite de cota como complementos, uma vez que finalizemos os preços.',
charge_notification_for_token_limit:
'Você atingiu o limite de cota de token de {{value}}M. Podemos adicionar cobranças para recursos que ultrapassam seu limite de cota como complementos, uma vez que finalizemos os preços.',
charge_notification_for_m2m_app_limit:
'Você atingiu o limite de cota de máquina a máquina. Podemos adicionar cobranças para recursos que ultrapassem seu limite de cota como complementos, uma vez que finalizemos os preços.',
'You have surpassed your {{item}} quota limit. Logto will add charges for the usage beyond your quota limit. Charging will commence on the day the new add-on pricing design is released. <a>Learn more</a>',
paywall,
};

View file

@ -31,12 +31,17 @@ const upsell = {
unpaid_bills: 'Faturas não pagas',
update_payment: 'Atualizar pagamento',
},
add_on_quota_item: {
/** UNTRANSLATED */
api_resource: 'API resource',
/** UNTRANSLATED */
machine_to_machine: 'machine-to-machine application',
/** UNTRANSLATED */
tokens: '{{limit}}M tokens',
},
/** UNTRANSLATED */
charge_notification_for_quota_limit:
'You have reached your quota limit. We may add charges for features that go beyond your quota limit as add-ons, once we finalize the prices.',
charge_notification_for_token_limit:
'You have reached your {{value}}M token quota limit. We may add charges for features that go beyond your quota limit as add-ons, once we finalize the prices.',
charge_notification_for_m2m_app_limit:
'You have reached your machine-to-machine quota limit. We may add charges for features that go beyond your quota limit as add-ons, once we finalize the prices.',
'You have surpassed your {{item}} quota limit. Logto will add charges for the usage beyond your quota limit. Charging will commence on the day the new add-on pricing design is released. <a>Learn more</a>',
paywall,
};

View file

@ -31,12 +31,17 @@ const upsell = {
unpaid_bills: 'Неоплаченные счета',
update_payment: 'Обновить платеж',
},
add_on_quota_item: {
/** UNTRANSLATED */
api_resource: 'API resource',
/** UNTRANSLATED */
machine_to_machine: 'machine-to-machine application',
/** UNTRANSLATED */
tokens: '{{limit}}M tokens',
},
/** UNTRANSLATED */
charge_notification_for_quota_limit:
'Вы достигли предела вашей квоты. Мы можем добавить плату за функции, которые превышают ваш предел квоты как дополнительные, когда мы установим окончательные цены.',
charge_notification_for_token_limit:
'Вы достигли предела вашей квоты токенов в размере {{value}}M. Мы можем добавить плату за функции, которые превышают ваш предел квоты как дополнительные, когда мы установим окончательные цены.',
charge_notification_for_m2m_app_limit:
'Вы достигли предела вашей квоты для использования коммуникации между машинами. Мы можем добавить плату за функции, которые превышают ваш предел квоты как дополнительные, когда мы установим окончательные цены.',
'You have surpassed your {{item}} quota limit. Logto will add charges for the usage beyond your quota limit. Charging will commence on the day the new add-on pricing design is released. <a>Learn more</a>',
paywall,
};

View file

@ -31,12 +31,17 @@ const upsell = {
unpaid_bills: 'Ödenmemiş faturalar',
update_payment: 'Ödemeyi Güncelle',
},
add_on_quota_item: {
/** UNTRANSLATED */
api_resource: 'API resource',
/** UNTRANSLATED */
machine_to_machine: 'machine-to-machine application',
/** UNTRANSLATED */
tokens: '{{limit}}M tokens',
},
/** UNTRANSLATED */
charge_notification_for_quota_limit:
'Kota limitinize ulaştınız. Fiyatlarımızı kesinleştirdiğimizde, kota limitinizi aşan özellikler için ek ücretler ekleyebiliriz.',
charge_notification_for_token_limit:
'Token kota limitinize ulaştınız. Fiyatlarımızı kesinleştirdiğimizde, kota limitinizi aşan özellikler için ek ücretler ekleyebiliriz.',
charge_notification_for_m2m_app_limit:
'Makine-makine kotası limitinize ulaştınız. Fiyatlarımızı kesinleştirdiğimizde, kota limitinizi aşan özellikler için ek ücretler ekleyebiliriz.',
'You have surpassed your {{item}} quota limit. Logto will add charges for the usage beyond your quota limit. Charging will commence on the day the new add-on pricing design is released. <a>Learn more</a>',
paywall,
};

View file

@ -31,12 +31,17 @@ const upsell = {
unpaid_bills: '未付账单',
update_payment: '更新支付',
},
add_on_quota_item: {
/** UNTRANSLATED */
api_resource: 'API resource',
/** UNTRANSLATED */
machine_to_machine: 'machine-to-machine application',
/** UNTRANSLATED */
tokens: '{{limit}}M tokens',
},
/** UNTRANSLATED */
charge_notification_for_quota_limit:
'您已达到您的配额限制。一旦我们最终确定价格,我们可能会为超出配额限制的功能添加额外费用。',
charge_notification_for_token_limit:
'您已达到{{value}}M令牌配额限制。一旦我们最终确定价格我们可能会为超出配额限制的功能添加额外费用。',
charge_notification_for_m2m_app_limit:
'您已达到您的机器对机器配额限制。一旦我们最终确定价格,我们可能会为超出配额限制的功能添加额外费用。',
'You have surpassed your {{item}} quota limit. Logto will add charges for the usage beyond your quota limit. Charging will commence on the day the new add-on pricing design is released. <a>Learn more</a>',
paywall,
};

View file

@ -31,12 +31,17 @@ const upsell = {
unpaid_bills: '未付賬單',
update_payment: '更新支付',
},
add_on_quota_item: {
/** UNTRANSLATED */
api_resource: 'API resource',
/** UNTRANSLATED */
machine_to_machine: 'machine-to-machine application',
/** UNTRANSLATED */
tokens: '{{limit}}M tokens',
},
/** UNTRANSLATED */
charge_notification_for_quota_limit:
'您已達到配額限制。我們可能會對超出配額限制的功能加收費用,一旦我們確定價格。',
charge_notification_for_token_limit:
'您已達到{{value}}M令牌配額限制。一旦我們確定價格我們可能會為超出配額限制的功能添加附加費。',
charge_notification_for_m2m_app_limit:
'您已達到機器對機器配額限制。一旦我們確定價格,我們可能會對超出配額限制的功能加收費用。',
'You have surpassed your {{item}} quota limit. Logto will add charges for the usage beyond your quota limit. Charging will commence on the day the new add-on pricing design is released. <a>Learn more</a>',
paywall,
};

View file

@ -31,12 +31,17 @@ const upsell = {
unpaid_bills: '未付賬單',
update_payment: '更新支付',
},
add_on_quota_item: {
/** UNTRANSLATED */
api_resource: 'API resource',
/** UNTRANSLATED */
machine_to_machine: 'machine-to-machine application',
/** UNTRANSLATED */
tokens: '{{limit}}M tokens',
},
/** UNTRANSLATED */
charge_notification_for_quota_limit:
'你已經達到你的配額限制。一旦我們確定價格,超出配額限制的功能可能會作為附加組件而被收費。',
charge_notification_for_token_limit:
'你已經達到你的{{value}}M令牌配額限制。一旦我們確定價格超出配額限制的功能可能會作炂附加組件而被收費。',
charge_notification_for_m2m_app_limit:
'你已經達到你的機器對機器配額限制。一旦我們確定價格,超出配額限制的功能可能會作炂附加組件而被收費。',
'You have surpassed your {{item}} quota limit. Logto will add charges for the usage beyond your quota limit. Charging will commence on the day the new add-on pricing design is released. <a>Learn more</a>',
paywall,
};