Compare commits

...

5 Commits

Author SHA1 Message Date
L1nSn0w
366f905b94 fix(workspace): update tenant plan retrieval logic to default to SANDBOX when billing is disabled 2026-03-20 00:20:56 +08:00
L1nSn0w
e7ac298ec6 feat(workspace): integrate BillingService for SaaS tenant plan retrieval
- Added BillingService to fetch tenant plans in bulk for SaaS configurations.
- Updated TenantListApi to handle plan assignment based on billing status.
- Enhanced unit tests to validate new billing logic and fallback mechanisms.
2026-03-20 00:20:56 +08:00
L1nSn0w
f864604c94 feat(workspace): enhance TenantListApi to handle enterprise and billing configurations
- Introduced dify_config to manage enterprise and billing settings.
- Updated tenant plan retrieval logic based on enterprise and billing status.
- Modified unit tests to cover new configurations and ensure correct plan assignment.
2026-03-20 00:20:56 +08:00
Tim Ren
7d19825659 fix(tests): correct keyword arguments in tool provider test constructors (#33767) 2026-03-20 00:16:44 +09:00
github-actions[bot]
11e1787100 chore(i18n): sync translations with en-US (#33749)
Some checks failed
Trigger i18n Sync on Push / trigger (push) Waiting to run
autofix.ci / autofix (push) Has been cancelled
Build and Push API & Web / build (api, DIFY_API_IMAGE_NAME, linux/amd64, build-api-amd64) (push) Has been cancelled
Build and Push API & Web / build (api, DIFY_API_IMAGE_NAME, linux/arm64, build-api-arm64) (push) Has been cancelled
Build and Push API & Web / build (web, DIFY_WEB_IMAGE_NAME, linux/amd64, build-web-amd64) (push) Has been cancelled
Build and Push API & Web / build (web, DIFY_WEB_IMAGE_NAME, linux/arm64, build-web-arm64) (push) Has been cancelled
Build and Push API & Web / create-manifest (api, DIFY_API_IMAGE_NAME, merge-api-images) (push) Has been cancelled
Build and Push API & Web / create-manifest (web, DIFY_WEB_IMAGE_NAME, merge-web-images) (push) Has been cancelled
Main CI Pipeline / Check Changed Files (push) Has been cancelled
Main CI Pipeline / API Tests (push) Has been cancelled
Main CI Pipeline / Web Tests (push) Has been cancelled
Main CI Pipeline / Style Check (push) Has been cancelled
Main CI Pipeline / VDB Tests (push) Has been cancelled
Main CI Pipeline / DB Migration Test (push) Has been cancelled
Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: yyh <92089059+lyzno1@users.noreply.github.com>
2026-03-19 22:03:07 +08:00
25 changed files with 260 additions and 22 deletions

View File

@@ -7,6 +7,7 @@ from sqlalchemy import select
from werkzeug.exceptions import Unauthorized
import services
from configs import dify_config
from controllers.common.errors import (
FilenameNotExistsError,
FileTooLargeError,
@@ -29,6 +30,7 @@ from libs.helper import TimestampField
from libs.login import current_account_with_tenant, login_required
from models.account import Tenant, TenantStatus
from services.account_service import TenantService
from services.billing_service import BillingService
from services.enterprise.enterprise_service import EnterpriseService
from services.feature_service import FeatureService
from services.file_service import FileService
@@ -108,9 +110,27 @@ class TenantListApi(Resource):
current_user, current_tenant_id = current_account_with_tenant()
tenants = TenantService.get_join_tenants(current_user)
tenant_dicts = []
is_enterprise_only = dify_config.ENTERPRISE_ENABLED and not dify_config.BILLING_ENABLED
is_saas = dify_config.EDITION == "CLOUD" and dify_config.BILLING_ENABLED
tenant_plans: dict[str, dict] = {}
use_legacy_feature_path = not is_enterprise_only and not is_saas
if is_saas:
tenant_ids = [tenant.id for tenant in tenants]
if tenant_ids:
try:
tenant_plans = BillingService.get_plan_bulk(tenant_ids)
except Exception:
logger.exception("failed to fetch workspace plans in bulk, falling back to legacy feature path")
use_legacy_feature_path = True
for tenant in tenants:
features = FeatureService.get_features(tenant.id)
plan = CloudPlan.SANDBOX
if is_saas and not use_legacy_feature_path:
plan = tenant_plans.get(tenant.id, {}).get("plan", CloudPlan.SANDBOX)
elif not is_enterprise_only:
features = FeatureService.get_features(tenant.id)
plan = features.billing.subscription.plan or CloudPlan.SANDBOX
# Create a dictionary with tenant attributes
tenant_dict = {
@@ -118,7 +138,7 @@ class TenantListApi(Resource):
"name": tenant.name,
"status": tenant.status,
"created_at": tenant.created_at,
"plan": features.billing.subscription.plan if features.billing.enabled else CloudPlan.SANDBOX,
"plan": plan,
"current": tenant.id == current_tenant_id if current_tenant_id else False,
}

View File

@@ -48,41 +48,42 @@ class TestToolTransformService:
name=fake.company(),
description=fake.text(max_nb_chars=100),
icon='{"background": "#FF6B6B", "content": "🔧"}',
icon_dark='{"background": "#252525", "content": "🔧"}',
tenant_id="test_tenant_id",
user_id="test_user_id",
credentials={"auth_type": "api_key_header", "api_key": "test_key"},
provider_type="api",
credentials_str='{"auth_type": "api_key_header", "api_key": "test_key"}',
schema="{}",
schema_type_str="openapi",
tools_str="[]",
)
elif provider_type == "builtin":
provider = BuiltinToolProvider(
name=fake.company(),
description=fake.text(max_nb_chars=100),
icon="🔧",
icon_dark="🔧",
tenant_id="test_tenant_id",
user_id="test_user_id",
provider="test_provider",
credential_type="api_key",
credentials={"api_key": "test_key"},
encrypted_credentials='{"api_key": "test_key"}',
)
elif provider_type == "workflow":
provider = WorkflowToolProvider(
name=fake.company(),
description=fake.text(max_nb_chars=100),
icon='{"background": "#FF6B6B", "content": "🔧"}',
icon_dark='{"background": "#252525", "content": "🔧"}',
tenant_id="test_tenant_id",
user_id="test_user_id",
workflow_id="test_workflow_id",
app_id="test_workflow_id",
label="Test Workflow",
version="1.0.0",
parameter_configuration="[]",
)
elif provider_type == "mcp":
provider = MCPToolProvider(
name=fake.company(),
description=fake.text(max_nb_chars=100),
provider_icon='{"background": "#FF6B6B", "content": "🔧"}',
icon='{"background": "#FF6B6B", "content": "🔧"}',
tenant_id="test_tenant_id",
user_id="test_user_id",
server_url="https://mcp.example.com",
server_url_hash="test_server_url_hash",
server_identifier="test_server",
tools='[{"name": "test_tool", "description": "Test tool"}]',
authed=True,

View File

@@ -36,7 +36,98 @@ def unwrap(func):
class TestTenantListApi:
def test_get_success(self, app):
def test_get_success_saas_path(self, app):
api = TenantListApi()
method = unwrap(api.get)
tenant1 = MagicMock(
id="t1",
name="Tenant 1",
status="active",
created_at=datetime.utcnow(),
)
tenant2 = MagicMock(
id="t2",
name="Tenant 2",
status="active",
created_at=datetime.utcnow(),
)
with (
app.test_request_context("/workspaces"),
patch(
"controllers.console.workspace.workspace.current_account_with_tenant", return_value=(MagicMock(), "t1")
),
patch(
"controllers.console.workspace.workspace.TenantService.get_join_tenants",
return_value=[tenant1, tenant2],
),
patch("controllers.console.workspace.workspace.dify_config.ENTERPRISE_ENABLED", False),
patch("controllers.console.workspace.workspace.dify_config.BILLING_ENABLED", True),
patch("controllers.console.workspace.workspace.dify_config.EDITION", "CLOUD"),
patch(
"controllers.console.workspace.workspace.BillingService.get_plan_bulk",
return_value={
"t1": {"plan": CloudPlan.TEAM, "expiration_date": 0},
"t2": {"plan": CloudPlan.PROFESSIONAL, "expiration_date": 0},
},
) as get_plan_bulk_mock,
patch("controllers.console.workspace.workspace.FeatureService.get_features") as get_features_mock,
):
result, status = method(api)
assert status == 200
assert len(result["workspaces"]) == 2
assert result["workspaces"][0]["current"] is True
assert result["workspaces"][0]["plan"] == CloudPlan.TEAM
assert result["workspaces"][1]["plan"] == CloudPlan.PROFESSIONAL
get_plan_bulk_mock.assert_called_once_with(["t1", "t2"])
get_features_mock.assert_not_called()
def test_get_saas_path_falls_back_to_sandbox_for_missing_tenant(self, app):
api = TenantListApi()
method = unwrap(api.get)
tenant1 = MagicMock(
id="t1",
name="Tenant 1",
status="active",
created_at=datetime.utcnow(),
)
tenant2 = MagicMock(
id="t2",
name="Tenant 2",
status="active",
created_at=datetime.utcnow(),
)
with (
app.test_request_context("/workspaces"),
patch(
"controllers.console.workspace.workspace.current_account_with_tenant", return_value=(MagicMock(), "t1")
),
patch(
"controllers.console.workspace.workspace.TenantService.get_join_tenants",
return_value=[tenant1, tenant2],
),
patch("controllers.console.workspace.workspace.dify_config.ENTERPRISE_ENABLED", False),
patch("controllers.console.workspace.workspace.dify_config.BILLING_ENABLED", True),
patch("controllers.console.workspace.workspace.dify_config.EDITION", "CLOUD"),
patch(
"controllers.console.workspace.workspace.BillingService.get_plan_bulk",
return_value={"t1": {"plan": CloudPlan.TEAM, "expiration_date": 0}},
) as get_plan_bulk_mock,
patch("controllers.console.workspace.workspace.FeatureService.get_features") as get_features_mock,
):
result, status = method(api)
assert status == 200
assert result["workspaces"][0]["plan"] == CloudPlan.TEAM
assert result["workspaces"][1]["plan"] == CloudPlan.SANDBOX
get_plan_bulk_mock.assert_called_once_with(["t1", "t2"])
get_features_mock.assert_not_called()
def test_get_saas_path_falls_back_to_legacy_feature_path_on_bulk_error(self, app):
api = TenantListApi()
method = unwrap(api.get)
@@ -54,27 +145,41 @@ class TestTenantListApi:
)
features = MagicMock()
features.billing.enabled = True
features.billing.subscription.plan = CloudPlan.SANDBOX
features.billing.enabled = False
features.billing.subscription.plan = CloudPlan.TEAM
with (
app.test_request_context("/workspaces"),
patch(
"controllers.console.workspace.workspace.current_account_with_tenant", return_value=(MagicMock(), "t1")
"controllers.console.workspace.workspace.current_account_with_tenant", return_value=(MagicMock(), "t2")
),
patch(
"controllers.console.workspace.workspace.TenantService.get_join_tenants",
return_value=[tenant1, tenant2],
),
patch("controllers.console.workspace.workspace.FeatureService.get_features", return_value=features),
patch("controllers.console.workspace.workspace.dify_config.ENTERPRISE_ENABLED", False),
patch("controllers.console.workspace.workspace.dify_config.BILLING_ENABLED", True),
patch("controllers.console.workspace.workspace.dify_config.EDITION", "CLOUD"),
patch(
"controllers.console.workspace.workspace.BillingService.get_plan_bulk",
side_effect=RuntimeError("billing down"),
) as get_plan_bulk_mock,
patch(
"controllers.console.workspace.workspace.FeatureService.get_features",
return_value=features,
) as get_features_mock,
patch("controllers.console.workspace.workspace.logger.exception") as logger_exception_mock,
):
result, status = method(api)
assert status == 200
assert len(result["workspaces"]) == 2
assert result["workspaces"][0]["current"] is True
assert result["workspaces"][0]["plan"] == CloudPlan.TEAM
assert result["workspaces"][1]["plan"] == CloudPlan.TEAM
get_plan_bulk_mock.assert_called_once_with(["t1", "t2"])
assert get_features_mock.call_count == 2
logger_exception_mock.assert_called_once()
def test_get_billing_disabled(self, app):
def test_get_billing_disabled_community_path(self, app):
api = TenantListApi()
method = unwrap(api.get)
@@ -98,15 +203,83 @@ class TestTenantListApi:
"controllers.console.workspace.workspace.TenantService.get_join_tenants",
return_value=[tenant],
),
patch("controllers.console.workspace.workspace.dify_config.ENTERPRISE_ENABLED", False),
patch("controllers.console.workspace.workspace.dify_config.BILLING_ENABLED", False),
patch("controllers.console.workspace.workspace.dify_config.EDITION", "SELF_HOSTED"),
patch(
"controllers.console.workspace.workspace.FeatureService.get_features",
return_value=features,
),
) as get_features_mock,
):
result, status = method(api)
assert status == 200
assert result["workspaces"][0]["plan"] == CloudPlan.SANDBOX
get_features_mock.assert_called_once_with("t1")
def test_get_enterprise_only_skips_feature_service(self, app):
api = TenantListApi()
method = unwrap(api.get)
tenant1 = MagicMock(
id="t1",
name="Tenant 1",
status="active",
created_at=datetime.utcnow(),
)
tenant2 = MagicMock(
id="t2",
name="Tenant 2",
status="active",
created_at=datetime.utcnow(),
)
with (
app.test_request_context("/workspaces"),
patch(
"controllers.console.workspace.workspace.current_account_with_tenant", return_value=(MagicMock(), "t2")
),
patch(
"controllers.console.workspace.workspace.TenantService.get_join_tenants",
return_value=[tenant1, tenant2],
),
patch("controllers.console.workspace.workspace.dify_config.ENTERPRISE_ENABLED", True),
patch("controllers.console.workspace.workspace.dify_config.BILLING_ENABLED", False),
patch("controllers.console.workspace.workspace.dify_config.EDITION", "SELF_HOSTED"),
patch("controllers.console.workspace.workspace.FeatureService.get_features") as get_features_mock,
):
result, status = method(api)
assert status == 200
assert result["workspaces"][0]["plan"] == CloudPlan.SANDBOX
assert result["workspaces"][1]["plan"] == CloudPlan.SANDBOX
assert result["workspaces"][0]["current"] is False
assert result["workspaces"][1]["current"] is True
get_features_mock.assert_not_called()
def test_get_enterprise_only_with_empty_tenants(self, app):
api = TenantListApi()
method = unwrap(api.get)
with (
app.test_request_context("/workspaces"),
patch(
"controllers.console.workspace.workspace.current_account_with_tenant", return_value=(MagicMock(), None)
),
patch(
"controllers.console.workspace.workspace.TenantService.get_join_tenants",
return_value=[],
),
patch("controllers.console.workspace.workspace.dify_config.ENTERPRISE_ENABLED", True),
patch("controllers.console.workspace.workspace.dify_config.BILLING_ENABLED", False),
patch("controllers.console.workspace.workspace.dify_config.EDITION", "SELF_HOSTED"),
patch("controllers.console.workspace.workspace.FeatureService.get_features") as get_features_mock,
):
result, status = method(api)
assert status == 200
assert result["workspaces"] == []
get_features_mock.assert_not_called()
class TestWorkspaceListApi:

