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:
Daniel Luiz Alves
2025-06-23 15:53:38 -03:00
parent 4e841b272c
commit 22c5a44af8
27 changed files with 1059 additions and 112 deletions

View File

@@ -161,18 +161,20 @@ model UserAuthProvider {
}
model ReverseShare {
id String @id @default(cuid())
name String?
description String?
expiration DateTime?
maxFiles Int?
maxFileSize BigInt?
allowedFileTypes String?
password String?
pageLayout PageLayout @default(DEFAULT)
isActive Boolean @default(true)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
id String @id @default(cuid())
name String?
description String?
expiration DateTime?
maxFiles Int?
maxFileSize BigInt?
allowedFileTypes String?
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
creatorId String
creator User @relation(fields: [creatorId], references: [id], onDelete: Cascade)
@@ -212,6 +214,12 @@ model ReverseShareAlias {
@@map("reverse_share_aliases")
}
enum FieldRequirement {
HIDDEN
OPTIONAL
REQUIRED
}
enum PageLayout {
DEFAULT
WETRANSFER

View File

@@ -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({

View File

@@ -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;

View File

@@ -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": "تم رفع الملفات بنجاح! 🎉",

View File

@@ -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! 🎉",
@@ -1373,4 +1397,4 @@
}
}
}
}
}

View File

@@ -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...",

View File

@@ -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! 🎉",

View File

@@ -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 ! 🎉",
@@ -1373,4 +1397,4 @@
}
}
}
}
}

View File

@@ -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": "फ़ाइलें सफलतापूर्वक भेजी गईं! 🎉",

View File

@@ -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! 🎉",
@@ -1373,4 +1397,4 @@
}
}
}
}
}

View File

@@ -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": "ファイルを送信しました! 🎉",

View File

@@ -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": "파일이 성공적으로 보내졌습니다! 🎉",

View File

@@ -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! 🎉",

View File

@@ -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! 🎉",

View File

@@ -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! 🎉",

View File

@@ -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": "Файлы успешно отправлены! 🎉",

View File

@@ -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! 🎉",

View File

@@ -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": "文件上传成功!🎉",

View 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())

View File

@@ -96,9 +96,15 @@ 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):
translated_value = f"[TO_TRANSLATE] {reference_value}"
# 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

View File

@@ -194,11 +194,27 @@ export function FileUploadSection({ reverseShare, password, alias, onUploadSucce
return false;
}
if (!uploaderName.trim() && !uploaderEmail.trim()) {
toast.error(t("reverseShares.upload.errors.provideNameOrEmail"));
// 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,33 +415,45 @@ export function FileUploadSection({ reverseShare, password, alias, onUploadSucce
<div className="space-y-4">
<div className="grid grid-cols-1 gap-4">
<div className="space-y-2">
<Label htmlFor="name">
<IconUser className="inline h-4 w-4" />
{t("reverseShares.upload.form.nameLabel")}
</Label>
<Input
id="name"
placeholder={t("reverseShares.upload.form.namePlaceholder")}
value={uploaderName}
onChange={(e) => setUploaderName(e.target.value)}
disabled={isUploading}
/>
</div>
<div className="space-y-2">
<Label htmlFor="email">
<IconMail className="inline h-4 w-4" />
{t("reverseShares.upload.form.emailLabel")}
</Label>
<Input
id="email"
type="email"
placeholder={t("reverseShares.upload.form.emailPlaceholder")}
value={uploaderEmail}
onChange={(e) => setUploaderEmail(e.target.value)}
disabled={isUploading}
/>
</div>
{reverseShare.nameFieldRequired !== "HIDDEN" && (
<div className="space-y-2">
<Label htmlFor="name">
<IconUser className="inline h-4 w-4" />
{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"
placeholder={t("reverseShares.upload.form.namePlaceholder")}
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" />
{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"
type="email"
placeholder={t("reverseShares.upload.form.emailPlaceholder")}
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>

View File

@@ -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")}

View File

@@ -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>
);
}

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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;
};
}>;