mirror of
https://github.com/logto-io/logto.git
synced 2025-04-07 23:01:25 -05:00
refactor(console): add machine-to-machine roles limit guard (#5131)
This commit is contained in:
parent
e4c73e7bb7
commit
2e9ccb08bd
19 changed files with 200 additions and 93 deletions
|
@ -0,0 +1,98 @@
|
|||
import { type RoleResponse, RoleType, ReservedPlanId } from '@logto/schemas';
|
||||
import { useContext } from 'react';
|
||||
import { Trans, useTranslation } from 'react-i18next';
|
||||
import useSWR from 'swr';
|
||||
|
||||
import ContactUsPhraseLink from '@/components/ContactUsPhraseLink';
|
||||
import PlanName from '@/components/PlanName';
|
||||
import QuotaGuardFooter from '@/components/QuotaGuardFooter';
|
||||
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 { buildUrl } from '@/utils/url';
|
||||
|
||||
type Props = {
|
||||
roleType: RoleType;
|
||||
selectedScopesCount: number;
|
||||
isCreating: boolean;
|
||||
onClickCreate: () => void;
|
||||
};
|
||||
|
||||
function Footer({ roleType, selectedScopesCount, isCreating, onClickCreate }: Props) {
|
||||
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
|
||||
const { currentTenantId } = useContext(TenantsContext);
|
||||
const { data: currentPlan } = useSubscriptionPlan(currentTenantId);
|
||||
const { data: [, roleCount] = [] } = useSWR<[RoleResponse[], number]>(
|
||||
isCloud &&
|
||||
buildUrl('api/roles', {
|
||||
page: String(1),
|
||||
page_size: String(1),
|
||||
type: roleType,
|
||||
})
|
||||
);
|
||||
|
||||
const hasRoleReachedLimit = 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,
|
||||
});
|
||||
|
||||
if (currentPlan && (hasRoleReachedLimit || hasBeyondScopesPerRoleLimit)) {
|
||||
return (
|
||||
<QuotaGuardFooter>
|
||||
<Trans
|
||||
components={{
|
||||
a: <ContactUsPhraseLink />,
|
||||
planName: <PlanName name={currentPlan.name} />,
|
||||
}}
|
||||
>
|
||||
{/* User roles limit paywall */}
|
||||
{hasRoleReachedLimit &&
|
||||
roleType === RoleType.User &&
|
||||
t('upsell.paywall.roles', { count: currentPlan.quota.rolesLimit ?? 0 })}
|
||||
{hasRoleReachedLimit &&
|
||||
roleType === RoleType.MachineToMachine &&
|
||||
/* Todo @xiaoyijun [Pricing] Remove feature flag */
|
||||
(!isDevFeaturesEnabled && currentPlan.id === ReservedPlanId.Free
|
||||
? t('upsell.paywall.deprecated_machine_to_machine_feature')
|
||||
: t('upsell.paywall.machine_to_machine_roles', {
|
||||
count: currentPlan.quota.machineToMachineRolesLimit ?? 0,
|
||||
}))}
|
||||
{/* Role scopes limit paywall */}
|
||||
{!hasRoleReachedLimit &&
|
||||
hasBeyondScopesPerRoleLimit &&
|
||||
t('upsell.paywall.scopes_per_role', {
|
||||
count: currentPlan.quota.scopesPerRoleLimit ?? 0,
|
||||
})}
|
||||
</Trans>
|
||||
</QuotaGuardFooter>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Button
|
||||
isLoading={isCreating}
|
||||
htmlType="submit"
|
||||
title="roles.create_role_button"
|
||||
size="large"
|
||||
type="primary"
|
||||
onClick={onClickCreate}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export default Footer;
|
|
@ -4,14 +4,11 @@ import { ReservedPlanId, RoleType, internalRolePrefix } from '@logto/schemas';
|
|||
import { conditional } from '@silverhand/essentials';
|
||||
import { useContext, useState } from 'react';
|
||||
import { Controller, useForm } from 'react-hook-form';
|
||||
import { Trans, useTranslation } from 'react-i18next';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import KeyboardArrowDown from '@/assets/icons/keyboard-arrow-down.svg';
|
||||
import KeyboardArrowUp from '@/assets/icons/keyboard-arrow-up.svg';
|
||||
import ContactUsPhraseLink from '@/components/ContactUsPhraseLink';
|
||||
import FeatureTag from '@/components/FeatureTag';
|
||||
import PlanName from '@/components/PlanName';
|
||||
import QuotaGuardFooter from '@/components/QuotaGuardFooter';
|
||||
import RoleScopesTransfer from '@/components/RoleScopesTransfer';
|
||||
import { isDevFeaturesEnabled } from '@/consts/env';
|
||||
import { TenantsContext } from '@/contexts/TenantsProvider';
|
||||
|
@ -23,10 +20,9 @@ import RadioGroup, { Radio } from '@/ds-components/RadioGroup';
|
|||
import TextInput from '@/ds-components/TextInput';
|
||||
import useApi from '@/hooks/use-api';
|
||||
import useSubscriptionPlan from '@/hooks/use-subscription-plan';
|
||||
import { ReservedPlanName } from '@/types/subscriptions';
|
||||
import { trySubmitSafe } from '@/utils/form';
|
||||
import { hasReachedQuotaLimit } from '@/utils/quota';
|
||||
|
||||
import Footer from './Footer';
|
||||
import * as styles from './index.module.scss';
|
||||
|
||||
type RadioOption = { key: AdminConsoleKey; value: RoleType; hasPaywall: boolean };
|
||||
|
@ -37,7 +33,6 @@ const radioOptions: RadioOption[] = [
|
|||
];
|
||||
|
||||
export type Props = {
|
||||
totalRoleCount: number;
|
||||
onClose: (createdRole?: Role) => void;
|
||||
};
|
||||
|
||||
|
@ -49,7 +44,7 @@ type CreateRolePayload = Pick<Role, 'name' | 'description' | 'type'> & {
|
|||
scopeIds?: string[];
|
||||
};
|
||||
|
||||
function CreateRoleForm({ totalRoleCount, onClose }: Props) {
|
||||
function CreateRoleForm({ onClose }: Props) {
|
||||
const { currentTenantId } = useContext(TenantsContext);
|
||||
const [isTypeSelectorVisible, setIsTypeSelectorVisible] = useState(false);
|
||||
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
|
||||
|
@ -63,7 +58,6 @@ function CreateRoleForm({ totalRoleCount, onClose }: Props) {
|
|||
} = useForm<CreateRoleFormData>({ defaultValues: { type: RoleType.User } });
|
||||
|
||||
const api = useApi();
|
||||
const roleScopes = watch('scopes', []);
|
||||
|
||||
const onSubmit = handleSubmit(
|
||||
trySubmitSafe(async ({ name, description, type, scopes }) => {
|
||||
|
@ -83,24 +77,6 @@ function CreateRoleForm({ totalRoleCount, onClose }: Props) {
|
|||
})
|
||||
);
|
||||
|
||||
const isRolesReachLimit = hasReachedQuotaLimit({
|
||||
quotaKey: 'rolesLimit',
|
||||
plan: currentPlan,
|
||||
usage: totalRoleCount,
|
||||
});
|
||||
|
||||
const isScopesPerReachLimit = 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: roleScopes.length - 1,
|
||||
});
|
||||
|
||||
return (
|
||||
<ModalLayout
|
||||
title="roles.create_role_title"
|
||||
|
@ -110,66 +86,14 @@ function CreateRoleForm({ totalRoleCount, onClose }: Props) {
|
|||
targetBlank: 'noopener',
|
||||
}}
|
||||
size="large"
|
||||
footer={(() => {
|
||||
if (
|
||||
currentPlan?.name === ReservedPlanName.Free &&
|
||||
watch('type') === RoleType.MachineToMachine
|
||||
) {
|
||||
return (
|
||||
<QuotaGuardFooter>
|
||||
<Trans
|
||||
components={{
|
||||
a: <ContactUsPhraseLink />,
|
||||
}}
|
||||
>
|
||||
{t('upsell.paywall.deprecated_machine_to_machine_feature')}
|
||||
</Trans>
|
||||
</QuotaGuardFooter>
|
||||
);
|
||||
}
|
||||
if (isRolesReachLimit && currentPlan) {
|
||||
return (
|
||||
<QuotaGuardFooter>
|
||||
<Trans
|
||||
components={{
|
||||
a: <ContactUsPhraseLink />,
|
||||
planName: <PlanName name={currentPlan.name} />,
|
||||
}}
|
||||
>
|
||||
{t('upsell.paywall.roles', { count: currentPlan.quota.rolesLimit ?? 0 })}
|
||||
</Trans>
|
||||
</QuotaGuardFooter>
|
||||
);
|
||||
}
|
||||
if (isScopesPerReachLimit && currentPlan && !isRolesReachLimit) {
|
||||
return (
|
||||
<QuotaGuardFooter>
|
||||
<Trans
|
||||
components={{
|
||||
a: <ContactUsPhraseLink />,
|
||||
planName: <PlanName name={currentPlan.name} />,
|
||||
}}
|
||||
>
|
||||
{t('upsell.paywall.scopes_per_role', {
|
||||
count: currentPlan.quota.scopesPerRoleLimit ?? 0,
|
||||
})}
|
||||
</Trans>
|
||||
</QuotaGuardFooter>
|
||||
);
|
||||
}
|
||||
if (!isRolesReachLimit && !isScopesPerReachLimit) {
|
||||
return (
|
||||
<Button
|
||||
isLoading={isSubmitting}
|
||||
htmlType="submit"
|
||||
title="roles.create_role_button"
|
||||
size="large"
|
||||
type="primary"
|
||||
onClick={onSubmit}
|
||||
/>
|
||||
);
|
||||
}
|
||||
})()}
|
||||
footer={
|
||||
<Footer
|
||||
roleType={watch('type')}
|
||||
selectedScopesCount={watch('scopes', []).length}
|
||||
isCreating={isSubmitting}
|
||||
onClickCreate={onSubmit}
|
||||
/>
|
||||
}
|
||||
onClose={onClose}
|
||||
>
|
||||
<form>
|
||||
|
|
|
@ -12,11 +12,10 @@ import type { Props as CreateRoleFormProps } from '../CreateRoleForm';
|
|||
import CreateRoleForm from '../CreateRoleForm';
|
||||
|
||||
type Props = {
|
||||
totalRoleCount: number;
|
||||
onClose: () => void;
|
||||
};
|
||||
|
||||
function CreateRoleModal({ totalRoleCount, onClose }: Props) {
|
||||
function CreateRoleModal({ onClose }: Props) {
|
||||
const { t } = useTranslation(undefined, { keyPrefix: 'admin_console' });
|
||||
const { navigate } = useTenantPathname();
|
||||
|
||||
|
@ -51,7 +50,7 @@ function CreateRoleModal({ totalRoleCount, onClose }: Props) {
|
|||
}}
|
||||
/>
|
||||
) : (
|
||||
<CreateRoleForm totalRoleCount={totalRoleCount} onClose={onCreateFormClose} />
|
||||
<CreateRoleForm onClose={onCreateFormClose} />
|
||||
)}
|
||||
</ReactModal>
|
||||
);
|
||||
|
|
|
@ -196,10 +196,8 @@ function Roles() {
|
|||
onRetry: async () => mutate(undefined, true),
|
||||
}}
|
||||
widgets={
|
||||
isCreating &&
|
||||
totalCount !== undefined && (
|
||||
isCreating && (
|
||||
<CreateRoleModal
|
||||
totalRoleCount={totalCount}
|
||||
onClose={() => {
|
||||
navigate({ pathname: rolesPathname, search });
|
||||
}}
|
||||
|
|
|
@ -40,6 +40,12 @@ const paywall = {
|
|||
'Sie haben das Limit von {{count, number}} <planName/>-Rollen erreicht. Upgraden Sie Ihren Plan, um zusätzliche Rollen und Berechtigungen hinzuzufügen. Zögern Sie nicht, <a>Kontaktieren Sie uns</a>, wenn Sie Hilfe benötigen.',
|
||||
roles_other:
|
||||
'Sie haben das Limit von {{count, number}} <planName/>-Rollen erreicht. Upgraden Sie Ihren Plan, um zusätzliche Rollen und Berechtigungen hinzuzufügen. Zögern Sie nicht, <a>Kontaktieren Sie uns</a>, wenn Sie Hilfe benötigen.',
|
||||
/** UNTRANSLATED */
|
||||
machine_to_machine_roles:
|
||||
'{{count, number}} machine-to-machine role of <planName/> limit reached. Upgrade plan to add additional roles and permissions. Feel free to <a>contact us</a> if you need any assistance.',
|
||||
/** UNTRANSLATED */
|
||||
machine_to_machine_roles_other:
|
||||
'{{count, number}} machine-to-machine roles of <planName/> limit reached. Upgrade plan to add additional roles and permissions. Feel free to <a>contact us</a> if you need any assistance.',
|
||||
scopes_per_role:
|
||||
'Sie haben das Limit von {{count, number}} Berechtigungen pro Rolle von <planName/> erreicht. Upgraden Sie Ihren Plan, um zusätzliche Rollen und Berechtigungen hinzuzufügen. Bei Fragen stehen wir Ihnen gerne zur Verfügung. <a>Kontaktieren Sie uns</a>.',
|
||||
scopes_per_role_other:
|
||||
|
|
|
@ -39,6 +39,10 @@ const paywall = {
|
|||
'{{count, number}} role of <planName/> limit reached. Upgrade plan to add additional roles and permissions. Feel free to <a>contact us</a> if you need any assistance.',
|
||||
roles_other:
|
||||
'{{count, number}} roles of <planName/> limit reached. Upgrade plan to add additional roles and permissions. Feel free to <a>contact us</a> if you need any assistance.',
|
||||
machine_to_machine_roles:
|
||||
'{{count, number}} machine-to-machine role of <planName/> limit reached. Upgrade plan to add additional roles and permissions. Feel free to <a>contact us</a> if you need any assistance.',
|
||||
machine_to_machine_roles_other:
|
||||
'{{count, number}} machine-to-machine roles of <planName/> limit reached. Upgrade plan to add additional roles and permissions. Feel free to <a>contact us</a> if you need any assistance.',
|
||||
scopes_per_role:
|
||||
'{{count, number}} permission per role of <planName/> limit reached. Upgrade plan to add additional roles and permissions. For any assistance, feel free to <a>contact us</a>.',
|
||||
scopes_per_role_other:
|
||||
|
|
|
@ -40,6 +40,12 @@ const paywall = {
|
|||
'Has alcanzado el límite de {{count, number}} roles de <planName/>. Actualiza el plan para agregar roles y permisos adicionales. Si necesitas ayuda, no dudes en <a>contactarnos</a>.',
|
||||
roles_other:
|
||||
'Has alcanzado el límite de {{count, number}} roles de <planName/>. Actualiza el plan para agregar roles y permisos adicionales. Si necesitas ayuda, no dudes en <a>contactarnos</a>.',
|
||||
/** UNTRANSLATED */
|
||||
machine_to_machine_roles:
|
||||
'{{count, number}} machine-to-machine role of <planName/> limit reached. Upgrade plan to add additional roles and permissions. Feel free to <a>contact us</a> if you need any assistance.',
|
||||
/** UNTRANSLATED */
|
||||
machine_to_machine_roles_other:
|
||||
'{{count, number}} machine-to-machine roles of <planName/> limit reached. Upgrade plan to add additional roles and permissions. Feel free to <a>contact us</a> if you need any assistance.',
|
||||
scopes_per_role:
|
||||
'Has alcanzado el límite de {{count, number}} permisos por rol de <planName/>. Actualiza el plan para agregar roles y permisos adicionales. Si necesitas ayuda, no dudes en <a>contactarnos</a>.',
|
||||
scopes_per_role_other:
|
||||
|
|
|
@ -40,6 +40,12 @@ const paywall = {
|
|||
"Vous avez atteint la limite de {{count, number}} rôle de <planName/>. Mettez à niveau votre plan pour ajouter des rôles et des permissions supplémentaires. N’hésitez pas à <a>nous contacter</a> si vous avez besoin d'aide.",
|
||||
roles_other:
|
||||
"Vous avez atteint la limite de {{count, number}} rôles de <planName/>. Mettez à niveau votre plan pour ajouter des rôles et des permissions supplémentaires. N’hésitez pas à <a>nous contacter</a> si vous avez besoin d'aide.",
|
||||
/** UNTRANSLATED */
|
||||
machine_to_machine_roles:
|
||||
'{{count, number}} machine-to-machine role of <planName/> limit reached. Upgrade plan to add additional roles and permissions. Feel free to <a>contact us</a> if you need any assistance.',
|
||||
/** UNTRANSLATED */
|
||||
machine_to_machine_roles_other:
|
||||
'{{count, number}} machine-to-machine roles of <planName/> limit reached. Upgrade plan to add additional roles and permissions. Feel free to <a>contact us</a> if you need any assistance.',
|
||||
scopes_per_role:
|
||||
'Vous avez atteint la limite de {{count, number}} permission par rôle de <planName/>. Mettez à niveau votre plan pour ajouter des rôles et des permissions supplémentaires. Pour toute assistance, n’hésitez pas à <a>nous contacter</a>.',
|
||||
scopes_per_role_other:
|
||||
|
|
|
@ -40,6 +40,12 @@ const paywall = {
|
|||
'Hai raggiunto il limite di {{count, number}} ruoli di <planName/>. Aggiorna il piano per aggiungere ruoli e autorizzazioni aggiuntive. Non esitare a <a>contattarci</a> se hai bisogno di assistenza.',
|
||||
roles_other:
|
||||
'Hai raggiunto il limite di {{count, number}} ruoli di <planName/>. Aggiorna il piano per aggiungere ruoli e autorizzazioni aggiuntive. Non esitare a <a>contattarci</a> se hai bisogno di assistenza.',
|
||||
/** UNTRANSLATED */
|
||||
machine_to_machine_roles:
|
||||
'{{count, number}} machine-to-machine role of <planName/> limit reached. Upgrade plan to add additional roles and permissions. Feel free to <a>contact us</a> if you need any assistance.',
|
||||
/** UNTRANSLATED */
|
||||
machine_to_machine_roles_other:
|
||||
'{{count, number}} machine-to-machine roles of <planName/> limit reached. Upgrade plan to add additional roles and permissions. Feel free to <a>contact us</a> if you need any assistance.',
|
||||
scopes_per_role:
|
||||
'Hai raggiunto il limite di {{count, number}} autorizzazioni per ruolo di <planName/>. Aggiorna il piano per aggiungere ruoli e autorizzazioni aggiuntive. Non esitare a <a>contattarci</a> se hai bisogno di assistenza.',
|
||||
scopes_per_role_other:
|
||||
|
|
|
@ -40,6 +40,12 @@ const paywall = {
|
|||
'{{count, number}}の<planName/>ロール制限に達しました。追加のロールと権限を追加するにはプランをアップグレードしてください。<a>お問い合わせ</a>は何かお手伝いが必要な場合はお気軽にどうぞ。',
|
||||
roles_other:
|
||||
'{{count, number}}の<planName/>ロール制限に達しました。追加のロールと権限を追加するにはプランをアップグレードしてください。<a>お問い合わせ</a>は何かお手伝いが必要な場合はお気軽にどうぞ。',
|
||||
/** UNTRANSLATED */
|
||||
machine_to_machine_roles:
|
||||
'{{count, number}} machine-to-machine role of <planName/> limit reached. Upgrade plan to add additional roles and permissions. Feel free to <a>contact us</a> if you need any assistance.',
|
||||
/** UNTRANSLATED */
|
||||
machine_to_machine_roles_other:
|
||||
'{{count, number}} machine-to-machine roles of <planName/> limit reached. Upgrade plan to add additional roles and permissions. Feel free to <a>contact us</a> if you need any assistance.',
|
||||
scopes_per_role:
|
||||
'{{count, number}}の<planName/>ロールあたりの許可制限に達しました。追加のロールと権限を追加するにはプランをアップグレードしてください。<a>お問い合わせ</a>は何かお手伝いが必要な場合はお気軽にどうぞ。',
|
||||
scopes_per_role_other:
|
||||
|
|
|
@ -40,6 +40,12 @@ const paywall = {
|
|||
'<planName/>의 {{count, number}}개 역할 한도에 도달했습니다. 플랜을 업그레이드하여 추가 역할과 권한을 추가하세요. 도움이 필요하면 <a>문의하기</a>로 연락 주세요.',
|
||||
roles_other:
|
||||
'<planName/>의 {{count, number}}개 역할 한도에 도달했습니다. 플랜을 업그레이드하여 추가 역할과 권한을 추가하세요. 도움이 필요하면 <a>문의하기</a>로 연락 주세요.',
|
||||
/** UNTRANSLATED */
|
||||
machine_to_machine_roles:
|
||||
'{{count, number}} machine-to-machine role of <planName/> limit reached. Upgrade plan to add additional roles and permissions. Feel free to <a>contact us</a> if you need any assistance.',
|
||||
/** UNTRANSLATED */
|
||||
machine_to_machine_roles_other:
|
||||
'{{count, number}} machine-to-machine roles of <planName/> limit reached. Upgrade plan to add additional roles and permissions. Feel free to <a>contact us</a> if you need any assistance.',
|
||||
scopes_per_role:
|
||||
'<planName/>의 {{count, number}}개 역할 당 권한 한도에 도달했습니다. 플랜을 업그레이드하여 추가 역할과 권한을 추가하세요. 도움이 필요하면 <a>문의하기</a>로 연락 주세요.',
|
||||
scopes_per_role_other:
|
||||
|
|
|
@ -40,6 +40,12 @@ const paywall = {
|
|||
'Osiągnięto limit {{count, number}} ról w planie <planName/>. Ulepsz plan, aby dodać dodatkowe role i uprawnienia. Jeśli potrzebujesz pomocy, nie wahaj się <a>skontaktować z nami</a>.',
|
||||
roles_other:
|
||||
'Osiągnięto limit {{count, number}} ról w planie <planName/>. Ulepsz plan, aby dodać dodatkowe role i uprawnienia. Jeśli potrzebujesz pomocy, nie wahaj się <a>skontaktować z nami</a>.',
|
||||
/** UNTRANSLATED */
|
||||
machine_to_machine_roles:
|
||||
'{{count, number}} machine-to-machine role of <planName/> limit reached. Upgrade plan to add additional roles and permissions. Feel free to <a>contact us</a> if you need any assistance.',
|
||||
/** UNTRANSLATED */
|
||||
machine_to_machine_roles_other:
|
||||
'{{count, number}} machine-to-machine roles of <planName/> limit reached. Upgrade plan to add additional roles and permissions. Feel free to <a>contact us</a> if you need any assistance.',
|
||||
scopes_per_role:
|
||||
'Osiągnięto limit {{count, number}} uprawnień na rolę w planie <planName/>. Ulepsz plan, aby dodać dodatkowe role i uprawnienia. W razie potrzeb, skontaktuj się z nami <a>tutaj</a>.',
|
||||
scopes_per_role_other:
|
||||
|
|
|
@ -40,6 +40,12 @@ const paywall = {
|
|||
'Atingiu o limite de {{count, number}} funções de <planName/>. Atualize o plano para adicionar funções e permissões adicionais. Não hesite em <a>Contacte-nos</a> se precisar de ajuda.',
|
||||
roles_other:
|
||||
'Atingiu o limite de {{count, number}} funções de <planName/>. Atualize o plano para adicionar funções e permissões adicionais. Não hesite em <a>Contacte-nos</a> se precisar de ajuda.',
|
||||
/** UNTRANSLATED */
|
||||
machine_to_machine_roles:
|
||||
'{{count, number}} machine-to-machine role of <planName/> limit reached. Upgrade plan to add additional roles and permissions. Feel free to <a>contact us</a> if you need any assistance.',
|
||||
/** UNTRANSLATED */
|
||||
machine_to_machine_roles_other:
|
||||
'{{count, number}} machine-to-machine roles of <planName/> limit reached. Upgrade plan to add additional roles and permissions. Feel free to <a>contact us</a> if you need any assistance.',
|
||||
scopes_per_role:
|
||||
'Atingiu o limite de {{count, number}} permissões por função de <planName/>. Atualize o plano para adicionar funções e permissões adicionais. Não hesite em <a>Contacte-nos</a> se precisar de ajuda.',
|
||||
scopes_per_role_other:
|
||||
|
|
|
@ -40,6 +40,12 @@ const paywall = {
|
|||
'Atingiu o limite de {{count, number}} funções de <planName/>. Atualize o plano para adicionar funções e permissões adicionais. Não hesite em <a>Contacte-nos</a> se precisar de ajuda.',
|
||||
roles_other:
|
||||
'Atingiu o limite de {{count, number}} funções de <planName/>. Atualize o plano para adicionar funções e permissões adicionais. Não hesite em <a>Contacte-nos</a> se precisar de ajuda.',
|
||||
/** UNTRANSLATED */
|
||||
machine_to_machine_roles:
|
||||
'{{count, number}} machine-to-machine role of <planName/> limit reached. Upgrade plan to add additional roles and permissions. Feel free to <a>contact us</a> if you need any assistance.',
|
||||
/** UNTRANSLATED */
|
||||
machine_to_machine_roles_other:
|
||||
'{{count, number}} machine-to-machine roles of <planName/> limit reached. Upgrade plan to add additional roles and permissions. Feel free to <a>contact us</a> if you need any assistance.',
|
||||
scopes_per_role:
|
||||
'Atingiu o limite de {{count, number}} permissões por função de <planName/>. Atualize o plano para adicionar funções e permissões adicionais. Não hesite em <a>Contacte-nos</a> se precisar de ajuda.',
|
||||
scopes_per_role_other:
|
||||
|
|
|
@ -40,6 +40,12 @@ const paywall = {
|
|||
'Достигнут лимит {{count, number}} ролей в плане <planName/>. Повысьте план, чтобы добавить дополнительные роли и разрешения. Если вам нужна помощь, не стесняйтесь <a>связаться с нами</a>.',
|
||||
roles_other:
|
||||
'Достигнут лимит {{count, number}} ролей в плане <planName/>. Повысьте план, чтобы добавить дополнительные роли и разрешения. Если вам нужна помощь, не стесняйтесь <a>связаться с нами</a>.',
|
||||
/** UNTRANSLATED */
|
||||
machine_to_machine_roles:
|
||||
'{{count, number}} machine-to-machine role of <planName/> limit reached. Upgrade plan to add additional roles and permissions. Feel free to <a>contact us</a> if you need any assistance.',
|
||||
/** UNTRANSLATED */
|
||||
machine_to_machine_roles_other:
|
||||
'{{count, number}} machine-to-machine roles of <planName/> limit reached. Upgrade plan to add additional roles and permissions. Feel free to <a>contact us</a> if you need any assistance.',
|
||||
scopes_per_role:
|
||||
'Достигнут лимит {{count, number}} разрешений на роль в плане <planName/>. Повысьте план, чтобы добавить дополнительные роли и разрешения. Если вам нужна помощь, не стесняйтесь <a>связаться с нами</a>.',
|
||||
scopes_per_role_other:
|
||||
|
|
|
@ -40,6 +40,12 @@ const paywall = {
|
|||
'{{count, number}} <planName/> rol sınırına ulaşıldı. İlave roller ve izinler eklemek için planı yükseltin. Yardıma ihtiyacınız olursa, <a>iletişime geçin</a>.',
|
||||
roles_other:
|
||||
'{{count, number}} <planName/> rol sınırına ulaşıldı. İlave roller ve izinler eklemek için planı yükseltin. Yardıma ihtiyacınız olursa, <a>iletişime geçin</a>.',
|
||||
/** UNTRANSLATED */
|
||||
machine_to_machine_roles:
|
||||
'{{count, number}} machine-to-machine role of <planName/> limit reached. Upgrade plan to add additional roles and permissions. Feel free to <a>contact us</a> if you need any assistance.',
|
||||
/** UNTRANSLATED */
|
||||
machine_to_machine_roles_other:
|
||||
'{{count, number}} machine-to-machine roles of <planName/> limit reached. Upgrade plan to add additional roles and permissions. Feel free to <a>contact us</a> if you need any assistance.',
|
||||
scopes_per_role:
|
||||
'{{count, number}} <planName/> rol başına izin sınırına ulaşıldı. İlave roller ve izinler eklemek için planı yükseltin. Yardıma ihtiyacınız olursa, <a>iletişime geçin</a>.',
|
||||
scopes_per_role_other:
|
||||
|
|
|
@ -40,6 +40,12 @@ const paywall = {
|
|||
'已达到<planName/>的{{count, number}}个角色限制。升级计划以添加额外的角色和权限。如需任何帮助,请<a>联系我们</a>。',
|
||||
roles_other:
|
||||
'已达到<planName/>的{{count, number}}个角色限制。升级计划以添加额外的角色和权限。如需任何帮助,请<a>联系我们</a>。',
|
||||
/** UNTRANSLATED */
|
||||
machine_to_machine_roles:
|
||||
'{{count, number}} machine-to-machine role of <planName/> limit reached. Upgrade plan to add additional roles and permissions. Feel free to <a>contact us</a> if you need any assistance.',
|
||||
/** UNTRANSLATED */
|
||||
machine_to_machine_roles_other:
|
||||
'{{count, number}} machine-to-machine roles of <planName/> limit reached. Upgrade plan to add additional roles and permissions. Feel free to <a>contact us</a> if you need any assistance.',
|
||||
scopes_per_role:
|
||||
'已达到<planName/>的{{count, number}}个角色每个权限限制。升级计划以添加额外的角色和权限。如需任何帮助,请<a>联系我们</a>。',
|
||||
scopes_per_role_other:
|
||||
|
|
|
@ -40,6 +40,12 @@ const paywall = {
|
|||
'已達到<planName/>的{{count, number}}個角色限制。升級計劃以添加額外的角色和權限。如需任何協助,歡迎<a>聯繫我們</a>。',
|
||||
roles_other:
|
||||
'已達到<planName/>的{{count, number}}個角色限制。升級計劃以添加額外的角色和權限。如需任何協助,歡迎<a>聯繫我們</a>。',
|
||||
/** UNTRANSLATED */
|
||||
machine_to_machine_roles:
|
||||
'{{count, number}} machine-to-machine role of <planName/> limit reached. Upgrade plan to add additional roles and permissions. Feel free to <a>contact us</a> if you need any assistance.',
|
||||
/** UNTRANSLATED */
|
||||
machine_to_machine_roles_other:
|
||||
'{{count, number}} machine-to-machine roles of <planName/> limit reached. Upgrade plan to add additional roles and permissions. Feel free to <a>contact us</a> if you need any assistance.',
|
||||
scopes_per_role:
|
||||
'已達到<planName/>的{{count, number}}個角色每個權限限制。升級計劃以添加額外的角色和權限。如需任何協助,歡迎<a>聯繫我們</a>。',
|
||||
scopes_per_role_other:
|
||||
|
|
|
@ -40,6 +40,12 @@ const paywall = {
|
|||
'已達到<planName/>的{{count, number}}個角色限制。升級計劃以添加額外的角色和權限。如需任何幫助,請<a>聯繫我們</a>。',
|
||||
roles_other:
|
||||
'已達到<planName/>的{{count, number}}個角色限制。升級計劃以添加額外的角色和權限。如需任何幫助,請<a>聯繫我們</a>。',
|
||||
/** UNTRANSLATED */
|
||||
machine_to_machine_roles:
|
||||
'{{count, number}} machine-to-machine role of <planName/> limit reached. Upgrade plan to add additional roles and permissions. Feel free to <a>contact us</a> if you need any assistance.',
|
||||
/** UNTRANSLATED */
|
||||
machine_to_machine_roles_other:
|
||||
'{{count, number}} machine-to-machine roles of <planName/> limit reached. Upgrade plan to add additional roles and permissions. Feel free to <a>contact us</a> if you need any assistance.',
|
||||
scopes_per_role:
|
||||
'已達到<planName/>的{{count, number}}個角色每個權限限制。升級計劃以添加額外的角色和權限。如需任何幫助,請<a>聯繫我們</a>。',
|
||||
scopes_per_role_other:
|
||||
|
|
Loading…
Add table
Reference in a new issue