View File

@@ -35,6 +35,8 @@
"error.emailEmpty": "عنوان البريد الإلكتروني مطلوب",
"error.emailInValid": "يرجى إدخال عنوان بريد إلكتروني صالح",
"error.invalidEmailOrPassword": "بريد إلكتروني أو كلمة مرور غير صالحة.",
"error.invalidRedirectUrlOrAppCode": "رابط إعادة التوجيه أو رمز التطبيق غير صالح",
"error.invalidSSOProtocol": "بروتوكول SSO غير صالح",
"error.nameEmpty": "الاسم مطلوب",
"error.passwordEmpty": "كلمة المرور مطلوبة",
"error.passwordInvalid": "يجب أن تحتوي كلمة المرور على أحرف وأرقام، ويجب أن يكون الطول أكبر من 8",

View File

@@ -35,6 +35,8 @@
"error.emailEmpty": "E-Mail-Adresse wird benötigt",
"error.emailInValid": "Bitte gib eine gültige E-Mail-Adresse ein",
"error.invalidEmailOrPassword": "Ungültige E-Mail oder Passwort.",
"error.invalidRedirectUrlOrAppCode": "Ungültige Weiterleitungs-URL oder App-Code",
"error.invalidSSOProtocol": "Ungültiges SSO-Protokoll",
"error.nameEmpty": "Name wird benötigt",
"error.passwordEmpty": "Passwort wird benötigt",
"error.passwordInvalid": "Das Passwort muss Buchstaben und Zahlen enthalten und länger als 8 Zeichen sein",

