mirror of
https://github.com/kyantech/Palmr.git
synced 2025-10-23 06:11:58 +00:00
feat: enhance reverse share functionality with field requirements
- Introduced new field requirements for name and email in the ReverseShare model, allowing for configurations of "HIDDEN", "OPTIONAL", or "REQUIRED". - Updated the Create and Update schemas to include these new fields, ensuring proper validation and handling in the UI. - Enhanced the file upload section to conditionally require name and email based on the new settings, improving user experience. - Localized new messages for field requirements across multiple languages, ensuring consistent user feedback. - Added a script to clean up translation files, addressing issues with multiple prefixes in translation keys.
This commit is contained in:
@@ -171,6 +171,8 @@ model ReverseShare {
|
||||
password String?
|
||||
pageLayout PageLayout @default(DEFAULT)
|
||||
isActive Boolean @default(true)
|
||||
nameFieldRequired FieldRequirement @default(OPTIONAL)
|
||||
emailFieldRequired FieldRequirement @default(OPTIONAL)
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
@@ -212,6 +214,12 @@ model ReverseShareAlias {
|
||||
@@map("reverse_share_aliases")
|
||||
}
|
||||
|
||||
enum FieldRequirement {
|
||||
HIDDEN
|
||||
OPTIONAL
|
||||
REQUIRED
|
||||
}
|
||||
|
||||
enum PageLayout {
|
||||
DEFAULT
|
||||
WETRANSFER
|
||||
|
@@ -1,5 +1,7 @@
|
||||
import { z } from "zod";
|
||||
|
||||
export const FieldRequirementSchema = z.enum(["HIDDEN", "OPTIONAL", "REQUIRED"]);
|
||||
|
||||
export const CreateReverseShareSchema = z.object({
|
||||
name: z.string().optional().describe("The reverse share name"),
|
||||
description: z.string().optional().describe("The reverse share description"),
|
||||
@@ -14,6 +16,8 @@ export const CreateReverseShareSchema = z.object({
|
||||
allowedFileTypes: z.string().nullable().optional().describe("Comma-separated list of allowed file extensions"),
|
||||
password: z.string().optional().describe("Password for private access"),
|
||||
pageLayout: z.enum(["WETRANSFER", "DEFAULT"]).default("DEFAULT").describe("Page layout type"),
|
||||
nameFieldRequired: FieldRequirementSchema.default("OPTIONAL").describe("Name field requirement setting"),
|
||||
emailFieldRequired: FieldRequirementSchema.default("OPTIONAL").describe("Email field requirement setting"),
|
||||
});
|
||||
|
||||
export const UpdateReverseShareSchema = z.object({
|
||||
@@ -27,6 +31,8 @@ export const UpdateReverseShareSchema = z.object({
|
||||
password: z.string().nullable().optional(),
|
||||
pageLayout: z.enum(["WETRANSFER", "DEFAULT"]).optional(),
|
||||
isActive: z.boolean().optional(),
|
||||
nameFieldRequired: FieldRequirementSchema.optional().describe("Name field requirement setting"),
|
||||
emailFieldRequired: FieldRequirementSchema.optional().describe("Email field requirement setting"),
|
||||
});
|
||||
|
||||
export const ReverseShareFileSchema = z.object({
|
||||
@@ -53,6 +59,8 @@ export const ReverseShareResponseSchema = z.object({
|
||||
pageLayout: z.string().describe("Page layout type"),
|
||||
isActive: z.boolean().describe("Whether the reverse share is active"),
|
||||
hasPassword: z.boolean().describe("Whether the reverse share has a password"),
|
||||
nameFieldRequired: z.string().describe("Name field requirement setting"),
|
||||
emailFieldRequired: z.string().describe("Email field requirement setting"),
|
||||
createdAt: z.string().describe("The reverse share creation date"),
|
||||
updatedAt: z.string().describe("The reverse share update date"),
|
||||
creatorId: z.string().describe("The creator ID"),
|
||||
@@ -80,6 +88,8 @@ export const ReverseSharePublicSchema = z.object({
|
||||
pageLayout: z.string().describe("Page layout type"),
|
||||
hasPassword: z.boolean().describe("Whether the reverse share has a password"),
|
||||
currentFileCount: z.number().describe("Current number of files uploaded"),
|
||||
nameFieldRequired: z.string().describe("Name field requirement setting"),
|
||||
emailFieldRequired: z.string().describe("Email field requirement setting"),
|
||||
});
|
||||
|
||||
export const UploadToReverseShareSchema = z.object({
|
||||
|
@@ -19,6 +19,8 @@ interface ReverseShareData {
|
||||
password: string | null;
|
||||
pageLayout: string;
|
||||
isActive: boolean;
|
||||
nameFieldRequired: string;
|
||||
emailFieldRequired: string;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
creatorId: string;
|
||||
@@ -102,6 +104,8 @@ export class ReverseShareService {
|
||||
pageLayout: reverseShare.pageLayout,
|
||||
hasPassword: !!reverseShare.password,
|
||||
currentFileCount,
|
||||
nameFieldRequired: reverseShare.nameFieldRequired,
|
||||
emailFieldRequired: reverseShare.emailFieldRequired,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -141,6 +145,8 @@ export class ReverseShareService {
|
||||
pageLayout: reverseShare.pageLayout,
|
||||
hasPassword: !!reverseShare.password,
|
||||
currentFileCount,
|
||||
nameFieldRequired: reverseShare.nameFieldRequired,
|
||||
emailFieldRequired: reverseShare.emailFieldRequired,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -613,6 +619,8 @@ export class ReverseShareService {
|
||||
updatedAt: reverseShare.alias.updatedAt.toISOString(),
|
||||
}
|
||||
: null,
|
||||
nameFieldRequired: reverseShare.nameFieldRequired,
|
||||
emailFieldRequired: reverseShare.emailFieldRequired,
|
||||
};
|
||||
|
||||
return result;
|
||||
|
@@ -1024,7 +1024,15 @@
|
||||
"noFilesLimit": "بدون حد للملفات",
|
||||
"noSizeLimit": "بدون حد للحجم",
|
||||
"allFileTypes": "جميع أنواع الملفات",
|
||||
"fileTypesHelp": "أدخل الامتدادات بدون نقطة، مفصولة بمسافة أو فاصلة أو شرطة أو خط عمودي"
|
||||
"fileTypesHelp": "أدخل الامتدادات بدون نقطة، مفصولة بمسافة أو فاصلة أو شرطة أو خط عمودي",
|
||||
"emailFieldRequired": "حقل البريد الإلكتروني",
|
||||
"fieldOptions": {
|
||||
"hidden": "مختفي",
|
||||
"optional": "خياري",
|
||||
"required": "مطلوب"
|
||||
},
|
||||
"fieldRequirements": "المتطلبات الميدانية",
|
||||
"nameFieldRequired": "حقل الاسم"
|
||||
},
|
||||
"card": {
|
||||
"untitled": "رابط بدون عنوان",
|
||||
@@ -1242,7 +1250,19 @@
|
||||
"passwordHelp": "يجب أن تكون كلمة المرور 4 أحرف على الأقل",
|
||||
"passwordPlaceholder": "أدخل كلمة مرور لحماية الرابط"
|
||||
},
|
||||
"submit": "إنشاء رابط استلام"
|
||||
"submit": "إنشاء رابط استلام",
|
||||
"emailFieldRequired": {
|
||||
"label": "متطلبات حقل البريد الإلكتروني",
|
||||
"description": "تكوين ما إذا كان يجب عرض حقل البريد الإلكتروني للتحميل وإذا كان مطلوبًا"
|
||||
},
|
||||
"fieldRequirements": {
|
||||
"title": "المتطلبات الميدانية",
|
||||
"description": "تكوين الحقول المعروضة في نموذج التحميل"
|
||||
},
|
||||
"nameFieldRequired": {
|
||||
"label": "اسم حقل الاسم",
|
||||
"description": "تكوين إذا كان يجب عرض حقل اسم التحميل وإذا كان مطلوبًا"
|
||||
}
|
||||
},
|
||||
"messages": {
|
||||
"created": "تم إنشاء رابط الاستلام بنجاح!",
|
||||
@@ -1302,7 +1322,9 @@
|
||||
"fileTypeNotAllowed": "نوع الملف غير مسموح به. الأنواع المقبولة: {allowedTypes}",
|
||||
"maxFilesExceeded": "الحد الأقصى المسموح به هو {maxFiles} ملف/ملفات",
|
||||
"selectAtLeastOneFile": "اختر ملفاً واحداً على الأقل",
|
||||
"provideNameOrEmail": "قم بتوفير اسمك أو بريدك الإلكتروني"
|
||||
"provideNameOrEmail": "قم بتوفير اسمك أو بريدك الإلكتروني",
|
||||
"provideEmailRequired": "البريد الإلكتروني مطلوب",
|
||||
"provideNameRequired": "الاسم مطلوب"
|
||||
},
|
||||
"fileDropzone": {
|
||||
"dragActive": "أفلت الملفات هنا",
|
||||
@@ -1325,7 +1347,9 @@
|
||||
"descriptionLabel": "الوصف (اختياري)",
|
||||
"descriptionPlaceholder": "أضف وصفاً للملفات...",
|
||||
"uploadButton": "رفع {count} ملف/ملفات",
|
||||
"uploading": "جارٍ الرفع..."
|
||||
"uploading": "جارٍ الرفع...",
|
||||
"emailLabelOptional": "البريد الإلكتروني (اختياري)",
|
||||
"nameLabelOptional": "الاسم (اختياري)"
|
||||
},
|
||||
"success": {
|
||||
"title": "تم رفع الملفات بنجاح! 🎉",
|
||||
|
@@ -1024,7 +1024,15 @@
|
||||
"noFilesLimit": "Kein Dateilimit",
|
||||
"noSizeLimit": "Kein Größenlimit",
|
||||
"allFileTypes": "Alle Dateitypen",
|
||||
"fileTypesHelp": "Geben Sie Erweiterungen ohne Punkt ein, getrennt durch Leerzeichen, Komma, Bindestrich oder Pipe"
|
||||
"fileTypesHelp": "Geben Sie Erweiterungen ohne Punkt ein, getrennt durch Leerzeichen, Komma, Bindestrich oder Pipe",
|
||||
"emailFieldRequired": "E -Mail -Feld",
|
||||
"fieldOptions": {
|
||||
"hidden": "Versteckt",
|
||||
"optional": "Fakultativ",
|
||||
"required": "Erforderlich"
|
||||
},
|
||||
"fieldRequirements": "Feldanforderungen",
|
||||
"nameFieldRequired": "Namensfeld"
|
||||
},
|
||||
"card": {
|
||||
"untitled": "Unbenannter Link",
|
||||
@@ -1242,7 +1250,19 @@
|
||||
"passwordHelp": "Das Passwort muss mindestens 4 Zeichen lang sein",
|
||||
"passwordPlaceholder": "Geben Sie ein Passwort ein, um den Link zu schützen"
|
||||
},
|
||||
"submit": "Empfangslink erstellen"
|
||||
"submit": "Empfangslink erstellen",
|
||||
"emailFieldRequired": {
|
||||
"label": "E -Mail -Feldanforderung",
|
||||
"description": "Konfigurieren Sie, ob das Feld Uploader -E -Mail angezeigt werden soll und ob es erforderlich ist"
|
||||
},
|
||||
"fieldRequirements": {
|
||||
"title": "Feldanforderungen",
|
||||
"description": "Konfigurieren Sie, welche Felder im Upload -Formular angezeigt werden"
|
||||
},
|
||||
"nameFieldRequired": {
|
||||
"label": "Namensfeldbedarf",
|
||||
"description": "Konfigurieren Sie, ob das Feld Uploader -Name angezeigt werden soll und ob es erforderlich ist"
|
||||
}
|
||||
},
|
||||
"messages": {
|
||||
"created": "Empfangslink erfolgreich erstellt!",
|
||||
@@ -1302,7 +1322,9 @@
|
||||
"fileTypeNotAllowed": "Dateityp nicht erlaubt. Erlaubte Typen: {allowedTypes}",
|
||||
"maxFilesExceeded": "Maximal {maxFiles} Dateien erlaubt",
|
||||
"selectAtLeastOneFile": "Wählen Sie mindestens eine Datei aus",
|
||||
"provideNameOrEmail": "Geben Sie Ihren Namen oder E-Mail an"
|
||||
"provideNameOrEmail": "Geben Sie Ihren Namen oder E-Mail an",
|
||||
"provideEmailRequired": "E -Mail ist erforderlich",
|
||||
"provideNameRequired": "Name ist erforderlich"
|
||||
},
|
||||
"fileDropzone": {
|
||||
"dragActive": "Dateien hier ablegen",
|
||||
@@ -1325,7 +1347,9 @@
|
||||
"descriptionLabel": "Beschreibung (optional)",
|
||||
"descriptionPlaceholder": "Fügen Sie eine Beschreibung zu den Dateien hinzu...",
|
||||
"uploadButton": "{count} Datei(en) senden",
|
||||
"uploading": "Wird hochgeladen..."
|
||||
"uploading": "Wird hochgeladen...",
|
||||
"emailLabelOptional": "E-Mail (optional)",
|
||||
"nameLabelOptional": "Name (optional)"
|
||||
},
|
||||
"success": {
|
||||
"title": "Dateien erfolgreich hochgeladen! 🎉",
|
||||
|
@@ -1024,7 +1024,15 @@
|
||||
"noFilesLimit": "No file limit",
|
||||
"noSizeLimit": "No size limit",
|
||||
"allFileTypes": "All file types",
|
||||
"fileTypesHelp": "Enter extensions without dots, separated by space, comma, dash or pipe"
|
||||
"fileTypesHelp": "Enter extensions without dots, separated by space, comma, dash or pipe",
|
||||
"fieldRequirements": "Field Requirements",
|
||||
"nameFieldRequired": "Name Field",
|
||||
"emailFieldRequired": "Email Field",
|
||||
"fieldOptions": {
|
||||
"hidden": "Hidden",
|
||||
"optional": "Optional",
|
||||
"required": "Required"
|
||||
}
|
||||
},
|
||||
"card": {
|
||||
"untitled": "Untitled Link",
|
||||
@@ -1242,6 +1250,18 @@
|
||||
"passwordHelp": "Password must be at least 4 characters",
|
||||
"passwordPlaceholder": "Enter a password to protect the link"
|
||||
},
|
||||
"nameFieldRequired": {
|
||||
"label": "Name Field Requirement",
|
||||
"description": "Configure if the uploader name field should be shown and if it's required"
|
||||
},
|
||||
"emailFieldRequired": {
|
||||
"label": "Email Field Requirement",
|
||||
"description": "Configure if the uploader email field should be shown and if it's required"
|
||||
},
|
||||
"fieldRequirements": {
|
||||
"title": "Field Requirements",
|
||||
"description": "Configure which fields are shown in the upload form"
|
||||
},
|
||||
"submit": "Create Receive Link"
|
||||
},
|
||||
"messages": {
|
||||
@@ -1302,7 +1322,9 @@
|
||||
"fileTypeNotAllowed": "File type not allowed. Accepted types: {allowedTypes}",
|
||||
"maxFilesExceeded": "Maximum of {maxFiles} files allowed",
|
||||
"selectAtLeastOneFile": "Select at least one file",
|
||||
"provideNameOrEmail": "Please provide your name or email"
|
||||
"provideNameOrEmail": "Please provide your name or email",
|
||||
"provideNameRequired": "Name is required",
|
||||
"provideEmailRequired": "Email is required"
|
||||
},
|
||||
"fileDropzone": {
|
||||
"dragActive": "Drop files here",
|
||||
@@ -1319,8 +1341,10 @@
|
||||
},
|
||||
"form": {
|
||||
"nameLabel": "Name",
|
||||
"nameLabelOptional": "Name (optional)",
|
||||
"namePlaceholder": "Your name",
|
||||
"emailLabel": "Email",
|
||||
"emailLabelOptional": "Email (optional)",
|
||||
"emailPlaceholder": "your@email.com",
|
||||
"descriptionLabel": "Description (optional)",
|
||||
"descriptionPlaceholder": "Add a description to the files...",
|
||||
|
@@ -1024,7 +1024,15 @@
|
||||
"noFilesLimit": "Sin límite de archivos",
|
||||
"noSizeLimit": "Sin límite de tamaño",
|
||||
"allFileTypes": "Todos los tipos de archivo",
|
||||
"fileTypesHelp": "Escribe las extensiones sin punto, separadas por espacio, coma, guion o barra vertical"
|
||||
"fileTypesHelp": "Escribe las extensiones sin punto, separadas por espacio, coma, guion o barra vertical",
|
||||
"emailFieldRequired": "Campo de correo electrónico",
|
||||
"fieldOptions": {
|
||||
"hidden": "Oculto",
|
||||
"optional": "Opcional",
|
||||
"required": "Obligatorio"
|
||||
},
|
||||
"fieldRequirements": "Requisitos de campo",
|
||||
"nameFieldRequired": "Campo de nombre"
|
||||
},
|
||||
"card": {
|
||||
"untitled": "Enlace sin título",
|
||||
@@ -1242,7 +1250,19 @@
|
||||
"passwordHelp": "La contraseña debe tener al menos 4 caracteres",
|
||||
"passwordPlaceholder": "Ingresa una contraseña para proteger el enlace"
|
||||
},
|
||||
"submit": "Crear Enlace de Recepción"
|
||||
"submit": "Crear Enlace de Recepción",
|
||||
"emailFieldRequired": {
|
||||
"label": "Requisito de campo de correo electrónico",
|
||||
"description": "Configurar si se debe mostrar el campo de correo electrónico del cargador y si es necesario"
|
||||
},
|
||||
"fieldRequirements": {
|
||||
"title": "Requisitos de campo",
|
||||
"description": "Configurar qué campos se muestran en el formulario de carga"
|
||||
},
|
||||
"nameFieldRequired": {
|
||||
"label": "Requisito de campo de nombre",
|
||||
"description": "Configurar si se debe mostrar el campo Nombre del cargador y si es necesario"
|
||||
}
|
||||
},
|
||||
"messages": {
|
||||
"created": "¡Enlace de recepción creado con éxito!",
|
||||
@@ -1302,7 +1322,9 @@
|
||||
"fileTypeNotAllowed": "Tipo de archivo no permitido. Tipos aceptados: {allowedTypes}",
|
||||
"maxFilesExceeded": "Máximo de {maxFiles} archivos permitidos",
|
||||
"selectAtLeastOneFile": "Selecciona al menos un archivo",
|
||||
"provideNameOrEmail": "Proporciona tu nombre o correo electrónico"
|
||||
"provideNameOrEmail": "Proporciona tu nombre o correo electrónico",
|
||||
"provideEmailRequired": "Se requiere correo electrónico",
|
||||
"provideNameRequired": "Se requiere el nombre"
|
||||
},
|
||||
"fileDropzone": {
|
||||
"dragActive": "Suelta los archivos aquí",
|
||||
@@ -1325,7 +1347,9 @@
|
||||
"descriptionLabel": "Descripción (opcional)",
|
||||
"descriptionPlaceholder": "Añade una descripción a los archivos...",
|
||||
"uploadButton": "Enviar {count} archivo(s)",
|
||||
"uploading": "Enviando..."
|
||||
"uploading": "Enviando...",
|
||||
"emailLabelOptional": "Correo electrónico (opcional)",
|
||||
"nameLabelOptional": "Nombre (opcional)"
|
||||
},
|
||||
"success": {
|
||||
"title": "¡Archivos enviados con éxito! 🎉",
|
||||
|
@@ -1024,7 +1024,15 @@
|
||||
"noFilesLimit": "Sans limite de fichiers",
|
||||
"noSizeLimit": "Sans limite de taille",
|
||||
"allFileTypes": "Tous types de fichiers",
|
||||
"fileTypesHelp": "Saisissez les extensions sans point, séparées par espace, virgule, tiret ou barre verticale"
|
||||
"fileTypesHelp": "Saisissez les extensions sans point, séparées par espace, virgule, tiret ou barre verticale",
|
||||
"emailFieldRequired": "Champ de courrier électronique",
|
||||
"fieldOptions": {
|
||||
"hidden": "Masqué",
|
||||
"optional": "Optionnel",
|
||||
"required": "Obligatoire"
|
||||
},
|
||||
"fieldRequirements": "Exigences sur le terrain",
|
||||
"nameFieldRequired": "Champ de nom"
|
||||
},
|
||||
"card": {
|
||||
"untitled": "Lien sans titre",
|
||||
@@ -1242,7 +1250,19 @@
|
||||
"passwordHelp": "Le mot de passe doit contenir au moins 4 caractères",
|
||||
"passwordPlaceholder": "Saisissez un mot de passe pour protéger le lien"
|
||||
},
|
||||
"submit": "Créer le Lien de Réception"
|
||||
"submit": "Créer le Lien de Réception",
|
||||
"emailFieldRequired": {
|
||||
"label": "Exigence de champ de messagerie",
|
||||
"description": "Configurez si le champ de messagerie du téléchargeur doit être affiché et s'il est requis"
|
||||
},
|
||||
"fieldRequirements": {
|
||||
"title": "Exigences sur le terrain",
|
||||
"description": "Configurer quels champs sont affichés dans le formulaire de téléchargement"
|
||||
},
|
||||
"nameFieldRequired": {
|
||||
"label": "Exigence de champ de nom",
|
||||
"description": "Configurer si le champ Nom du téléchargeur doit être affiché et s'il est requis"
|
||||
}
|
||||
},
|
||||
"messages": {
|
||||
"created": "Lien de réception créé avec succès !",
|
||||
@@ -1302,7 +1322,9 @@
|
||||
"fileTypeNotAllowed": "Type de fichier non autorisé. Types acceptés : {allowedTypes}",
|
||||
"maxFilesExceeded": "Maximum de {maxFiles} fichiers autorisés",
|
||||
"selectAtLeastOneFile": "Sélectionnez au moins un fichier",
|
||||
"provideNameOrEmail": "Indiquez votre nom ou e-mail"
|
||||
"provideNameOrEmail": "Indiquez votre nom ou e-mail",
|
||||
"provideEmailRequired": "Un e-mail est requis",
|
||||
"provideNameRequired": "Le nom est requis"
|
||||
},
|
||||
"fileDropzone": {
|
||||
"dragActive": "Déposez les fichiers ici",
|
||||
@@ -1325,7 +1347,9 @@
|
||||
"descriptionLabel": "Description (optionnelle)",
|
||||
"descriptionPlaceholder": "Ajoutez une description aux fichiers...",
|
||||
"uploadButton": "Envoyer {count} fichier(s)",
|
||||
"uploading": "Envoi en cours..."
|
||||
"uploading": "Envoi en cours...",
|
||||
"emailLabelOptional": "E-mail (facultatif)",
|
||||
"nameLabelOptional": "Nom (facultatif)"
|
||||
},
|
||||
"success": {
|
||||
"title": "Fichiers envoyés avec succès ! 🎉",
|
||||
|
@@ -1024,7 +1024,15 @@
|
||||
"noFilesLimit": "फ़ाइलों की कोई सीमा नहीं",
|
||||
"noSizeLimit": "आकार की कोई सीमा नहीं",
|
||||
"allFileTypes": "सभी फ़ाइल प्रकार",
|
||||
"fileTypesHelp": "एक्सटेंशन बिना बिंदु के, स्पेस, कॉमा, हाइफन या पाइप से अलग करके टाइप करें"
|
||||
"fileTypesHelp": "एक्सटेंशन बिना बिंदु के, स्पेस, कॉमा, हाइफन या पाइप से अलग करके टाइप करें",
|
||||
"emailFieldRequired": "ईमेल क्षेत्र",
|
||||
"fieldOptions": {
|
||||
"hidden": "छिपा हुआ",
|
||||
"optional": "वैकल्पिक",
|
||||
"required": "आवश्यक"
|
||||
},
|
||||
"fieldRequirements": "क्षेत्र आवश्यकताएँ",
|
||||
"nameFieldRequired": "नाम क्षेत्र"
|
||||
},
|
||||
"card": {
|
||||
"untitled": "शीर्षकहीन लिंक",
|
||||
@@ -1242,7 +1250,19 @@
|
||||
"passwordHelp": "पासवर्ड कम से कम 4 अक्षर का होना चाहिए",
|
||||
"passwordPlaceholder": "लिंक की सुरक्षा के लिए पासवर्ड दर्ज करें"
|
||||
},
|
||||
"submit": "प्राप्ति लिंक बनाएं"
|
||||
"submit": "प्राप्ति लिंक बनाएं",
|
||||
"emailFieldRequired": {
|
||||
"label": "ईमेल क्षेत्र की आवश्यकता",
|
||||
"description": "कॉन्फ़िगर करें कि क्या अपलोडर ईमेल फ़ील्ड दिखाया जाना चाहिए और यदि आवश्यक है"
|
||||
},
|
||||
"fieldRequirements": {
|
||||
"title": "क्षेत्र आवश्यकताएँ",
|
||||
"description": "कॉन्फ़िगर करें कि कौन से फ़ील्ड अपलोड फॉर्म में दिखाए गए हैं"
|
||||
},
|
||||
"nameFieldRequired": {
|
||||
"label": "नाम क्षेत्र की आवश्यकता",
|
||||
"description": "कॉन्फ़िगर करें कि क्या अपलोडर नाम फ़ील्ड दिखाया जाना चाहिए और यदि आवश्यक है"
|
||||
}
|
||||
},
|
||||
"messages": {
|
||||
"created": "प्राप्ति लिंक सफलतापूर्वक बनाया गया!",
|
||||
@@ -1302,7 +1322,9 @@
|
||||
"fileTypeNotAllowed": "फ़ाइल प्रकार अनुमत नहीं है। स्वीकृत प्रकार: {allowedTypes}",
|
||||
"maxFilesExceeded": "अधिकतम {maxFiles} फ़ाइलें अनुमत हैं",
|
||||
"selectAtLeastOneFile": "कम से कम एक फ़ाइल चुनें",
|
||||
"provideNameOrEmail": "अपना नाम या ईमेल प्रदान करें"
|
||||
"provideNameOrEmail": "अपना नाम या ईमेल प्रदान करें",
|
||||
"provideEmailRequired": "ईमेल की जरूरत है",
|
||||
"provideNameRequired": "नाम आवश्यक है"
|
||||
},
|
||||
"fileDropzone": {
|
||||
"dragActive": "फ़ाइलें यहां छोड़ें",
|
||||
@@ -1325,7 +1347,9 @@
|
||||
"descriptionLabel": "विवरण (वैकल्पिक)",
|
||||
"descriptionPlaceholder": "फ़ाइलों का विवरण जोड़ें...",
|
||||
"uploadButton": "{count} फ़ाइल(ें) भेजें",
|
||||
"uploading": "भेजा जा रहा है..."
|
||||
"uploading": "भेजा जा रहा है...",
|
||||
"emailLabelOptional": "ईमेल वैकल्पिक)",
|
||||
"nameLabelOptional": "नाम: (वैकल्पिक)"
|
||||
},
|
||||
"success": {
|
||||
"title": "फ़ाइलें सफलतापूर्वक भेजी गईं! 🎉",
|
||||
|
@@ -1024,7 +1024,15 @@
|
||||
"noFilesLimit": "Nessun limite di file",
|
||||
"noSizeLimit": "Nessun limite di dimensione",
|
||||
"allFileTypes": "Tutti i tipi di file",
|
||||
"fileTypesHelp": "Inserisci le estensioni senza punto, separate da spazio, virgola, trattino o barra verticale"
|
||||
"fileTypesHelp": "Inserisci le estensioni senza punto, separate da spazio, virgola, trattino o barra verticale",
|
||||
"emailFieldRequired": "Campo e -mail",
|
||||
"fieldOptions": {
|
||||
"hidden": "Nascosto",
|
||||
"optional": "Opzionale",
|
||||
"required": "Obbligatorio"
|
||||
},
|
||||
"fieldRequirements": "Requisiti sul campo",
|
||||
"nameFieldRequired": "Campo nome"
|
||||
},
|
||||
"card": {
|
||||
"untitled": "Link senza titolo",
|
||||
@@ -1242,7 +1250,19 @@
|
||||
"passwordHelp": "La password deve contenere almeno 4 caratteri",
|
||||
"passwordPlaceholder": "Inserisci una password per proteggere il link"
|
||||
},
|
||||
"submit": "Crea Link di Ricezione"
|
||||
"submit": "Crea Link di Ricezione",
|
||||
"emailFieldRequired": {
|
||||
"label": "Requisito del campo e -mail",
|
||||
"description": "Configurare se il campo e -mail del caricatore deve essere visualizzato e se è richiesto"
|
||||
},
|
||||
"fieldRequirements": {
|
||||
"title": "Requisiti sul campo",
|
||||
"description": "Configurare quali campi sono mostrati nel modulo di caricamento"
|
||||
},
|
||||
"nameFieldRequired": {
|
||||
"label": "Requisiti del campo Nome",
|
||||
"description": "Configurare se il campo Nome del caricatore deve essere visualizzato e se è richiesto"
|
||||
}
|
||||
},
|
||||
"messages": {
|
||||
"created": "Link di ricezione creato con successo!",
|
||||
@@ -1302,7 +1322,9 @@
|
||||
"fileTypeNotAllowed": "Tipo di file non consentito. Tipi accettati: {allowedTypes}",
|
||||
"maxFilesExceeded": "Massimo {maxFiles} file consentiti",
|
||||
"selectAtLeastOneFile": "Seleziona almeno un file",
|
||||
"provideNameOrEmail": "Inserisci il tuo nome o email"
|
||||
"provideNameOrEmail": "Inserisci il tuo nome o email",
|
||||
"provideEmailRequired": "È richiesta l'e -mail",
|
||||
"provideNameRequired": "È richiesto il nome"
|
||||
},
|
||||
"fileDropzone": {
|
||||
"dragActive": "Rilascia i file qui",
|
||||
@@ -1325,7 +1347,9 @@
|
||||
"descriptionLabel": "Descrizione (opzionale)",
|
||||
"descriptionPlaceholder": "Aggiungi una descrizione ai file...",
|
||||
"uploadButton": "Invia {count} file",
|
||||
"uploading": "Invio in corso..."
|
||||
"uploading": "Invio in corso...",
|
||||
"emailLabelOptional": "Email (opzionale)",
|
||||
"nameLabelOptional": "Nome (opzionale)"
|
||||
},
|
||||
"success": {
|
||||
"title": "File inviati con successo! 🎉",
|
||||
|
@@ -1024,7 +1024,15 @@
|
||||
"noFilesLimit": "ファイル数制限なし",
|
||||
"noSizeLimit": "サイズ制限なし",
|
||||
"allFileTypes": "すべてのファイル形式",
|
||||
"fileTypesHelp": "拡張子をドット無しで入力し、スペース、カンマ、ハイフン、パイプで区切ってください"
|
||||
"fileTypesHelp": "拡張子をドット無しで入力し、スペース、カンマ、ハイフン、パイプで区切ってください",
|
||||
"emailFieldRequired": "電子メールフィールド",
|
||||
"fieldOptions": {
|
||||
"hidden": "隠れた",
|
||||
"optional": "オプション",
|
||||
"required": "必須"
|
||||
},
|
||||
"fieldRequirements": "フィールド要件",
|
||||
"nameFieldRequired": "名前フィールド"
|
||||
},
|
||||
"card": {
|
||||
"untitled": "無題のリンク",
|
||||
@@ -1242,7 +1250,19 @@
|
||||
"passwordHelp": "パスワードは4文字以上必要です",
|
||||
"passwordPlaceholder": "リンクを保護するパスワードを入力"
|
||||
},
|
||||
"submit": "受信リンクを作成"
|
||||
"submit": "受信リンクを作成",
|
||||
"emailFieldRequired": {
|
||||
"label": "電子メールフィールドの要件",
|
||||
"description": "アップローダーの電子メールフィールドが表示されるかどうか、そしてそれが必要かどうかを構成します"
|
||||
},
|
||||
"fieldRequirements": {
|
||||
"title": "フィールド要件",
|
||||
"description": "アップロードフォームに表示されるフィールドを構成します"
|
||||
},
|
||||
"nameFieldRequired": {
|
||||
"label": "名前フィールドの要件",
|
||||
"description": "アップローダー名フィールドが表示されるかどうか、そしてそれが必要かどうかを構成します"
|
||||
}
|
||||
},
|
||||
"messages": {
|
||||
"created": "受信リンクを作成しました!",
|
||||
@@ -1302,7 +1322,9 @@
|
||||
"fileTypeNotAllowed": "このファイル形式は許可されていません。許可される形式: {allowedTypes}",
|
||||
"maxFilesExceeded": "最大 {maxFiles} ファイルまで許可されています",
|
||||
"selectAtLeastOneFile": "少なくとも1つのファイルを選択してください",
|
||||
"provideNameOrEmail": "名前またはメールアドレスを入力してください"
|
||||
"provideNameOrEmail": "名前またはメールアドレスを入力してください",
|
||||
"provideEmailRequired": "メールが必要です",
|
||||
"provideNameRequired": "名前が必要です"
|
||||
},
|
||||
"fileDropzone": {
|
||||
"dragActive": "ここにファイルをドロップ",
|
||||
@@ -1325,7 +1347,9 @@
|
||||
"descriptionLabel": "説明(オプション)",
|
||||
"descriptionPlaceholder": "ファイルの説明を追加...",
|
||||
"uploadButton": "{count} ファイルを送信",
|
||||
"uploading": "送信中..."
|
||||
"uploading": "送信中...",
|
||||
"emailLabelOptional": "メール(オプション)",
|
||||
"nameLabelOptional": "名前(オプション)"
|
||||
},
|
||||
"success": {
|
||||
"title": "ファイルを送信しました! 🎉",
|
||||
|
@@ -1024,7 +1024,15 @@
|
||||
"noFilesLimit": "파일 수 제한 없음",
|
||||
"noSizeLimit": "크기 제한 없음",
|
||||
"allFileTypes": "모든 파일 유형",
|
||||
"fileTypesHelp": "확장자를 점 없이 입력하고 공백, 쉼표, 대시 또는 파이프로 구분"
|
||||
"fileTypesHelp": "확장자를 점 없이 입력하고 공백, 쉼표, 대시 또는 파이프로 구분",
|
||||
"emailFieldRequired": "이메일 필드",
|
||||
"fieldOptions": {
|
||||
"hidden": "숨겨진",
|
||||
"optional": "선택 과목",
|
||||
"required": "필수의"
|
||||
},
|
||||
"fieldRequirements": "필드 요구 사항",
|
||||
"nameFieldRequired": "이름 필드"
|
||||
},
|
||||
"card": {
|
||||
"untitled": "제목 없는 링크",
|
||||
@@ -1242,7 +1250,19 @@
|
||||
"passwordHelp": "비밀번호는 최소 4자 이상이어야 합니다",
|
||||
"passwordPlaceholder": "링크를 보호할 비밀번호 입력"
|
||||
},
|
||||
"submit": "수신 링크 생성"
|
||||
"submit": "수신 링크 생성",
|
||||
"emailFieldRequired": {
|
||||
"label": "이메일 필드 요구 사항",
|
||||
"description": "업 로더 이메일 필드를 표시 해야하는지 확인하고 필요한 경우 구성하십시오."
|
||||
},
|
||||
"fieldRequirements": {
|
||||
"title": "필드 요구 사항",
|
||||
"description": "업로드 양식으로 표시되는 필드를 구성하십시오"
|
||||
},
|
||||
"nameFieldRequired": {
|
||||
"label": "이름 필드 요구 사항",
|
||||
"description": "업로더 이름 필드를 표시 해야하는지 확인하고 필요한 경우 구성하십시오."
|
||||
}
|
||||
},
|
||||
"messages": {
|
||||
"created": "수신 링크가 성공적으로 생성되었습니다!",
|
||||
@@ -1302,7 +1322,9 @@
|
||||
"fileTypeNotAllowed": "허용되지 않는 파일 유형입니다. 허용된 유형: {allowedTypes}",
|
||||
"maxFilesExceeded": "최대 {maxFiles}개의 파일만 허용됩니다",
|
||||
"selectAtLeastOneFile": "최소 한 개의 파일을 선택하세요",
|
||||
"provideNameOrEmail": "이름 또는 이메일을 입력하세요"
|
||||
"provideNameOrEmail": "이름 또는 이메일을 입력하세요",
|
||||
"provideEmailRequired": "이메일이 필요합니다",
|
||||
"provideNameRequired": "이름이 필요합니다"
|
||||
},
|
||||
"fileDropzone": {
|
||||
"dragActive": "여기에 파일을 놓으세요",
|
||||
@@ -1325,7 +1347,9 @@
|
||||
"descriptionLabel": "설명 (선택사항)",
|
||||
"descriptionPlaceholder": "파일에 대한 설명 추가...",
|
||||
"uploadButton": "{count}개 파일 보내기",
|
||||
"uploading": "보내는 중..."
|
||||
"uploading": "보내는 중...",
|
||||
"emailLabelOptional": "이메일 (선택 사항)",
|
||||
"nameLabelOptional": "이름 (선택 사항)"
|
||||
},
|
||||
"success": {
|
||||
"title": "파일이 성공적으로 보내졌습니다! 🎉",
|
||||
|
@@ -1024,7 +1024,15 @@
|
||||
"noFilesLimit": "Geen bestandslimiet",
|
||||
"noSizeLimit": "Geen groottelimiet",
|
||||
"allFileTypes": "Alle bestandstypes",
|
||||
"fileTypesHelp": "Voer extensies in zonder punt, gescheiden door spatie, komma, streepje of verticale streep"
|
||||
"fileTypesHelp": "Voer extensies in zonder punt, gescheiden door spatie, komma, streepje of verticale streep",
|
||||
"emailFieldRequired": "E -mailveld",
|
||||
"fieldOptions": {
|
||||
"hidden": "Verborgen",
|
||||
"optional": "Optioneel",
|
||||
"required": "Vereist"
|
||||
},
|
||||
"fieldRequirements": "Veldvereisten",
|
||||
"nameFieldRequired": "Naamveld"
|
||||
},
|
||||
"card": {
|
||||
"untitled": "Link zonder titel",
|
||||
@@ -1242,7 +1250,19 @@
|
||||
"passwordHelp": "Wachtwoord moet minimaal 4 tekens bevatten",
|
||||
"passwordPlaceholder": "Voer een wachtwoord in om de link te beveiligen"
|
||||
},
|
||||
"submit": "Ontvangstlink Aanmaken"
|
||||
"submit": "Ontvangstlink Aanmaken",
|
||||
"emailFieldRequired": {
|
||||
"label": "E -mailveldvereiste",
|
||||
"description": "Configureer of het veld Uploader e -mail moet worden getoond en of het vereist is"
|
||||
},
|
||||
"fieldRequirements": {
|
||||
"title": "Veldvereisten",
|
||||
"description": "Configureer welke velden worden weergegeven in het uploadformulier"
|
||||
},
|
||||
"nameFieldRequired": {
|
||||
"label": "Naam veldvereiste",
|
||||
"description": "Configureer of het veld Uploader Naam moet worden getoond en of het vereist is"
|
||||
}
|
||||
},
|
||||
"messages": {
|
||||
"created": "Ontvangstlink succesvol aangemaakt!",
|
||||
@@ -1302,7 +1322,9 @@
|
||||
"fileTypeNotAllowed": "Bestandstype niet toegestaan. Toegestane types: {allowedTypes}",
|
||||
"maxFilesExceeded": "Maximum van {maxFiles} bestanden toegestaan",
|
||||
"selectAtLeastOneFile": "Selecteer ten minste één bestand",
|
||||
"provideNameOrEmail": "Voer uw naam of e-mail in"
|
||||
"provideNameOrEmail": "Voer uw naam of e-mail in",
|
||||
"provideEmailRequired": "E -mail is vereist",
|
||||
"provideNameRequired": "Naam is vereist"
|
||||
},
|
||||
"fileDropzone": {
|
||||
"dragActive": "Laat bestanden hier los",
|
||||
@@ -1325,7 +1347,9 @@
|
||||
"descriptionLabel": "Beschrijving (optioneel)",
|
||||
"descriptionPlaceholder": "Voeg een beschrijving toe aan de bestanden...",
|
||||
"uploadButton": "{count} bestand(en) verzenden",
|
||||
"uploading": "Verzenden..."
|
||||
"uploading": "Verzenden...",
|
||||
"emailLabelOptional": "E -mail (optioneel)",
|
||||
"nameLabelOptional": "Naam (optioneel)"
|
||||
},
|
||||
"success": {
|
||||
"title": "Bestanden succesvol verzonden! 🎉",
|
||||
|
@@ -1024,7 +1024,15 @@
|
||||
"noFilesLimit": "Bez limitu plików",
|
||||
"noSizeLimit": "Bez limitu rozmiaru",
|
||||
"allFileTypes": "Wszystkie typy plików",
|
||||
"fileTypesHelp": "Wprowadź rozszerzenia bez kropek, oddzielone spacją, przecinkiem, myślnikiem lub kreską pionową"
|
||||
"fileTypesHelp": "Wprowadź rozszerzenia bez kropek, oddzielone spacją, przecinkiem, myślnikiem lub kreską pionową",
|
||||
"emailFieldRequired": "Pole e -mail",
|
||||
"fieldOptions": {
|
||||
"hidden": "Ukryty",
|
||||
"optional": "Fakultatywny",
|
||||
"required": "Wymagany"
|
||||
},
|
||||
"fieldRequirements": "Wymagania terenowe",
|
||||
"nameFieldRequired": "Pole Nazwa"
|
||||
},
|
||||
"card": {
|
||||
"untitled": "Link bez tytułu",
|
||||
@@ -1242,7 +1250,19 @@
|
||||
"passwordHelp": "Hasło musi mieć co najmniej 4 znaki",
|
||||
"passwordPlaceholder": "Wprowadź hasło, aby chronić link"
|
||||
},
|
||||
"submit": "Utwórz link do odbierania"
|
||||
"submit": "Utwórz link do odbierania",
|
||||
"emailFieldRequired": {
|
||||
"label": "Wymagania pola e -mail",
|
||||
"description": "Skonfiguruj, czy należy wyświetlić pole e -mail przesyłania, a jeśli jest to wymagane"
|
||||
},
|
||||
"fieldRequirements": {
|
||||
"title": "Wymagania terenowe",
|
||||
"description": "Skonfiguruj, które pola są pokazane w formularzu przesyłania"
|
||||
},
|
||||
"nameFieldRequired": {
|
||||
"label": "Nazwa Wymagania pola",
|
||||
"description": "Skonfiguruj, czy należy wyświetlić pole nazwy przesyłania, a jeśli jest to wymagane"
|
||||
}
|
||||
},
|
||||
"messages": {
|
||||
"created": "Link do odbierania utworzony pomyślnie!",
|
||||
@@ -1302,7 +1322,9 @@
|
||||
"fileTypeNotAllowed": "Typ pliku niedozwolony. Akceptowane typy: {allowedTypes}",
|
||||
"maxFilesExceeded": "Dozwolono maksymalnie {maxFiles} plików",
|
||||
"selectAtLeastOneFile": "Wybierz co najmniej jeden plik",
|
||||
"provideNameOrEmail": "Proszę podać swoje imię lub adres e-mail"
|
||||
"provideNameOrEmail": "Proszę podać swoje imię lub adres e-mail",
|
||||
"provideEmailRequired": "Wymagany jest e -mail",
|
||||
"provideNameRequired": "Nazwa jest wymagana"
|
||||
},
|
||||
"fileDropzone": {
|
||||
"dragActive": "Upuść pliki tutaj",
|
||||
@@ -1325,7 +1347,9 @@
|
||||
"descriptionLabel": "Opis (opcjonalnie)",
|
||||
"descriptionPlaceholder": "Dodaj opis do plików...",
|
||||
"uploadButton": "Wyślij {count} plik(ów)",
|
||||
"uploading": "Wysyłanie..."
|
||||
"uploading": "Wysyłanie...",
|
||||
"emailLabelOptional": "E -mail (opcjonalnie)",
|
||||
"nameLabelOptional": "Nazwa (opcjonalnie)"
|
||||
},
|
||||
"success": {
|
||||
"title": "Pliki wysłane pomyślnie! 🎉",
|
||||
|
@@ -1024,7 +1024,15 @@
|
||||
"noFilesLimit": "Sem limite de arquivos",
|
||||
"noSizeLimit": "Sem limite de tamanho",
|
||||
"allFileTypes": "Todos os tipos de arquivo",
|
||||
"fileTypesHelp": "Digite as extensões sem ponto, separadas por espaço, vírgula, traço ou pipe"
|
||||
"fileTypesHelp": "Digite as extensões sem ponto, separadas por espaço, vírgula, traço ou pipe",
|
||||
"emailFieldRequired": "Campo de e -mail",
|
||||
"fieldOptions": {
|
||||
"hidden": "Oculto",
|
||||
"optional": "Opcional",
|
||||
"required": "Obrigatório"
|
||||
},
|
||||
"fieldRequirements": "Requisitos de campo",
|
||||
"nameFieldRequired": "Campo de nome"
|
||||
},
|
||||
"card": {
|
||||
"untitled": "Link sem título",
|
||||
@@ -1242,7 +1250,19 @@
|
||||
"passwordHelp": "A senha deve ter pelo menos 4 caracteres",
|
||||
"passwordPlaceholder": "Digite uma senha para proteger o link"
|
||||
},
|
||||
"submit": "Criar Link de Recebimento"
|
||||
"submit": "Criar Link de Recebimento",
|
||||
"emailFieldRequired": {
|
||||
"label": "Requisito de campo de e -mail",
|
||||
"description": "Configure se o campo de email do upload deve ser mostrado e se for necessário"
|
||||
},
|
||||
"fieldRequirements": {
|
||||
"title": "Requisitos de campo",
|
||||
"description": "Configure quais campos são mostrados no formulário de upload"
|
||||
},
|
||||
"nameFieldRequired": {
|
||||
"label": "Nome Requisito de campo",
|
||||
"description": "Configure se o campo de nome do upload deve ser mostrado e se for necessário"
|
||||
}
|
||||
},
|
||||
"messages": {
|
||||
"created": "Link de recebimento criado com sucesso!",
|
||||
@@ -1302,7 +1322,9 @@
|
||||
"fileTypeNotAllowed": "Tipo de arquivo não permitido. Tipos aceitos: {allowedTypes}",
|
||||
"maxFilesExceeded": "Máximo de {maxFiles} arquivos permitidos",
|
||||
"selectAtLeastOneFile": "Selecione pelo menos um arquivo",
|
||||
"provideNameOrEmail": "Informe seu nome ou e-mail"
|
||||
"provideNameOrEmail": "Informe seu nome ou e-mail",
|
||||
"provideEmailRequired": "O email é necessário",
|
||||
"provideNameRequired": "O nome é necessário"
|
||||
},
|
||||
"fileDropzone": {
|
||||
"dragActive": "Solte os arquivos aqui",
|
||||
@@ -1325,7 +1347,9 @@
|
||||
"descriptionLabel": "Descrição (opcional)",
|
||||
"descriptionPlaceholder": "Adicione uma descrição aos arquivos...",
|
||||
"uploadButton": "Enviar {count} arquivo(s)",
|
||||
"uploading": "Enviando..."
|
||||
"uploading": "Enviando...",
|
||||
"emailLabelOptional": "Email (opcional)",
|
||||
"nameLabelOptional": "Nome (opcional)"
|
||||
},
|
||||
"success": {
|
||||
"title": "Arquivos enviados com sucesso! 🎉",
|
||||
|
@@ -1024,7 +1024,15 @@
|
||||
"noFilesLimit": "Без ограничения файлов",
|
||||
"noSizeLimit": "Без ограничения размера",
|
||||
"allFileTypes": "Все типы файлов",
|
||||
"fileTypesHelp": "Введите расширения без точки, разделенные пробелом, запятой, дефисом или вертикальной чертой"
|
||||
"fileTypesHelp": "Введите расширения без точки, разделенные пробелом, запятой, дефисом или вертикальной чертой",
|
||||
"emailFieldRequired": "Поле электронной почты",
|
||||
"fieldOptions": {
|
||||
"hidden": "Скрытый",
|
||||
"optional": "Необязательный",
|
||||
"required": "Необходимый"
|
||||
},
|
||||
"fieldRequirements": "Полевые требования",
|
||||
"nameFieldRequired": "Имя Поле"
|
||||
},
|
||||
"card": {
|
||||
"untitled": "Ссылка без названия",
|
||||
@@ -1242,7 +1250,19 @@
|
||||
"passwordHelp": "Пароль должен содержать минимум 4 символа",
|
||||
"passwordPlaceholder": "Введите пароль для защиты ссылки"
|
||||
},
|
||||
"submit": "Создать ссылку для получения"
|
||||
"submit": "Создать ссылку для получения",
|
||||
"emailFieldRequired": {
|
||||
"label": "Требование поля электронной почты",
|
||||
"description": "Настройка, если следует отобразить поле электронной почты загрузчика и если это требуется"
|
||||
},
|
||||
"fieldRequirements": {
|
||||
"title": "Полевые требования",
|
||||
"description": "Настройте, какие поля показаны в форме загрузки"
|
||||
},
|
||||
"nameFieldRequired": {
|
||||
"label": "Требование поля имени",
|
||||
"description": "Настройка, если должно быть показано поле имени загрузчика и если оно требуется"
|
||||
}
|
||||
},
|
||||
"messages": {
|
||||
"created": "Ссылка для получения успешно создана!",
|
||||
@@ -1302,7 +1322,9 @@
|
||||
"fileTypeNotAllowed": "Тип файла не разрешен. Разрешенные типы: {allowedTypes}",
|
||||
"maxFilesExceeded": "Максимально разрешено {maxFiles} файлов",
|
||||
"selectAtLeastOneFile": "Выберите хотя бы один файл",
|
||||
"provideNameOrEmail": "Укажите ваше имя или email"
|
||||
"provideNameOrEmail": "Укажите ваше имя или email",
|
||||
"provideEmailRequired": "Электронная почта требуется",
|
||||
"provideNameRequired": "Имя требуется"
|
||||
},
|
||||
"fileDropzone": {
|
||||
"dragActive": "Отпустите файлы здесь",
|
||||
@@ -1325,7 +1347,9 @@
|
||||
"descriptionLabel": "Описание (необязательно)",
|
||||
"descriptionPlaceholder": "Добавьте описание к файлам...",
|
||||
"uploadButton": "Отправить {count} файл(ов)",
|
||||
"uploading": "Отправка..."
|
||||
"uploading": "Отправка...",
|
||||
"emailLabelOptional": "Электронная почта (необязательно)",
|
||||
"nameLabelOptional": "Имя (необязательно)"
|
||||
},
|
||||
"success": {
|
||||
"title": "Файлы успешно отправлены! 🎉",
|
||||
|
@@ -1024,7 +1024,15 @@
|
||||
"noFilesLimit": "Dosya sınırı yok",
|
||||
"noSizeLimit": "Boyut sınırı yok",
|
||||
"allFileTypes": "Tüm dosya türleri",
|
||||
"fileTypesHelp": "Uzantıları nokta olmadan, boşluk, virgül, tire veya dikey çizgi ile ayırarak girin"
|
||||
"fileTypesHelp": "Uzantıları nokta olmadan, boşluk, virgül, tire veya dikey çizgi ile ayırarak girin",
|
||||
"emailFieldRequired": "E -posta alanı",
|
||||
"fieldOptions": {
|
||||
"hidden": "Gizlenmiş",
|
||||
"optional": "İsteğe bağlı",
|
||||
"required": "Gerekli"
|
||||
},
|
||||
"fieldRequirements": "Saha Gereksinimleri",
|
||||
"nameFieldRequired": "İsim alanı"
|
||||
},
|
||||
"card": {
|
||||
"untitled": "Başlıksız bağlantı",
|
||||
@@ -1242,7 +1250,19 @@
|
||||
"passwordHelp": "Şifre en az 4 karakter olmalıdır",
|
||||
"passwordPlaceholder": "Bağlantıyı korumak için şifre girin"
|
||||
},
|
||||
"submit": "Alma Bağlantısı Oluştur"
|
||||
"submit": "Alma Bağlantısı Oluştur",
|
||||
"emailFieldRequired": {
|
||||
"label": "E -posta alanı gereksinimi",
|
||||
"description": "Yükleyici e -posta alanının gösterilmesi gerekip gerekmediğini ve gerekli olup olmadığını yapılandırın"
|
||||
},
|
||||
"fieldRequirements": {
|
||||
"title": "Saha Gereksinimleri",
|
||||
"description": "Hangi alanların yükleme formunda gösterildiğini yapılandırın"
|
||||
},
|
||||
"nameFieldRequired": {
|
||||
"label": "İsim alanı gereksinimi",
|
||||
"description": "Yükleyici adı alanının gösterilmesi gerekip gerekmediğini ve gerekli olup olmadığını yapılandırın"
|
||||
}
|
||||
},
|
||||
"messages": {
|
||||
"created": "Alma bağlantısı başarıyla oluşturuldu!",
|
||||
@@ -1302,7 +1322,9 @@
|
||||
"fileTypeNotAllowed": "Dosya türüne izin verilmiyor. İzin verilen türler: {allowedTypes}",
|
||||
"maxFilesExceeded": "Maksimum {maxFiles} dosyaya izin veriliyor",
|
||||
"selectAtLeastOneFile": "En az bir dosya seçin",
|
||||
"provideNameOrEmail": "Adınızı veya e-postanızı girin"
|
||||
"provideNameOrEmail": "Adınızı veya e-postanızı girin",
|
||||
"provideEmailRequired": "E -posta gerekli",
|
||||
"provideNameRequired": "İsim gerekli"
|
||||
},
|
||||
"fileDropzone": {
|
||||
"dragActive": "Dosyaları buraya bırakın",
|
||||
@@ -1325,7 +1347,9 @@
|
||||
"descriptionLabel": "Açıklama (isteğe bağlı)",
|
||||
"descriptionPlaceholder": "Dosyalara açıklama ekleyin...",
|
||||
"uploadButton": "{count} dosya gönder",
|
||||
"uploading": "Gönderiliyor..."
|
||||
"uploading": "Gönderiliyor...",
|
||||
"emailLabelOptional": "E -posta (isteğe bağlı)",
|
||||
"nameLabelOptional": "İsim (isteğe bağlı)"
|
||||
},
|
||||
"success": {
|
||||
"title": "Dosyalar başarıyla gönderildi! 🎉",
|
||||
|
@@ -1024,7 +1024,15 @@
|
||||
"noFilesLimit": "无文件数限制",
|
||||
"noSizeLimit": "无大小限制",
|
||||
"allFileTypes": "所有文件类型",
|
||||
"fileTypesHelp": "输入不带点的扩展名,用空格、逗号、横线或竖线分隔"
|
||||
"fileTypesHelp": "输入不带点的扩展名,用空格、逗号、横线或竖线分隔",
|
||||
"emailFieldRequired": "电子邮件字段",
|
||||
"fieldOptions": {
|
||||
"hidden": "隐",
|
||||
"optional": "选修的",
|
||||
"required": "必需的"
|
||||
},
|
||||
"fieldRequirements": "现场要求",
|
||||
"nameFieldRequired": "名称字段"
|
||||
},
|
||||
"card": {
|
||||
"untitled": "无标题链接",
|
||||
@@ -1242,7 +1250,19 @@
|
||||
"passwordHelp": "密码至少需要4个字符",
|
||||
"passwordPlaceholder": "输入密码以保护链接"
|
||||
},
|
||||
"submit": "创建接收链接"
|
||||
"submit": "创建接收链接",
|
||||
"emailFieldRequired": {
|
||||
"label": "电子邮件字段要求",
|
||||
"description": "配置是否应显示上传器电子邮件字段以及是否需要"
|
||||
},
|
||||
"fieldRequirements": {
|
||||
"title": "现场要求",
|
||||
"description": "配置在上传表单中显示哪些字段"
|
||||
},
|
||||
"nameFieldRequired": {
|
||||
"label": "名称字段要求",
|
||||
"description": "配置是否应显示上传器名称字段以及是否需要"
|
||||
}
|
||||
},
|
||||
"messages": {
|
||||
"created": "接收链接创建成功!",
|
||||
@@ -1302,7 +1322,9 @@
|
||||
"fileTypeNotAllowed": "不允许的文件类型。允许的类型:{allowedTypes}",
|
||||
"maxFilesExceeded": "最多允许 {maxFiles} 个文件",
|
||||
"selectAtLeastOneFile": "请至少选择一个文件",
|
||||
"provideNameOrEmail": "请提供您的姓名或电子邮件"
|
||||
"provideNameOrEmail": "请提供您的姓名或电子邮件",
|
||||
"provideEmailRequired": "需要电子邮件",
|
||||
"provideNameRequired": "需要名称"
|
||||
},
|
||||
"fileDropzone": {
|
||||
"dragActive": "将文件拖放到此处",
|
||||
@@ -1325,7 +1347,9 @@
|
||||
"descriptionLabel": "描述(可选)",
|
||||
"descriptionPlaceholder": "为文件添加描述...",
|
||||
"uploadButton": "上传 {count} 个文件",
|
||||
"uploading": "上传中..."
|
||||
"uploading": "上传中...",
|
||||
"emailLabelOptional": "电子邮件(可选)",
|
||||
"nameLabelOptional": "名称(可选)"
|
||||
},
|
||||
"success": {
|
||||
"title": "文件上传成功!🎉",
|
||||
|
187
apps/web/scripts/clean_translations.py
Executable file
187
apps/web/scripts/clean_translations.py
Executable file
@@ -0,0 +1,187 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Script to clean up translation files that have multiple [TO_TRANSLATE] prefixes.
|
||||
This fixes the issue where sync_translations.py added multiple prefixes.
|
||||
"""
|
||||
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
from pathlib import Path
|
||||
from typing import Dict, Any
|
||||
import argparse
|
||||
|
||||
|
||||
def load_json_file(file_path: Path) -> Dict[str, Any]:
|
||||
"""Load a JSON file."""
|
||||
try:
|
||||
with open(file_path, 'r', encoding='utf-8') as f:
|
||||
return json.load(f)
|
||||
except Exception as e:
|
||||
print(f"Error loading {file_path}: {e}")
|
||||
return {}
|
||||
|
||||
|
||||
def save_json_file(file_path: Path, data: Dict[str, Any], indent: int = 2) -> bool:
|
||||
"""Save a JSON file with consistent formatting."""
|
||||
try:
|
||||
with open(file_path, 'w', encoding='utf-8') as f:
|
||||
json.dump(data, f, ensure_ascii=False, indent=indent, separators=(',', ': '))
|
||||
f.write('\n') # Add newline at the end
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"Error saving {file_path}: {e}")
|
||||
return False
|
||||
|
||||
|
||||
def clean_translate_prefixes(value: Any) -> Any:
|
||||
"""Clean multiple [TO_TRANSLATE] prefixes from a value."""
|
||||
if isinstance(value, str):
|
||||
# Remove multiple [TO_TRANSLATE] prefixes, keeping only one
|
||||
# Pattern matches multiple [TO_TRANSLATE] followed by optional spaces
|
||||
pattern = r'(\[TO_TRANSLATE\]\s*)+'
|
||||
cleaned = re.sub(pattern, '[TO_TRANSLATE] ', value)
|
||||
|
||||
# If the original value had [TO_TRANSLATE] prefixes, ensure it starts with exactly one
|
||||
if '[TO_TRANSLATE]' in value:
|
||||
# Remove any leading [TO_TRANSLATE] first
|
||||
without_prefix = re.sub(r'^\[TO_TRANSLATE\]\s*', '', cleaned)
|
||||
# Add exactly one prefix
|
||||
cleaned = f'[TO_TRANSLATE] {without_prefix}'
|
||||
|
||||
return cleaned
|
||||
elif isinstance(value, dict):
|
||||
return {k: clean_translate_prefixes(v) for k, v in value.items()}
|
||||
elif isinstance(value, list):
|
||||
return [clean_translate_prefixes(item) for item in value]
|
||||
else:
|
||||
return value
|
||||
|
||||
|
||||
def clean_translation_file(file_path: Path, dry_run: bool = False) -> Dict[str, int]:
|
||||
"""Clean a single translation file and return statistics."""
|
||||
print(f"Processing: {file_path.name}")
|
||||
|
||||
# Load the file
|
||||
data = load_json_file(file_path)
|
||||
if not data:
|
||||
print(f" ❌ Error loading file")
|
||||
return {'errors': 1, 'cleaned': 0, 'unchanged': 0}
|
||||
|
||||
# Clean the data
|
||||
cleaned_data = clean_translate_prefixes(data)
|
||||
|
||||
# Count changes by comparing JSON strings
|
||||
original_str = json.dumps(data, sort_keys=True)
|
||||
cleaned_str = json.dumps(cleaned_data, sort_keys=True)
|
||||
|
||||
if original_str == cleaned_str:
|
||||
print(f" ✅ No changes needed")
|
||||
return {'errors': 0, 'cleaned': 0, 'unchanged': 1}
|
||||
|
||||
# Count how many strings were affected
|
||||
def count_translate_strings(obj, prefix_count=0):
|
||||
if isinstance(obj, str):
|
||||
return prefix_count + (1 if '[TO_TRANSLATE]' in obj else 0)
|
||||
elif isinstance(obj, dict):
|
||||
return sum(count_translate_strings(v, prefix_count) for v in obj.values())
|
||||
elif isinstance(obj, list):
|
||||
return sum(count_translate_strings(item, prefix_count) for item in obj)
|
||||
return prefix_count
|
||||
|
||||
original_count = count_translate_strings(data)
|
||||
cleaned_count = count_translate_strings(cleaned_data)
|
||||
|
||||
if dry_run:
|
||||
print(f" 📝 [DRY RUN] Would clean {original_count - cleaned_count} strings with multiple prefixes")
|
||||
return {'errors': 0, 'cleaned': 1, 'unchanged': 0}
|
||||
else:
|
||||
# Save the cleaned data
|
||||
if save_json_file(file_path, cleaned_data):
|
||||
print(f" 🔄 Cleaned {original_count - cleaned_count} strings with multiple prefixes")
|
||||
return {'errors': 0, 'cleaned': 1, 'unchanged': 0}
|
||||
else:
|
||||
print(f" ❌ Error saving file")
|
||||
return {'errors': 1, 'cleaned': 0, 'unchanged': 0}
|
||||
|
||||
|
||||
def clean_translations(messages_dir: Path, exclude_reference: str = 'en-US.json',
|
||||
dry_run: bool = False) -> None:
|
||||
"""Clean all translation files in the directory."""
|
||||
|
||||
# Find all JSON files except the reference file
|
||||
json_files = [f for f in messages_dir.glob('*.json') if f.name != exclude_reference]
|
||||
|
||||
if not json_files:
|
||||
print("No translation files found")
|
||||
return
|
||||
|
||||
print(f"Found {len(json_files)} translation files to process\n")
|
||||
|
||||
stats = {'errors': 0, 'cleaned': 0, 'unchanged': 0}
|
||||
|
||||
for json_file in sorted(json_files):
|
||||
file_stats = clean_translation_file(json_file, dry_run)
|
||||
for key in stats:
|
||||
stats[key] += file_stats[key]
|
||||
print()
|
||||
|
||||
# Show summary
|
||||
print("=" * 60)
|
||||
print("SUMMARY")
|
||||
print("=" * 60)
|
||||
|
||||
if dry_run:
|
||||
print("🔍 DRY RUN MODE - No changes were made\n")
|
||||
|
||||
print(f"✅ Files unchanged: {stats['unchanged']}")
|
||||
print(f"🔄 Files cleaned: {stats['cleaned']}")
|
||||
print(f"❌ Files with errors: {stats['errors']}")
|
||||
|
||||
if stats['cleaned'] > 0 and not dry_run:
|
||||
print(f"\n🎉 Successfully cleaned {stats['cleaned']} files!")
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
description='Clean up translation files with multiple [TO_TRANSLATE] prefixes'
|
||||
)
|
||||
parser.add_argument(
|
||||
'--messages-dir',
|
||||
type=Path,
|
||||
default=Path(__file__).parent.parent / 'messages',
|
||||
help='Directory containing message files (default: ../messages)'
|
||||
)
|
||||
parser.add_argument(
|
||||
'--exclude-reference',
|
||||
default='en-US.json',
|
||||
help='Reference file to exclude from cleaning (default: en-US.json)'
|
||||
)
|
||||
parser.add_argument(
|
||||
'--dry-run',
|
||||
action='store_true',
|
||||
help='Only show what would be changed without making modifications'
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
if not args.messages_dir.exists():
|
||||
print(f"Directory not found: {args.messages_dir}")
|
||||
return 1
|
||||
|
||||
print(f"Directory: {args.messages_dir}")
|
||||
print(f"Exclude: {args.exclude_reference}")
|
||||
print(f"Dry run: {args.dry_run}")
|
||||
print("-" * 60)
|
||||
|
||||
clean_translations(
|
||||
messages_dir=args.messages_dir,
|
||||
exclude_reference=args.exclude_reference,
|
||||
dry_run=args.dry_run
|
||||
)
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
exit(main())
|
@@ -96,8 +96,14 @@ def add_missing_keys(reference_data: Dict[str, Any], target_data: Dict[str, Any]
|
||||
reference_value = get_nested_value(reference_data, key_path)
|
||||
|
||||
if reference_value is not None:
|
||||
# If marking as untranslated, add prefix
|
||||
# Check if the key already exists in target with a [TO_TRANSLATE] prefix
|
||||
existing_value = get_nested_value(target_data, key_path)
|
||||
|
||||
if mark_as_untranslated and isinstance(reference_value, str):
|
||||
# If the existing value already starts with [TO_TRANSLATE], don't add another prefix
|
||||
if existing_value and isinstance(existing_value, str) and existing_value.startswith("[TO_TRANSLATE]"):
|
||||
translated_value = existing_value
|
||||
else:
|
||||
translated_value = f"[TO_TRANSLATE] {reference_value}"
|
||||
else:
|
||||
translated_value = reference_value
|
||||
|
@@ -194,10 +194,26 @@ export function FileUploadSection({ reverseShare, password, alias, onUploadSucce
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if either name or email is required
|
||||
const nameRequired = reverseShare.nameFieldRequired === "REQUIRED";
|
||||
const emailRequired = reverseShare.emailFieldRequired === "REQUIRED";
|
||||
|
||||
if (nameRequired && !uploaderName.trim()) {
|
||||
toast.error(t("reverseShares.upload.errors.provideNameRequired"));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (emailRequired && !uploaderEmail.trim()) {
|
||||
toast.error(t("reverseShares.upload.errors.provideEmailRequired"));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (reverseShare.nameFieldRequired === "OPTIONAL" && reverseShare.emailFieldRequired === "OPTIONAL") {
|
||||
if (!uploaderName.trim() && !uploaderEmail.trim()) {
|
||||
toast.error(t("reverseShares.upload.errors.provideNameOrEmail"));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
@@ -231,7 +247,27 @@ export function FileUploadSection({ reverseShare, password, alias, onUploadSucce
|
||||
}
|
||||
};
|
||||
|
||||
const canUpload = files.length > 0 && (uploaderName.trim() || uploaderEmail.trim()) && !isUploading;
|
||||
const getCanUpload = (): boolean => {
|
||||
if (files.length === 0 || isUploading) return false;
|
||||
|
||||
const nameRequired = reverseShare.nameFieldRequired === "REQUIRED";
|
||||
const emailRequired = reverseShare.emailFieldRequired === "REQUIRED";
|
||||
const nameHidden = reverseShare.nameFieldRequired === "HIDDEN";
|
||||
const emailHidden = reverseShare.emailFieldRequired === "HIDDEN";
|
||||
|
||||
if (nameHidden && emailHidden) return true;
|
||||
|
||||
if (nameRequired && !uploaderName.trim()) return false;
|
||||
|
||||
if (emailRequired && !uploaderEmail.trim()) return false;
|
||||
|
||||
if (reverseShare.nameFieldRequired === "OPTIONAL" && reverseShare.emailFieldRequired === "OPTIONAL") {
|
||||
return !!(uploaderName.trim() || uploaderEmail.trim());
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
const canUpload = getCanUpload();
|
||||
const allFilesProcessed = files.every(
|
||||
(file) => file.status === FILE_STATUS.SUCCESS || file.status === FILE_STATUS.ERROR
|
||||
);
|
||||
@@ -379,10 +415,14 @@ export function FileUploadSection({ reverseShare, password, alias, onUploadSucce
|
||||
|
||||
<div className="space-y-4">
|
||||
<div className="grid grid-cols-1 gap-4">
|
||||
{reverseShare.nameFieldRequired !== "HIDDEN" && (
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="name">
|
||||
<IconUser className="inline h-4 w-4" />
|
||||
{t("reverseShares.upload.form.nameLabel")}
|
||||
{reverseShare.nameFieldRequired === "OPTIONAL"
|
||||
? t("reverseShares.upload.form.nameLabelOptional")
|
||||
: t("reverseShares.upload.form.nameLabel")}
|
||||
{reverseShare.nameFieldRequired === "REQUIRED" && <span className="text-red-500 ml-1">*</span>}
|
||||
</Label>
|
||||
<Input
|
||||
id="name"
|
||||
@@ -390,12 +430,18 @@ export function FileUploadSection({ reverseShare, password, alias, onUploadSucce
|
||||
value={uploaderName}
|
||||
onChange={(e) => setUploaderName(e.target.value)}
|
||||
disabled={isUploading}
|
||||
required={reverseShare.nameFieldRequired === "REQUIRED"}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{reverseShare.emailFieldRequired !== "HIDDEN" && (
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="email">
|
||||
<IconMail className="inline h-4 w-4" />
|
||||
{t("reverseShares.upload.form.emailLabel")}
|
||||
{reverseShare.emailFieldRequired === "OPTIONAL"
|
||||
? t("reverseShares.upload.form.emailLabelOptional")
|
||||
: t("reverseShares.upload.form.emailLabel")}
|
||||
{reverseShare.emailFieldRequired === "REQUIRED" && <span className="text-red-500 ml-1">*</span>}
|
||||
</Label>
|
||||
<Input
|
||||
id="email"
|
||||
@@ -404,8 +450,10 @@ export function FileUploadSection({ reverseShare, password, alias, onUploadSucce
|
||||
value={uploaderEmail}
|
||||
onChange={(e) => setUploaderEmail(e.target.value)}
|
||||
disabled={isUploading}
|
||||
required={reverseShare.emailFieldRequired === "REQUIRED"}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="description">{t("reverseShares.upload.form.descriptionLabel")}</Label>
|
||||
|
@@ -8,8 +8,10 @@ import {
|
||||
IconFile,
|
||||
IconFiles,
|
||||
IconLock,
|
||||
IconMail,
|
||||
IconSettings,
|
||||
IconUpload,
|
||||
IconUser,
|
||||
} from "@tabler/icons-react";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { useForm } from "react-hook-form";
|
||||
@@ -55,9 +57,12 @@ interface CreateReverseShareFormData {
|
||||
allowedFileTypes?: string;
|
||||
password?: string;
|
||||
pageLayout?: "DEFAULT" | "WETRANSFER";
|
||||
nameFieldRequired: "HIDDEN" | "OPTIONAL" | "REQUIRED";
|
||||
emailFieldRequired: "HIDDEN" | "OPTIONAL" | "REQUIRED";
|
||||
isPasswordProtected: boolean;
|
||||
hasExpiration: boolean;
|
||||
hasFileLimits: boolean;
|
||||
hasFieldRequirements: boolean;
|
||||
noFilesLimit: boolean;
|
||||
noSizeLimit: boolean;
|
||||
allFileTypes: boolean;
|
||||
@@ -79,9 +84,12 @@ const DEFAULT_FORM_VALUES: CreateReverseShareFormData = {
|
||||
allowedFileTypes: "",
|
||||
password: "",
|
||||
pageLayout: "DEFAULT",
|
||||
nameFieldRequired: "OPTIONAL",
|
||||
emailFieldRequired: "OPTIONAL",
|
||||
isPasswordProtected: false,
|
||||
hasExpiration: false,
|
||||
hasFileLimits: false,
|
||||
hasFieldRequirements: false,
|
||||
noFilesLimit: true,
|
||||
noSizeLimit: true,
|
||||
allFileTypes: true,
|
||||
@@ -103,6 +111,7 @@ export function CreateReverseShareModal({
|
||||
isPasswordProtected: form.watch("isPasswordProtected"),
|
||||
hasExpiration: form.watch("hasExpiration"),
|
||||
hasFileLimits: form.watch("hasFileLimits"),
|
||||
hasFieldRequirements: form.watch("hasFieldRequirements"),
|
||||
noFilesLimit: form.watch("noFilesLimit"),
|
||||
noSizeLimit: form.watch("noSizeLimit"),
|
||||
allFileTypes: form.watch("allFileTypes"),
|
||||
@@ -112,6 +121,8 @@ export function CreateReverseShareModal({
|
||||
const payload: CreateReverseShareBody = {
|
||||
name: formData.name,
|
||||
pageLayout: formData.pageLayout || "DEFAULT",
|
||||
nameFieldRequired: formData.nameFieldRequired,
|
||||
emailFieldRequired: formData.emailFieldRequired,
|
||||
};
|
||||
|
||||
if (formData.description?.trim()) {
|
||||
@@ -466,6 +477,126 @@ export function CreateReverseShareModal({
|
||||
)}
|
||||
</div>
|
||||
|
||||
<Separator />
|
||||
|
||||
{/* Field Requirements */}
|
||||
<div className="space-y-4">
|
||||
{renderSectionToggle(
|
||||
watchedValues.hasFieldRequirements,
|
||||
<IconUser size={ICON_SIZES.medium} />,
|
||||
t("reverseShares.form.fieldRequirements.title"),
|
||||
toggleSection("hasFieldRequirements")
|
||||
)}
|
||||
|
||||
{watchedValues.hasFieldRequirements && (
|
||||
<div className="bg-gray-50 dark:bg-gray-800/50 rounded-lg p-4 space-y-4">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="nameFieldRequired"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel className="flex items-center gap-2 font-medium">
|
||||
<IconUser size={ICON_SIZES.small} />
|
||||
{t("reverseShares.form.nameFieldRequired.label")}
|
||||
</FormLabel>
|
||||
<Select onValueChange={field.onChange} defaultValue={field.value}>
|
||||
<FormControl>
|
||||
<SelectTrigger className="bg-white dark:bg-gray-900">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
</FormControl>
|
||||
<SelectContent>
|
||||
<SelectItem value="HIDDEN">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="w-2 h-2 rounded-full bg-gray-400" />
|
||||
{t("reverseShares.labels.fieldOptions.hidden")}
|
||||
</div>
|
||||
</SelectItem>
|
||||
<SelectItem value="OPTIONAL">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="w-2 h-2 rounded-full bg-blue-500" />
|
||||
{t("reverseShares.labels.fieldOptions.optional")}
|
||||
</div>
|
||||
</SelectItem>
|
||||
<SelectItem value="REQUIRED">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="w-2 h-2 rounded-full bg-red-500" />
|
||||
{t("reverseShares.labels.fieldOptions.required")}
|
||||
</div>
|
||||
</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="emailFieldRequired"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel className="flex items-center gap-2 font-medium">
|
||||
<IconUser size={ICON_SIZES.small} />
|
||||
{t("reverseShares.form.emailFieldRequired.label")}
|
||||
</FormLabel>
|
||||
<Select onValueChange={field.onChange} defaultValue={field.value}>
|
||||
<FormControl>
|
||||
<SelectTrigger className="bg-white dark:bg-gray-900">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
</FormControl>
|
||||
<SelectContent>
|
||||
<SelectItem value="HIDDEN">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="w-2 h-2 rounded-full bg-gray-400" />
|
||||
{t("reverseShares.labels.fieldOptions.hidden")}
|
||||
</div>
|
||||
</SelectItem>
|
||||
<SelectItem value="OPTIONAL">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="w-2 h-2 rounded-full bg-blue-500" />
|
||||
{t("reverseShares.labels.fieldOptions.optional")}
|
||||
</div>
|
||||
</SelectItem>
|
||||
<SelectItem value="REQUIRED">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="w-2 h-2 rounded-full bg-red-500" />
|
||||
{t("reverseShares.labels.fieldOptions.required")}
|
||||
</div>
|
||||
</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="text-xs text-muted-foreground bg-blue-50 dark:bg-blue-950/20 p-3 rounded-md border border-blue-200 dark:border-blue-800">
|
||||
<div className="flex items-start gap-2">
|
||||
<IconSettings size={12} className="mt-0.5 text-blue-600 dark:text-blue-400" />
|
||||
<div className="space-y-1">
|
||||
<p className="font-medium text-blue-900 dark:text-blue-100">Field Configuration:</p>
|
||||
<ul className="space-y-0.5 text-blue-800 dark:text-blue-200">
|
||||
<li>
|
||||
• <strong>Hidden:</strong> Field won't appear in the upload form
|
||||
</li>
|
||||
<li>
|
||||
• <strong>Optional:</strong> Field appears but isn't required
|
||||
</li>
|
||||
<li>
|
||||
• <strong>Required:</strong> Field appears and must be filled
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<DialogFooter className="gap-2">
|
||||
<Button type="button" variant="outline" onClick={handleClose} disabled={isCreating}>
|
||||
{t("common.cancel")}
|
||||
|
@@ -11,6 +11,7 @@ import {
|
||||
IconFiles,
|
||||
IconLock,
|
||||
IconSettings,
|
||||
IconUser,
|
||||
} from "@tabler/icons-react";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { useForm } from "react-hook-form";
|
||||
@@ -51,8 +52,11 @@ interface EditReverseShareFormData {
|
||||
maxFileSize?: string;
|
||||
allowedFileTypes?: string;
|
||||
pageLayout?: "DEFAULT" | "WETRANSFER";
|
||||
nameFieldRequired: "HIDDEN" | "OPTIONAL" | "REQUIRED";
|
||||
emailFieldRequired: "HIDDEN" | "OPTIONAL" | "REQUIRED";
|
||||
hasExpiration: boolean;
|
||||
hasFileLimits: boolean;
|
||||
hasFieldRequirements: boolean;
|
||||
hasPassword: boolean;
|
||||
password?: string;
|
||||
isActive: boolean;
|
||||
@@ -85,6 +89,7 @@ export function EditReverseShareModal({
|
||||
const watchedValues = {
|
||||
hasExpiration: form.watch("hasExpiration"),
|
||||
hasFileLimits: form.watch("hasFileLimits"),
|
||||
hasFieldRequirements: form.watch("hasFieldRequirements"),
|
||||
noFilesLimit: form.watch("noFilesLimit"),
|
||||
noSizeLimit: form.watch("noSizeLimit"),
|
||||
allFileTypes: form.watch("allFileTypes"),
|
||||
@@ -136,6 +141,8 @@ export function EditReverseShareModal({
|
||||
/>
|
||||
<Separator />
|
||||
<PasswordSection form={form} t={t} hasPassword={watchedValues.hasPassword} />
|
||||
<Separator />
|
||||
<FieldRequirementsSection form={form} t={t} hasFieldRequirements={watchedValues.hasFieldRequirements} />
|
||||
|
||||
<DialogFooter className="gap-2">
|
||||
<Button type="button" variant="outline" onClick={onClose} disabled={isUpdating}>
|
||||
@@ -169,8 +176,11 @@ function getFormDefaultValues(): EditReverseShareFormData {
|
||||
maxFileSize: DEFAULT_VALUES.EMPTY_STRING,
|
||||
allowedFileTypes: DEFAULT_VALUES.EMPTY_STRING,
|
||||
pageLayout: DEFAULT_VALUES.PAGE_LAYOUT,
|
||||
nameFieldRequired: "OPTIONAL",
|
||||
emailFieldRequired: "OPTIONAL",
|
||||
hasExpiration: false,
|
||||
hasFileLimits: false,
|
||||
hasFieldRequirements: false,
|
||||
hasPassword: false,
|
||||
password: DEFAULT_VALUES.EMPTY_STRING,
|
||||
isActive: true,
|
||||
@@ -196,8 +206,11 @@ function mapReverseShareToFormData(reverseShare: ReverseShare): EditReverseShare
|
||||
maxFileSize: maxFileSizeValue,
|
||||
allowedFileTypes: allowedFileTypesValue,
|
||||
pageLayout: (reverseShare.pageLayout as "DEFAULT" | "WETRANSFER") || DEFAULT_VALUES.PAGE_LAYOUT,
|
||||
nameFieldRequired: (reverseShare.nameFieldRequired as "HIDDEN" | "OPTIONAL" | "REQUIRED") || "OPTIONAL",
|
||||
emailFieldRequired: (reverseShare.emailFieldRequired as "HIDDEN" | "OPTIONAL" | "REQUIRED") || "OPTIONAL",
|
||||
hasExpiration: false,
|
||||
hasFileLimits: false,
|
||||
hasFieldRequirements: false,
|
||||
hasPassword: false,
|
||||
password: DEFAULT_VALUES.EMPTY_STRING,
|
||||
isActive: reverseShare.isActive,
|
||||
@@ -213,6 +226,8 @@ function buildUpdatePayload(data: EditReverseShareFormData, id: string): UpdateR
|
||||
name: data.name,
|
||||
pageLayout: data.pageLayout || DEFAULT_VALUES.PAGE_LAYOUT,
|
||||
isActive: data.isActive,
|
||||
nameFieldRequired: data.nameFieldRequired,
|
||||
emailFieldRequired: data.emailFieldRequired,
|
||||
};
|
||||
|
||||
if (data.description?.trim()) {
|
||||
@@ -572,3 +587,137 @@ function PasswordSection({ form, t, hasPassword }: { form: any; t: any; hasPassw
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function FieldRequirementsSection({
|
||||
form,
|
||||
t,
|
||||
hasFieldRequirements,
|
||||
}: {
|
||||
form: any;
|
||||
t: any;
|
||||
hasFieldRequirements: boolean;
|
||||
}) {
|
||||
const toggleFieldRequirements = () => {
|
||||
const newValue = !hasFieldRequirements;
|
||||
form.setValue("hasFieldRequirements", newValue);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
{createToggleButton(
|
||||
hasFieldRequirements,
|
||||
toggleFieldRequirements,
|
||||
<IconUser size={16} />,
|
||||
t("reverseShares.form.fieldRequirements.title")
|
||||
)}
|
||||
|
||||
{hasFieldRequirements && (
|
||||
<div className="bg-gray-50 dark:bg-gray-800/50 rounded-lg p-4 space-y-4">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="nameFieldRequired"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel className="flex items-center gap-2 font-medium">
|
||||
<IconUser size={14} />
|
||||
{t("reverseShares.form.nameFieldRequired.label")}
|
||||
</FormLabel>
|
||||
<Select onValueChange={field.onChange} value={field.value}>
|
||||
<FormControl>
|
||||
<SelectTrigger className="bg-white dark:bg-gray-900">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
</FormControl>
|
||||
<SelectContent>
|
||||
<SelectItem value="HIDDEN">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="w-2 h-2 rounded-full bg-gray-400" />
|
||||
{t("reverseShares.labels.fieldOptions.hidden")}
|
||||
</div>
|
||||
</SelectItem>
|
||||
<SelectItem value="OPTIONAL">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="w-2 h-2 rounded-full bg-blue-500" />
|
||||
{t("reverseShares.labels.fieldOptions.optional")}
|
||||
</div>
|
||||
</SelectItem>
|
||||
<SelectItem value="REQUIRED">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="w-2 h-2 rounded-full bg-red-500" />
|
||||
{t("reverseShares.labels.fieldOptions.required")}
|
||||
</div>
|
||||
</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="emailFieldRequired"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel className="flex items-center gap-2 font-medium">
|
||||
<IconUser size={14} />
|
||||
{t("reverseShares.form.emailFieldRequired.label")}
|
||||
</FormLabel>
|
||||
<Select onValueChange={field.onChange} value={field.value}>
|
||||
<FormControl>
|
||||
<SelectTrigger className="bg-white dark:bg-gray-900">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
</FormControl>
|
||||
<SelectContent>
|
||||
<SelectItem value="HIDDEN">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="w-2 h-2 rounded-full bg-gray-400" />
|
||||
{t("reverseShares.labels.fieldOptions.hidden")}
|
||||
</div>
|
||||
</SelectItem>
|
||||
<SelectItem value="OPTIONAL">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="w-2 h-2 rounded-full bg-blue-500" />
|
||||
{t("reverseShares.labels.fieldOptions.optional")}
|
||||
</div>
|
||||
</SelectItem>
|
||||
<SelectItem value="REQUIRED">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="w-2 h-2 rounded-full bg-red-500" />
|
||||
{t("reverseShares.labels.fieldOptions.required")}
|
||||
</div>
|
||||
</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="text-xs text-muted-foreground bg-blue-50 dark:bg-blue-950/20 p-3 rounded-md border border-blue-200 dark:border-blue-800">
|
||||
<div className="flex items-start gap-2">
|
||||
<IconSettings size={12} className="mt-0.5 text-blue-600 dark:text-blue-400" />
|
||||
<div className="space-y-1">
|
||||
<p className="font-medium text-blue-900 dark:text-blue-100">Field Configuration:</p>
|
||||
<ul className="space-y-0.5 text-blue-800 dark:text-blue-200">
|
||||
<li>
|
||||
• <strong>Hidden:</strong> Field won't appear in the upload form
|
||||
</li>
|
||||
<li>
|
||||
• <strong>Optional:</strong> Field appears but isn't required
|
||||
</li>
|
||||
<li>
|
||||
• <strong>Required:</strong> Field appears and must be filled
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@@ -109,6 +109,9 @@ const getFileNameWithoutExtension = (fileName: string) => {
|
||||
};
|
||||
|
||||
const getSenderDisplay = (file: ReverseShareFile, t: any) => {
|
||||
if (file.uploaderName && file.uploaderEmail) {
|
||||
return `${file.uploaderName} (${file.uploaderEmail})`;
|
||||
}
|
||||
if (file.uploaderName) return file.uploaderName;
|
||||
if (file.uploaderEmail) return file.uploaderEmail;
|
||||
return t("reverseShares.components.fileRow.anonymous");
|
||||
@@ -341,12 +344,12 @@ function FileRow({
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell className="font-mono text-sm">{formatFileSize(file.size)}</TableCell>
|
||||
<TableCell>
|
||||
<div className="flex items-center gap-2">
|
||||
<Avatar className="h-6 w-6">
|
||||
<TableCell className="max-w-[200px]">
|
||||
<div className="flex items-center gap-2 min-w-0">
|
||||
<Avatar className="h-6 w-6 flex-shrink-0">
|
||||
<AvatarFallback className="text-xs">{getSenderInitials(file)}</AvatarFallback>
|
||||
</Avatar>
|
||||
<span className="text-sm truncate" title={getSenderDisplay(file, t)}>
|
||||
<span className="text-sm truncate min-w-0" title={getSenderDisplay(file, t)}>
|
||||
{getSenderDisplay(file, t)}
|
||||
</span>
|
||||
</div>
|
||||
|
@@ -32,6 +32,15 @@ export function ReceivedFilesSection({ files, onFileDeleted }: ReceivedFilesSect
|
||||
const t = useTranslations();
|
||||
const [previewFile, setPreviewFile] = useState<ReverseShareFile | null>(null);
|
||||
|
||||
const getSenderDisplay = (file: ReverseShareFile) => {
|
||||
if (file.uploaderName && file.uploaderEmail) {
|
||||
return `${file.uploaderName}(${file.uploaderEmail})`;
|
||||
}
|
||||
if (file.uploaderName) return file.uploaderName;
|
||||
if (file.uploaderEmail) return file.uploaderEmail;
|
||||
return t("reverseShares.components.fileRow.anonymous");
|
||||
};
|
||||
|
||||
const formatFileSize = (size: string | number | null) => {
|
||||
if (!size) return "0 B";
|
||||
const sizeInBytes = typeof size === "string" ? parseInt(size) : size;
|
||||
@@ -119,10 +128,10 @@ export function ReceivedFilesSection({ files, onFileDeleted }: ReceivedFilesSect
|
||||
</div>
|
||||
<div className="flex items-center gap-2 text-xs text-muted-foreground">
|
||||
<span>{formatFileSize(file.size)}</span>
|
||||
{file.uploaderEmail && (
|
||||
{(file.uploaderName || file.uploaderEmail) && (
|
||||
<>
|
||||
<span>•</span>
|
||||
<span title={file.uploaderEmail}>{file.uploaderName || file.uploaderEmail}</span>
|
||||
<span title={getSenderDisplay(file)}>{getSenderDisplay(file)}</span>
|
||||
</>
|
||||
)}
|
||||
<span>•</span>
|
||||
|
@@ -243,6 +243,16 @@ export function ReverseShareCard({
|
||||
<IconEye className="h-3 w-3" />
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="h-6 w-6 p-0 hover:bg-background/80 rounded-sm"
|
||||
onClick={() => onEdit(reverseShare)}
|
||||
title={t("reverseShares.actions.edit")}
|
||||
>
|
||||
<IconEdit className="h-3 w-3" />
|
||||
</Button>
|
||||
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="outline" size="sm" className="h-6 w-6 p-0 hover:bg-background/80 rounded-sm">
|
||||
@@ -270,7 +280,7 @@ export function ReverseShareCard({
|
||||
)}
|
||||
|
||||
<DropdownMenuItem onClick={() => onEdit(reverseShare)}>
|
||||
<IconFileText className="h-4 w-4 mr-2" />
|
||||
<IconEdit className="h-4 w-4 mr-2" />
|
||||
{t("reverseShares.actions.edit")}
|
||||
</DropdownMenuItem>
|
||||
|
||||
|
@@ -1,5 +1,7 @@
|
||||
import type { AxiosResponse } from "axios";
|
||||
|
||||
export type FieldRequirement = "HIDDEN" | "OPTIONAL" | "REQUIRED";
|
||||
|
||||
export type CreateReverseShareBody = {
|
||||
name?: string;
|
||||
description?: string;
|
||||
@@ -9,6 +11,8 @@ export type CreateReverseShareBody = {
|
||||
allowedFileTypes?: string | null;
|
||||
password?: string;
|
||||
pageLayout?: "WETRANSFER" | "DEFAULT";
|
||||
nameFieldRequired?: FieldRequirement;
|
||||
emailFieldRequired?: FieldRequirement;
|
||||
};
|
||||
|
||||
export type CreateReverseShareResult = AxiosResponse<{
|
||||
@@ -23,6 +27,8 @@ export type CreateReverseShareResult = AxiosResponse<{
|
||||
pageLayout: string;
|
||||
isActive: boolean;
|
||||
hasPassword: boolean;
|
||||
nameFieldRequired: string;
|
||||
emailFieldRequired: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
creatorId: string;
|
||||
@@ -41,6 +47,8 @@ export type UpdateReverseShareBody = {
|
||||
password?: string | null;
|
||||
pageLayout?: "WETRANSFER" | "DEFAULT";
|
||||
isActive?: boolean;
|
||||
nameFieldRequired?: FieldRequirement;
|
||||
emailFieldRequired?: FieldRequirement;
|
||||
};
|
||||
|
||||
export type UpdateReverseShareResult = AxiosResponse<{
|
||||
@@ -55,6 +63,8 @@ export type UpdateReverseShareResult = AxiosResponse<{
|
||||
pageLayout: string;
|
||||
isActive: boolean;
|
||||
hasPassword: boolean;
|
||||
nameFieldRequired: string;
|
||||
emailFieldRequired: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
creatorId: string;
|
||||
@@ -74,6 +84,8 @@ export type ListUserReverseSharesResult = AxiosResponse<{
|
||||
pageLayout: string;
|
||||
isActive: boolean;
|
||||
hasPassword: boolean;
|
||||
nameFieldRequired: string;
|
||||
emailFieldRequired: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
creatorId: string;
|
||||
@@ -100,6 +112,8 @@ export type GetReverseShareResult = AxiosResponse<{
|
||||
pageLayout: string;
|
||||
isActive: boolean;
|
||||
hasPassword: boolean;
|
||||
nameFieldRequired: string;
|
||||
emailFieldRequired: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
creatorId: string;
|
||||
@@ -126,6 +140,8 @@ export type DeleteReverseShareResult = AxiosResponse<{
|
||||
pageLayout: string;
|
||||
isActive: boolean;
|
||||
hasPassword: boolean;
|
||||
nameFieldRequired: string;
|
||||
emailFieldRequired: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
creatorId: string;
|
||||
@@ -148,6 +164,8 @@ export type GetReverseShareForUploadResult = AxiosResponse<{
|
||||
pageLayout: string;
|
||||
hasPassword: boolean;
|
||||
currentFileCount: number;
|
||||
nameFieldRequired: string;
|
||||
emailFieldRequired: string;
|
||||
};
|
||||
}>;
|
||||
|
||||
|
Reference in New Issue
Block a user