feat(captcha): make ALTCHA the only captcha (#3035)

This commit is contained in:
Hampus
2026-09-29 17:00:15 +02:00
committed by GitHub
parent d433a039b5
commit 4f968bbc47
209 changed files with 4097 additions and 8692 deletions
-24
View File
@@ -194,10 +194,6 @@ function defaultConfig(): MasterConfig {
from_name: 'Fluxer',
app_base_url: '',
},
captcha: {
enabled: false,
provider: 'none',
},
voice: {
enabled: false,
api_key: '',
@@ -425,24 +421,6 @@ function validatePostgresConfig(config: MasterConfig): void {
}
}
function validateCaptchaConfig(config: MasterConfig): void {
const captcha = config.integrations.captcha;
if (!captcha.enabled) {
return;
}
if (captcha.provider === 'hcaptcha') {
requireString(captcha.hcaptcha?.site_key, 'FLUXER_CAPTCHA_HCAPTCHA_SITE_KEY');
requireString(captcha.hcaptcha?.secret_key, 'FLUXER_CAPTCHA_HCAPTCHA_SECRET_KEY');
return;
}
if (captcha.provider === 'turnstile') {
requireString(captcha.turnstile?.site_key, 'FLUXER_CAPTCHA_TURNSTILE_SITE_KEY');
requireString(captcha.turnstile?.secret_key, 'FLUXER_CAPTCHA_TURNSTILE_SECRET_KEY');
return;
}
throw new Error('FLUXER_CAPTCHA_PROVIDER must be hcaptcha or turnstile when FLUXER_CAPTCHA_ENABLED is true');
}
function validateApiWorkerConfig(config: MasterConfig): void {
const worker = config.services.api?.worker;
if (!worker) {
@@ -564,11 +542,9 @@ function normalizeConfig(config: MasterConfig): MasterConfig {
assertOneOf(config.internal.kv_provider, ['redis'], 'FLUXER_KV_PROVIDER');
assertOneOf(config.internal.kv_mode, ['standalone', 'cluster'], 'FLUXER_KV_MODE');
assertOneOf(config.integrations.email.provider, ['smtp', 'none'], 'FLUXER_EMAIL_PROVIDER');
assertOneOf(config.integrations.captcha.provider, ['hcaptcha', 'turnstile', 'none'], 'FLUXER_CAPTCHA_PROVIDER');
assertOneOf(config.integrations.search.engine, ['elasticsearch', 'meilisearch'], 'FLUXER_SEARCH_ENGINE');
assertOneOf(config.integrations.cache_purge.adapter, CACHE_PURGE_ADAPTER_NAMES, 'FLUXER_CACHE_PURGE_ADAPTER');
validatePostgresConfig(config);
validateCaptchaConfig(config);
validateApiWorkerConfig(config);
validateStorageChangeFeedConfig(config);
validateCachePurgeConfig(config);
-12
View File
@@ -207,18 +207,6 @@ export interface MasterConfig {
secure: boolean;
};
};
captcha: {
enabled: boolean;
provider: 'hcaptcha' | 'turnstile' | 'none';
hcaptcha?: {
site_key: string;
secret_key: string;
};
turnstile?: {
site_key: string;
secret_key: string;
};
};
voice: {
enabled: boolean;
api_key: string;
@@ -507,46 +507,6 @@ describe('ConfigLoader', () => {
expect(config.instance.setup.configured).toBe(true);
});
test('rejects an enabled captcha with no keys for the selected provider', async () => {
stubMinimalEnv({FLUXER_CAPTCHA_ENABLED: 'true', FLUXER_CAPTCHA_PROVIDER: 'hcaptcha'});
await expect(loadConfig()).rejects.toThrow('FLUXER_CAPTCHA_HCAPTCHA_SITE_KEY is required');
});
test('rejects an enabled captcha with a site key but no secret key', async () => {
stubMinimalEnv({
FLUXER_CAPTCHA_ENABLED: 'true',
FLUXER_CAPTCHA_PROVIDER: 'turnstile',
FLUXER_CAPTCHA_TURNSTILE_SITE_KEY: 'turnstile-site-key',
});
await expect(loadConfig()).rejects.toThrow('FLUXER_CAPTCHA_TURNSTILE_SECRET_KEY is required');
});
test('rejects an enabled captcha with no provider', async () => {
stubMinimalEnv({FLUXER_CAPTCHA_ENABLED: 'true'});
await expect(loadConfig()).rejects.toThrow(
'FLUXER_CAPTCHA_PROVIDER must be hcaptcha or turnstile when FLUXER_CAPTCHA_ENABLED is true',
);
});
test('accepts an enabled captcha with both keys for the selected provider', async () => {
stubMinimalEnv({
FLUXER_CAPTCHA_ENABLED: 'true',
FLUXER_CAPTCHA_PROVIDER: 'hcaptcha',
FLUXER_CAPTCHA_HCAPTCHA_SITE_KEY: 'hcaptcha-site-key',
FLUXER_CAPTCHA_HCAPTCHA_SECRET_KEY: 'hcaptcha-secret-key',
});
const config = await loadConfig();
expect(config.integrations.captcha.enabled).toBe(true);
expect(config.integrations.captcha.hcaptcha?.secret_key).toBe('hcaptcha-secret-key');
});
test('leaves a disabled captcha unvalidated', async () => {
stubMinimalEnv({FLUXER_CAPTCHA_PROVIDER: 'hcaptcha'});
expect((await loadConfig()).integrations.captcha.enabled).toBe(false);
});
test('defaults the cache purge adapter to none', async () => {
stubMinimalEnv();
expect((await loadConfig()).integrations.cache_purge).toEqual({
@@ -233,12 +233,6 @@ const NAMED_FLUXER_ENV_OVERRIDES: Record<string, NamedEnvOverride> = {
FLUXER_EMAIL_SMTP_USERNAME: {path: ['integrations', 'email', 'smtp', 'username']},
FLUXER_EMAIL_SMTP_PASSWORD: {path: ['integrations', 'email', 'smtp', 'password']},
FLUXER_EMAIL_SMTP_SECURE: {path: ['integrations', 'email', 'smtp', 'secure'], parse: parseBoolean},
FLUXER_CAPTCHA_ENABLED: {path: ['integrations', 'captcha', 'enabled'], parse: parseBoolean},
FLUXER_CAPTCHA_PROVIDER: {path: ['integrations', 'captcha', 'provider']},
FLUXER_CAPTCHA_HCAPTCHA_SITE_KEY: {path: ['integrations', 'captcha', 'hcaptcha', 'site_key']},
FLUXER_CAPTCHA_HCAPTCHA_SECRET_KEY: {path: ['integrations', 'captcha', 'hcaptcha', 'secret_key']},
FLUXER_CAPTCHA_TURNSTILE_SITE_KEY: {path: ['integrations', 'captcha', 'turnstile', 'site_key']},
FLUXER_CAPTCHA_TURNSTILE_SECRET_KEY: {path: ['integrations', 'captcha', 'turnstile', 'secret_key']},
FLUXER_LIVEKIT_ENABLED: {path: ['integrations', 'voice', 'enabled'], parse: parseBoolean},
FLUXER_LIVEKIT_API_KEY: {path: ['integrations', 'voice', 'api_key']},
FLUXER_LIVEKIT_API_SECRET: {path: ['integrations', 'voice', 'api_secret']},
-1
View File
@@ -26,7 +26,6 @@ export const Headers = {
X_FLUXER_SUDO_MODE_JWT: 'X-Fluxer-Sudo-Mode-JWT',
X_FLUXER_VERSION: 'X-Fluxer-Version',
X_CAPTCHA_TOKEN: 'X-Captcha-Token',
X_CAPTCHA_TYPE: 'X-Captcha-Type',
X_AUDIT_LOG_REASON: 'X-Audit-Log-Reason',
X_INTERNAL_API_KEY: 'X-Internal-API-Key',
X_AMZ_REQUEST_ID: 'x-amz-request-id',
@@ -1,12 +0,0 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import {APIErrorCodes} from '@fluxer/constants/src/ApiErrorCodes';
import {BadRequestError} from '@fluxer/errors/src/domains/core/BadRequestError';
export class CaptchaVerificationRequiredError extends BadRequestError {
constructor() {
super({
code: APIErrorCodes.CAPTCHA_REQUIRED,
});
}
}
@@ -161,8 +161,8 @@ export const ERROR_I18N_MESSAGES = {
'calls_and_voice.voice_message_duration_required': 'Voice message attachments must specify a duration.',
'calls_and_voice.voice_message_waveform_required': 'Voice message attachments must include waveform data.',
'calls_and_voice.voice_messages_require_single_attachment': 'Voice messages must contain exactly one attachment.',
'captcha.invalid': 'Invalid captcha.',
'captcha.required': 'Captcha is required.',
'captcha.invalid': 'Verification failed. Try again.',
'captcha.required': 'Verification required. Try again.',
'channels_and_guilds.afk_channel_must_be_in_guild': 'AFK channel must be in this community.',
'channels_and_guilds.afk_channel_must_be_voice': 'AFK channel must be a voice channel.',
'channels_and_guilds.all_channels_must_belong_to_guild': 'All channels must belong to this community.',
+2 -2
View File
@@ -140,8 +140,8 @@ const ERROR_I18N_AR_MESSAGES = defineErrorI18nLocaleMessages({
"calls_and_voice.voice_message_duration_required": "يجب أن تحدد مرفقات الرسائل الصوتية مدة.",
"calls_and_voice.voice_message_waveform_required": "يجب أن تحتوي مرفقات الرسائل الصوتية على بيانات الموجة الصوتية.",
"calls_and_voice.voice_messages_require_single_attachment": "يجب أن تحتوي الرسائل الصوتية على مرفق واحد فقط.",
"captcha.invalid": "اختبار الكابتشا غير صالح.",
"captcha.required": "اختبار الكابتشا مطلوب.",
"captcha.invalid": "فشل التحقق. حاول مرة أخرى.",
"captcha.required": "مطلوب التحقق. حاول مرة أخرى.",
"channels_and_guilds.afk_channel_must_be_in_guild": "يجب أن تكون قناة AFK ضمن هذا المجتمع.",
"channels_and_guilds.afk_channel_must_be_voice": "يجب أن تكون قناة AFK قناة صوتية.",
"channels_and_guilds.all_channels_must_belong_to_guild": "يجب أن تكون جميع القنوات تابعة لهذا المجتمع.",
+2 -2
View File
@@ -140,8 +140,8 @@ const ERROR_I18N_BG_MESSAGES = defineErrorI18nLocaleMessages({
"calls_and_voice.voice_message_duration_required": "Прикачените файлове към гласови съобщения трябва да имат продължителност.",
"calls_and_voice.voice_message_waveform_required": "Прикачените файлове към гласови съобщения трябва да съдържат данни за вълновата форма.",
"calls_and_voice.voice_messages_require_single_attachment": "Гласовите съобщения трябва да съдържат точно един прикачен файл.",
"captcha.invalid": "Невалидна капча.",
"captcha.required": "Изисква се капча.",
"captcha.invalid": "Проверката не успя. Опитай отново.",
"captcha.required": "Изисква се проверка. Опитай отново.",
"channels_and_guilds.afk_channel_must_be_in_guild": "AFK каналът трябва да е в рамките на тази общност.",
"channels_and_guilds.afk_channel_must_be_voice": "AFK каналът трябва да е гласов канал.",
"channels_and_guilds.all_channels_must_belong_to_guild": "Всички канали трябва да принадлежат на тази общност.",
+2 -2
View File
@@ -140,8 +140,8 @@ const ERROR_I18N_CS_MESSAGES = defineErrorI18nLocaleMessages({
"calls_and_voice.voice_message_duration_required": "Přílohy hlasových zpráv musí uvádět délku.",
"calls_and_voice.voice_message_waveform_required": "Přílohy hlasových zpráv musí obsahovat data průběhu zvukové vlny.",
"calls_and_voice.voice_messages_require_single_attachment": "Hlasové zprávy musí obsahovat právě jednu přílohu.",
"captcha.invalid": "Neplatná CAPTCHA.",
"captcha.required": "CAPTCHA je vyžadována.",
"captcha.invalid": "Ověření se nezdařilo. Zkuste to znovu.",
"captcha.required": "Ověření je nutné. Zkuste to znovu.",
"channels_and_guilds.afk_channel_must_be_in_guild": "AFK kanál musí být v této komunitě.",
"channels_and_guilds.afk_channel_must_be_voice": "AFK kanál musí být hlasový kanál.",
"channels_and_guilds.all_channels_must_belong_to_guild": "Všechny kanály musí být v této komunitě.",
+2 -2
View File
@@ -140,8 +140,8 @@ const ERROR_I18N_DA_MESSAGES = defineErrorI18nLocaleMessages({
"calls_and_voice.voice_message_duration_required": "Vedhæftede filer til talebeskeder skal angive en varighed.",
"calls_and_voice.voice_message_waveform_required": "Vedhæftede filer til talebeskeder skal indeholde bølgeformdata.",
"calls_and_voice.voice_messages_require_single_attachment": "Talebeskeder skal indeholde præcis én vedhæftet fil.",
"captcha.invalid": "Ugyldig captcha.",
"captcha.required": "Captcha er påkrævet.",
"captcha.invalid": "Bekræftelse mislykkedes. Prøv igen.",
"captcha.required": "Bekræftelse påkrævet. Prøv igen.",
"channels_and_guilds.afk_channel_must_be_in_guild": "AFK-kanalen skal være i dette fællesskab.",
"channels_and_guilds.afk_channel_must_be_voice": "AFK-kanalen skal være en talekanal.",
"channels_and_guilds.all_channels_must_belong_to_guild": "Alle kanaler skal tilhøre dette fællesskab.",
+2 -2
View File
@@ -140,8 +140,8 @@ const ERROR_I18N_DE_MESSAGES = defineErrorI18nLocaleMessages({
"calls_and_voice.voice_message_duration_required": "Für Anhänge von Sprachnachrichten muss eine Dauer angegeben werden.",
"calls_and_voice.voice_message_waveform_required": "Anhänge von Sprachnachrichten müssen Wellenformdaten enthalten.",
"calls_and_voice.voice_messages_require_single_attachment": "Sprachnachrichten müssen genau einen Anhang enthalten.",
"captcha.invalid": "Das Captcha ist ungültig.",
"captcha.required": "Das Captcha ist erforderlich.",
"captcha.invalid": "Überprüfung fehlgeschlagen. Versuche es erneut.",
"captcha.required": "Überprüfung erforderlich. Versuche es erneut.",
"channels_and_guilds.afk_channel_must_be_in_guild": "Der AFK-Kanal muss sich in dieser Community befinden.",
"channels_and_guilds.afk_channel_must_be_voice": "Der AFK-Kanal muss ein Sprachkanal sein.",
"channels_and_guilds.all_channels_must_belong_to_guild": "Alle Kanäle müssen sich in dieser Community befinden.",
+2 -2
View File
@@ -140,8 +140,8 @@ const ERROR_I18N_EL_MESSAGES = defineErrorI18nLocaleMessages({
"calls_and_voice.voice_message_duration_required": "Τα συνημμένα φωνητικών μηνυμάτων πρέπει να έχουν διάρκεια.",
"calls_and_voice.voice_message_waveform_required": "Τα συνημμένα φωνητικών μηνυμάτων πρέπει να περιέχουν δεδομένα κυματομορφής.",
"calls_and_voice.voice_messages_require_single_attachment": "Τα φωνητικά μηνύματα πρέπει να περιέχουν ακριβώς ένα συνημμένο.",
"captcha.invalid": "Μη έγκυρο captcha.",
"captcha.required": "Απαιτείται captcha.",
"captcha.invalid": "Η επαλήθευση απέτυχε. Δοκίμασε ξανά.",
"captcha.required": "Απαιτείται επαλήθευση. Δοκίμασε ξανά.",
"channels_and_guilds.afk_channel_must_be_in_guild": "Το κανάλι AFK πρέπει να βρίσκεται σε αυτήν την κοινότητα.",
"channels_and_guilds.afk_channel_must_be_voice": "Το κανάλι AFK πρέπει να είναι φωνητικό κανάλι.",
"channels_and_guilds.all_channels_must_belong_to_guild": "Όλα τα κανάλια πρέπει να βρίσκονται σε αυτήν την κοινότητα.",
+2 -2
View File
@@ -140,8 +140,8 @@ const ERROR_I18N_EN_GB_MESSAGES = defineErrorI18nLocaleMessages({
"calls_and_voice.voice_message_duration_required": "Voice message attachments must specify a duration.",
"calls_and_voice.voice_message_waveform_required": "Voice message attachments must include waveform data.",
"calls_and_voice.voice_messages_require_single_attachment": "Voice messages must contain exactly one attachment.",
"captcha.invalid": "Invalid captcha.",
"captcha.required": "Captcha is required.",
"captcha.invalid": "Verification failed. Try again.",
"captcha.required": "Verification required. Try again.",
"channels_and_guilds.afk_channel_must_be_in_guild": "AFK channel must be in this community.",
"channels_and_guilds.afk_channel_must_be_voice": "AFK channel must be a voice channel.",
"channels_and_guilds.all_channels_must_belong_to_guild": "All channels must belong to this community.",
+2 -2
View File
@@ -140,8 +140,8 @@ const ERROR_I18N_ES_419_MESSAGES = defineErrorI18nLocaleMessages({
"calls_and_voice.voice_message_duration_required": "Los archivos adjuntos de mensajes de voz deben especificar una duración.",
"calls_and_voice.voice_message_waveform_required": "Los archivos adjuntos de mensajes de voz deben incluir datos de onda.",
"calls_and_voice.voice_messages_require_single_attachment": "Los mensajes de voz deben contener exactamente un archivo adjunto.",
"captcha.invalid": "Captcha inválido.",
"captcha.required": "Se requiere captcha.",
"captcha.invalid": "Verificación fallida. Inténtalo de nuevo.",
"captcha.required": "Verificación requerida. Inténtalo de nuevo.",
"channels_and_guilds.afk_channel_must_be_in_guild": "El canal AFK debe estar en esta comunidad.",
"channels_and_guilds.afk_channel_must_be_voice": "El canal AFK debe ser un canal de voz.",
"channels_and_guilds.all_channels_must_belong_to_guild": "Todos los canales deben pertenecer a esta comunidad.",
+2 -2
View File
@@ -140,8 +140,8 @@ const ERROR_I18N_ES_ES_MESSAGES = defineErrorI18nLocaleMessages({
"calls_and_voice.voice_message_duration_required": "Los archivos adjuntos de mensajes de voz deben especificar una duración.",
"calls_and_voice.voice_message_waveform_required": "Los archivos adjuntos de mensajes de voz deben incluir datos de forma de onda.",
"calls_and_voice.voice_messages_require_single_attachment": "Los mensajes de voz deben contener exactamente un archivo adjunto.",
"captcha.invalid": "Captcha no válido.",
"captcha.required": "Se requiere captcha.",
"captcha.invalid": "Verificación fallida. Inténtalo de nuevo.",
"captcha.required": "Verificación necesaria. Inténtalo de nuevo.",
"channels_and_guilds.afk_channel_must_be_in_guild": "El canal AFK debe pertenecer a esta comunidad.",
"channels_and_guilds.afk_channel_must_be_voice": "El canal AFK debe ser un canal de voz.",
"channels_and_guilds.all_channels_must_belong_to_guild": "Todos los canales deben pertenecer a esta comunidad.",
+2 -2
View File
@@ -140,8 +140,8 @@ const ERROR_I18N_FI_MESSAGES = defineErrorI18nLocaleMessages({
"calls_and_voice.voice_message_duration_required": "Ääniviestin liitteiden on sisällettävä kesto.",
"calls_and_voice.voice_message_waveform_required": "Ääniviestin liitteiden on sisällettävä aaltomuotodataa.",
"calls_and_voice.voice_messages_require_single_attachment": "Ääniviestien on sisällettävä täsmälleen yksi liite.",
"captcha.invalid": "Virheellinen captcha.",
"captcha.required": "Captcha vaaditaan.",
"captcha.invalid": "Vahvistus epäonnistui. Yritä uudelleen.",
"captcha.required": "Vahvistus vaaditaan. Yritä uudelleen.",
"channels_and_guilds.afk_channel_must_be_in_guild": "AFK-kanavan on oltava tässä yhteisössä.",
"channels_and_guilds.afk_channel_must_be_voice": "AFK-kanavan on oltava äänikanava.",
"channels_and_guilds.all_channels_must_belong_to_guild": "Kaikkien kanavien on kuuluttava tähän yhteisöön.",
+2 -2
View File
@@ -140,8 +140,8 @@ const ERROR_I18N_FR_MESSAGES = defineErrorI18nLocaleMessages({
"calls_and_voice.voice_message_duration_required": "Les pièces jointes des messages vocaux doivent spécifier une durée.",
"calls_and_voice.voice_message_waveform_required": "Les pièces jointes des messages vocaux doivent inclure des données de forme d'onde.",
"calls_and_voice.voice_messages_require_single_attachment": "Les messages vocaux doivent contenir exactement une pièce jointe.",
"captcha.invalid": "Captcha invalide.",
"captcha.required": "Le captcha est requis.",
"captcha.invalid": "Échec de la vérification. Réessayez.",
"captcha.required": "Vérification requise. Réessayez.",
"channels_and_guilds.afk_channel_must_be_in_guild": "Le salon AFK doit être dans cette communauté.",
"channels_and_guilds.afk_channel_must_be_voice": "Le salon AFK doit être un salon vocal.",
"channels_and_guilds.all_channels_must_belong_to_guild": "Tous les salons doivent appartenir à cette communauté.",
+2 -2
View File
@@ -140,8 +140,8 @@ const ERROR_I18N_HE_MESSAGES = defineErrorI18nLocaleMessages({
"calls_and_voice.voice_message_duration_required": "קבצים מצורפים של הודעות קוליות חייבים לציין משך זמן.",
"calls_and_voice.voice_message_waveform_required": "קבצים מצורפים של הודעות קוליות חייבים לכלול נתוני צורת גל.",
"calls_and_voice.voice_messages_require_single_attachment": "הודעות קוליות חייבות לכלול קובץ מצורף אחד בלבד.",
"captcha.invalid": "קאפצ'ה שגויה.",
"captcha.required": "נדרשת קאפצ'ה.",
"captcha.invalid": "האימות נכשל. ניתן לנסות שוב.",
"captcha.required": "נדרש אימות. ניתן לנסות שוב.",
"channels_and_guilds.afk_channel_must_be_in_guild": "ערוץ AFK חייב להיות בקהילה זו.",
"channels_and_guilds.afk_channel_must_be_voice": "ערוץ AFK חייב להיות ערוץ קולי.",
"channels_and_guilds.all_channels_must_belong_to_guild": "כל הערוצים חייבים להשתייך לקהילה זו.",
+2 -2
View File
@@ -140,8 +140,8 @@ const ERROR_I18N_HI_MESSAGES = defineErrorI18nLocaleMessages({
"calls_and_voice.voice_message_duration_required": "वॉइस मैसेज अटैचमेंट में ड्यूरेशन बतानी होगी।",
"calls_and_voice.voice_message_waveform_required": "वॉइस मैसेज अटैचमेंट में वेवफ़ॉर्म डेटा होना चाहिए।",
"calls_and_voice.voice_messages_require_single_attachment": "वॉइस मैसेज में केवल एक अटैचमेंट होना चाहिए।",
"captcha.invalid": "अमान्य कैप्चा।",
"captcha.required": "कैप्चा ज़रूरी है।",
"captcha.invalid": "सत्यापन विफल रहा। फिर से कोशिश करें।",
"captcha.required": "सत्यापन आवश्यक है। फिर से कोशिश करें।",
"channels_and_guilds.afk_channel_must_be_in_guild": "AFK चैनल इस कम्युनिटी में होना चाहिए।",
"channels_and_guilds.afk_channel_must_be_voice": "AFK चैनल एक वॉइस चैनल होना चाहिए।",
"channels_and_guilds.all_channels_must_belong_to_guild": "सभी चैनल इस कम्युनिटी के होने चाहिए।",
+2 -2
View File
@@ -140,8 +140,8 @@ const ERROR_I18N_HR_MESSAGES = defineErrorI18nLocaleMessages({
"calls_and_voice.voice_message_duration_required": "Privici glasovne poruke moraju imati navedeno trajanje.",
"calls_and_voice.voice_message_waveform_required": "Privici glasovne poruke moraju sadržavati podatke o valnom obliku.",
"calls_and_voice.voice_messages_require_single_attachment": "Glasovne poruke moraju sadržavati točno jedan privitak.",
"captcha.invalid": "Nevažeća captcha.",
"captcha.required": "Captcha je obavezna.",
"captcha.invalid": "Provjera nije uspjela. Pokušaj ponovno.",
"captcha.required": "Potrebna je provjera. Pokušaj ponovno.",
"channels_and_guilds.afk_channel_must_be_in_guild": "AFK kanal mora biti u ovoj zajednici.",
"channels_and_guilds.afk_channel_must_be_voice": "AFK kanal mora biti glasovni kanal.",
"channels_and_guilds.all_channels_must_belong_to_guild": "Svi kanali moraju pripadati ovoj zajednici.",
+2 -2
View File
@@ -140,8 +140,8 @@ const ERROR_I18N_HU_MESSAGES = defineErrorI18nLocaleMessages({
"calls_and_voice.voice_message_duration_required": "A hangüzenet mellékleteihez meg kell adni az időtartamot.",
"calls_and_voice.voice_message_waveform_required": "A hangüzenet mellékleteinek hullámformaadatokat kell tartalmazniuk.",
"calls_and_voice.voice_messages_require_single_attachment": "A hangüzeneteknek pontosan egy mellékletet kell tartalmazniuk.",
"captcha.invalid": "Érvénytelen captcha kód.",
"captcha.required": "A captcha kód szükséges.",
"captcha.invalid": "Ellenőrzés sikertelen. Próbáld újra.",
"captcha.required": "Ellenőrzés szükséges. Próbáld újra.",
"channels_and_guilds.afk_channel_must_be_in_guild": "Az AFK-csatornának ebben a közösségben kell lennie.",
"channels_and_guilds.afk_channel_must_be_voice": "Az AFK-csatornának hangcsatornának kell lennie.",
"channels_and_guilds.all_channels_must_belong_to_guild": "Minden csatornának ehhez a közösséghez kell tartoznia.",
+2 -2
View File
@@ -140,8 +140,8 @@ const ERROR_I18N_ID_MESSAGES = defineErrorI18nLocaleMessages({
"calls_and_voice.voice_message_duration_required": "Lampiran pesan suara harus menentukan durasi.",
"calls_and_voice.voice_message_waveform_required": "Lampiran pesan suara harus menyertakan data bentuk gelombang.",
"calls_and_voice.voice_messages_require_single_attachment": "Pesan suara harus berisi tepat satu lampiran.",
"captcha.invalid": "Captcha tidak valid.",
"captcha.required": "Captcha diperlukan.",
"captcha.invalid": "Verifikasi gagal. Coba lagi.",
"captcha.required": "Verifikasi diperlukan. Coba lagi.",
"channels_and_guilds.afk_channel_must_be_in_guild": "Saluran AFK harus ada di komunitas ini.",
"channels_and_guilds.afk_channel_must_be_voice": "Saluran AFK harus berupa saluran suara.",
"channels_and_guilds.all_channels_must_belong_to_guild": "Semua saluran harus ada di komunitas ini.",
+2 -2
View File
@@ -140,8 +140,8 @@ const ERROR_I18N_IT_MESSAGES = defineErrorI18nLocaleMessages({
"calls_and_voice.voice_message_duration_required": "Gli allegati dei messaggi vocali devono specificare una durata.",
"calls_and_voice.voice_message_waveform_required": "Gli allegati dei messaggi vocali devono includere i dati della forma d'onda.",
"calls_and_voice.voice_messages_require_single_attachment": "I messaggi vocali devono contenere esattamente un allegato.",
"captcha.invalid": "Captcha non valido.",
"captcha.required": "Il Captcha è richiesto.",
"captcha.invalid": "Verifica non riuscita. Riprova.",
"captcha.required": "Verifica richiesta. Riprova.",
"channels_and_guilds.afk_channel_must_be_in_guild": "Il canale AFK deve appartenere a questa Community.",
"channels_and_guilds.afk_channel_must_be_voice": "Il canale AFK deve essere un canale vocale.",
"channels_and_guilds.all_channels_must_belong_to_guild": "Tutti i canali devono appartenere a questa Community.",
+2 -2
View File
@@ -140,8 +140,8 @@ const ERROR_I18N_JA_MESSAGES = defineErrorI18nLocaleMessages({
"calls_and_voice.voice_message_duration_required": "ボイスメッセージの添付ファイルには長さを指定する必要があります。",
"calls_and_voice.voice_message_waveform_required": "ボイスメッセージの添付ファイルには波形データを含める必要があります。",
"calls_and_voice.voice_messages_require_single_attachment": "ボイスメッセージには添付ファイルが1つだけ必要です。",
"captcha.invalid": "CAPTCHAが無効です。",
"captcha.required": "CAPTCHAが必要です。",
"captcha.invalid": "確認に失敗しました。もう一度お試しください。",
"captcha.required": "確認が必要です。もう一度お試しください。",
"channels_and_guilds.afk_channel_must_be_in_guild": "AFKチャンネルはこのコミュニティ内に設定する必要があります。",
"channels_and_guilds.afk_channel_must_be_voice": "AFKチャンネルはボイスチャンネルである必要があります。",
"channels_and_guilds.all_channels_must_belong_to_guild": "すべてのチャンネルはこのコミュニティに属している必要があります。",
+2 -2
View File
@@ -140,8 +140,8 @@ const ERROR_I18N_KO_MESSAGES = defineErrorI18nLocaleMessages({
"calls_and_voice.voice_message_duration_required": "음성 메시지 첨부 파일은 길이를 지정해야 해요.",
"calls_and_voice.voice_message_waveform_required": "음성 메시지 첨부 파일에는 파형 데이터가 포함되어야 해요.",
"calls_and_voice.voice_messages_require_single_attachment": "음성 메시지에는 첨부 파일이 하나만 포함되어야 해요.",
"captcha.invalid": "유효하지 않은 캡차예요.",
"captcha.required": "캡차가 필요해요.",
"captcha.invalid": "확인하지 못했어요. 다시 시도해 주세요.",
"captcha.required": "확인이 필요해요. 다시 시도해 주세요.",
"channels_and_guilds.afk_channel_must_be_in_guild": "자리 비움 채널은 이 커뮤니티에 있어야 해요.",
"channels_and_guilds.afk_channel_must_be_voice": "자리 비움 채널은 음성 채널이어야 해요.",
"channels_and_guilds.all_channels_must_belong_to_guild": "모든 채널은 이 커뮤니티에 속해야 해요.",
+2 -2
View File
@@ -140,8 +140,8 @@ const ERROR_I18N_LT_MESSAGES = defineErrorI18nLocaleMessages({
"calls_and_voice.voice_message_duration_required": "Balso žinutės priedai turi nurodyti trukmę.",
"calls_and_voice.voice_message_waveform_required": "Balso žinutės prieduose turi būti bangos formos duomenys.",
"calls_and_voice.voice_messages_require_single_attachment": "Balso žinutėse turi būti lygiai vienas priedas.",
"captcha.invalid": "Neteisinga captcha.",
"captcha.required": "Reikalinga captcha.",
"captcha.invalid": "Nepavyko patvirtinti. Bandyk dar kartą.",
"captcha.required": "Reikalingas patvirtinimas. Bandyk dar kartą.",
"channels_and_guilds.afk_channel_must_be_in_guild": "AFK kanalas turi būti šioje bendruomenėje.",
"channels_and_guilds.afk_channel_must_be_voice": "AFK kanalas turi būti balso kanalas.",
"channels_and_guilds.all_channels_must_belong_to_guild": "Visi kanalai turi būti šioje bendruomenėje.",
+2 -2
View File
@@ -140,8 +140,8 @@ const ERROR_I18N_NL_MESSAGES = defineErrorI18nLocaleMessages({
"calls_and_voice.voice_message_duration_required": "Bij spraakberichtbijlagen moet de duur worden opgegeven.",
"calls_and_voice.voice_message_waveform_required": "Spraakberichtbijlagen moeten golfvormgegevens bevatten.",
"calls_and_voice.voice_messages_require_single_attachment": "Spraakberichten moeten precies één bijlage bevatten.",
"captcha.invalid": "Ongeldige CAPTCHA.",
"captcha.required": "CAPTCHA is vereist.",
"captcha.invalid": "Verificatie mislukt. Probeer het opnieuw.",
"captcha.required": "Verificatie vereist. Probeer het opnieuw.",
"channels_and_guilds.afk_channel_must_be_in_guild": "Het AFK-kanaal moet deel uitmaken van deze community.",
"channels_and_guilds.afk_channel_must_be_voice": "Het AFK-kanaal moet een spraakkanaal zijn.",
"channels_and_guilds.all_channels_must_belong_to_guild": "Alle kanalen moeten deel uitmaken van deze community.",
+2 -2
View File
@@ -140,8 +140,8 @@ const ERROR_I18N_NO_MESSAGES = defineErrorI18nLocaleMessages({
"calls_and_voice.voice_message_duration_required": "Vedlegg til talemeldinger må angi en varighet.",
"calls_and_voice.voice_message_waveform_required": "Vedlegg til talemeldinger må inneholde bølgeformdata.",
"calls_and_voice.voice_messages_require_single_attachment": "Talemeldinger må inneholde nøyaktig ett vedlegg.",
"captcha.invalid": "Ugyldig captcha.",
"captcha.required": "Captcha er påkrevd.",
"captcha.invalid": "Verifisering mislyktes. Prøv igjen.",
"captcha.required": "Verifisering kreves. Prøv igjen.",
"channels_and_guilds.afk_channel_must_be_in_guild": "AFK-kanalen må være i dette fellesskapet.",
"channels_and_guilds.afk_channel_must_be_voice": "AFK-kanalen må være en talekanal.",
"channels_and_guilds.all_channels_must_belong_to_guild": "Alle kanaler må tilhøre dette fellesskapet.",
+2 -2
View File
@@ -140,8 +140,8 @@ const ERROR_I18N_PL_MESSAGES = defineErrorI18nLocaleMessages({
"calls_and_voice.voice_message_duration_required": "Załączniki wiadomości głosowych muszą określać czas trwania.",
"calls_and_voice.voice_message_waveform_required": "Załączniki wiadomości głosowych muszą zawierać dane przebiegu fali dźwiękowej.",
"calls_and_voice.voice_messages_require_single_attachment": "Wiadomości głosowe muszą zawierać dokładnie jeden załącznik.",
"captcha.invalid": "Nieprawidłowa captcha.",
"captcha.required": "Captcha jest wymagana.",
"captcha.invalid": "Weryfikacja nie powiodła się. Spróbuj ponownie.",
"captcha.required": "Weryfikacja wymagana. Spróbuj ponownie.",
"channels_and_guilds.afk_channel_must_be_in_guild": "Kanał AFK musi znajdować się w tej społeczności.",
"channels_and_guilds.afk_channel_must_be_voice": "Kanał AFK musi być kanałem głosowym.",
"channels_and_guilds.all_channels_must_belong_to_guild": "Wszystkie kanały muszą znajdować się w tej społeczności.",
+2 -2
View File
@@ -140,8 +140,8 @@ const ERROR_I18N_PT_BR_MESSAGES = defineErrorI18nLocaleMessages({
"calls_and_voice.voice_message_duration_required": "Os anexos de mensagem de voz devem especificar uma duração.",
"calls_and_voice.voice_message_waveform_required": "Os anexos de mensagem de voz devem incluir dados da forma de onda.",
"calls_and_voice.voice_messages_require_single_attachment": "As mensagens de voz devem conter exatamente um anexo.",
"captcha.invalid": "CAPTCHA inválido.",
"captcha.required": "CAPTCHA é obrigatório.",
"captcha.invalid": "Falha na verificação. Tente novamente.",
"captcha.required": "Verificação necessária. Tente novamente.",
"channels_and_guilds.afk_channel_must_be_in_guild": "O canal AFK deve estar nesta comunidade.",
"channels_and_guilds.afk_channel_must_be_voice": "O canal AFK deve ser um canal de voz.",
"channels_and_guilds.all_channels_must_belong_to_guild": "Todos os canais devem pertencer a esta comunidade.",
+2 -2
View File
@@ -140,8 +140,8 @@ const ERROR_I18N_RO_MESSAGES = defineErrorI18nLocaleMessages({
"calls_and_voice.voice_message_duration_required": "Atașamentele mesajelor vocale trebuie să specifice o durată.",
"calls_and_voice.voice_message_waveform_required": "Atașamentele mesajelor vocale trebuie să includă datele formei de undă.",
"calls_and_voice.voice_messages_require_single_attachment": "Mesajele vocale trebuie să conțină exact un atașament.",
"captcha.invalid": "Captcha invalid.",
"captcha.required": "Captcha este necesar.",
"captcha.invalid": "Verificare eșuată. Încearcă din nou.",
"captcha.required": "Verificare necesară. Încearcă din nou.",
"channels_and_guilds.afk_channel_must_be_in_guild": "Canalul AFK trebuie să fie în această comunitate.",
"channels_and_guilds.afk_channel_must_be_voice": "Canalul AFK trebuie să fie un canal vocal.",
"channels_and_guilds.all_channels_must_belong_to_guild": "Toate canalele trebuie să facă parte din această comunitate.",
+2 -2
View File
@@ -140,8 +140,8 @@ const ERROR_I18N_RU_MESSAGES = defineErrorI18nLocaleMessages({
"calls_and_voice.voice_message_duration_required": "У вложений голосовых сообщений должна быть указана длительность.",
"calls_and_voice.voice_message_waveform_required": "Вложения голосовых сообщений должны содержать данные формы волны.",
"calls_and_voice.voice_messages_require_single_attachment": "Голосовые сообщения должны содержать ровно одно вложение.",
"captcha.invalid": "Капча недействительна.",
"captcha.required": "Требуется капча.",
"captcha.invalid": "Проверка не удалась. Повторите попытку.",
"captcha.required": "Требуется проверка. Повторите попытку.",
"channels_and_guilds.afk_channel_must_be_in_guild": "Канал AFK должен быть в этом сообществе.",
"channels_and_guilds.afk_channel_must_be_voice": "Канал AFK должен быть голосовым.",
"channels_and_guilds.all_channels_must_belong_to_guild": "Все каналы должны принадлежать этому сообществу.",
+2 -2
View File
@@ -140,8 +140,8 @@ const ERROR_I18N_SV_SE_MESSAGES = defineErrorI18nLocaleMessages({
"calls_and_voice.voice_message_duration_required": "Bilagor till röstmeddelanden måste ha en angiven speltid.",
"calls_and_voice.voice_message_waveform_required": "Bilagor till röstmeddelanden måste innehålla vågformsdata.",
"calls_and_voice.voice_messages_require_single_attachment": "Röstmeddelanden måste innehålla exakt en bilaga.",
"captcha.invalid": "Ogiltig captcha.",
"captcha.required": "Captcha krävs.",
"captcha.invalid": "Verifieringen misslyckades. Försök igen.",
"captcha.required": "Verifiering krävs. Försök igen.",
"channels_and_guilds.afk_channel_must_be_in_guild": "AFK-kanalen måste tillhöra denna community.",
"channels_and_guilds.afk_channel_must_be_voice": "AFK-kanalen måste vara en röstkanal.",
"channels_and_guilds.all_channels_must_belong_to_guild": "Alla kanaler måste tillhöra denna community.",
+2 -2
View File
@@ -140,8 +140,8 @@ const ERROR_I18N_TH_MESSAGES = defineErrorI18nLocaleMessages({
"calls_and_voice.voice_message_duration_required": "ไฟล์แนบข้อความเสียงต้องกำหนดระยะเวลา",
"calls_and_voice.voice_message_waveform_required": "ไฟล์แนบข้อความเสียงต้องแนบข้อมูลรูปคลื่น",
"calls_and_voice.voice_messages_require_single_attachment": "ข้อความเสียงต้องมีไฟล์แนบเพียงไฟล์เดียวเท่านั้น",
"captcha.invalid": "แคปต์ชาไม่ถูกต้อง",
"captcha.required": "ต้องใส่แคปต์ชา",
"captcha.invalid": "การยืนยันล้มเหลว ลองอีกครั้ง",
"captcha.required": "ต้องมีการยืนยัน ลองอีกครั้ง",
"channels_and_guilds.afk_channel_must_be_in_guild": "ช่อง AFK ต้องอยู่ในคอมมูนิตี้นี้",
"channels_and_guilds.afk_channel_must_be_voice": "ช่อง AFK ต้องเป็นช่องเสียง",
"channels_and_guilds.all_channels_must_belong_to_guild": "ทุกช่องต้องอยู่ในคอมมูนิตี้นี้เท่านั้น",
+2 -2
View File
@@ -140,8 +140,8 @@ const ERROR_I18N_TR_MESSAGES = defineErrorI18nLocaleMessages({
"calls_and_voice.voice_message_duration_required": "Sesli mesaj ekleri bir süre belirtmeli.",
"calls_and_voice.voice_message_waveform_required": "Sesli mesaj ekleri dalga biçimi verilerini içermeli.",
"calls_and_voice.voice_messages_require_single_attachment": "Sesli mesajlar tam olarak bir ek içermeli.",
"captcha.invalid": "Geçersiz captcha.",
"captcha.required": "Captcha gerekli.",
"captcha.invalid": "Doğrulama başarısız oldu. Tekrar dene.",
"captcha.required": "Doğrulama gerekli. Tekrar dene.",
"channels_and_guilds.afk_channel_must_be_in_guild": "AFK kanalı bu toplulukta olmalı.",
"channels_and_guilds.afk_channel_must_be_voice": "AFK kanalı bir sesli kanal olmalı.",
"channels_and_guilds.all_channels_must_belong_to_guild": "Tüm kanallar bu toplulukta olmalı.",
+2 -2
View File
@@ -140,8 +140,8 @@ const ERROR_I18N_UK_MESSAGES = defineErrorI18nLocaleMessages({
"calls_and_voice.voice_message_duration_required": "У вкладеннях голосових повідомлень має бути вказана тривалість.",
"calls_and_voice.voice_message_waveform_required": "Вкладення голосових повідомлень мають містити дані форми хвилі.",
"calls_and_voice.voice_messages_require_single_attachment": "Голосові повідомлення мають містити рівно одне вкладення.",
"captcha.invalid": "Недійсна капча.",
"captcha.required": "Капча обов'язкова.",
"captcha.invalid": "Перевірку не пройдено. Спробуй ще раз.",
"captcha.required": "Потрібна перевірка. Спробуй ще раз.",
"channels_and_guilds.afk_channel_must_be_in_guild": "AFK-канал має належати цій спільноті.",
"channels_and_guilds.afk_channel_must_be_voice": "AFK-канал має бути голосовим каналом.",
"channels_and_guilds.all_channels_must_belong_to_guild": "Усі канали мають належати цій спільноті.",
+2 -2
View File
@@ -140,8 +140,8 @@ const ERROR_I18N_VI_MESSAGES = defineErrorI18nLocaleMessages({
"calls_and_voice.voice_message_duration_required": "Tệp đính kèm tin nhắn thoại phải có thời lượng.",
"calls_and_voice.voice_message_waveform_required": "Tệp đính kèm tin nhắn thoại phải bao gồm dữ liệu dạng sóng.",
"calls_and_voice.voice_messages_require_single_attachment": "Tin nhắn thoại phải chứa chính xác một tệp đính kèm.",
"captcha.invalid": "Captcha không hợp lệ.",
"captcha.required": "Captcha là bắt buộc.",
"captcha.invalid": "Xác minh thất bại. Hãy thử lại.",
"captcha.required": "Cần xác minh. Hãy thử lại.",
"channels_and_guilds.afk_channel_must_be_in_guild": "Kênh AFK phải thuộc cộng đồng này.",
"channels_and_guilds.afk_channel_must_be_voice": "Kênh AFK phải là kênh thoại.",
"channels_and_guilds.all_channels_must_belong_to_guild": "Tất cả các kênh phải thuộc cộng đồng này.",
+2 -2
View File
@@ -140,8 +140,8 @@ const ERROR_I18N_ZH_CN_MESSAGES = defineErrorI18nLocaleMessages({
"calls_and_voice.voice_message_duration_required": "语音消息附件必须指定时长。",
"calls_and_voice.voice_message_waveform_required": "语音消息附件必须包含波形数据。",
"calls_and_voice.voice_messages_require_single_attachment": "语音消息必须仅包含一个附件。",
"captcha.invalid": "无效验证码。",
"captcha.required": "需要验证码。",
"captcha.invalid": "验证失败。请重试。",
"captcha.required": "需要验证。请重试。",
"channels_and_guilds.afk_channel_must_be_in_guild": "AFK 频道必须属于此社区。",
"channels_and_guilds.afk_channel_must_be_voice": "AFK 频道必须是语音频道。",
"channels_and_guilds.all_channels_must_belong_to_guild": "所有频道必须属于此社区。",
+2 -2
View File
@@ -140,8 +140,8 @@ const ERROR_I18N_ZH_TW_MESSAGES = defineErrorI18nLocaleMessages({
"calls_and_voice.voice_message_duration_required": "語音訊息附件必須設定持續時間。",
"calls_and_voice.voice_message_waveform_required": "語音訊息附件必須包含波形資料。",
"calls_and_voice.voice_messages_require_single_attachment": "語音訊息必須只包含一個附件。",
"captcha.invalid": "無效的驗證碼。",
"captcha.required": "需要驗證碼。",
"captcha.invalid": "驗證失敗。請再試一次。",
"captcha.required": "需要驗證。請再試一次。",
"channels_and_guilds.afk_channel_must_be_in_guild": "AFK 頻道必須屬於此社群。",
"channels_and_guilds.afk_channel_must_be_voice": "AFK 頻道必須為語音頻道。",
"channels_and_guilds.all_channels_must_belong_to_guild": "所有頻道都必須屬於此社群。",
@@ -136,8 +136,8 @@
"calls_and_voice.voice_message_duration_required": "يجب أن تحدد مرفقات الرسائل الصوتية مدة.",
"calls_and_voice.voice_message_waveform_required": "يجب أن تحتوي مرفقات الرسائل الصوتية على بيانات الموجة الصوتية.",
"calls_and_voice.voice_messages_require_single_attachment": "يجب أن تحتوي الرسائل الصوتية على مرفق واحد فقط.",
"captcha.invalid": "اختبار الكابتشا غير صالح.",
"captcha.required": "اختبار الكابتشا مطلوب.",
"captcha.invalid": "فشل التحقق. حاول مرة أخرى.",
"captcha.required": "مطلوب التحقق. حاول مرة أخرى.",
"channels_and_guilds.afk_channel_must_be_in_guild": "يجب أن تكون قناة AFK ضمن هذا المجتمع.",
"channels_and_guilds.afk_channel_must_be_voice": "يجب أن تكون قناة AFK قناة صوتية.",
"channels_and_guilds.all_channels_must_belong_to_guild": "يجب أن تكون جميع القنوات تابعة لهذا المجتمع.",
@@ -136,8 +136,8 @@
"calls_and_voice.voice_message_duration_required": "Прикачените файлове към гласови съобщения трябва да имат продължителност.",
"calls_and_voice.voice_message_waveform_required": "Прикачените файлове към гласови съобщения трябва да съдържат данни за вълновата форма.",
"calls_and_voice.voice_messages_require_single_attachment": "Гласовите съобщения трябва да съдържат точно един прикачен файл.",
"captcha.invalid": "Невалидна капча.",
"captcha.required": "Изисква се капча.",
"captcha.invalid": "Проверката не успя. Опитай отново.",
"captcha.required": "Изисква се проверка. Опитай отново.",
"channels_and_guilds.afk_channel_must_be_in_guild": "AFK каналът трябва да е в рамките на тази общност.",
"channels_and_guilds.afk_channel_must_be_voice": "AFK каналът трябва да е гласов канал.",
"channels_and_guilds.all_channels_must_belong_to_guild": "Всички канали трябва да принадлежат на тази общност.",
@@ -136,8 +136,8 @@
"calls_and_voice.voice_message_duration_required": "Přílohy hlasových zpráv musí uvádět délku.",
"calls_and_voice.voice_message_waveform_required": "Přílohy hlasových zpráv musí obsahovat data průběhu zvukové vlny.",
"calls_and_voice.voice_messages_require_single_attachment": "Hlasové zprávy musí obsahovat právě jednu přílohu.",
"captcha.invalid": "Neplatná CAPTCHA.",
"captcha.required": "CAPTCHA je vyžadována.",
"captcha.invalid": "Ověření se nezdařilo. Zkuste to znovu.",
"captcha.required": "Ověření je nutné. Zkuste to znovu.",
"channels_and_guilds.afk_channel_must_be_in_guild": "AFK kanál musí být v této komunitě.",
"channels_and_guilds.afk_channel_must_be_voice": "AFK kanál musí být hlasový kanál.",
"channels_and_guilds.all_channels_must_belong_to_guild": "Všechny kanály musí být v této komunitě.",
@@ -136,8 +136,8 @@
"calls_and_voice.voice_message_duration_required": "Vedhæftede filer til talebeskeder skal angive en varighed.",
"calls_and_voice.voice_message_waveform_required": "Vedhæftede filer til talebeskeder skal indeholde bølgeformdata.",
"calls_and_voice.voice_messages_require_single_attachment": "Talebeskeder skal indeholde præcis én vedhæftet fil.",
"captcha.invalid": "Ugyldig captcha.",
"captcha.required": "Captcha er påkrævet.",
"captcha.invalid": "Bekræftelse mislykkedes. Prøv igen.",
"captcha.required": "Bekræftelse påkrævet. Prøv igen.",
"channels_and_guilds.afk_channel_must_be_in_guild": "AFK-kanalen skal være i dette fællesskab.",
"channels_and_guilds.afk_channel_must_be_voice": "AFK-kanalen skal være en talekanal.",
"channels_and_guilds.all_channels_must_belong_to_guild": "Alle kanaler skal tilhøre dette fællesskab.",
@@ -136,8 +136,8 @@
"calls_and_voice.voice_message_duration_required": "Für Anhänge von Sprachnachrichten muss eine Dauer angegeben werden.",
"calls_and_voice.voice_message_waveform_required": "Anhänge von Sprachnachrichten müssen Wellenformdaten enthalten.",
"calls_and_voice.voice_messages_require_single_attachment": "Sprachnachrichten müssen genau einen Anhang enthalten.",
"captcha.invalid": "Das Captcha ist ungültig.",
"captcha.required": "Das Captcha ist erforderlich.",
"captcha.invalid": "Überprüfung fehlgeschlagen. Versuche es erneut.",
"captcha.required": "Überprüfung erforderlich. Versuche es erneut.",
"channels_and_guilds.afk_channel_must_be_in_guild": "Der AFK-Kanal muss sich in dieser Community befinden.",
"channels_and_guilds.afk_channel_must_be_voice": "Der AFK-Kanal muss ein Sprachkanal sein.",
"channels_and_guilds.all_channels_must_belong_to_guild": "Alle Kanäle müssen sich in dieser Community befinden.",
@@ -136,8 +136,8 @@
"calls_and_voice.voice_message_duration_required": "Τα συνημμένα φωνητικών μηνυμάτων πρέπει να έχουν διάρκεια.",
"calls_and_voice.voice_message_waveform_required": "Τα συνημμένα φωνητικών μηνυμάτων πρέπει να περιέχουν δεδομένα κυματομορφής.",
"calls_and_voice.voice_messages_require_single_attachment": "Τα φωνητικά μηνύματα πρέπει να περιέχουν ακριβώς ένα συνημμένο.",
"captcha.invalid": "Μη έγκυρο captcha.",
"captcha.required": "Απαιτείται captcha.",
"captcha.invalid": "Η επαλήθευση απέτυχε. Δοκίμασε ξανά.",
"captcha.required": "Απαιτείται επαλήθευση. Δοκίμασε ξανά.",
"channels_and_guilds.afk_channel_must_be_in_guild": "Το κανάλι AFK πρέπει να βρίσκεται σε αυτήν την κοινότητα.",
"channels_and_guilds.afk_channel_must_be_voice": "Το κανάλι AFK πρέπει να είναι φωνητικό κανάλι.",
"channels_and_guilds.all_channels_must_belong_to_guild": "Όλα τα κανάλια πρέπει να βρίσκονται σε αυτήν την κοινότητα.",
@@ -136,8 +136,8 @@
"calls_and_voice.voice_message_duration_required": "Voice message attachments must specify a duration.",
"calls_and_voice.voice_message_waveform_required": "Voice message attachments must include waveform data.",
"calls_and_voice.voice_messages_require_single_attachment": "Voice messages must contain exactly one attachment.",
"captcha.invalid": "Invalid captcha.",
"captcha.required": "Captcha is required.",
"captcha.invalid": "Verification failed. Try again.",
"captcha.required": "Verification required. Try again.",
"channels_and_guilds.afk_channel_must_be_in_guild": "AFK channel must be in this community.",
"channels_and_guilds.afk_channel_must_be_voice": "AFK channel must be a voice channel.",
"channels_and_guilds.all_channels_must_belong_to_guild": "All channels must belong to this community.",
@@ -136,8 +136,8 @@
"calls_and_voice.voice_message_duration_required": "Los archivos adjuntos de mensajes de voz deben especificar una duración.",
"calls_and_voice.voice_message_waveform_required": "Los archivos adjuntos de mensajes de voz deben incluir datos de onda.",
"calls_and_voice.voice_messages_require_single_attachment": "Los mensajes de voz deben contener exactamente un archivo adjunto.",
"captcha.invalid": "Captcha inválido.",
"captcha.required": "Se requiere captcha.",
"captcha.invalid": "Verificación fallida. Inténtalo de nuevo.",
"captcha.required": "Verificación requerida. Inténtalo de nuevo.",
"channels_and_guilds.afk_channel_must_be_in_guild": "El canal AFK debe estar en esta comunidad.",
"channels_and_guilds.afk_channel_must_be_voice": "El canal AFK debe ser un canal de voz.",
"channels_and_guilds.all_channels_must_belong_to_guild": "Todos los canales deben pertenecer a esta comunidad.",
@@ -136,8 +136,8 @@
"calls_and_voice.voice_message_duration_required": "Los archivos adjuntos de mensajes de voz deben especificar una duración.",
"calls_and_voice.voice_message_waveform_required": "Los archivos adjuntos de mensajes de voz deben incluir datos de forma de onda.",
"calls_and_voice.voice_messages_require_single_attachment": "Los mensajes de voz deben contener exactamente un archivo adjunto.",
"captcha.invalid": "Captcha no válido.",
"captcha.required": "Se requiere captcha.",
"captcha.invalid": "Verificación fallida. Inténtalo de nuevo.",
"captcha.required": "Verificación necesaria. Inténtalo de nuevo.",
"channels_and_guilds.afk_channel_must_be_in_guild": "El canal AFK debe pertenecer a esta comunidad.",
"channels_and_guilds.afk_channel_must_be_voice": "El canal AFK debe ser un canal de voz.",
"channels_and_guilds.all_channels_must_belong_to_guild": "Todos los canales deben pertenecer a esta comunidad.",
@@ -136,8 +136,8 @@
"calls_and_voice.voice_message_duration_required": "Ääniviestin liitteiden on sisällettävä kesto.",
"calls_and_voice.voice_message_waveform_required": "Ääniviestin liitteiden on sisällettävä aaltomuotodataa.",
"calls_and_voice.voice_messages_require_single_attachment": "Ääniviestien on sisällettävä täsmälleen yksi liite.",
"captcha.invalid": "Virheellinen captcha.",
"captcha.required": "Captcha vaaditaan.",
"captcha.invalid": "Vahvistus epäonnistui. Yritä uudelleen.",
"captcha.required": "Vahvistus vaaditaan. Yritä uudelleen.",
"channels_and_guilds.afk_channel_must_be_in_guild": "AFK-kanavan on oltava tässä yhteisössä.",
"channels_and_guilds.afk_channel_must_be_voice": "AFK-kanavan on oltava äänikanava.",
"channels_and_guilds.all_channels_must_belong_to_guild": "Kaikkien kanavien on kuuluttava tähän yhteisöön.",
@@ -136,8 +136,8 @@
"calls_and_voice.voice_message_duration_required": "Les pièces jointes des messages vocaux doivent spécifier une durée.",
"calls_and_voice.voice_message_waveform_required": "Les pièces jointes des messages vocaux doivent inclure des données de forme d'onde.",
"calls_and_voice.voice_messages_require_single_attachment": "Les messages vocaux doivent contenir exactement une pièce jointe.",
"captcha.invalid": "Captcha invalide.",
"captcha.required": "Le captcha est requis.",
"captcha.invalid": "Échec de la vérification. Réessayez.",
"captcha.required": "Vérification requise. Réessayez.",
"channels_and_guilds.afk_channel_must_be_in_guild": "Le salon AFK doit être dans cette communauté.",
"channels_and_guilds.afk_channel_must_be_voice": "Le salon AFK doit être un salon vocal.",
"channels_and_guilds.all_channels_must_belong_to_guild": "Tous les salons doivent appartenir à cette communauté.",
@@ -136,8 +136,8 @@
"calls_and_voice.voice_message_duration_required": "קבצים מצורפים של הודעות קוליות חייבים לציין משך זמן.",
"calls_and_voice.voice_message_waveform_required": "קבצים מצורפים של הודעות קוליות חייבים לכלול נתוני צורת גל.",
"calls_and_voice.voice_messages_require_single_attachment": "הודעות קוליות חייבות לכלול קובץ מצורף אחד בלבד.",
"captcha.invalid": "קאפצ'ה שגויה.",
"captcha.required": "נדרשת קאפצ'ה.",
"captcha.invalid": "האימות נכשל. ניתן לנסות שוב.",
"captcha.required": "נדרש אימות. ניתן לנסות שוב.",
"channels_and_guilds.afk_channel_must_be_in_guild": "ערוץ AFK חייב להיות בקהילה זו.",
"channels_and_guilds.afk_channel_must_be_voice": "ערוץ AFK חייב להיות ערוץ קולי.",
"channels_and_guilds.all_channels_must_belong_to_guild": "כל הערוצים חייבים להשתייך לקהילה זו.",
@@ -136,8 +136,8 @@
"calls_and_voice.voice_message_duration_required": "वॉइस मैसेज अटैचमेंट में ड्यूरेशन बतानी होगी।",
"calls_and_voice.voice_message_waveform_required": "वॉइस मैसेज अटैचमेंट में वेवफ़ॉर्म डेटा होना चाहिए।",
"calls_and_voice.voice_messages_require_single_attachment": "वॉइस मैसेज में केवल एक अटैचमेंट होना चाहिए।",
"captcha.invalid": "अमान्य कैप्चा।",
"captcha.required": "कैप्चा ज़रूरी है।",
"captcha.invalid": "सत्यापन विफल रहा। फिर से कोशिश करें।",
"captcha.required": "सत्यापन आवश्यक है। फिर से कोशिश करें।",
"channels_and_guilds.afk_channel_must_be_in_guild": "AFK चैनल इस कम्युनिटी में होना चाहिए।",
"channels_and_guilds.afk_channel_must_be_voice": "AFK चैनल एक वॉइस चैनल होना चाहिए।",
"channels_and_guilds.all_channels_must_belong_to_guild": "सभी चैनल इस कम्युनिटी के होने चाहिए।",
@@ -136,8 +136,8 @@
"calls_and_voice.voice_message_duration_required": "Privici glasovne poruke moraju imati navedeno trajanje.",
"calls_and_voice.voice_message_waveform_required": "Privici glasovne poruke moraju sadržavati podatke o valnom obliku.",
"calls_and_voice.voice_messages_require_single_attachment": "Glasovne poruke moraju sadržavati točno jedan privitak.",
"captcha.invalid": "Nevažeća captcha.",
"captcha.required": "Captcha je obavezna.",
"captcha.invalid": "Provjera nije uspjela. Pokušaj ponovno.",
"captcha.required": "Potrebna je provjera. Pokušaj ponovno.",
"channels_and_guilds.afk_channel_must_be_in_guild": "AFK kanal mora biti u ovoj zajednici.",
"channels_and_guilds.afk_channel_must_be_voice": "AFK kanal mora biti glasovni kanal.",
"channels_and_guilds.all_channels_must_belong_to_guild": "Svi kanali moraju pripadati ovoj zajednici.",
@@ -136,8 +136,8 @@
"calls_and_voice.voice_message_duration_required": "A hangüzenet mellékleteihez meg kell adni az időtartamot.",
"calls_and_voice.voice_message_waveform_required": "A hangüzenet mellékleteinek hullámformaadatokat kell tartalmazniuk.",
"calls_and_voice.voice_messages_require_single_attachment": "A hangüzeneteknek pontosan egy mellékletet kell tartalmazniuk.",
"captcha.invalid": "Érvénytelen captcha kód.",
"captcha.required": "A captcha kód szükséges.",
"captcha.invalid": "Ellenőrzés sikertelen. Próbáld újra.",
"captcha.required": "Ellenőrzés szükséges. Próbáld újra.",
"channels_and_guilds.afk_channel_must_be_in_guild": "Az AFK-csatornának ebben a közösségben kell lennie.",
"channels_and_guilds.afk_channel_must_be_voice": "Az AFK-csatornának hangcsatornának kell lennie.",
"channels_and_guilds.all_channels_must_belong_to_guild": "Minden csatornának ehhez a közösséghez kell tartoznia.",
@@ -136,8 +136,8 @@
"calls_and_voice.voice_message_duration_required": "Lampiran pesan suara harus menentukan durasi.",
"calls_and_voice.voice_message_waveform_required": "Lampiran pesan suara harus menyertakan data bentuk gelombang.",
"calls_and_voice.voice_messages_require_single_attachment": "Pesan suara harus berisi tepat satu lampiran.",
"captcha.invalid": "Captcha tidak valid.",
"captcha.required": "Captcha diperlukan.",
"captcha.invalid": "Verifikasi gagal. Coba lagi.",
"captcha.required": "Verifikasi diperlukan. Coba lagi.",
"channels_and_guilds.afk_channel_must_be_in_guild": "Saluran AFK harus ada di komunitas ini.",
"channels_and_guilds.afk_channel_must_be_voice": "Saluran AFK harus berupa saluran suara.",
"channels_and_guilds.all_channels_must_belong_to_guild": "Semua saluran harus ada di komunitas ini.",
@@ -136,8 +136,8 @@
"calls_and_voice.voice_message_duration_required": "Gli allegati dei messaggi vocali devono specificare una durata.",
"calls_and_voice.voice_message_waveform_required": "Gli allegati dei messaggi vocali devono includere i dati della forma d'onda.",
"calls_and_voice.voice_messages_require_single_attachment": "I messaggi vocali devono contenere esattamente un allegato.",
"captcha.invalid": "Captcha non valido.",
"captcha.required": "Il Captcha è richiesto.",
"captcha.invalid": "Verifica non riuscita. Riprova.",
"captcha.required": "Verifica richiesta. Riprova.",
"channels_and_guilds.afk_channel_must_be_in_guild": "Il canale AFK deve appartenere a questa Community.",
"channels_and_guilds.afk_channel_must_be_voice": "Il canale AFK deve essere un canale vocale.",
"channels_and_guilds.all_channels_must_belong_to_guild": "Tutti i canali devono appartenere a questa Community.",
@@ -136,8 +136,8 @@
"calls_and_voice.voice_message_duration_required": "ボイスメッセージの添付ファイルには長さを指定する必要があります。",
"calls_and_voice.voice_message_waveform_required": "ボイスメッセージの添付ファイルには波形データを含める必要があります。",
"calls_and_voice.voice_messages_require_single_attachment": "ボイスメッセージには添付ファイルが1つだけ必要です。",
"captcha.invalid": "CAPTCHAが無効です。",
"captcha.required": "CAPTCHAが必要です。",
"captcha.invalid": "確認に失敗しました。もう一度お試しください。",
"captcha.required": "確認が必要です。もう一度お試しください。",
"channels_and_guilds.afk_channel_must_be_in_guild": "AFKチャンネルはこのコミュニティ内に設定する必要があります。",
"channels_and_guilds.afk_channel_must_be_voice": "AFKチャンネルはボイスチャンネルである必要があります。",
"channels_and_guilds.all_channels_must_belong_to_guild": "すべてのチャンネルはこのコミュニティに属している必要があります。",
@@ -136,8 +136,8 @@
"calls_and_voice.voice_message_duration_required": "음성 메시지 첨부 파일은 길이를 지정해야 해요.",
"calls_and_voice.voice_message_waveform_required": "음성 메시지 첨부 파일에는 파형 데이터가 포함되어야 해요.",
"calls_and_voice.voice_messages_require_single_attachment": "음성 메시지에는 첨부 파일이 하나만 포함되어야 해요.",
"captcha.invalid": "유효하지 않은 캡차예요.",
"captcha.required": "캡차가 필요해요.",
"captcha.invalid": "확인하지 못했어요. 다시 시도해 주세요.",
"captcha.required": "확인이 필요해요. 다시 시도해 주세요.",
"channels_and_guilds.afk_channel_must_be_in_guild": "자리 비움 채널은 이 커뮤니티에 있어야 해요.",
"channels_and_guilds.afk_channel_must_be_voice": "자리 비움 채널은 음성 채널이어야 해요.",
"channels_and_guilds.all_channels_must_belong_to_guild": "모든 채널은 이 커뮤니티에 속해야 해요.",
@@ -136,8 +136,8 @@
"calls_and_voice.voice_message_duration_required": "Balso žinutės priedai turi nurodyti trukmę.",
"calls_and_voice.voice_message_waveform_required": "Balso žinutės prieduose turi būti bangos formos duomenys.",
"calls_and_voice.voice_messages_require_single_attachment": "Balso žinutėse turi būti lygiai vienas priedas.",
"captcha.invalid": "Neteisinga captcha.",
"captcha.required": "Reikalinga captcha.",
"captcha.invalid": "Nepavyko patvirtinti. Bandyk dar kartą.",
"captcha.required": "Reikalingas patvirtinimas. Bandyk dar kartą.",
"channels_and_guilds.afk_channel_must_be_in_guild": "AFK kanalas turi būti šioje bendruomenėje.",
"channels_and_guilds.afk_channel_must_be_voice": "AFK kanalas turi būti balso kanalas.",
"channels_and_guilds.all_channels_must_belong_to_guild": "Visi kanalai turi būti šioje bendruomenėje.",
@@ -136,8 +136,8 @@
"calls_and_voice.voice_message_duration_required": "Bij spraakberichtbijlagen moet de duur worden opgegeven.",
"calls_and_voice.voice_message_waveform_required": "Spraakberichtbijlagen moeten golfvormgegevens bevatten.",
"calls_and_voice.voice_messages_require_single_attachment": "Spraakberichten moeten precies één bijlage bevatten.",
"captcha.invalid": "Ongeldige CAPTCHA.",
"captcha.required": "CAPTCHA is vereist.",
"captcha.invalid": "Verificatie mislukt. Probeer het opnieuw.",
"captcha.required": "Verificatie vereist. Probeer het opnieuw.",
"channels_and_guilds.afk_channel_must_be_in_guild": "Het AFK-kanaal moet deel uitmaken van deze community.",
"channels_and_guilds.afk_channel_must_be_voice": "Het AFK-kanaal moet een spraakkanaal zijn.",
"channels_and_guilds.all_channels_must_belong_to_guild": "Alle kanalen moeten deel uitmaken van deze community.",
@@ -136,8 +136,8 @@
"calls_and_voice.voice_message_duration_required": "Vedlegg til talemeldinger må angi en varighet.",
"calls_and_voice.voice_message_waveform_required": "Vedlegg til talemeldinger må inneholde bølgeformdata.",
"calls_and_voice.voice_messages_require_single_attachment": "Talemeldinger må inneholde nøyaktig ett vedlegg.",
"captcha.invalid": "Ugyldig captcha.",
"captcha.required": "Captcha er påkrevd.",
"captcha.invalid": "Verifisering mislyktes. Prøv igjen.",
"captcha.required": "Verifisering kreves. Prøv igjen.",
"channels_and_guilds.afk_channel_must_be_in_guild": "AFK-kanalen må være i dette fellesskapet.",
"channels_and_guilds.afk_channel_must_be_voice": "AFK-kanalen må være en talekanal.",
"channels_and_guilds.all_channels_must_belong_to_guild": "Alle kanaler må tilhøre dette fellesskapet.",
@@ -136,8 +136,8 @@
"calls_and_voice.voice_message_duration_required": "Załączniki wiadomości głosowych muszą określać czas trwania.",
"calls_and_voice.voice_message_waveform_required": "Załączniki wiadomości głosowych muszą zawierać dane przebiegu fali dźwiękowej.",
"calls_and_voice.voice_messages_require_single_attachment": "Wiadomości głosowe muszą zawierać dokładnie jeden załącznik.",
"captcha.invalid": "Nieprawidłowa captcha.",
"captcha.required": "Captcha jest wymagana.",
"captcha.invalid": "Weryfikacja nie powiodła się. Spróbuj ponownie.",
"captcha.required": "Weryfikacja wymagana. Spróbuj ponownie.",
"channels_and_guilds.afk_channel_must_be_in_guild": "Kanał AFK musi znajdować się w tej społeczności.",
"channels_and_guilds.afk_channel_must_be_voice": "Kanał AFK musi być kanałem głosowym.",
"channels_and_guilds.all_channels_must_belong_to_guild": "Wszystkie kanały muszą znajdować się w tej społeczności.",
@@ -136,8 +136,8 @@
"calls_and_voice.voice_message_duration_required": "Os anexos de mensagem de voz devem especificar uma duração.",
"calls_and_voice.voice_message_waveform_required": "Os anexos de mensagem de voz devem incluir dados da forma de onda.",
"calls_and_voice.voice_messages_require_single_attachment": "As mensagens de voz devem conter exatamente um anexo.",
"captcha.invalid": "CAPTCHA inválido.",
"captcha.required": "CAPTCHA é obrigatório.",
"captcha.invalid": "Falha na verificação. Tente novamente.",
"captcha.required": "Verificação necessária. Tente novamente.",
"channels_and_guilds.afk_channel_must_be_in_guild": "O canal AFK deve estar nesta comunidade.",
"channels_and_guilds.afk_channel_must_be_voice": "O canal AFK deve ser um canal de voz.",
"channels_and_guilds.all_channels_must_belong_to_guild": "Todos os canais devem pertencer a esta comunidade.",
@@ -136,8 +136,8 @@
"calls_and_voice.voice_message_duration_required": "Atașamentele mesajelor vocale trebuie să specifice o durată.",
"calls_and_voice.voice_message_waveform_required": "Atașamentele mesajelor vocale trebuie să includă datele formei de undă.",
"calls_and_voice.voice_messages_require_single_attachment": "Mesajele vocale trebuie să conțină exact un atașament.",
"captcha.invalid": "Captcha invalid.",
"captcha.required": "Captcha este necesar.",
"captcha.invalid": "Verificare eșuată. Încearcă din nou.",
"captcha.required": "Verificare necesară. Încearcă din nou.",
"channels_and_guilds.afk_channel_must_be_in_guild": "Canalul AFK trebuie să fie în această comunitate.",
"channels_and_guilds.afk_channel_must_be_voice": "Canalul AFK trebuie să fie un canal vocal.",
"channels_and_guilds.all_channels_must_belong_to_guild": "Toate canalele trebuie să facă parte din această comunitate.",
@@ -136,8 +136,8 @@
"calls_and_voice.voice_message_duration_required": "У вложений голосовых сообщений должна быть указана длительность.",
"calls_and_voice.voice_message_waveform_required": "Вложения голосовых сообщений должны содержать данные формы волны.",
"calls_and_voice.voice_messages_require_single_attachment": "Голосовые сообщения должны содержать ровно одно вложение.",
"captcha.invalid": "Капча недействительна.",
"captcha.required": "Требуется капча.",
"captcha.invalid": "Проверка не удалась. Повторите попытку.",
"captcha.required": "Требуется проверка. Повторите попытку.",
"channels_and_guilds.afk_channel_must_be_in_guild": "Канал AFK должен быть в этом сообществе.",
"channels_and_guilds.afk_channel_must_be_voice": "Канал AFK должен быть голосовым.",
"channels_and_guilds.all_channels_must_belong_to_guild": "Все каналы должны принадлежать этому сообществу.",
@@ -136,8 +136,8 @@
"calls_and_voice.voice_message_duration_required": "Bilagor till röstmeddelanden måste ha en angiven speltid.",
"calls_and_voice.voice_message_waveform_required": "Bilagor till röstmeddelanden måste innehålla vågformsdata.",
"calls_and_voice.voice_messages_require_single_attachment": "Röstmeddelanden måste innehålla exakt en bilaga.",
"captcha.invalid": "Ogiltig captcha.",
"captcha.required": "Captcha krävs.",
"captcha.invalid": "Verifieringen misslyckades. Försök igen.",
"captcha.required": "Verifiering krävs. Försök igen.",
"channels_and_guilds.afk_channel_must_be_in_guild": "AFK-kanalen måste tillhöra denna community.",
"channels_and_guilds.afk_channel_must_be_voice": "AFK-kanalen måste vara en röstkanal.",
"channels_and_guilds.all_channels_must_belong_to_guild": "Alla kanaler måste tillhöra denna community.",
@@ -136,8 +136,8 @@
"calls_and_voice.voice_message_duration_required": "ไฟล์แนบข้อความเสียงต้องกำหนดระยะเวลา",
"calls_and_voice.voice_message_waveform_required": "ไฟล์แนบข้อความเสียงต้องแนบข้อมูลรูปคลื่น",
"calls_and_voice.voice_messages_require_single_attachment": "ข้อความเสียงต้องมีไฟล์แนบเพียงไฟล์เดียวเท่านั้น",
"captcha.invalid": "แคปต์ชาไม่ถูกต้อง",
"captcha.required": "ต้องใส่แคปต์ชา",
"captcha.invalid": "การยืนยันล้มเหลว ลองอีกครั้ง",
"captcha.required": "ต้องมีการยืนยัน ลองอีกครั้ง",
"channels_and_guilds.afk_channel_must_be_in_guild": "ช่อง AFK ต้องอยู่ในคอมมูนิตี้นี้",
"channels_and_guilds.afk_channel_must_be_voice": "ช่อง AFK ต้องเป็นช่องเสียง",
"channels_and_guilds.all_channels_must_belong_to_guild": "ทุกช่องต้องอยู่ในคอมมูนิตี้นี้เท่านั้น",
@@ -136,8 +136,8 @@
"calls_and_voice.voice_message_duration_required": "Sesli mesaj ekleri bir süre belirtmeli.",
"calls_and_voice.voice_message_waveform_required": "Sesli mesaj ekleri dalga biçimi verilerini içermeli.",
"calls_and_voice.voice_messages_require_single_attachment": "Sesli mesajlar tam olarak bir ek içermeli.",
"captcha.invalid": "Geçersiz captcha.",
"captcha.required": "Captcha gerekli.",
"captcha.invalid": "Doğrulama başarısız oldu. Tekrar dene.",
"captcha.required": "Doğrulama gerekli. Tekrar dene.",
"channels_and_guilds.afk_channel_must_be_in_guild": "AFK kanalı bu toplulukta olmalı.",
"channels_and_guilds.afk_channel_must_be_voice": "AFK kanalı bir sesli kanal olmalı.",
"channels_and_guilds.all_channels_must_belong_to_guild": "Tüm kanallar bu toplulukta olmalı.",
@@ -136,8 +136,8 @@
"calls_and_voice.voice_message_duration_required": "У вкладеннях голосових повідомлень має бути вказана тривалість.",
"calls_and_voice.voice_message_waveform_required": "Вкладення голосових повідомлень мають містити дані форми хвилі.",
"calls_and_voice.voice_messages_require_single_attachment": "Голосові повідомлення мають містити рівно одне вкладення.",
"captcha.invalid": "Недійсна капча.",
"captcha.required": "Капча обов'язкова.",
"captcha.invalid": "Перевірку не пройдено. Спробуй ще раз.",
"captcha.required": "Потрібна перевірка. Спробуй ще раз.",
"channels_and_guilds.afk_channel_must_be_in_guild": "AFK-канал має належати цій спільноті.",
"channels_and_guilds.afk_channel_must_be_voice": "AFK-канал має бути голосовим каналом.",
"channels_and_guilds.all_channels_must_belong_to_guild": "Усі канали мають належати цій спільноті.",
@@ -136,8 +136,8 @@
"calls_and_voice.voice_message_duration_required": "Tệp đính kèm tin nhắn thoại phải có thời lượng.",
"calls_and_voice.voice_message_waveform_required": "Tệp đính kèm tin nhắn thoại phải bao gồm dữ liệu dạng sóng.",
"calls_and_voice.voice_messages_require_single_attachment": "Tin nhắn thoại phải chứa chính xác một tệp đính kèm.",
"captcha.invalid": "Captcha không hợp lệ.",
"captcha.required": "Captcha là bắt buộc.",
"captcha.invalid": "Xác minh thất bại. Hãy thử lại.",
"captcha.required": "Cần xác minh. Hãy thử lại.",
"channels_and_guilds.afk_channel_must_be_in_guild": "Kênh AFK phải thuộc cộng đồng này.",
"channels_and_guilds.afk_channel_must_be_voice": "Kênh AFK phải là kênh thoại.",
"channels_and_guilds.all_channels_must_belong_to_guild": "Tất cả các kênh phải thuộc cộng đồng này.",
@@ -136,8 +136,8 @@
"calls_and_voice.voice_message_duration_required": "语音消息附件必须指定时长。",
"calls_and_voice.voice_message_waveform_required": "语音消息附件必须包含波形数据。",
"calls_and_voice.voice_messages_require_single_attachment": "语音消息必须仅包含一个附件。",
"captcha.invalid": "无效验证码。",
"captcha.required": "需要验证码。",
"captcha.invalid": "验证失败。请重试。",
"captcha.required": "需要验证。请重试。",
"channels_and_guilds.afk_channel_must_be_in_guild": "AFK 频道必须属于此社区。",
"channels_and_guilds.afk_channel_must_be_voice": "AFK 频道必须是语音频道。",
"channels_and_guilds.all_channels_must_belong_to_guild": "所有频道必须属于此社区。",
@@ -136,8 +136,8 @@
"calls_and_voice.voice_message_duration_required": "語音訊息附件必須設定持續時間。",
"calls_and_voice.voice_message_waveform_required": "語音訊息附件必須包含波形資料。",
"calls_and_voice.voice_messages_require_single_attachment": "語音訊息必須只包含一個附件。",
"captcha.invalid": "無效的驗證碼。",
"captcha.required": "需要驗證碼。",
"captcha.invalid": "驗證失敗。請再試一次。",
"captcha.required": "需要驗證。請再試一次。",
"channels_and_guilds.afk_channel_must_be_in_guild": "AFK 頻道必須屬於此社群。",
"channels_and_guilds.afk_channel_must_be_voice": "AFK 頻道必須為語音頻道。",
"channels_and_guilds.all_channels_must_belong_to_guild": "所有頻道都必須屬於此社群。",
@@ -136,8 +136,8 @@
"calls_and_voice.voice_message_duration_required": "Voice message attachments must specify a duration.",
"calls_and_voice.voice_message_waveform_required": "Voice message attachments must include waveform data.",
"calls_and_voice.voice_messages_require_single_attachment": "Voice messages must contain exactly one attachment.",
"captcha.invalid": "Invalid captcha.",
"captcha.required": "Captcha is required.",
"captcha.invalid": "Verification failed. Try again.",
"captcha.required": "Verification required. Try again.",
"channels_and_guilds.afk_channel_must_be_in_guild": "AFK channel must be in this community.",
"channels_and_guilds.afk_channel_must_be_voice": "AFK channel must be a voice channel.",
"channels_and_guilds.all_channels_must_belong_to_guild": "All channels must belong to this community.",
@@ -11,10 +11,7 @@ import {ADMIN_ACL_COUNT, AdminAclType} from '@fluxer/schema/src/domains/admin/Ad
import {AdminArchiveResponseSchema} from '@fluxer/schema/src/domains/admin/AdminArchiveSchemas';
import {GuildAdminResponse} from '@fluxer/schema/src/domains/admin/AdminGuildSchemas';
import {UserAdminResponseSchema} from '@fluxer/schema/src/domains/admin/AdminUserSchemas';
import {
AltchaCaptchaConfigResponse,
AltchaCaptchaConfigUpdateRequest,
} from '@fluxer/schema/src/domains/admin/AltchaCaptchaSchemas';
import {CaptchaConfigResponse, CaptchaConfigUpdateRequest} from '@fluxer/schema/src/domains/admin/CaptchaSchemas';
import {
DomainMigrationConfigResponse,
DomainMigrationConfigUpdateRequest,
@@ -33,10 +30,7 @@ import {
ExperimentDeliveryConfigUpdateRequest,
} from '@fluxer/schema/src/domains/experiment/ExperimentSchemas';
import {GuildMemberResponse} from '@fluxer/schema/src/domains/guild/GuildMemberSchemas';
import {
InstanceCaptchaProviderSchema,
InstanceRegistrationModeSchema,
} from '@fluxer/schema/src/domains/instance/InstanceSchemas';
import {InstanceRegistrationModeSchema} from '@fluxer/schema/src/domains/instance/InstanceSchemas';
import {MessageResponseSchema} from '@fluxer/schema/src/domains/message/MessageResponseSchemas';
import {GiftCodeDurationTypeSchema} from '@fluxer/schema/src/domains/premium/GiftCodeSchemas';
import {ChannelTypeSchema} from '@fluxer/schema/src/primitives/ChannelValidators';
@@ -608,15 +602,6 @@ const InstanceIntegrationsResponse = z.object({
api_key_set: z.boolean(),
effective_available: z.boolean(),
}),
captcha: z.object({
provider: InstanceCaptchaProviderSchema.nullable(),
effective_provider: InstanceCaptchaProviderSchema,
hcaptcha_site_key: z.string().nullable(),
hcaptcha_secret_key_set: z.boolean(),
turnstile_site_key: z.string().nullable(),
turnstile_secret_key_set: z.boolean(),
effective_enabled: z.boolean(),
}),
email: z.object({
enabled: z.boolean().nullable(),
effective_enabled: z.boolean(),
@@ -651,7 +636,7 @@ export const InstanceConfigResponse = z.object({
gateway_rollout: GatewayRolloutConfigResponse,
push_relay: PushRelayConfigResponse,
domain_migration: DomainMigrationConfigResponse,
altcha_captcha: AltchaCaptchaConfigResponse,
captcha: CaptchaConfigResponse,
experiment_delivery: ExperimentDeliveryConfigResponse,
registration: InstanceRegistrationResponse,
self_hosted: z.boolean(),
@@ -683,7 +668,7 @@ export const InstanceConfigUpdateRequest = z.object({
gateway_rollout: GatewayRolloutConfigUpdateRequest.nullish(),
push_relay: PushRelayConfigUpdateRequest.nullish(),
domain_migration: DomainMigrationConfigUpdateRequest.nullish(),
altcha_captcha: AltchaCaptchaConfigUpdateRequest.nullish(),
captcha: CaptchaConfigUpdateRequest.nullish(),
experiment_delivery: ExperimentDeliveryConfigUpdateRequest.nullish(),
registration: z
.object({
@@ -721,15 +706,6 @@ export const InstanceConfigUpdateRequest = z.object({
api_key: z.string().trim().max(4096).nullish(),
})
.nullish(),
captcha: z
.object({
provider: InstanceCaptchaProviderSchema.nullish(),
hcaptcha_site_key: z.string().trim().max(4096).nullish(),
hcaptcha_secret_key: z.string().trim().max(4096).nullish(),
turnstile_site_key: z.string().trim().max(4096).nullish(),
turnstile_secret_key: z.string().trim().max(4096).nullish(),
})
.nullish(),
email: z
.object({
enabled: z.boolean().nullish(),
@@ -1,145 +0,0 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import {
type AltchaCaptchaConfig,
AltchaCaptchaConfigSchema,
AltchaCaptchaConfigUpdateRequest,
altchaCaptchaAppliesTo,
DEFAULT_ALTCHA_CAPTCHA_CONFIG,
resolveAltchaCaptchaAssignment,
} from '@fluxer/schema/src/domains/admin/AltchaCaptchaSchemas';
import {type ExperimentTargeting, experimentBucket} from '@fluxer/schema/src/domains/experiment/ExperimentBucket';
import {describe, expect, test} from 'vitest';
const NO_TARGETING: ExperimentTargeting = {memberGuildIds: new Set(), premium: false};
const TARGETED_USER_ID = '1000000000000000001';
function createConfig(overrides: Partial<AltchaCaptchaConfig> = {}): AltchaCaptchaConfig {
return {...DEFAULT_ALTCHA_CAPTCHA_CONFIG, included_user_ids: [], excluded_user_ids: [], ...overrides};
}
function syntheticUserIds(count: number): Array<string> {
return Array.from({length: count}, (_, index) => (1400000000000000000n + BigInt(index)).toString());
}
describe('altcha captcha configuration', () => {
test('defaults to disabled with no anonymous traffic', () => {
expect(AltchaCaptchaConfigSchema.parse({})).toEqual({
enabled: false,
config_version: 0,
rollout_basis_points: 0,
rollout_salt: 'altcha-captcha-v1',
included_user_ids: [],
excluded_user_ids: [],
included_guild_ids: [],
include_premium_users: false,
anonymous_enabled: false,
cost: 5000,
max_counter: 10000,
});
});
test('rejects difficulty outside the supported range', () => {
expect(AltchaCaptchaConfigUpdateRequest.safeParse({cost: 999}).success).toBe(false);
expect(AltchaCaptchaConfigUpdateRequest.safeParse({cost: 100001}).success).toBe(false);
expect(AltchaCaptchaConfigUpdateRequest.safeParse({max_counter: 99}).success).toBe(false);
expect(AltchaCaptchaConfigUpdateRequest.safeParse({max_counter: 1000001}).success).toBe(false);
expect(AltchaCaptchaConfigUpdateRequest.safeParse({config_version: 3}).data).toEqual({});
});
});
describe('resolveAltchaCaptchaAssignment', () => {
test('serves nobody while disabled, even included users', () => {
const config = createConfig({rollout_basis_points: 10000, included_user_ids: [TARGETED_USER_ID]});
expect(resolveAltchaCaptchaAssignment(config, TARGETED_USER_ID, NO_TARGETING)).toEqual({enabled: false});
});
test('applies exclusions before inclusions', () => {
const config = createConfig({
enabled: true,
included_user_ids: [TARGETED_USER_ID],
excluded_user_ids: [TARGETED_USER_ID],
});
expect(resolveAltchaCaptchaAssignment(config, TARGETED_USER_ID, NO_TARGETING)).toEqual({enabled: false});
});
test('serves included users at zero rollout', () => {
const config = createConfig({enabled: true, included_user_ids: [TARGETED_USER_ID]});
expect(resolveAltchaCaptchaAssignment(config, TARGETED_USER_ID, NO_TARGETING)).toEqual({enabled: true});
});
test('buckets the rollout by salt and user id', () => {
const config = createConfig({enabled: true, rollout_basis_points: 2500});
for (const userId of syntheticUserIds(200)) {
expect(resolveAltchaCaptchaAssignment(config, userId, NO_TARGETING).enabled).toBe(
experimentBucket(userId, config.rollout_salt) < 2500,
);
}
});
});
describe('altchaCaptchaAppliesTo', () => {
test('serves anonymous requests only when anonymous_enabled is set', () => {
expect(altchaCaptchaAppliesTo(createConfig({enabled: true}), null, NO_TARGETING)).toBe(false);
expect(altchaCaptchaAppliesTo(createConfig({enabled: true, anonymous_enabled: true}), null, NO_TARGETING)).toBe(
true,
);
expect(altchaCaptchaAppliesTo(createConfig({anonymous_enabled: true}), null, NO_TARGETING)).toBe(false);
});
test('keeps signed-in users on their own bucket regardless of the anonymous switch', () => {
const config = createConfig({enabled: true, anonymous_enabled: true, excluded_user_ids: [TARGETED_USER_ID]});
expect(altchaCaptchaAppliesTo(config, TARGETED_USER_ID, NO_TARGETING)).toBe(false);
});
});
describe('resolveAltchaCaptchaAssignment guild targeting', () => {
const INCLUDED_GUILD_ID = '3000000000000000001';
const MEMBER_GUILDS: ExperimentTargeting = {
memberGuildIds: new Set(['3000000000000000009', INCLUDED_GUILD_ID]),
premium: false,
};
test('serves members of an included guild at zero rollout', () => {
const config = createConfig({enabled: true, included_guild_ids: [INCLUDED_GUILD_ID]});
expect(resolveAltchaCaptchaAssignment(config, TARGETED_USER_ID, MEMBER_GUILDS)).toEqual({enabled: true});
expect(resolveAltchaCaptchaAssignment(config, TARGETED_USER_ID, NO_TARGETING)).toEqual({enabled: false});
});
test('keeps user exclusions ahead of guild membership', () => {
const config = createConfig({
enabled: true,
included_guild_ids: [INCLUDED_GUILD_ID],
excluded_user_ids: [TARGETED_USER_ID],
});
expect(resolveAltchaCaptchaAssignment(config, TARGETED_USER_ID, MEMBER_GUILDS)).toEqual({enabled: false});
});
test('serves no guild members while disabled', () => {
const config = createConfig({included_guild_ids: [INCLUDED_GUILD_ID]});
expect(resolveAltchaCaptchaAssignment(config, TARGETED_USER_ID, MEMBER_GUILDS)).toEqual({enabled: false});
});
});
describe('resolveAltchaCaptchaAssignment premium targeting', () => {
const PREMIUM: ExperimentTargeting = {memberGuildIds: new Set(), premium: true};
test('serves premium users only when the switch is on', () => {
const on = createConfig({enabled: true, include_premium_users: true});
expect(resolveAltchaCaptchaAssignment(on, TARGETED_USER_ID, PREMIUM)).toEqual({enabled: true});
expect(resolveAltchaCaptchaAssignment(on, TARGETED_USER_ID, NO_TARGETING)).toEqual({enabled: false});
expect(resolveAltchaCaptchaAssignment(createConfig({enabled: true}), TARGETED_USER_ID, PREMIUM)).toEqual({
enabled: false,
});
});
test('keeps user exclusions ahead of the premium switch', () => {
const config = createConfig({
enabled: true,
include_premium_users: true,
excluded_user_ids: [TARGETED_USER_ID],
});
expect(resolveAltchaCaptchaAssignment(config, TARGETED_USER_ID, PREMIUM)).toEqual({enabled: false});
});
});
@@ -1,97 +0,0 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import {
EXPERIMENT_BUCKET_RESOLUTION,
type ExperimentTargeting,
experimentAudienceIncludes,
experimentBucket,
} from '@fluxer/schema/src/domains/experiment/ExperimentBucket';
import {z} from 'zod';
const ALTCHA_CAPTCHA_ROLLOUT_BASIS_POINTS_MAX = EXPERIMENT_BUCKET_RESOLUTION;
const ALTCHA_CAPTCHA_MAX_TARGETED_USERS = 1000;
const DEFAULT_ALTCHA_CAPTCHA_SALT = 'altcha-captcha-v1';
export const ALTCHA_CAPTCHA_MIN_COST = 1000;
export const ALTCHA_CAPTCHA_MAX_COST = 100000;
export const ALTCHA_CAPTCHA_MIN_MAX_COUNTER = 100;
export const ALTCHA_CAPTCHA_MAX_MAX_COUNTER = 1000000;
const ALTCHA_CAPTCHA_SALT_PATTERN = /^[\x20-\x7e]+$/u;
const AltchaCaptchaTargetIdSchema = z.string().regex(/^\d{1,20}$/u);
const AltchaCaptchaTargetedUserIdsSchema = z.array(AltchaCaptchaTargetIdSchema).max(ALTCHA_CAPTCHA_MAX_TARGETED_USERS);
const altchaCaptchaConfigFields = {
enabled: z.boolean(),
config_version: z.number().int().min(0),
rollout_basis_points: z.number().int().min(0).max(ALTCHA_CAPTCHA_ROLLOUT_BASIS_POINTS_MAX),
rollout_salt: z.string().trim().min(1).max(64).regex(ALTCHA_CAPTCHA_SALT_PATTERN),
included_user_ids: AltchaCaptchaTargetedUserIdsSchema,
included_guild_ids: AltchaCaptchaTargetedUserIdsSchema,
include_premium_users: z.boolean(),
excluded_user_ids: AltchaCaptchaTargetedUserIdsSchema,
anonymous_enabled: z.boolean(),
cost: z.number().int().min(ALTCHA_CAPTCHA_MIN_COST).max(ALTCHA_CAPTCHA_MAX_COST),
max_counter: z.number().int().min(ALTCHA_CAPTCHA_MIN_MAX_COUNTER).max(ALTCHA_CAPTCHA_MAX_MAX_COUNTER),
};
export const AltchaCaptchaConfigSchema = z.object({
enabled: altchaCaptchaConfigFields.enabled.default(false),
config_version: altchaCaptchaConfigFields.config_version.default(0),
rollout_basis_points: altchaCaptchaConfigFields.rollout_basis_points.default(0),
rollout_salt: altchaCaptchaConfigFields.rollout_salt.default(DEFAULT_ALTCHA_CAPTCHA_SALT),
included_user_ids: altchaCaptchaConfigFields.included_user_ids.default([]),
included_guild_ids: altchaCaptchaConfigFields.included_guild_ids.default([]),
include_premium_users: altchaCaptchaConfigFields.include_premium_users.default(false),
excluded_user_ids: altchaCaptchaConfigFields.excluded_user_ids.default([]),
anonymous_enabled: altchaCaptchaConfigFields.anonymous_enabled.default(false),
cost: altchaCaptchaConfigFields.cost.default(5000),
max_counter: altchaCaptchaConfigFields.max_counter.default(10000),
});
export type AltchaCaptchaConfig = z.infer<typeof AltchaCaptchaConfigSchema>;
export const DEFAULT_ALTCHA_CAPTCHA_CONFIG: AltchaCaptchaConfig = AltchaCaptchaConfigSchema.parse({});
export const AltchaCaptchaConfigUpdateRequest = z
.object(altchaCaptchaConfigFields)
.omit({config_version: true})
.partial();
export type AltchaCaptchaConfigUpdateRequest = z.infer<typeof AltchaCaptchaConfigUpdateRequest>;
export const AltchaCaptchaConfigResponse = AltchaCaptchaConfigSchema;
export type AltchaCaptchaConfigResponse = z.infer<typeof AltchaCaptchaConfigResponse>;
export const AltchaCaptchaAssignmentResponse = z.object({
enabled: altchaCaptchaConfigFields.enabled,
});
export type AltchaCaptchaAssignmentResponse = z.infer<typeof AltchaCaptchaAssignmentResponse>;
export const INERT_ALTCHA_CAPTCHA_ASSIGNMENT: AltchaCaptchaAssignmentResponse = {
enabled: false,
};
export function resolveAltchaCaptchaAssignment(
config: AltchaCaptchaConfig,
userId: string,
targeting: ExperimentTargeting,
): AltchaCaptchaAssignmentResponse {
if (!config.enabled) return {...INERT_ALTCHA_CAPTCHA_ASSIGNMENT};
if (config.excluded_user_ids.includes(userId)) return {...INERT_ALTCHA_CAPTCHA_ASSIGNMENT};
if (config.included_user_ids.includes(userId)) return {enabled: true};
if (experimentAudienceIncludes(config, targeting)) return {enabled: true};
return {enabled: experimentBucket(userId, config.rollout_salt) < config.rollout_basis_points};
}
export function altchaCaptchaAppliesTo(
config: AltchaCaptchaConfig,
userId: string | null,
targeting: ExperimentTargeting,
): boolean {
if (userId === null) return config.enabled && config.anonymous_enabled;
return resolveAltchaCaptchaAssignment(config, userId, targeting).enabled;
}
@@ -0,0 +1,29 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import {
CaptchaConfigSchema,
CaptchaConfigUpdateRequest,
DEFAULT_CAPTCHA_CONFIG,
} from '@fluxer/schema/src/domains/admin/CaptchaSchemas';
import {describe, expect, test} from 'vitest';
describe('captcha configuration', () => {
test('defaults to enabled at the phone-friendly difficulty', () => {
expect(CaptchaConfigSchema.parse({})).toEqual({enabled: true, cost: 5000, max_counter: 1000});
expect(DEFAULT_CAPTCHA_CONFIG).toEqual({enabled: true, cost: 5000, max_counter: 1000});
});
test('rejects difficulty outside the supported range', () => {
expect(CaptchaConfigUpdateRequest.safeParse({cost: 999}).success).toBe(false);
expect(CaptchaConfigUpdateRequest.safeParse({cost: 20001}).success).toBe(false);
expect(CaptchaConfigUpdateRequest.safeParse({max_counter: 99}).success).toBe(false);
expect(CaptchaConfigUpdateRequest.safeParse({max_counter: 20001}).success).toBe(false);
expect(CaptchaConfigSchema.safeParse({cost: 20001}).success).toBe(false);
});
test('keeps a partial update partial without filling defaults', () => {
expect(CaptchaConfigUpdateRequest.parse({})).toEqual({});
expect(CaptchaConfigUpdateRequest.parse({enabled: false})).toEqual({enabled: false});
expect(CaptchaConfigUpdateRequest.parse({cost: 1000, max_counter: 100})).toEqual({cost: 1000, max_counter: 100});
});
});
@@ -0,0 +1,32 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import {z} from 'zod';
export const CAPTCHA_MIN_COST = 1000;
export const CAPTCHA_MAX_COST = 20000;
export const CAPTCHA_MIN_MAX_COUNTER = 100;
export const CAPTCHA_MAX_MAX_COUNTER = 20000;
const captchaConfigFields = {
enabled: z.boolean(),
cost: z.number().int().min(CAPTCHA_MIN_COST).max(CAPTCHA_MAX_COST),
max_counter: z.number().int().min(CAPTCHA_MIN_MAX_COUNTER).max(CAPTCHA_MAX_MAX_COUNTER),
};
export const CaptchaConfigSchema = z.object({
enabled: captchaConfigFields.enabled.default(true),
cost: captchaConfigFields.cost.default(5000),
max_counter: captchaConfigFields.max_counter.default(1000),
});
export type CaptchaConfig = z.infer<typeof CaptchaConfigSchema>;
export const DEFAULT_CAPTCHA_CONFIG: CaptchaConfig = CaptchaConfigSchema.parse({});
export const CaptchaConfigUpdateRequest = z.object(captchaConfigFields).partial();
export type CaptchaConfigUpdateRequest = z.infer<typeof CaptchaConfigUpdateRequest>;
export const CaptchaConfigResponse = CaptchaConfigSchema;
export type CaptchaConfigResponse = z.infer<typeof CaptchaConfigResponse>;
@@ -1,6 +1,5 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import {AltchaCaptchaAssignmentResponse} from '@fluxer/schema/src/domains/admin/AltchaCaptchaSchemas';
import {
DomainMigrationAssignmentResponse,
INERT_DOMAIN_MIGRATION_ASSIGNMENT,
@@ -43,7 +42,6 @@ export type ExperimentDeliveryConfigResponse = z.infer<typeof ExperimentDelivery
const ExperimentAssignmentsSchema = z.object({
domain_migration: DomainMigrationAssignmentResponse.optional(),
altcha_captcha: AltchaCaptchaAssignmentResponse.optional(),
});
export const ExperimentAssignmentsResponse = z.object({
@@ -74,8 +74,7 @@ export const InstanceAppPublicSchema = z.object({
});
export type InstanceAppPublic = z.infer<typeof InstanceAppPublicSchema>;
export const InstanceCaptchaProviderSchema = z.enum(['hcaptcha', 'turnstile', 'none']);
export type InstanceCaptchaProvider = z.infer<typeof InstanceCaptchaProviderSchema>;
export const InstanceCaptchaProviderSchema = z.enum(['altcha', 'none']);
export const InstanceEndpointsSchema = z
.object({
@@ -96,9 +95,7 @@ export type InstanceEndpoints = z.infer<typeof InstanceEndpointsSchema>;
export const InstanceCaptchaSchema = z
.object({
provider: InstanceCaptchaProviderSchema.describe('Captcha provider name (hcaptcha, turnstile, none)'),
hcaptcha_site_key: z.string().nullable().describe('hCaptcha site key if using hCaptcha'),
turnstile_site_key: z.string().nullable().describe('Cloudflare Turnstile site key if using Turnstile'),
provider: InstanceCaptchaProviderSchema.describe('Captcha provider (altcha or none)'),
})
.describe('Captcha configuration');
export type InstanceCaptcha = z.infer<typeof InstanceCaptchaSchema>;