View File

@@ -35,6 +35,8 @@
"error.emailEmpty": "Se requiere una dirección de correo electrónico",
"error.emailInValid": "Por favor, ingresa una dirección de correo electrónico válida",
"error.invalidEmailOrPassword": "Correo electrónico o contraseña inválidos.",
"error.invalidRedirectUrlOrAppCode": "URL de redirección o código de aplicación inválido",
"error.invalidSSOProtocol": "Protocolo SSO inválido",
"error.nameEmpty": "Se requiere un nombre",
"error.passwordEmpty": "Se requiere una contraseña",
"error.passwordInvalid": "La contraseña debe contener letras y números, y tener una longitud mayor a 8",

View File

@@ -35,6 +35,8 @@
"error.emailEmpty": "آدرس ایمیل لازم است",
"error.emailInValid": "لطفاً یک آدرس ایمیل معتبر وارد کنید",
"error.invalidEmailOrPassword": "ایمیل یا رمز عبور نامعتبر است.",
"error.invalidRedirectUrlOrAppCode": "آدرس تغییر مسیر یا کد برنامه نامعتبر است",
"error.invalidSSOProtocol": "پروتکل SSO نامعتبر است",
"error.nameEmpty": "نام لازم است",
"error.passwordEmpty": "رمز عبور لازم است",
"error.passwordInvalid": "رمز عبور باید شامل حروف و اعداد باشد و طول آن بیشتر از ۸ کاراکتر باشد",

