mirror of
https://github.com/langgenius/dify.git
synced 2026-08-15 04:59:46 +08:00
chore: reopen the education application flow (#40760)
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
This commit is contained in:
parent
6edc8ba147
commit
35132d6892
@ -109,12 +109,6 @@ class EducationActivateLimitError(BaseHTTPException):
|
||||
code = 429
|
||||
|
||||
|
||||
class EducationDiscountTemporarilyPausedError(BaseHTTPException):
|
||||
error_code = "education_discount_temporarily_paused"
|
||||
description = "Education discount temporarily paused, while we upgrade our security measures."
|
||||
code = 503
|
||||
|
||||
|
||||
class ComplianceRateLimitError(BaseHTTPException):
|
||||
error_code = "compliance_rate_limit"
|
||||
description = "Rate limit exceeded for downloading compliance report."
|
||||
|
||||
@ -28,12 +28,7 @@ from controllers.console.auth.error import (
|
||||
InvalidEmailError,
|
||||
InvalidTokenError,
|
||||
)
|
||||
from controllers.console.error import (
|
||||
AccountInFreezeError,
|
||||
AccountNotFound,
|
||||
EducationDiscountTemporarilyPausedError,
|
||||
EmailSendIpLimitError,
|
||||
)
|
||||
from controllers.console.error import AccountInFreezeError, AccountNotFound, EmailSendIpLimitError
|
||||
from controllers.console.workspace.error import (
|
||||
AccountAlreadyInitedError,
|
||||
CurrentPasswordIncorrectError,
|
||||
@ -559,7 +554,11 @@ class EducationApi(Resource):
|
||||
@only_edition_cloud
|
||||
@with_current_user
|
||||
def post(self, account: Account):
|
||||
raise EducationDiscountTemporarilyPausedError()
|
||||
payload = console_ns.payload or {}
|
||||
args = EducationActivatePayload.model_validate(payload)
|
||||
|
||||
result = BillingService.EducationIdentity.activate(account, args.token, args.institution, args.role)
|
||||
return result
|
||||
|
||||
@setup_required
|
||||
@login_required
|
||||
|
||||
@ -8,7 +8,6 @@ import pytest
|
||||
from flask import Flask
|
||||
from sqlalchemy.orm import Session, scoped_session, sessionmaker
|
||||
|
||||
from controllers.console.error import EducationDiscountTemporarilyPausedError
|
||||
from controllers.console.workspace.account import (
|
||||
AccountDeleteUpdateFeedbackApi,
|
||||
ChangeEmailCheckApi,
|
||||
@ -110,23 +109,21 @@ def _build_change_email_token(
|
||||
|
||||
class TestEducationApi:
|
||||
@patch("controllers.console.workspace.account.BillingService.EducationIdentity.activate")
|
||||
def test_post_returns_temporarily_paused_error_without_activating_discount(
|
||||
self, mock_activate: MagicMock, app: Flask
|
||||
):
|
||||
def test_post_activates_education_discount(self, mock_activate: MagicMock, app: Flask):
|
||||
account = _build_account("student@example.edu")
|
||||
mock_activate.return_value = {"message": "success"}
|
||||
|
||||
with app.test_request_context("/account/education", method="POST", json={}):
|
||||
with app.test_request_context(
|
||||
"/account/education",
|
||||
method="POST",
|
||||
json={"token": "education-token", "institution": "Dify University", "role": "Student"},
|
||||
):
|
||||
api = EducationApi()
|
||||
method = inspect.unwrap(api.post)
|
||||
with pytest.raises(EducationDiscountTemporarilyPausedError) as exc_info:
|
||||
method(api, account)
|
||||
result = method(api, account)
|
||||
|
||||
assert exc_info.value.data == {
|
||||
"code": "education_discount_temporarily_paused",
|
||||
"message": "Education discount temporarily paused, while we upgrade our security measures.",
|
||||
"status": 503,
|
||||
}
|
||||
mock_activate.assert_not_called()
|
||||
assert result == {"message": "success"}
|
||||
mock_activate.assert_called_once_with(account, "education-token", "Dify University", "Student")
|
||||
|
||||
|
||||
class TestChangeEmailSend:
|
||||
|
||||
2
api/uv.lock
generated
2
api/uv.lock
generated
@ -1317,7 +1317,7 @@ requires-dist = [
|
||||
{ name = "jwcrypto", marker = "extra == 'server'", specifier = ">=1.5.6,<2" },
|
||||
{ name = "logfire", extras = ["fastapi", "httpx", "redis"], marker = "extra == 'server'", specifier = ">=4.37.0,<5.0.0" },
|
||||
{ name = "pydantic", specifier = ">=2.12.5,<2.13" },
|
||||
{ name = "pydantic-ai-slim", specifier = ">=1.102.0,<2.0.0" },
|
||||
{ name = "pydantic-ai-slim", specifier = ">=1.106.0,<2.0.0" },
|
||||
{ name = "pydantic-ai-slim", extras = ["anthropic", "google", "openai"], marker = "extra == 'server'", specifier = ">=1.85.1,<2.0.0" },
|
||||
{ name = "pydantic-settings", marker = "extra == 'server'", specifier = ">=2.12.0,<3.0.0" },
|
||||
{ name = "redis", marker = "extra == 'server'", specifier = ">=7.4.0,<8.0.0" },
|
||||
|
||||
@ -9,10 +9,6 @@ vi.mock('@/next/navigation', () => ({
|
||||
redirect: mockRedirect,
|
||||
}))
|
||||
|
||||
vi.mock('@/app/education/paused-content', () => ({
|
||||
EducationPausedContent: () => <div>Education paused</div>,
|
||||
}))
|
||||
|
||||
vi.mock('../application-form', () => ({
|
||||
default: ({ token }: { token: string }) => <div>Application form: {token}</div>,
|
||||
}))
|
||||
@ -31,7 +27,7 @@ describe('EducationApplyRoute', () => {
|
||||
expect(mockRedirect).toHaveBeenCalledWith('/')
|
||||
})
|
||||
|
||||
it('renders the pause state for a direct token URL', () => {
|
||||
it('renders the application form for a direct token URL', () => {
|
||||
const { queryClient, wrapper } = createConsoleQueryWrapper({
|
||||
educationStatus: { is_student: false },
|
||||
})
|
||||
@ -42,7 +38,6 @@ describe('EducationApplyRoute', () => {
|
||||
|
||||
render(<EducationApplyRoute token="education-token" />, { wrapper })
|
||||
|
||||
expect(screen.getByText('Education paused')).toBeInTheDocument()
|
||||
expect(screen.queryByText(/Application form/)).not.toBeInTheDocument()
|
||||
expect(screen.getByText('Application form: education-token')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
@ -4,8 +4,6 @@ import type { GetFeaturesResponse } from '@dify/contracts/api/console/features/t
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import { redirect } from '@/next/navigation'
|
||||
import { consoleQuery } from '@/service/client'
|
||||
import { EDUCATION_APPLICATIONS_PAUSED } from '../constants'
|
||||
import { EducationPausedContent } from '../paused-content'
|
||||
import EducationApplyPage from './application-form'
|
||||
|
||||
const selectEducationPlan = ({ billing, education }: GetFeaturesResponse) => ({
|
||||
@ -22,7 +20,5 @@ export default function EducationApplyRoute({ token }: { token: string }) {
|
||||
|
||||
if (!featuresQuery.data?.enabled) return redirect('/')
|
||||
|
||||
if (EDUCATION_APPLICATIONS_PAUSED) return <EducationPausedContent />
|
||||
|
||||
return <EducationApplyPage token={token} plan={featuresQuery.data.plan} />
|
||||
}
|
||||
|
||||
@ -1 +0,0 @@
|
||||
export const EDUCATION_APPLICATIONS_PAUSED = true
|
||||
@ -1,53 +0,0 @@
|
||||
'use client'
|
||||
|
||||
import { buttonVariants } from '@langgenius/dify-ui/button'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useDocLink } from '@/context/i18n'
|
||||
import Link from '@/next/link'
|
||||
import { EducationStatusCard } from './status-card'
|
||||
import UserInfo from './user-info'
|
||||
|
||||
export function EducationPausedContent() {
|
||||
const { t } = useTranslation()
|
||||
const docLink = useDocLink()
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="mb-7">
|
||||
<UserInfo />
|
||||
</div>
|
||||
<EducationStatusCard
|
||||
icon={
|
||||
<span
|
||||
className="i-ri-pause-circle-fill size-6 text-text-warning-secondary"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
}
|
||||
title={t(($) => $['educationDiscountPaused.title'], { ns: 'education' })}
|
||||
actions={
|
||||
<>
|
||||
<Link className={buttonVariants({ variant: 'secondary' })} href="/">
|
||||
<span className="i-ri-arrow-left-line size-4" aria-hidden="true" />
|
||||
{t(($) => $['applied.noPaymentPermission.returnHome'], { ns: 'education' })}
|
||||
</Link>
|
||||
<a
|
||||
className={buttonVariants({ variant: 'ghost-accent' })}
|
||||
href={docLink('/use-dify/workspace/subscription-management#dify-for-education')}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
{t(($) => $.learn, { ns: 'education' })}
|
||||
<span className="i-ri-external-link-line size-3" aria-hidden="true" />
|
||||
</a>
|
||||
</>
|
||||
}
|
||||
>
|
||||
<p>{t(($) => $['educationDiscountPaused.description'], { ns: 'education' })}</p>
|
||||
<p className="mt-4">{t(($) => $['educationDiscountPaused.thanks'], { ns: 'education' })}</p>
|
||||
<p className="mt-4 system-xs-regular">
|
||||
{t(($) => $['educationDiscountPaused.publishedAt'], { ns: 'education' })}
|
||||
</p>
|
||||
</EducationStatusCard>
|
||||
</>
|
||||
)
|
||||
}
|
||||
@ -22,11 +22,9 @@ vi.mock('@/app/education/user-info', () => ({
|
||||
}))
|
||||
|
||||
function renderFlow({
|
||||
applicationsPaused,
|
||||
allowRefresh = false,
|
||||
isEducationAccount = false,
|
||||
}: {
|
||||
applicationsPaused: boolean
|
||||
allowRefresh?: boolean
|
||||
isEducationAccount?: boolean
|
||||
}) {
|
||||
@ -39,13 +37,7 @@ function renderFlow({
|
||||
})
|
||||
seedFeatures(queryClient, { education: { enabled: true } })
|
||||
|
||||
return render(
|
||||
<EducationVerifyFlow
|
||||
applicationsPaused={applicationsPaused}
|
||||
requestVerification={mockRequestVerification}
|
||||
/>,
|
||||
{ wrapper },
|
||||
)
|
||||
return render(<EducationVerifyFlow requestVerification={mockRequestVerification} />, { wrapper })
|
||||
}
|
||||
|
||||
describe('EducationVerifyFlow', () => {
|
||||
@ -54,17 +46,8 @@ describe('EducationVerifyFlow', () => {
|
||||
mockRequestVerification.mockResolvedValue({ token: 'education-token' })
|
||||
})
|
||||
|
||||
it('renders the pause state without requesting a verification token', () => {
|
||||
renderFlow({ applicationsPaused: true })
|
||||
|
||||
expect(
|
||||
screen.getByRole('heading', { name: 'education.educationDiscountPaused.title' }),
|
||||
).toBeInTheDocument()
|
||||
expect(mockRequestVerification).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('shows an already verified account before the pause gate', () => {
|
||||
renderFlow({ applicationsPaused: true, isEducationAccount: true })
|
||||
it('shows an already verified account', () => {
|
||||
renderFlow({ isEducationAccount: true })
|
||||
|
||||
expect(
|
||||
screen.getByRole('heading', { name: 'education.applied.step1.description' }),
|
||||
@ -85,10 +68,7 @@ describe('EducationVerifyFlow', () => {
|
||||
|
||||
render(
|
||||
<StrictMode>
|
||||
<EducationVerifyFlow
|
||||
applicationsPaused={false}
|
||||
requestVerification={mockRequestVerification}
|
||||
/>
|
||||
<EducationVerifyFlow requestVerification={mockRequestVerification} />
|
||||
</StrictMode>,
|
||||
{ wrapper },
|
||||
)
|
||||
@ -102,7 +82,7 @@ describe('EducationVerifyFlow', () => {
|
||||
it('renders the rejection state when verification returns no token', async () => {
|
||||
mockRequestVerification.mockResolvedValue({ token: null })
|
||||
|
||||
renderFlow({ applicationsPaused: false })
|
||||
renderFlow({})
|
||||
|
||||
expect(
|
||||
await screen.findByRole('heading', { name: 'education.rejectTitle' }),
|
||||
@ -110,4 +90,17 @@ describe('EducationVerifyFlow', () => {
|
||||
expect(screen.getByText('student@university.edu')).toBeInTheDocument()
|
||||
expect(mockReplace).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('renders the generic error state when verification fails', async () => {
|
||||
mockRequestVerification.mockRejectedValue(new Response(null, { status: 503 }))
|
||||
|
||||
renderFlow({})
|
||||
|
||||
expect(
|
||||
await screen.findByRole('heading', { name: 'common.errorBoundary.title' }),
|
||||
).toBeInTheDocument()
|
||||
expect(
|
||||
screen.getByRole('button', { name: 'common.errorBoundary.tryAgain' }),
|
||||
).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
@ -13,13 +13,10 @@ import { userProfileQueryOptions } from '@/features/account-profile/client'
|
||||
import Link from '@/next/link'
|
||||
import { redirect, useRouter } from '@/next/navigation'
|
||||
import { consoleClient, consoleQuery } from '@/service/client'
|
||||
import { EDUCATION_APPLICATIONS_PAUSED } from '../constants'
|
||||
import { EducationPausedContent } from '../paused-content'
|
||||
import { EducationStatusCard } from '../status-card'
|
||||
import UserInfo from '../user-info'
|
||||
|
||||
class EducationVerificationRejectedError extends Error {}
|
||||
class EducationVerificationPausedError extends Error {}
|
||||
|
||||
type EducationVerificationRequest = () => Promise<{ token?: string | null }>
|
||||
|
||||
@ -36,34 +33,19 @@ const requestEducationVerification: EducationVerificationRequest = () =>
|
||||
async function requestEducationVerificationToken(
|
||||
requestVerification: EducationVerificationRequest,
|
||||
) {
|
||||
try {
|
||||
const response = await requestVerification()
|
||||
if (!response.token) throw new EducationVerificationRejectedError()
|
||||
const response = await requestVerification()
|
||||
if (!response.token) throw new EducationVerificationRejectedError()
|
||||
|
||||
return response.token
|
||||
} catch (error) {
|
||||
if (error instanceof Response) {
|
||||
const body = (await error
|
||||
.clone()
|
||||
.json()
|
||||
.catch(() => null)) as { code?: unknown } | null
|
||||
if (body?.code === 'education_discount_temporarily_paused')
|
||||
throw new EducationVerificationPausedError()
|
||||
}
|
||||
|
||||
throw error
|
||||
}
|
||||
return response.token
|
||||
}
|
||||
|
||||
export default function EducationVerifyPage() {
|
||||
return <EducationVerifyFlow applicationsPaused={EDUCATION_APPLICATIONS_PAUSED} />
|
||||
return <EducationVerifyFlow />
|
||||
}
|
||||
|
||||
export function EducationVerifyFlow({
|
||||
applicationsPaused,
|
||||
requestVerification = requestEducationVerification,
|
||||
}: {
|
||||
applicationsPaused: boolean
|
||||
requestVerification?: EducationVerificationRequest
|
||||
}) {
|
||||
const { t } = useTranslation()
|
||||
@ -114,8 +96,8 @@ export function EducationVerifyFlow({
|
||||
(!educationStatus.isEducationAccount || educationStatus.allowRefresh)
|
||||
|
||||
useEffect(() => {
|
||||
if (!applicationsPaused && canVerify) startVerification()
|
||||
}, [applicationsPaused, canVerify, startVerification])
|
||||
if (canVerify) startVerification()
|
||||
}, [canVerify, startVerification])
|
||||
|
||||
if (featuresQuery.isPending) return <EducationVerifyLoading />
|
||||
|
||||
@ -134,9 +116,6 @@ export function EducationVerifyFlow({
|
||||
|
||||
if (isAlreadyVerified) return <EducationVerifiedContent />
|
||||
|
||||
if (applicationsPaused || verificationError instanceof EducationVerificationPausedError)
|
||||
return <EducationPausedContent />
|
||||
|
||||
if (verificationError instanceof EducationVerificationRejectedError) {
|
||||
return (
|
||||
<EducationVerifyContent>
|
||||
|
||||
@ -5,10 +5,6 @@
|
||||
"applied.step1.description": "لقد قدمت بنجاح طلباً للحصول على الخصم التعليمي.",
|
||||
"applied.step2.description": "اختر مساحة العمل التي تريد استخدامها مع الخصم التعليمي.",
|
||||
"currentSigned": "تم تسجيل الدخول حاليًا باسم",
|
||||
"educationDiscountPaused.description": "نظرًا للزيادة الأخيرة في الطلبات المشبوهة وإساءة الاستخدام، أوقفنا مؤقتًا الطلبات الجديدة وعمليات الاستفادة من الخصم بينما نعمل على ترقية إجراءاتنا الأمنية.",
|
||||
"educationDiscountPaused.publishedAt": "نُشر في 10 أغسطس 2026",
|
||||
"educationDiscountPaused.thanks": "شكرًا لتفهمكم.",
|
||||
"educationDiscountPaused.title": "تم إيقاف الخصم التعليمي مؤقتًا",
|
||||
"educationPricingConfirm.cancel": "الاحتفاظ بالخطة الحالية",
|
||||
"educationPricingConfirm.continue": "التبديل إلى Professional السنوية",
|
||||
"educationPricingConfirm.description": "ينطبق الخصم التعليمي على خطة Professional السنوية فقط. الاحتفاظ بخطتك الحالية لن يتضمن الخصم.",
|
||||
|
||||
@ -5,10 +5,6 @@
|
||||
"applied.step1.description": "Sie haben erfolgreich den Bildungsrabatt beantragt.",
|
||||
"applied.step2.description": "Wählen Sie den Arbeitsbereich aus, den Sie mit dem Bildungsrabatt verwenden möchten.",
|
||||
"currentSigned": "DERZEIT ANGEMELDET ALS",
|
||||
"educationDiscountPaused.description": "Aufgrund eines jüngsten Anstiegs verdächtiger Anträge und missbräuchlicher Nutzung haben wir neue Anträge und Einlösungen vorübergehend pausiert, während wir unsere Sicherheitsmaßnahmen verbessern.",
|
||||
"educationDiscountPaused.publishedAt": "Veröffentlicht am 10. August 2026",
|
||||
"educationDiscountPaused.thanks": "Vielen Dank für Ihr Verständnis.",
|
||||
"educationDiscountPaused.title": "Bildungsrabatt vorübergehend pausiert",
|
||||
"educationPricingConfirm.cancel": "Aktuellen Plan behalten",
|
||||
"educationPricingConfirm.continue": "Zu Professional jährlich wechseln",
|
||||
"educationPricingConfirm.description": "Der Bildungsrabatt gilt nur für den jährlichen Professional-Plan. Wenn Sie Ihren aktuellen Plan behalten, ist der Rabatt nicht enthalten.",
|
||||
|
||||
@ -5,10 +5,6 @@
|
||||
"applied.step1.description": "You've successfully applied for the education discount.",
|
||||
"applied.step2.description": "Select the workspace you want to use the education discount with.",
|
||||
"currentSigned": "CURRENTLY SIGNED IN AS",
|
||||
"educationDiscountPaused.description": "Due to a recent increase in suspicious applications and misuse, we have temporarily paused new applications and redemptions while we upgrade our security measures.",
|
||||
"educationDiscountPaused.publishedAt": "Published August 10, 2026",
|
||||
"educationDiscountPaused.thanks": "Thank you for your understanding.",
|
||||
"educationDiscountPaused.title": "Education Discount Temporarily Paused",
|
||||
"educationPricingConfirm.cancel": "Keep current plan",
|
||||
"educationPricingConfirm.continue": "Switch to Professional Annual",
|
||||
"educationPricingConfirm.description": "The education discount applies to the Professional annual plan only. Keeping your current plan won't include the discount.",
|
||||
|
||||
@ -5,10 +5,6 @@
|
||||
"applied.step1.description": "Has solicitado exitosamente el descuento educativo.",
|
||||
"applied.step2.description": "Selecciona el workspace que deseas usar con el descuento educativo.",
|
||||
"currentSigned": "ACTUALMENTE CONECTADO COMO",
|
||||
"educationDiscountPaused.description": "Debido al reciente aumento de solicitudes sospechosas y usos indebidos, hemos pausado temporalmente las nuevas solicitudes y los canjes mientras mejoramos nuestras medidas de seguridad.",
|
||||
"educationDiscountPaused.publishedAt": "Publicado el 10 de agosto de 2026",
|
||||
"educationDiscountPaused.thanks": "Gracias por tu comprensión.",
|
||||
"educationDiscountPaused.title": "Descuento educativo pausado temporalmente",
|
||||
"educationPricingConfirm.cancel": "Mantener el plan actual",
|
||||
"educationPricingConfirm.continue": "Cambiar a Professional anual",
|
||||
"educationPricingConfirm.description": "El descuento educativo solo se aplica al plan Professional anual. Si mantienes tu plan actual, no se incluirá el descuento.",
|
||||
|
||||
@ -5,10 +5,6 @@
|
||||
"applied.step1.description": "درخواست تخفیف آموزشی شما با موفقیت ثبت شد.",
|
||||
"applied.step2.description": "workspaceای را که میخواهید با تخفیف آموزشی استفاده کنید انتخاب کنید.",
|
||||
"currentSigned": "اکنون به عنوان",
|
||||
"educationDiscountPaused.description": "به دلیل افزایش اخیر درخواستهای مشکوک و سوءاستفاده، تا زمانی که اقدامات امنیتی خود را ارتقا میدهیم، درخواستهای جدید و استفاده از تخفیف را موقتاً متوقف کردهایم.",
|
||||
"educationDiscountPaused.publishedAt": "منتشرشده در ۱۰ اوت ۲۰۲۶",
|
||||
"educationDiscountPaused.thanks": "از درک شما سپاسگزاریم.",
|
||||
"educationDiscountPaused.title": "تخفیف آموزشی موقتاً متوقف شده است",
|
||||
"educationPricingConfirm.cancel": "حفظ طرح فعلی",
|
||||
"educationPricingConfirm.continue": "تغییر به Professional سالانه",
|
||||
"educationPricingConfirm.description": "تخفیف آموزشی فقط برای طرح سالانه Professional اعمال میشود. با حفظ طرح فعلی، این تخفیف شامل نمیشود.",
|
||||
|
||||
@ -5,10 +5,6 @@
|
||||
"applied.step1.description": "Vous avez fait la demande de remise éducative avec succès.",
|
||||
"applied.step2.description": "Sélectionnez l'espace de travail que vous souhaitez utiliser avec la remise éducative.",
|
||||
"currentSigned": "ACTUELLEMENT CONNECTÉ EN TANT QUE",
|
||||
"educationDiscountPaused.description": "En raison d’une récente augmentation des demandes suspectes et des utilisations abusives, nous avons temporairement suspendu les nouvelles demandes et les utilisations de la réduction pendant que nous renforçons nos mesures de sécurité.",
|
||||
"educationDiscountPaused.publishedAt": "Publié le 10 août 2026",
|
||||
"educationDiscountPaused.thanks": "Merci de votre compréhension.",
|
||||
"educationDiscountPaused.title": "Réduction éducation temporairement suspendue",
|
||||
"educationPricingConfirm.cancel": "Conserver le plan actuel",
|
||||
"educationPricingConfirm.continue": "Passer à Professional annuel",
|
||||
"educationPricingConfirm.description": "La remise éducation s'applique uniquement au plan Professional annuel. En conservant votre plan actuel, la remise ne sera pas incluse.",
|
||||
|
||||
@ -5,10 +5,6 @@
|
||||
"applied.step1.description": "आपने शिक्षा छूट के लिए सफलतापूर्वक आवेदन किया है।",
|
||||
"applied.step2.description": "वह workspace चुनें जिसे आप शिक्षा छूट के साथ उपयोग करना चाहते हैं।",
|
||||
"currentSigned": "वर्तमान में साइन इन किया गया है के रूप में",
|
||||
"educationDiscountPaused.description": "हाल ही में संदिग्ध आवेदनों और दुरुपयोग में वृद्धि के कारण, सुरक्षा उपायों को बेहतर बनाते समय हमने नए आवेदन और छूट रिडेम्प्शन अस्थायी रूप से रोक दिए हैं।",
|
||||
"educationDiscountPaused.publishedAt": "10 अगस्त 2026 को प्रकाशित",
|
||||
"educationDiscountPaused.thanks": "आपकी समझ के लिए धन्यवाद।",
|
||||
"educationDiscountPaused.title": "शिक्षा छूट अस्थायी रूप से रोकी गई",
|
||||
"educationPricingConfirm.cancel": "वर्तमान प्लान रखें",
|
||||
"educationPricingConfirm.continue": "Professional वार्षिक पर स्विच करें",
|
||||
"educationPricingConfirm.description": "शिक्षा छूट केवल Professional वार्षिक प्लान पर लागू होती है। अपना वर्तमान प्लान रखने पर छूट शामिल नहीं होगी।",
|
||||
|
||||
@ -5,10 +5,6 @@
|
||||
"applied.step1.description": "Anda telah berhasil mengajukan diskon pendidikan.",
|
||||
"applied.step2.description": "Pilih workspace yang ingin Anda gunakan dengan diskon pendidikan.",
|
||||
"currentSigned": "SAAT INI MASUK SEBAGAI",
|
||||
"educationDiscountPaused.description": "Karena peningkatan permohonan mencurigakan dan penyalahgunaan baru-baru ini, kami menghentikan sementara permohonan baru dan penukaran diskon selagi meningkatkan langkah-langkah keamanan kami.",
|
||||
"educationDiscountPaused.publishedAt": "Diterbitkan 10 Agustus 2026",
|
||||
"educationDiscountPaused.thanks": "Terima kasih atas pengertian Anda.",
|
||||
"educationDiscountPaused.title": "Diskon pendidikan dihentikan sementara",
|
||||
"educationPricingConfirm.cancel": "Tetap gunakan paket saat ini",
|
||||
"educationPricingConfirm.continue": "Beralih ke Professional Tahunan",
|
||||
"educationPricingConfirm.description": "Diskon pendidikan hanya berlaku untuk paket Professional tahunan. Jika tetap menggunakan paket saat ini, diskon tidak akan disertakan.",
|
||||
|
||||
@ -5,10 +5,6 @@
|
||||
"applied.step1.description": "Hai fatto domanda per lo sconto educativo con successo.",
|
||||
"applied.step2.description": "Seleziona il workspace che vuoi utilizzare con lo sconto educativo.",
|
||||
"currentSigned": "ATTUALMENTE ACCEDUTO COME",
|
||||
"educationDiscountPaused.description": "A causa del recente aumento di richieste sospette e utilizzi impropri, abbiamo temporaneamente sospeso le nuove richieste e l’utilizzo degli sconti mentre potenziamo le nostre misure di sicurezza.",
|
||||
"educationDiscountPaused.publishedAt": "Pubblicato il 10 agosto 2026",
|
||||
"educationDiscountPaused.thanks": "Grazie per la comprensione.",
|
||||
"educationDiscountPaused.title": "Sconto Education temporaneamente sospeso",
|
||||
"educationPricingConfirm.cancel": "Mantieni il piano attuale",
|
||||
"educationPricingConfirm.continue": "Passa a Professional annuale",
|
||||
"educationPricingConfirm.description": "Lo sconto Education si applica solo al piano Professional annuale. Mantenendo il piano attuale, lo sconto non verrà incluso.",
|
||||
|
||||
@ -5,10 +5,6 @@
|
||||
"applied.step1.description": "教育割引の申請が成功しました。",
|
||||
"applied.step2.description": "教育割引を使用するワークスペースを選択してください。",
|
||||
"currentSigned": "現在ログイン中のアカウントは",
|
||||
"educationDiscountPaused.description": "不審な申請や不正利用が最近増加しているため、セキュリティ対策の強化が完了するまで、新規申請と割引の利用を一時停止しています。",
|
||||
"educationDiscountPaused.publishedAt": "2026年8月10日公開",
|
||||
"educationDiscountPaused.thanks": "ご理解いただきありがとうございます。",
|
||||
"educationDiscountPaused.title": "教育割引は一時停止中です",
|
||||
"educationPricingConfirm.cancel": "現在のプランを維持",
|
||||
"educationPricingConfirm.continue": "Professional 年間プランに切り替える",
|
||||
"educationPricingConfirm.description": "教育割引は Professional 年間プランにのみ適用されます。現在のプランを維持すると、割引は適用されません。",
|
||||
|
||||
@ -5,10 +5,6 @@
|
||||
"applied.step1.description": "교육 할인 신청이 성공적으로 완료되었습니다.",
|
||||
"applied.step2.description": "교육 할인을 사용할 워크스페이스를 선택하세요.",
|
||||
"currentSigned": "현재 로그인 중입니다",
|
||||
"educationDiscountPaused.description": "최근 의심스러운 신청과 오용이 증가함에 따라 보안 조치를 강화하는 동안 신규 신청과 할인 사용을 일시 중단했습니다.",
|
||||
"educationDiscountPaused.publishedAt": "2026년 8월 10일 게시",
|
||||
"educationDiscountPaused.thanks": "양해해 주셔서 감사합니다.",
|
||||
"educationDiscountPaused.title": "교육 할인이 일시 중단되었습니다",
|
||||
"educationPricingConfirm.cancel": "현재 플랜 유지",
|
||||
"educationPricingConfirm.continue": "Professional 연간으로 전환",
|
||||
"educationPricingConfirm.description": "교육 할인은 Professional 연간 플랜에만 적용됩니다. 현재 플랜을 유지하면 할인이 포함되지 않습니다.",
|
||||
|
||||
@ -5,10 +5,6 @@
|
||||
"applied.step1.description": "ທ່ານໄດ້ສະໝັກຂໍສ່ວນຫຼຸດເພື່ອການສຶກສາສຳເລັດແລ້ວ.",
|
||||
"applied.step2.description": "ເລືອກພື້ນທີ່ເຮັດວຽກທີ່ທ່ານຕ້ອງການໃຊ້ສ່ວນຫຼຸດເພື່ອການສຶກສາ.",
|
||||
"currentSigned": "ເຂົ້າສູ່ລະບົບໃນປັດຈຸບັນໃນນາມ",
|
||||
"educationDiscountPaused.description": "ເນື່ອງຈາກມີການສະໝັກທີ່ໜ້າສົງໄສ ແລະ ການນຳໃຊ້ໃນທາງທີ່ຜິດເພີ່ມຂຶ້ນໃນໄລຍະຫຼ້ານີ້, ພວກເຮົາໄດ້ຢຸດການສະໝັກໃໝ່ ແລະ ການໃຊ້ສ່ວນຫຼຸດໄວ້ຊົ່ວຄາວ ໃນຂະນະທີ່ປັບປຸງມາດຕະການຄວາມປອດໄພ.",
|
||||
"educationDiscountPaused.publishedAt": "ເຜີຍແຜ່ວັນທີ 10 ສິງຫາ 2026",
|
||||
"educationDiscountPaused.thanks": "ຂອບໃຈສຳລັບຄວາມເຂົ້າໃຈຂອງທ່ານ.",
|
||||
"educationDiscountPaused.title": "ສ່ວນຫຼຸດການສຶກສາຖືກຢຸດໄວ້ຊົ່ວຄາວ",
|
||||
"educationPricingConfirm.cancel": "ໃຊ້ແພັກເກດປັດຈຸບັນຕໍ່ໄປ",
|
||||
"educationPricingConfirm.continue": "ປ່ຽນເປັນ Professional ແບບລາຍປີ",
|
||||
"educationPricingConfirm.description": "ສ່ວນຫຼຸດເພື່ອການສຶກສານຳໃຊ້ໄດ້ກັບແພັກເກດ Professional ແບບລາຍປີເທົັ້ນັ້ນ. ການໃຊ້ແພັກເກດປັດຈຸບັນຂອງທ່ານຕໍ່ໄປຈະບໍ່ລວມເອົາສ່ວນຫຼຸດນີ້.",
|
||||
|
||||
@ -5,10 +5,6 @@
|
||||
"applied.step1.description": "U heeft met succes de onderwijskorting aangevraagd.",
|
||||
"applied.step2.description": "Selecteer de werkruimte die u wilt gebruiken met de onderwijskorting.",
|
||||
"currentSigned": "CURRENTLY SIGNED IN AS",
|
||||
"educationDiscountPaused.description": "Door een recente toename van verdachte aanvragen en misbruik hebben we nieuwe aanvragen en verzilveringen tijdelijk gepauzeerd terwijl we onze beveiligingsmaatregelen verbeteren.",
|
||||
"educationDiscountPaused.publishedAt": "Gepubliceerd op 10 augustus 2026",
|
||||
"educationDiscountPaused.thanks": "Bedankt voor uw begrip.",
|
||||
"educationDiscountPaused.title": "Onderwijskorting tijdelijk gepauzeerd",
|
||||
"educationPricingConfirm.cancel": "Huidig abonnement behouden",
|
||||
"educationPricingConfirm.continue": "Overschakelen naar Professional jaarlijks",
|
||||
"educationPricingConfirm.description": "De onderwijskorting is alleen van toepassing op het jaarlijkse Professional-abonnement. Als u uw huidige abonnement behoudt, is de korting niet inbegrepen.",
|
||||
|
||||
@ -5,10 +5,6 @@
|
||||
"applied.step1.description": "Pomyślnie złożono wniosek o rabat edukacyjny.",
|
||||
"applied.step2.description": "Wybierz obszar roboczy, który chcesz używać z rabatem edukacyjnym.",
|
||||
"currentSigned": "AKTUALNIE ZALOGOWANY JAKO",
|
||||
"educationDiscountPaused.description": "Ze względu na niedawny wzrost liczby podejrzanych zgłoszeń i nadużyć tymczasowo wstrzymaliśmy nowe zgłoszenia i realizację zniżek na czas ulepszania naszych zabezpieczeń.",
|
||||
"educationDiscountPaused.publishedAt": "Opublikowano 10 sierpnia 2026 r.",
|
||||
"educationDiscountPaused.thanks": "Dziękujemy za wyrozumiałość.",
|
||||
"educationDiscountPaused.title": "Zniżka edukacyjna tymczasowo wstrzymana",
|
||||
"educationPricingConfirm.cancel": "Zachowaj obecny plan",
|
||||
"educationPricingConfirm.continue": "Przełącz na Professional roczny",
|
||||
"educationPricingConfirm.description": "Zniżka edukacyjna dotyczy tylko rocznego planu Professional. Pozostanie przy obecnym planie nie obejmie zniżki.",
|
||||
|
||||
@ -5,10 +5,6 @@
|
||||
"applied.step1.description": "Você solicitou com sucesso o desconto educacional.",
|
||||
"applied.step2.description": "Selecione o workspace que deseja usar com o desconto educacional.",
|
||||
"currentSigned": "ATUALMENTE CONECTADO COMO",
|
||||
"educationDiscountPaused.description": "Devido ao recente aumento de solicitações suspeitas e uso indevido, pausamos temporariamente novas solicitações e resgates enquanto aprimoramos nossas medidas de segurança.",
|
||||
"educationDiscountPaused.publishedAt": "Publicado em 10 de agosto de 2026",
|
||||
"educationDiscountPaused.thanks": "Agradecemos a sua compreensão.",
|
||||
"educationDiscountPaused.title": "Desconto educacional temporariamente pausado",
|
||||
"educationPricingConfirm.cancel": "Manter plano atual",
|
||||
"educationPricingConfirm.continue": "Mudar para Professional anual",
|
||||
"educationPricingConfirm.description": "O desconto educacional se aplica apenas ao plano Professional anual. Manter seu plano atual não incluirá o desconto.",
|
||||
|
||||
@ -5,10 +5,6 @@
|
||||
"applied.step1.description": "Ai aplicat cu succes pentru reducerea educațională.",
|
||||
"applied.step2.description": "Selectează workspace-ul pe care dorești să-l utilizezi cu reducerea educațională.",
|
||||
"currentSigned": "CONEXIUNE ÎN PREZENT CA",
|
||||
"educationDiscountPaused.description": "Din cauza creșterii recente a numărului de solicitări suspecte și a utilizării abuzive, am suspendat temporar solicitările noi și valorificarea reducerilor până când ne îmbunătățim măsurile de securitate.",
|
||||
"educationDiscountPaused.publishedAt": "Publicat la 10 august 2026",
|
||||
"educationDiscountPaused.thanks": "Vă mulțumim pentru înțelegere.",
|
||||
"educationDiscountPaused.title": "Reducerea pentru educație este suspendată temporar",
|
||||
"educationPricingConfirm.cancel": "Păstrează planul curent",
|
||||
"educationPricingConfirm.continue": "Treci la Professional anual",
|
||||
"educationPricingConfirm.description": "Reducerea educațională se aplică doar planului Professional anual. Dacă păstrezi planul curent, reducerea nu va fi inclusă.",
|
||||
|
||||
@ -5,10 +5,6 @@
|
||||
"applied.step1.description": "Вы успешно подали заявку на образовательную скидку.",
|
||||
"applied.step2.description": "Выберите рабочее пространство, которое хотите использовать с образовательной скидкой.",
|
||||
"currentSigned": "В ДАННЫЙ МОМЕНТ ВХОД В ПРОФИЛЬ КАК",
|
||||
"educationDiscountPaused.description": "Из-за недавнего роста числа подозрительных заявок и случаев злоупотребления мы временно приостановили прием новых заявок и использование скидок, пока совершенствуем меры безопасности.",
|
||||
"educationDiscountPaused.publishedAt": "Опубликовано 10 августа 2026 г.",
|
||||
"educationDiscountPaused.thanks": "Благодарим за понимание.",
|
||||
"educationDiscountPaused.title": "Образовательная скидка временно приостановлена",
|
||||
"educationPricingConfirm.cancel": "Оставить текущий план",
|
||||
"educationPricingConfirm.continue": "Перейти на Professional годовой",
|
||||
"educationPricingConfirm.description": "Образовательная скидка применяется только к годовому плану Professional. Если оставить текущий план, скидка не будет включена.",
|
||||
|
||||
@ -5,10 +5,6 @@
|
||||
"applied.step1.description": "Uspešno ste se prijavili za izobraževalni popust.",
|
||||
"applied.step2.description": "Izberite delovni prostor, ki ga želite uporabiti z izobraževalnim popustom.",
|
||||
"currentSigned": "Trenutno prijavljen kot",
|
||||
"educationDiscountPaused.description": "Zaradi nedavnega povečanja števila sumljivih prijav in zlorab smo začasno ustavili nove prijave in unovčitve popustov, medtem ko nadgrajujemo varnostne ukrepe.",
|
||||
"educationDiscountPaused.publishedAt": "Objavljeno 10. avgusta 2026",
|
||||
"educationDiscountPaused.thanks": "Hvala za razumevanje.",
|
||||
"educationDiscountPaused.title": "Izobraževalni popust je začasno ustavljen",
|
||||
"educationPricingConfirm.cancel": "Obdrži trenutni paket",
|
||||
"educationPricingConfirm.continue": "Preklopi na letni Professional",
|
||||
"educationPricingConfirm.description": "Izobraževalni popust velja samo za letni paket Professional. Če obdržite trenutni paket, popust ne bo vključen.",
|
||||
|
||||
@ -5,10 +5,6 @@
|
||||
"applied.step1.description": "คุณได้สมัครรับส่วนลดการศึกษาสำเร็จแล้ว",
|
||||
"applied.step2.description": "เลือกพื้นที่ทำงานที่คุณต้องการใช้กับส่วนลดการศึกษา",
|
||||
"currentSigned": "ลงชื่อเข้าใช้ในฐานะ",
|
||||
"educationDiscountPaused.description": "เนื่องจากมีการสมัครที่น่าสงสัยและการใช้งานในทางที่ผิดเพิ่มขึ้นในช่วงที่ผ่านมา เราจึงหยุดรับสมัครใหม่และการแลกรับส่วนลดไว้ชั่วคราวระหว่างที่ปรับปรุงมาตรการรักษาความปลอดภัย",
|
||||
"educationDiscountPaused.publishedAt": "เผยแพร่เมื่อ 10 สิงหาคม 2026",
|
||||
"educationDiscountPaused.thanks": "ขอขอบคุณสำหรับความเข้าใจ",
|
||||
"educationDiscountPaused.title": "ส่วนลดเพื่อการศึกษาหยุดให้บริการชั่วคราว",
|
||||
"educationPricingConfirm.cancel": "ใช้แผนปัจจุบันต่อ",
|
||||
"educationPricingConfirm.continue": "เปลี่ยนเป็น Professional รายปี",
|
||||
"educationPricingConfirm.description": "ส่วนลดการศึกษาใช้ได้เฉพาะกับแผน Professional รายปีเท่านั้น หากใช้แผนปัจจุบันต่อ จะไม่มีส่วนลดนี้รวมอยู่ด้วย",
|
||||
|
||||
@ -5,10 +5,6 @@
|
||||
"applied.step1.description": "Eğitim indirimi için başarıyla başvurdunuz.",
|
||||
"applied.step2.description": "Eğitim indirimiyle kullanmak istediğiniz çalışma alanını seçin.",
|
||||
"currentSigned": "ŞU ANDA GİRİŞ YAPILDIĞI KİŞİ",
|
||||
"educationDiscountPaused.description": "Şüpheli başvuruların ve kötüye kullanımın son dönemde artması nedeniyle, güvenlik önlemlerimizi geliştirirken yeni başvuruları ve indirim kullanımlarını geçici olarak duraklattık.",
|
||||
"educationDiscountPaused.publishedAt": "10 Ağustos 2026'da yayımlandı",
|
||||
"educationDiscountPaused.thanks": "Anlayışınız için teşekkür ederiz.",
|
||||
"educationDiscountPaused.title": "Eğitim indirimi geçici olarak duraklatıldı",
|
||||
"educationPricingConfirm.cancel": "Mevcut planı koru",
|
||||
"educationPricingConfirm.continue": "Professional yıllık plana geç",
|
||||
"educationPricingConfirm.description": "Eğitim indirimi yalnızca yıllık Professional planı için geçerlidir. Mevcut planınızı korursanız indirim dahil edilmez.",
|
||||
|
||||
@ -5,10 +5,6 @@
|
||||
"applied.step1.description": "Ви успішно подали заявку на освітню знижку.",
|
||||
"applied.step2.description": "Виберіть робочий простір, який ви хочете використовувати з освітньою знижкою.",
|
||||
"currentSigned": "В даний момент ви підписані як",
|
||||
"educationDiscountPaused.description": "Через нещодавнє зростання кількості підозрілих заявок і випадків зловживання ми тимчасово призупинили прийом нових заявок і використання знижок, поки вдосконалюємо заходи безпеки.",
|
||||
"educationDiscountPaused.publishedAt": "Опубліковано 10 серпня 2026 року",
|
||||
"educationDiscountPaused.thanks": "Дякуємо за розуміння.",
|
||||
"educationDiscountPaused.title": "Освітню знижку тимчасово призупинено",
|
||||
"educationPricingConfirm.cancel": "Залишити поточний план",
|
||||
"educationPricingConfirm.continue": "Перейти на Professional річний",
|
||||
"educationPricingConfirm.description": "Освітня знижка застосовується лише до річного плану Professional. Якщо залишити поточний план, знижку не буде включено.",
|
||||
|
||||
@ -5,10 +5,6 @@
|
||||
"applied.step1.description": "Bạn đã đăng ký giảm giá giáo dục thành công.",
|
||||
"applied.step2.description": "Chọn workspace bạn muốn sử dụng với giảm giá giáo dục.",
|
||||
"currentSigned": "HIỆN ĐANG ĐĂNG NHẬP VÀO",
|
||||
"educationDiscountPaused.description": "Do số lượng đơn đăng ký đáng ngờ và hành vi lạm dụng gần đây gia tăng, chúng tôi đã tạm dừng các đơn đăng ký mới và việc sử dụng ưu đãi trong khi nâng cấp các biện pháp bảo mật.",
|
||||
"educationDiscountPaused.publishedAt": "Đăng ngày 10 tháng 8 năm 2026",
|
||||
"educationDiscountPaused.thanks": "Cảm ơn bạn đã thông cảm.",
|
||||
"educationDiscountPaused.title": "Ưu đãi giáo dục tạm thời bị tạm dừng",
|
||||
"educationPricingConfirm.cancel": "Giữ gói hiện tại",
|
||||
"educationPricingConfirm.continue": "Chuyển sang Professional hằng năm",
|
||||
"educationPricingConfirm.description": "Giảm giá giáo dục chỉ áp dụng cho gói Professional hằng năm. Nếu giữ gói hiện tại, giảm giá sẽ không được áp dụng.",
|
||||
|
||||
@ -5,10 +5,6 @@
|
||||
"applied.step1.description": "您已成功申请教育优惠。",
|
||||
"applied.step2.description": "选择要使用教育优惠的 workspace。",
|
||||
"currentSigned": "您当前登录的账户是",
|
||||
"educationDiscountPaused.description": "由于近期可疑申请和滥用行为有所增加,我们在升级安全措施期间,暂时停止接受新的申请及优惠兑换。",
|
||||
"educationDiscountPaused.publishedAt": "发布于 2026 年 8 月 10 日",
|
||||
"educationDiscountPaused.thanks": "感谢您的理解。",
|
||||
"educationDiscountPaused.title": "教育优惠暂时停止",
|
||||
"educationPricingConfirm.cancel": "保留当前计划",
|
||||
"educationPricingConfirm.continue": "切换到 Professional 年付",
|
||||
"educationPricingConfirm.description": "教育优惠仅适用于 Professional 年付计划。保留当前计划将不包含该优惠。",
|
||||
|
||||
@ -5,10 +5,6 @@
|
||||
"applied.step1.description": "您已成功申請教育優惠。",
|
||||
"applied.step2.description": "選擇要使用教育優惠的 workspace。",
|
||||
"currentSigned": "當前以以下身份登入",
|
||||
"educationDiscountPaused.description": "由於近期可疑申請和濫用行為有所增加,我們在升級安全措施期間,暫時停止接受新的申請及優惠兌換。",
|
||||
"educationDiscountPaused.publishedAt": "發布於 2026 年 8 月 10 日",
|
||||
"educationDiscountPaused.thanks": "感謝您的理解。",
|
||||
"educationDiscountPaused.title": "教育優惠暫時停止",
|
||||
"educationPricingConfirm.cancel": "保留目前方案",
|
||||
"educationPricingConfirm.continue": "切換到 Professional 年付",
|
||||
"educationPricingConfirm.description": "教育優惠僅適用於 Professional 年付方案。保留目前方案將不包含此優惠。",
|
||||
|
||||
Loading…
Reference in New Issue
Block a user