View File

@@ -35,6 +35,8 @@
"error.emailEmpty": "Une adresse e-mail est requise",
"error.emailInValid": "Veuillez entrer une adresse email valide",
"error.invalidEmailOrPassword": "Adresse e-mail ou mot de passe invalide.",
"error.invalidRedirectUrlOrAppCode": "URL de redirection ou code d'application invalide",
"error.invalidSSOProtocol": "Protocole SSO invalide",
"error.nameEmpty": "Le nom est requis",
"error.passwordEmpty": "Un mot de passe est requis",
"error.passwordInvalid": "Le mot de passe doit contenir des lettres et des chiffres, et la longueur doit être supérieure à 8.",

View File

@@ -35,6 +35,8 @@
"error.emailEmpty": "ईमेल पता आवश्यक है",
"error.emailInValid": "कृपया एक मान्य ईमेल पता दर्ज करें",
"error.invalidEmailOrPassword": "अमान्य ईमेल या पासवर्ड।",
"error.invalidRedirectUrlOrAppCode": "अमान्य रीडायरेक्ट URL या ऐप कोड",
"error.invalidSSOProtocol": "अमान्य SSO प्रोटोकॉल",
"error.nameEmpty": "नाम आवश्यक है",
"error.passwordEmpty": "पासवर्ड आवश्यक है",
"error.passwordInvalid": "पासवर्ड में अक्षर और अंक होने चाहिए, और लंबाई 8 से अधिक होनी चाहिए",

View File

@@ -35,6 +35,8 @@
"error.emailEmpty": "Alamat email diperlukan",
"error.emailInValid": "Silakan masukkan alamat email yang valid",
"error.invalidEmailOrPassword": "Email atau kata sandi tidak valid.",
"error.invalidRedirectUrlOrAppCode": "URL pengalihan atau kode aplikasi tidak valid",
"error.invalidSSOProtocol": "Protokol SSO tidak valid",
"error.nameEmpty": "Nama diperlukan",
"error.passwordEmpty": "Kata sandi diperlukan",
"error.passwordInvalid": "Kata sandi harus berisi huruf dan angka, dan panjangnya harus lebih besar dari 8",

View File

@@ -35,6 +35,8 @@
"error.emailEmpty": "L'indirizzo email è obbligatorio",
"error.emailInValid": "Per favore inserisci un indirizzo email valido",
"error.invalidEmailOrPassword": "Email o password non validi.",
"error.invalidRedirectUrlOrAppCode": "URL di reindirizzamento o codice app non valido",
"error.invalidSSOProtocol": "Protocollo SSO non valido",
"error.nameEmpty": "Il nome è obbligatorio",
"error.passwordEmpty": "La password è obbligatoria",
"error.passwordInvalid": "La password deve contenere lettere e numeri, e la lunghezza deve essere maggiore di 8",

View File

@@ -35,6 +35,8 @@
"error.emailEmpty": "メールアドレスは必須です",
"error.emailInValid": "有効なメールアドレスを入力してください",
"error.invalidEmailOrPassword": "無効なメールアドレスまたはパスワードです。",
"error.invalidRedirectUrlOrAppCode": "無効なリダイレクトURLまたはアプリコード",
"error.invalidSSOProtocol": "無効なSSOプロトコル",
"error.nameEmpty": "名前は必須です",
"error.passwordEmpty": "パスワードは必須です",
"error.passwordInvalid": "パスワードは文字と数字を含み、長さは 8 以上である必要があります",

View File

@@ -35,6 +35,8 @@
"error.emailEmpty": "이메일 주소를 입력하세요.",
"error.emailInValid": "유효한 이메일 주소를 입력하세요.",
"error.invalidEmailOrPassword": "유효하지 않은 이메일이나 비밀번호입니다.",
"error.invalidRedirectUrlOrAppCode": "유효하지 않은 리디렉션 URL 또는 앱 코드",
"error.invalidSSOProtocol": "유효하지 않은 SSO 프로토콜",
"error.nameEmpty": "사용자 이름을 입력하세요.",
"error.passwordEmpty": "비밀번호를 입력하세요.",
"error.passwordInvalid": "비밀번호는 문자와 숫자를 포함하고 8 자 이상이어야 합니다.",

View File

@@ -35,6 +35,8 @@
"error.emailEmpty": "Email address is required",
"error.emailInValid": "Please enter a valid email address",
"error.invalidEmailOrPassword": "Invalid email or password.",
"error.invalidRedirectUrlOrAppCode": "Ongeldige doorstuur-URL of app-code",
"error.invalidSSOProtocol": "Ongeldig SSO-protocol",
"error.nameEmpty": "Name is required",
"error.passwordEmpty": "Password is required",
"error.passwordInvalid": "Password must contain letters and numbers, and the length must be greater than 8",

View File

@@ -35,6 +35,8 @@
"error.emailEmpty": "Adres e-mail jest wymagany",
"error.emailInValid": "Proszę wpisać prawidłowy adres e-mail",
"error.invalidEmailOrPassword": "Nieprawidłowy adres e-mail lub hasło.",
"error.invalidRedirectUrlOrAppCode": "Nieprawidłowy adres URL przekierowania lub kod aplikacji",
"error.invalidSSOProtocol": "Nieprawidłowy protokół SSO",
"error.nameEmpty": "Nazwa jest wymagana",
"error.passwordEmpty": "Hasło jest wymagane",
"error.passwordInvalid": "Hasło musi zawierać litery i cyfry, a jego długość musi być większa niż 8",

View File

@@ -35,6 +35,8 @@
"error.emailEmpty": "O endereço de e-mail é obrigatório",
"error.emailInValid": "Digite um endereço de e-mail válido",
"error.invalidEmailOrPassword": "E-mail ou senha inválidos.",
"error.invalidRedirectUrlOrAppCode": "URL de redirecionamento ou código de aplicativo inválido",
"error.invalidSSOProtocol": "Protocolo SSO inválido",
"error.nameEmpty": "O nome é obrigatório",
"error.passwordEmpty": "A senha é obrigatória",
"error.passwordInvalid": "A senha deve conter letras e números e ter um comprimento maior que 8",

View File

@@ -35,6 +35,8 @@
"error.emailEmpty": "Adresa de email este obligatorie",
"error.emailInValid": "Te rugăm să introduci o adresă de email validă",
"error.invalidEmailOrPassword": "Email sau parolă invalidă.",
"error.invalidRedirectUrlOrAppCode": "URL de redirecționare sau cod de aplicație invalid",
"error.invalidSSOProtocol": "Protocol SSO invalid",
"error.nameEmpty": "Numele este obligatoriu",
"error.passwordEmpty": "Parola este obligatorie",
"error.passwordInvalid": "Parola trebuie să conțină litere și cifre, iar lungimea trebuie să fie mai mare de 8 caractere",

View File

@@ -35,6 +35,8 @@
"error.emailEmpty": "Адрес электронной почты обязателен",
"error.emailInValid": "Пожалуйста, введите действительный адрес электронной почты",
"error.invalidEmailOrPassword": "Неверный адрес электронной почты или пароль.",
"error.invalidRedirectUrlOrAppCode": "Неверный URL перенаправления или код приложения",
"error.invalidSSOProtocol": "Неверный протокол SSO",
"error.nameEmpty": "Имя обязательно",
"error.passwordEmpty": "Пароль обязателен",
"error.passwordInvalid": "Пароль должен содержать буквы и цифры, а длина должна быть больше 8",

View File

@@ -35,6 +35,8 @@
"error.emailEmpty": "E-poštni naslov je obvezen",
"error.emailInValid": "Prosimo, vnesite veljaven e-poštni naslov",
"error.invalidEmailOrPassword": "Neveljaven e-poštni naslov ali geslo.",
"error.invalidRedirectUrlOrAppCode": "Neveljaven URL preusmeritve ali koda aplikacije",
"error.invalidSSOProtocol": "Neveljaven protokol SSO",
"error.nameEmpty": "Ime je obvezno",
"error.passwordEmpty": "Geslo je obvezno",
"error.passwordInvalid": "Geslo mora vsebovati črke in številke, dolžina pa mora biti več kot 8 znakov",

View File

@@ -35,6 +35,8 @@
"error.emailEmpty": "ต้องระบุที่อยู่อีเมล",
"error.emailInValid": "โปรดป้อนที่อยู่อีเมลที่ถูกต้อง",
"error.invalidEmailOrPassword": "อีเมลหรือรหัสผ่านไม่ถูกต้อง.",
"error.invalidRedirectUrlOrAppCode": "URL เปลี่ยนเส้นทางหรือรหัสแอปไม่ถูกต้อง",
"error.invalidSSOProtocol": "โปรโตคอล SSO ไม่ถูกต้อง",
"error.nameEmpty": "ต้องระบุชื่อ",
"error.passwordEmpty": "ต้องใช้รหัสผ่าน",
"error.passwordInvalid": "รหัสผ่านต้องมีตัวอักษรและตัวเลข และความยาวต้องมากกว่า 8",

View File

@@ -35,6 +35,8 @@
"error.emailEmpty": "E-posta adresi gereklidir",
"error.emailInValid": "Geçerli bir e-posta adresi girin",
"error.invalidEmailOrPassword": "Geçersiz e-posta veya şifre.",
"error.invalidRedirectUrlOrAppCode": "Geçersiz yönlendirme URL'si veya uygulama kodu",
"error.invalidSSOProtocol": "Geçersiz SSO protokolü",
"error.nameEmpty": "İsim gereklidir",
"error.passwordEmpty": "Şifre gereklidir",
"error.passwordInvalid": "Şifre harf ve rakamlardan oluşmalı ve uzunluğu 8 karakterden fazla olmalıdır",

View File

@@ -35,6 +35,8 @@
"error.emailEmpty": "Адреса електронної пошти обов'язкова",
"error.emailInValid": "Введіть дійсну адресу електронної пошти",
"error.invalidEmailOrPassword": "Невірний електронний лист або пароль.",
"error.invalidRedirectUrlOrAppCode": "Недійсний URL перенаправлення або код додатку",
"error.invalidSSOProtocol": "Недійсний протокол SSO",
"error.nameEmpty": "Ім'я обов'язкове",
"error.passwordEmpty": "Пароль є обов’язковим",
"error.passwordInvalid": "Пароль повинен містити літери та цифри, а довжина повинна бути більшою за 8",

View File

@@ -35,6 +35,8 @@
"error.emailEmpty": "Vui lòng nhập địa chỉ email",
"error.emailInValid": "Vui lòng nhập một địa chỉ email hợp lệ",
"error.invalidEmailOrPassword": "Email hoặc mật khẩu không hợp lệ.",
"error.invalidRedirectUrlOrAppCode": "URL chuyển hướng hoặc mã ứng dụng không hợp lệ",
"error.invalidSSOProtocol": "Giao thức SSO không hợp lệ",
"error.nameEmpty": "Vui lòng nhập tên",
"error.passwordEmpty": "Vui lòng nhập mật khẩu",
"error.passwordInvalid": "Mật khẩu phải chứa cả chữ và số, và có độ dài ít nhất 8 ký tự",

View File

@@ -35,6 +35,8 @@
"error.emailEmpty": "邮箱不能为空",
"error.emailInValid": "请输入有效的邮箱地址",
"error.invalidEmailOrPassword": "邮箱或密码错误",
"error.invalidRedirectUrlOrAppCode": "无效的重定向 URL 或应用代码",
"error.invalidSSOProtocol": "无效的 SSO 协议",
"error.nameEmpty": "用户名不能为空",
"error.passwordEmpty": "密码不能为空",
"error.passwordInvalid": "密码必须包含字母和数字,且长度不小于 8 位",

View File

@@ -35,6 +35,8 @@
"error.emailEmpty": "郵箱不能為空",
"error.emailInValid": "請輸入有效的郵箱地址",
"error.invalidEmailOrPassword": "無效的電子郵件或密碼。",
"error.invalidRedirectUrlOrAppCode": "無效的重定向 URL 或應用程式代碼",
"error.invalidSSOProtocol": "無效的 SSO 協定",
"error.nameEmpty": "使用者名稱不能為空",
"error.passwordEmpty": "密碼不能為空",
"error.passwordInvalid": "密碼必須包含字母和數字,且長度不小於 8 位",