diff --git a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/responses/components/ResponseTable.test.tsx b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/responses/components/ResponseTable.test.tsx index eb59fd739c..d7d4d4c209 100644 --- a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/responses/components/ResponseTable.test.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/responses/components/ResponseTable.test.tsx @@ -325,6 +325,11 @@ beforeEach(() => { } } as any ); + + vi.stubGlobal("URL", { + createObjectURL: vi.fn(), + revokeObjectURL: vi.fn(), + }); }); // Cleanup after each test @@ -398,6 +403,8 @@ describe("ResponseTable", () => { const container = document.getElementById("test-container"); render(, { container: container! }); + // Ensure URL.createObjectURL returns a deterministic URL for assertions + (URL.createObjectURL as any).mockReturnValueOnce("https://download.url/file.csv"); const downloadCsvButton = screen.getByTestId("download-csv"); await userEvent.click(downloadCsvButton); @@ -427,6 +434,8 @@ describe("ResponseTable", () => { const container = document.getElementById("test-container"); render(, { container: container! }); + // Ensure URL.createObjectURL returns a deterministic URL for assertions + (URL.createObjectURL as any).mockReturnValueOnce("https://download.url/file.xlsx"); const downloadXlsxButton = screen.getByTestId("download-xlsx"); await userEvent.click(downloadXlsxButton); diff --git a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/responses/components/ResponseTable.tsx b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/responses/components/ResponseTable.tsx index 7092e8acde..7173c1529c 100644 --- a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/responses/components/ResponseTable.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/(analysis)/responses/components/ResponseTable.tsx @@ -221,14 +221,10 @@ export const ResponseTable = ({ }); } - try { - const url = URL.createObjectURL(file); - const fileName = downloadResponse.data.fileName || `${survey.name}-${format}.${format}`; - downloadFile(url, fileName); - URL.revokeObjectURL(url); - } catch { - toast.error(t("environments.surveys.responses.error_downloading_responses")); - } + const url = URL.createObjectURL(file); + const fileName = downloadResponse.data.fileName || `${survey.name}-${format}.${format}`; + downloadFile(url, fileName); + URL.revokeObjectURL(url); } else { toast.error(t("environments.surveys.responses.error_downloading_responses")); } diff --git a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/components/CustomFilter.tsx b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/components/CustomFilter.tsx index 0dead5d9ca..8fa91dc9ed 100755 --- a/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/components/CustomFilter.tsx +++ b/apps/web/app/(app)/environments/[environmentId]/surveys/[surveyId]/components/CustomFilter.tsx @@ -258,30 +258,30 @@ export const CustomFilter = ({ survey }: CustomFilterProps) => { }); if (responsesDownloadUrlResponse?.data) { - let file: File; - - if (filetype === "xlsx") { - // Convert base64 back to binary data for XLSX files - const binaryString = atob(responsesDownloadUrlResponse.data.fileContents); - const bytes = new Uint8Array(binaryString.length); - for (let i = 0; i < binaryString.length; i++) { - bytes[i] = binaryString.charCodeAt(i); - } - file = new File([bytes], responsesDownloadUrlResponse.data.fileName, { - type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", - }); - } else { - // For CSV files, use the string directly - file = new File( - [responsesDownloadUrlResponse.data.fileContents], - responsesDownloadUrlResponse.data.fileName, - { - type: "text/csv", - } - ); - } - try { + let file: File; + + if (filetype === "xlsx") { + // Convert base64 back to binary data for XLSX files + const binaryString = atob(responsesDownloadUrlResponse.data.fileContents); + const bytes = new Uint8Array(binaryString.length); + for (let i = 0; i < binaryString.length; i++) { + bytes[i] = binaryString.charCodeAt(i); + } + file = new File([bytes], responsesDownloadUrlResponse.data.fileName, { + type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", + }); + } else { + // For CSV files, use the string directly + file = new File( + [responsesDownloadUrlResponse.data.fileContents], + responsesDownloadUrlResponse.data.fileName, + { + type: "text/csv", + } + ); + } + const url = URL.createObjectURL(file); const fileName = responsesDownloadUrlResponse.data.fileName || `${survey.name}-${filetype}.${filetype}`; diff --git a/apps/web/app/storage/[environmentId]/[accessType]/[fileName]/lib/audit-logs.ts b/apps/web/app/storage/[environmentId]/[accessType]/[fileName]/lib/audit-logs.ts new file mode 100644 index 0000000000..41c849a963 --- /dev/null +++ b/apps/web/app/storage/[environmentId]/[accessType]/[fileName]/lib/audit-logs.ts @@ -0,0 +1,54 @@ +import { getOrganizationIdFromEnvironmentId } from "@/lib/utils/helper"; +import { queueAuditEvent } from "@/modules/ee/audit-logs/lib/handler"; +import { TAuditStatus, UNKNOWN_DATA } from "@/modules/ee/audit-logs/types/audit-log"; +import { logger } from "@formbricks/logger"; + +const getOrgId = async (environmentId: string): Promise => { + try { + return await getOrganizationIdFromEnvironmentId(environmentId); + } catch (error) { + logger.error("Failed to get organization ID for environment", { error }); + return UNKNOWN_DATA; + } +}; + +export const logFileDeletion = async ({ + environmentId, + accessType, + userId, + status = "failure", + failureReason, + oldObject, + apiUrl, +}: { + environmentId: string; + accessType?: string; + userId?: string; + status?: TAuditStatus; + failureReason?: string; + oldObject?: Record; + apiUrl: string; +}) => { + try { + const organizationId = await getOrgId(environmentId); + + await queueAuditEvent({ + action: "deleted", + targetType: "file", + userId: userId || UNKNOWN_DATA, // NOSONAR // We want to check for empty user IDs too + userType: "user", + targetId: `${environmentId}:${accessType}`, // Generic target identifier + organizationId, + status, + newObject: { + environmentId, + accessType, + ...(failureReason && { failureReason }), + }, + oldObject, + apiUrl, + }); + } catch (auditError) { + logger.error("Failed to log file deletion audit event:", auditError); + } +}; diff --git a/apps/web/app/storage/[environmentId]/[accessType]/[fileName]/route.ts b/apps/web/app/storage/[environmentId]/[accessType]/[fileName]/route.ts index f3e96553af..278ade2073 100644 --- a/apps/web/app/storage/[environmentId]/[accessType]/[fileName]/route.ts +++ b/apps/web/app/storage/[environmentId]/[accessType]/[fileName]/route.ts @@ -2,66 +2,15 @@ import { authenticateRequest } from "@/app/api/v1/auth"; import { responses } from "@/app/lib/api/response"; import { transformErrorToDetails } from "@/app/lib/api/validator"; import { hasUserEnvironmentAccess } from "@/lib/environment/auth"; -import { getOrganizationIdFromEnvironmentId } from "@/lib/utils/helper"; import { authOptions } from "@/modules/auth/lib/authOptions"; -import { queueAuditEvent } from "@/modules/ee/audit-logs/lib/handler"; -import { TAuditStatus, UNKNOWN_DATA } from "@/modules/ee/audit-logs/types/audit-log"; +import { hasPermission } from "@/modules/organization/settings/api-keys/lib/utils"; import { deleteFile, getSignedUrlForDownload } from "@/modules/storage/service"; import { getErrorResponseFromStorageError } from "@/modules/storage/utils"; import { getServerSession } from "next-auth"; import { type NextRequest } from "next/server"; import { logger } from "@formbricks/logger"; import { TAccessType, ZDeleteFileRequest, ZDownloadFileRequest } from "@formbricks/types/storage"; - -const getOrgId = async (environmentId: string): Promise => { - try { - return await getOrganizationIdFromEnvironmentId(environmentId); - } catch (error) { - logger.error("Failed to get organization ID for environment", { error }); - return UNKNOWN_DATA; - } -}; - -const logFileDeletion = async ({ - environmentId, - accessType, - userId, - status = "failure", - failureReason, - oldObject, - apiUrl, -}: { - environmentId: string; - accessType?: string; - userId?: string; - status?: TAuditStatus; - failureReason?: string; - oldObject?: Record; - apiUrl: string; -}) => { - try { - const organizationId = await getOrgId(environmentId); - - await queueAuditEvent({ - action: "deleted", - targetType: "file", - userId: userId || UNKNOWN_DATA, // NOSONAR // We want to check for empty user IDs too - userType: "user", - targetId: `${environmentId}:${accessType}`, // Generic target identifier - organizationId, - status, - newObject: { - environmentId, - accessType, - ...(failureReason && { failureReason }), - }, - oldObject, - apiUrl, - }); - } catch (auditError) { - logger.error("Failed to log file deletion audit event:", auditError); - } -}; +import { logFileDeletion } from "./lib/audit-logs"; export const GET = async ( request: NextRequest, @@ -86,11 +35,15 @@ export const GET = async ( if (!session?.user) { // check for api key auth - const res = await authenticateRequest(request); + const auth = await authenticateRequest(request); - if (!res) { + if (!auth) { return responses.notAuthenticatedResponse(); } + + if (!hasPermission(auth.environmentPermissions, environmentId, "GET")) { + return responses.unauthorizedResponse(); + } } else { const isUserAuthorized = await hasUserEnvironmentAccess(session.user.id, environmentId); @@ -145,18 +98,28 @@ export const DELETE = async ( if (!session?.user) { // check for api key auth - const res = await authenticateRequest(request); + const auth = await authenticateRequest(request); - if (!res) { + if (!auth) { await logFileDeletion({ failureReason: "User not authenticated", accessType, environmentId, apiUrl: request.url, }); - return responses.notAuthenticatedResponse(); } + + if (!hasPermission(auth.environmentPermissions, environmentId, "DELETE")) { + await logFileDeletion({ + failureReason: "User not authorized to access environment", + accessType, + environmentId, + apiUrl: request.url, + }); + + return responses.unauthorizedResponse(); + } } else { const isUserAuthorized = await hasUserEnvironmentAccess(session.user.id, environmentId); diff --git a/apps/web/locales/ro-RO.json b/apps/web/locales/ro-RO.json index 7ab0ddb562..ca5fda4a77 100644 --- a/apps/web/locales/ro-RO.json +++ b/apps/web/locales/ro-RO.json @@ -1,7 +1,7 @@ { "auth": { "continue_with_azure": "Continuă cu Microsoft", - "continue_with_email": "Continuă cu Email", + "continue_with_email": "Continuă cu email", "continue_with_github": "Continuă cu GitHub", "continue_with_google": "Continuă cu Google", "continue_with_oidc": "Continuă cu {oidcDisplayName}", @@ -49,7 +49,7 @@ "invite_not_found": "Invitația nu a fost găsită \uD83D\uDE25", "invite_not_found_description": "Codul de invitație nu poate fi găsit sau a fost deja utilizat.", "login": "Autentificare", - "welcome_to_organization": "Ești în \uD83C\uDF89", + "welcome_to_organization": "Ai fost acceptat \uD83C\uDF89", "welcome_to_organization_description": "Bun venit în organizație." }, "last_used": "Ultima utilizare", @@ -65,7 +65,7 @@ "new_to_formbricks": "Nou în Formbricks?", "use_a_backup_code": "Folosiți un cod de rezervă" }, - "saml_connection_error": "Ceva a mers prost. Vă rugăm să verificați consola aplicației pentru mai multe detalii.", + "saml_connection_error": "Ceva nu a mers. Vă rugăm să verificați consola aplicației pentru mai multe detalii.", "signup": { "captcha_failed": "Captcha eșuat", "have_an_account": "Ai un cont?", @@ -73,16 +73,16 @@ "password_validation_contain_at_least_1_number": "Conține cel puțin 1 număr", "password_validation_minimum_8_and_maximum_128_characters": "Minim 8 & Maxim 128 caractere", "password_validation_uppercase_and_lowercase": "Amestec de majuscule și minuscule", - "please_verify_captcha": "Vă rugăm să verificați reCAPTCHA", - "privacy_policy": "Politica de Confidențialitate", - "terms_of_service": "Termeni de Serviciu", + "please_verify_captcha": "Vă rugăm să verificați CAPTCHA", + "privacy_policy": "Politica de confidențialitate", + "terms_of_service": "Termeni de utilizare a serviciului", "title": "Creați-vă contul Formbricks" }, "signup_without_verification_success": { "user_successfully_created": "Utilizator creat cu succes", "user_successfully_created_info": "Am verificat pentru un cont asociat cu {email}. Dacă nu a existat niciunul, am creat unul pentru tine. Dacă un cont deja exista, nu s-au făcut modificări. Vă rugăm să vă conectați mai jos pentru a continua." }, - "testimonial_1": "\"Măsurăm claritatea documentațiilor noastre și învățăm din pierderi în folosirea aceleași platforme. Produs grozav, echipă foarte receptivă!\"", + "testimonial_1": "\"Măsurăm claritatea documentațiilor noastre și învățăm din greșeli în folosirea platformei. Produs grozav, echipă foarte receptivă!\"", "testimonial_all_features_included": "Toate funcționalitățile incluse", "testimonial_free_and_open_source": "Gratuit și open-source", "testimonial_no_credit_card_required": "Nu este necesar niciun card de credit", @@ -137,7 +137,7 @@ "anonymous": "Anonim", "api_keys": "Chei API", "app": "Aplicație", - "app_survey": "Sondaj Aplicație", + "app_survey": "Sondaj aplicație", "apply_filters": "Aplică filtre", "are_you_sure": "Ești sigur?", "attributes": "Atribute", @@ -282,13 +282,13 @@ "or": "sau", "organization": "Organizație", "organization_id": "ID Organizație", - "organization_not_found": "Organizație nu a fost găsită", + "organization_not_found": "Organizația nu a fost găsită", "organization_teams_not_found": "Echipele organizației nu au fost găsite", "other": "Altele", "others": "Altele", "overview": "Prezentare generală", "password": "Parolă", - "paused": "Pauzat", + "paused": "Pauză", "pending_downgrade": "Reducere în aşteptare", "people_manager": "Manager de persoane", "person": "Persoană", @@ -324,7 +324,7 @@ "report_survey": "Raportează chestionarul", "request_pricing": "Solicită Prețuri", "request_trial_license": "Solicitați o licență de încercare", - "reset_to_default": "Revină la implicit", + "reset_to_default": "Revino la implicit", "response": "Răspuns", "responses": "Răspunsuri", "restart": "Repornește", @@ -354,7 +354,7 @@ "share_feedback": "Împărtășește feedback", "show": "Afișează", "show_response_count": "Afișează numărul de răspunsuri", - "shown": "Arătat", + "shown": "Afișat", "size": "Mărime", "skipped": "Sărit", "skips": "Salturi", @@ -362,7 +362,7 @@ "something_went_wrong": "Ceva nu a mers bine", "something_went_wrong_please_try_again": "Ceva nu a mers bine. Vă rugăm să încercați din nou.", "sort_by": "Sortare după", - "start_free_trial": "Începe Perioada de Testare Gratuită", + "start_free_trial": "Începe perioada de testare gratuită", "status": "Stare", "step_by_step_manual": "Manual pas cu pas", "styling": "Stilizare", @@ -433,15 +433,15 @@ "click_or_drag_to_upload_files": "Faceți clic sau trageți pentru a încărca fișiere.", "email_customization_preview_email_heading": "Salut {userName}", "email_customization_preview_email_subject": "Previzualizare Personalizare Email Formbricks", - "email_customization_preview_email_text": "Acesta este un previzualizare a e-mailului pentru a vă arăta ce logo va fi afișat în e-mailurile.", + "email_customization_preview_email_text": "Acesta este o previzualizare a emailului pentru a vă arăta ce logo va fi afișat în emailurile viitoare.", "email_footer_text_1": "O zi minunată!", "email_footer_text_2": "Echipa Formbricks", "email_template_text_1": "Acest email a fost trimis prin Formbricks.", "embed_survey_preview_email_didnt_request": "Nu ați solicitat asta?", "embed_survey_preview_email_environment_id": "ID de mediu", "embed_survey_preview_email_fight_spam": "Ajută-ne să combatem spam-ul și trimite acest e-mail la hola@formbricks.com", - "embed_survey_preview_email_heading": "Previzualizare Incorporare Email", - "embed_survey_preview_email_subject": "Previzualizare Chestionar Email Formbricks", + "embed_survey_preview_email_heading": "Previzualizare încorporare email", + "embed_survey_preview_email_subject": "Previzualizare chestionar email Formbricks", "embed_survey_preview_email_text": "Așa arată fragmentul de cod încorporat într-un email:", "forgot_password_email_change_password": "Schimbați parola", "forgot_password_email_did_not_request": "Dacă nu ați solicitat acest lucru, vă rugăm să ignorați acest email.", @@ -463,7 +463,7 @@ "password_changed_email_heading": "Parola modificată", "password_changed_email_text": "Parola dumneavoastră a fost schimbată cu succes.", "password_reset_notify_email_subject": "Parola dumneavoastră Formbricks a fost schimbată", - "privacy_policy": "Politica de Confidențialitate", + "privacy_policy": "Politica de confidențialitate", "reject": "Respinge", "render_email_response_value_file_upload_response_link_not_included": "Linkul către fișierul încărcat nu este inclus din motive de confidențialitate a datelor", "response_finished_email_subject": "Un răspuns pentru {surveyName} a fost finalizat ✅", @@ -491,7 +491,7 @@ "verification_email_to_fill_survey": "Pentru a completa sondajul, vă rugăm să faceți clic pe butonul de mai jos:", "verification_email_verify_email": "Verifică emailul", "verification_new_email_subject": "Verificare schimbare email", - "verification_security_notice": "Dacă nu ați cerut această modificare a e-mailului, vă rugăm să ignorați acest e-mail sau să contactați suportul imediat.", + "verification_security_notice": "Dacă nu ați cerut această modificare a emailului, vă rugăm să ignorați acest email sau să contactați suportul imediat.", "verified_link_survey_email_subject": "Chestionarul tău este gata să fie completat." }, "environments": { @@ -500,7 +500,7 @@ "action_copy_failed": "Copierea acțiunii a eșuat", "action_created_successfully": "Acțiune creată cu succes", "action_deleted_successfully": "Acțiune ștearsă cu succes.", - "action_type": "Tip Acțiune", + "action_type": "Tip acțiune", "action_updated_successfully": "Acțiune actualizată cu succes", "action_with_key_already_exists": "Acțiunea cu cheia {key} există deja", "action_with_name_already_exists": "Acțiunea cu numele {name} există deja", @@ -563,7 +563,7 @@ "congrats": "Felicitări!", "connection_successful_message": "Bravo! Suntem conectați.", "do_it_later": "Am să o fac mai târziu", - "finish_onboarding": "Încheie Înregistrarea", + "finish_onboarding": "Încheie înregistrarea", "headline": "Conectați aplicația sau site-ul dvs.", "import_formbricks_and_initialize_the_widget_in_your_component": "Importați Formbricks și inițializați widgetul în componenta dumneavoastră (de exemplu, App.tsx):", "insert_this_code_into_the_head_tag_of_your_website": "Introduceți acest cod în eticheta head a site-ului dvs.:", @@ -579,9 +579,9 @@ "first_name": "Prenume", "last_name": "Nume de familie", "no_responses_found": "Nu s-au găsit răspunsuri", - "not_provided": "Neprovidat", + "not_provided": "Nu a fost furnizat", "search_contact": "Căutați contact", - "select_attribute": "Selectează Atributul", + "select_attribute": "Selectează atributul", "unlock_contacts_description": "Gestionează contactele și trimite sondaje țintite", "unlock_contacts_title": "Deblocați contactele cu un plan superior.", "upload_contacts_modal_attributes_description": "Mapează coloanele din CSV-ul tău la atributele din Formbricks.", @@ -628,7 +628,7 @@ "connected_with_email": "Conectat cu {email}", "connecting_integration_failed_please_try_again": "Conectarea integrării a eșuat. Vă rugăm să încercați din nou!", "create_survey_warning": "Trebuie să creezi un sondaj pentru a putea configura această integrare", - "delete_integration": "Șterge Integrarea", + "delete_integration": "Șterge integrarea", "delete_integration_confirmation": "Sigur doriți să ștergeți această integrare?", "google_sheet_integration_description": "Completați instantaneu foile de calcul cu datele chestionarului", "google_sheets": { @@ -646,7 +646,7 @@ "no_integrations_yet": "Integrațiile tale Google Sheet vor apărea aici de îndată ce le vei adăuga. ⏲️", "spreadsheet_url": "URL foaie de calcul" }, - "include_created_at": "Include Data Creării", + "include_created_at": "Include data creării", "include_hidden_fields": "Include câmpuri ascunse", "include_metadata": "Includere Metadata (Browser, Țară, etc.)", "include_variables": "Include Variabile", @@ -971,52 +971,52 @@ "all_integrations": "Toate integrațiile", "annually": "Anual", "api_webhooks": "API & Webhook-uri", - "app_surveys": "Sondaje de Aplicație", + "app_surveys": "Sondaje în aplicație", "attribute_based_targeting": "Targetare bazată pe atribute", "current": "Curent", "current_plan": "Plan curent", "current_tier_limit": "Limită curentă a nivelului", "custom": "Personalizat & Scalare", - "custom_contacts_limit": "Limit Personalizat Contacte", + "custom_contacts_limit": "Limită personalizată contacte", "custom_project_limit": "Limit Personalizat Proiect", "custom_response_limit": "Limit Personalizat Răspunsuri", "email_embedded_surveys": "Sondaje încorporate în email", - "email_follow_ups": "Urmăriri Email", + "email_follow_ups": "Email follow-up", "enterprise_description": "Suport Premium și limite personalizate.", "everybody_has_the_free_plan_by_default": "Toată lumea are planul gratuit implicit!", "everything_in_free": "Totul în Gratuit", "everything_in_startup": "Totul în Startup", "free": "Gratuit", - "free_description": "Sondaje Nelimitate, Membri În Echipă și altele.", + "free_description": "Sondaje nelimitate, membri în echipă și altele.", "get_2_months_free": "Primește 2 luni gratuite", "get_in_touch": "Contactați-ne", "hosted_in_frankfurt": "Găzduit în Frankfurt", "ios_android_sdks": "SDK iOS & Android pentru sondaje mobile", "link_surveys": "Sondaje Link (Distribuibil)", "logic_jumps_hidden_fields_recurring_surveys": "Salturi Logice, Câmpuri Ascunse, Sondaje Recurente, etc.", - "manage_card_details": "Gestionați Detaliile Cardului", - "manage_subscription": "Gestionați Abonamentul", + "manage_card_details": "Gestionați detaliile cardului", + "manage_subscription": "Gestionați abonamentul", "monthly": "Lunar", - "monthly_identified_users": "Utilizatori Identificați Lunar", + "monthly_identified_users": "Utilizatori identificați lunar", "per_month": "pe lună", "per_year": "pe an", "plan_upgraded_successfully": "Planul a fost upgradat cu succes", "premium_support_with_slas": "Suport premium cu SLA-uri", - "remove_branding": "Eliminare Branding", + "remove_branding": "Eliminare branding", "startup": "Pornire", "startup_description": "Totul din versiunea gratuită cu funcții suplimentare.", - "switch_plan": "Schimbă Planul", + "switch_plan": "Schimbă planul", "switch_plan_confirmation_text": "Sigur doriți să treceți la planul {plan}? Vi se va percepe {price} {period}.", - "team_access_roles": "Roluri Acces Echipă", + "team_access_roles": "Roluri acces echipă", "unable_to_upgrade_plan": "Nu se poate upgrada planul", "unlimited_miu": "MIU Nelimitat", - "unlimited_projects": "Proiecte Nelimitate", + "unlimited_projects": "Proiecte nelimitate", "unlimited_responses": "Răspunsuri nelimitate", - "unlimited_surveys": "Sondaje Nelimitate", - "unlimited_team_members": "Membri Nelimitați În Echipă", + "unlimited_surveys": "Sondaje nelimitate", + "unlimited_team_members": "Membri nelimitați în echipă", "upgrade": "Actualizare", "uptime_sla_99": "Disponibilitate SLA (99%)", - "website_surveys": "Sondaje ale Site-ului" + "website_surveys": "Sondaje ale site-ului" }, "enterprise": { "audit_logs": "Jurnale de audit", @@ -1048,11 +1048,11 @@ "create_new_organization_description": "Creați o organizație nouă pentru a gestiona un alt set de proiecte.", "customize_email_with_a_higher_plan": "Personalizați emailul cu un plan superior", "delete_member_confirmation": "Membrii șterși vor pierde accesul la toate proiectele și sondajele organizației tale.", - "delete_organization": "Șterge Organizație", + "delete_organization": "Șterge organizație", "delete_organization_description": "Șterge organizația cu toate proiectele ei, incluzând toate sondajele, răspunsurile, persoanele, acțiunile și atributele.", "delete_organization_warning": "Înainte de a continua cu ștergerea acestei organizații, vă rugăm să fiți conștienți de următoarele consecințe:", "delete_organization_warning_1": "Ștergerea permanentă a tuturor proiectelor legate de această organizație.", - "delete_organization_warning_2": "Această acțiune nu poate fi anulată. Dacă e dispărută, e dispărută.", + "delete_organization_warning_2": "Această acțiune este ireversibilă", "delete_organization_warning_3": "Vă rugăm să introduceți {organizationName} în câmpul următor pentru a confirma ștergerea definitivă a acestei organizații:", "eliminate_branding_with_whitelabel": "Eliminați brandingul Formbricks și activați opțiuni suplimentare de personalizare white-label.", "email_customization_preview_email_heading": "Salut {userName}", @@ -1074,7 +1074,7 @@ "manage_members_description": "Adăugați sau eliminați membri din organizația dvs.", "member_deleted_successfully": "Membru șters cu succes", "member_invited_successfully": "Membru invitat cu succes", - "once_its_gone_its_gone": "Odată ce a dispărut, a dispărut.", + "once_its_gone_its_gone": "Odată șters, nu va putea fi recuperat.", "only_org_owner_can_perform_action": "Doar proprietarii organizației pot accesa această setare.", "organization_created_successfully": "Organizație creată cu succes!", "organization_deleted_successfully": "Organizație ștearsă cu succes!", @@ -1090,7 +1090,7 @@ "remove_logo": "Înlătură siglă", "replace_logo": "Înlocuiește sigla", "resend_invitation_email": "Retrimite emailul de invitație", - "share_invite_link": "Distribuie Link-ul de Invitație", + "share_invite_link": "Distribuie link-ul de invitație", "share_this_link_to_let_your_organization_member_join_your_organization": "Distribuie acest link pentru a permite membrului organizației să se alăture organizației tale:", "test_email_sent_successfully": "Email de test trimis cu succes", "use_multi_language_surveys_with_a_higher_plan": "Utilizați chestionare multilingve cu un plan superior", @@ -1113,9 +1113,9 @@ "account_deletion_consequences_warning": "Consecințele ștergerii contului", "backup_code": "Cod de rezervă", "confirm_delete_account": "Șterge contul tău cu toate informațiile personale și datele tale", - "confirm_delete_my_account": "Șterge Contul Meu", + "confirm_delete_my_account": "Șterge contul meu", "confirm_your_current_password_to_get_started": "Confirmaţi parola curentă pentru a începe.", - "delete_account": "Șterge Cont", + "delete_account": "Șterge cont", "disable_two_factor_authentication": "Dezactivează autentificarea în doi pași", "disable_two_factor_authentication_description": "Dacă este nevoie să dezactivați autentificarea în doi pași, vă recomandăm să o reactivați cât mai curând posibil.", "each_backup_code_can_be_used_exactly_once_to_grant_access_without_your_authenticator": "Fiecare cod de rezervă poate fi utilizat o singură dată pentru a acorda acces fără autentificatorul tău.", @@ -1135,7 +1135,7 @@ "two_factor_authentication": "Autentificare în doi pași", "two_factor_authentication_description": "Adăugați un strat suplimentar de securitate la contul dvs. în cazul în care parola este furată.", "two_factor_authentication_enabled_please_enter_the_six_digit_code_from_your_authenticator_app": "Autentificare în doi pași activată. Introduceți codul de șase cifre din aplicația dvs. de autentificare.", - "two_factor_code": "Codul cu doi factori", + "two_factor_code": "Codul pentru dublă autentificare", "unlock_two_factor_authentication": "Deblocați autentificarea în doi pași cu un plan superior", "update_personal_info": "Actualizează informațiile tale personale", "warning_cannot_delete_account": "Ești singurul proprietar al acestei organizații. Te rugăm să transferi proprietatea către un alt membru mai întâi.", @@ -1188,7 +1188,7 @@ } }, "surveys": { - "all_set_time_to_create_first_survey": "Ești gata! Timp să creezi primul tău chestionar", + "all_set_time_to_create_first_survey": "Ești gata! Este timpul să creezi primul tău chestionar", "alphabetical": "Alfabetic", "copy_survey": "Copiază sondajul", "copy_survey_description": "Copiază acest sondaj într-un alt mediu", @@ -1277,7 +1277,7 @@ "caution_explanation_new_responses_separated": "Răspunsurile înainte de schimbare pot să nu fie sau să fie incluse doar parțial în rezumatul sondajului.", "caution_explanation_only_new_responses_in_summary": "Toate datele, inclusiv răspunsurile anterioare, rămân disponibile ca descărcare pe pagina de rezumat a sondajului.", "caution_explanation_responses_are_safe": "Răspunsurile mai vechi și mai noi se amestecă, ceea ce poate duce la rezumate de date înșelătoare.", - "caution_recommendation": "Aceasta poate cauza inconsistențe de date în rezumatul sondajului. Vă recomandăm să duplicați sondajul în schimb.", + "caution_recommendation": "Aceasta poate cauza inconsistențe de date în rezultatul sondajului. Vă recomandăm să duplicați sondajul în schimb.", "caution_text": "Schimbările vor duce la inconsecvențe", "centered_modal_overlay_color": "Culoare suprapunere modală centralizată", "change_anyway": "Schimbă oricum", @@ -1313,7 +1313,7 @@ "conditional_logic": "Logică condițională", "confirm_default_language": "Confirmați limba implicită", "confirm_survey_changes": "Confirmă modificările sondajului", - "contact_fields": "C�mpuri de contact", + "contact_fields": "Câmpuri de contact", "contains": "Conține", "continue_to_settings": "Continuă către Setări", "control_which_file_types_can_be_uploaded": "Controlează ce tipuri de fișiere pot fi încărcate.", @@ -1340,7 +1340,7 @@ "does_not_include_all_of": "Nu include toate", "does_not_include_one_of": "Nu include una dintre", "does_not_start_with": "Nu începe cu", - "edit_recall": "Editează Amintirea", + "edit_recall": "Editează Referințele", "edit_translations": "Editează traducerile {lang}", "enable_participants_to_switch_the_survey_language_at_any_point_during_the_survey": "Permite participanților să schimbe limba sondajului în orice moment în timpul sondajului.", "enable_recaptcha_to_protect_your_survey_from_spam": "Protecția împotriva spamului folosește reCAPTCHA v3 pentru a filtra răspunsurile de spam.", @@ -1361,19 +1361,19 @@ "field_name_eg_score_price": "Nume câmp, de exemplu, scor, preț", "first_name": "Prenume", "five_points_recommended": "5 puncte (recomandat)", - "follow_ups": "Urmăriri", + "follow_ups": "Follow-up", "follow_ups_delete_modal_text": "Sigur doriți să ștergeți acest follow-up?", - "follow_ups_delete_modal_title": "Ștergeți urmărirea?", + "follow_ups_delete_modal_title": "Ștergeți follow-up-ul?", "follow_ups_empty_description": "Trimite mesaje respondentilor, ție sau colegilor de echipă.", - "follow_ups_empty_heading": "Trimitere automată de urmăriri", - "follow_ups_ending_card_delete_modal_text": "Această cartă de sfârșit este folosită în urmăriri ulterioare. Ștergerea sa o va elimina din toate urmăriri ulterioare. Ești sigur că vrei să o ștergi?", + "follow_ups_empty_heading": "Trimitere automată de follow-up", + "follow_ups_ending_card_delete_modal_text": "Această cartă de sfârșit este folosită în follow-up-uri ulterioare. Ștergerea sa o va elimina din toate follow-up-uri ulterioare. Ești sigur că vrei să o ștergi?", "follow_ups_ending_card_delete_modal_title": "Șterge cardul de finalizare?", "follow_ups_hidden_field_error": "Câmpul ascuns este utilizat într-un follow-up. Vă rugăm să îl eliminați mai întâi din follow-up.", "follow_ups_item_ending_tag": "Finalizare", "follow_ups_item_issue_detected_tag": "Problemă detectată", "follow_ups_item_response_tag": "Orice răspuns", "follow_ups_item_send_email_tag": "Trimite email", - "follow_ups_modal_action_attach_response_data_description": "Adăugați datele răspunsului la sondaj la urmărire", + "follow_ups_modal_action_attach_response_data_description": "Adăugați datele răspunsului la sondaj la follow-up", "follow_ups_modal_action_attach_response_data_label": "Atașează datele răspunsului", "follow_ups_modal_action_body_label": "Corp", "follow_ups_modal_action_body_placeholder": "Corpul emailului", @@ -1393,8 +1393,8 @@ "follow_ups_modal_create_heading": "Creați o nouă urmărire", "follow_ups_modal_edit_heading": "Editează acest follow-up", "follow_ups_modal_edit_no_id": "Nu a fost furnizat un ID de urmărire al chestionarului, nu pot actualiza urmărirea chestionarului", - "follow_ups_modal_name_label": "Numele urmăririi", - "follow_ups_modal_name_placeholder": "Denumirea urmăririi tale", + "follow_ups_modal_name_label": "Numele ", + "follow_ups_modal_name_placeholder": "Denumirea follow-up-ului tău", "follow_ups_modal_subheading": "Trimite mesaje respondentilor, ție sau colegilor de echipă", "follow_ups_modal_trigger_description": "Când ar trebui să fie declanșat acest follow-up?", "follow_ups_modal_trigger_label": "Declanșator", @@ -1402,7 +1402,7 @@ "follow_ups_modal_trigger_type_ending_select": "Selectează finalurile:", "follow_ups_modal_trigger_type_ending_warning": "Nu s-au găsit finalizări în sondaj!", "follow_ups_modal_trigger_type_response": "Respondent finalizează sondajul", - "follow_ups_new": "Urmărire nouă", + "follow_ups_new": "Follow-up nou", "follow_ups_upgrade_button_text": "Actualizați pentru a activa urmărările", "form_styling": "Stilizare formular", "formbricks_sdk_is_not_connected": "SDK Formbricks nu este conectat", @@ -1459,10 +1459,10 @@ "logic_error_warning_text": "Schimbarea tipului de întrebare va elimina condițiile de logică din această întrebare", "long_answer": "Răspuns lung", "lower_label": "Etichetă inferioară", - "manage_languages": "Gestionați Limbile", + "manage_languages": "Gestionați limbile", "max_file_size": "Dimensiune maximă fișier", "max_file_size_limit_is": "Limita dimensiunii maxime a fișierului este", - "multiply": "Înmulțire *", + "multiply": "Multiplicare", "needed_for_self_hosted_cal_com_instance": "Necesar pentru un exemplu autogăzduit Cal.com", "next_button_label": "Etichetă buton \"Următorul\"", "next_question": "Întrebarea următoare", @@ -1512,15 +1512,15 @@ "release_survey_on_date": "Eliberați sondajul la dată", "remove_description": "Eliminați descrierea", "remove_translations": "Eliminați traducerile", - "require_answer": "Cere Răspuns", + "require_answer": "Cere răspuns", "required": "Obligatoriu", "reset_to_theme_styles": "Resetare la stilurile temei", "reset_to_theme_styles_main_text": "Sigur doriți să resetați stilul la stilurile de temă? Acest lucru va elimina toate stilizările personalizate.", "response_limit_can_t_be_set_to_0": "Limitul de răspunsuri nu poate fi setat la 0", "response_limit_needs_to_exceed_number_of_received_responses": "Limita răspunsurilor trebuie să depășească numărul de răspunsuri primite ({responseCount}).", "response_limits_redirections_and_more": "Limite de răspunsuri, redirecționări și altele.", - "response_options": "Opțiuni Răspuns", - "roundness": "Rotunjirea", + "response_options": "Opțiuni răspuns", + "roundness": "Rotunjire", "row_used_in_logic_error": "Această linie este folosită în logica întrebării {questionIndex}. Vă rugăm să-l eliminați din logică mai întâi.", "rows": "Rânduri", "save_and_close": "Salvează & Închide", @@ -1580,7 +1580,7 @@ "three_points": "3 puncte", "times": "ori", "to_keep_the_placement_over_all_surveys_consistent_you_can": "Pentru a menține amplasarea consecventă pentru toate sondajele, puteți", - "trigger_survey_when_one_of_the_actions_is_fired": "Declanșați sondajul atunci când una dintre acțiuni este declanșată...", + "trigger_survey_when_one_of_the_actions_is_fired": "Declanșați sondajul atunci când una dintre acțiuni este realizată...", "try_lollipop_or_mountain": "Încercați „lollipop” sau „mountain”...", "type_field_id": "ID câmp tip", "unlock_targeting_description": "Vizează grupuri specifice de utilizatori pe baza atributelor sau a informațiilor despre dispozitiv", @@ -1619,7 +1619,7 @@ "complete_responses": "Răspunsuri complete", "partial_responses": "Răspunsuri parțiale" }, - "new_survey": "Chestionar Nou", + "new_survey": "Chestionar nou", "no_surveys_created_yet": "Nu au fost create încă chestionare", "open_options": "Opțiuni deschise", "preview_survey_in_a_new_tab": "Previzualizare chestionar în alt tab", @@ -1732,7 +1732,7 @@ "send_email": { "copy_embed_code": "Copiază codul de inserare", "description": "Inserați sondajul dvs. într-un e-mail pentru a obține răspunsuri de la audiența dvs.", - "email_preview_tab": "Previzualizare Email", + "email_preview_tab": "Previzualizare email", "email_sent": "Email trimis!", "email_subject_label": "Subiect", "email_to_label": "Către", @@ -1776,7 +1776,7 @@ "filter_updated_successfully": "Filtru actualizat cu succes", "filtered_responses_csv": "Răspunsuri filtrate (CSV)", "filtered_responses_excel": "Răspunsuri filtrate (Excel)", - "go_to_setup_checklist": "Mergi la Lista de Verificare a Configurării \uD83D\uDC49", + "go_to_setup_checklist": "Mergi la lista de verificare a configurării \uD83D\uDC49", "impressions": "Impresii", "impressions_tooltip": "Număr de ori când sondajul a fost vizualizat.", "in_app": { @@ -1943,7 +1943,7 @@ "intro": { "get_started": "Începeți", "made_with_love_in_kiel": "Creat cu \uD83E\uDD0D în Germania", - "paragraph_1": "Formbricks este o Suită de Management al Experiențelor construită pe baza platformei de sondaje open source care crește cel mai rapid din lume.", + "paragraph_1": "Formbricks este o suită de management al experiențelor construită pe baza platformei de sondaje open source care crește cel mai rapid din lume.", "paragraph_2": "Rulați sondaje direcționate pe site-uri web, în aplicații sau oriunde online. Adunați informații valoroase pentru a crea experiențe irezistibile pentru clienți, utilizatori și angajați.", "paragraph_3": "Suntem angajați la cel mai înalt grad de confidențialitate a datelor. Găzduirea proprie vă oferă control deplin asupra datelor dumneavoastră.", "welcome_to_formbricks": "Bine ai venit la Formbricks!" @@ -2043,7 +2043,7 @@ "career_development_survey_question_4_headline": "Sunt mulțumit de investiția pe care organizația mea o face în formare și educație.", "career_development_survey_question_4_lower_label": "Dezacord puternic", "career_development_survey_question_4_upper_label": "De acord cu tărie", - "career_development_survey_question_5_choice_1": "Dezvoltare de Produs", + "career_development_survey_question_5_choice_1": "Dezvoltare de produs", "career_development_survey_question_5_choice_2": "Marketing", "career_development_survey_question_5_choice_3": "Relații Publice", "career_development_survey_question_5_choice_4": "Contabilitate", @@ -2113,7 +2113,7 @@ "collect_feedback_question_5_headline": "Mai dorești să împărtășești altceva cu echipa noastră?", "collect_feedback_question_5_placeholder": "Tastează răspunsul aici...", "collect_feedback_question_6_choice_1": "Google", - "collect_feedback_question_6_choice_2": "Rețele Sociale", + "collect_feedback_question_6_choice_2": "Rețele sociale", "collect_feedback_question_6_choice_3": "Prieteni", "collect_feedback_question_6_choice_4": "Podcast", "collect_feedback_question_6_choice_5": "Altele", @@ -2242,7 +2242,7 @@ "earned_advocacy_score_question_5_headline": "Ce te-a făcut să îi descurajezi?", "earned_advocacy_score_question_5_placeholder": "Tastează răspunsul aici...", "employee_satisfaction_description": "Evaluează satisfacția angajaților și identifică domeniile de îmbunătățire.", - "employee_satisfaction_name": "Satisfacție a Angajatului", + "employee_satisfaction_name": "Satisfacție a angajatului", "employee_satisfaction_question_1_headline": "Cât de satisfăcut sunteți de rolul dvs. actual?", "employee_satisfaction_question_1_lower_label": "Nesatisfăcut", "employee_satisfaction_question_1_upper_label": "Foarte mulțumit", @@ -2266,7 +2266,7 @@ "employee_satisfaction_question_7_choice_5": "Deloc probabil", "employee_satisfaction_question_7_headline": "Cât de probabil este să recomandați compania noastră unui prieten?", "employee_well_being_description": "Evaluează bunăstarea angajatului prin echilibrul între muncă și viață, volumul de muncă și mediul de lucru.", - "employee_well_being_name": "Bunăstarea Angajatului", + "employee_well_being_name": "Bunăstarea angajatului", "employee_well_being_question_1_headline": "Simt că am un echilibru bun între viața mea profesională și cea personală.", "employee_well_being_question_1_lower_label": "Echilibru foarte slab", "employee_well_being_question_1_upper_label": "Echilibru excelent", @@ -2328,7 +2328,7 @@ "fake_door_follow_up_question_2_choice_4": "Aspectul 4", "fake_door_follow_up_question_2_headline": "Ce ar trebui să includem cu siguranță în construirea acestuia?", "feature_chaser_description": "Urmăriți utilizatorii care tocmai au folosit o funcție specifică.", - "feature_chaser_name": "Urmăritor de Funcționalități", + "feature_chaser_name": "Urmăritor de funcționalități", "feature_chaser_question_1_headline": "Cât de importantă este [ADD FEATURE] pentru tine?", "feature_chaser_question_1_lower_label": "Neimportant", "feature_chaser_question_1_upper_label": "Foarte important", @@ -2369,7 +2369,7 @@ "identify_customer_goals_description": "Înțelegeți mai bine dacă mesajele voastre creează așteptările corecte privind valoarea pe care o oferă produsul vostru.", "identify_customer_goals_name": "Identifică Obiectivele Clienților", "identify_sign_up_barriers_description": "Oferiți o reducere pentru a obține informații despre barierele de înscriere.", - "identify_sign_up_barriers_name": "Identificați Barierele de Înscriere", + "identify_sign_up_barriers_name": "Identificați barierele de înscriere", "identify_sign_up_barriers_question_1_button_label": "Obține reducere de 10%", "identify_sign_up_barriers_question_1_dismiss_button_label": "Nu, mulţumesc", "identify_sign_up_barriers_question_1_headline": "Răspunde acestui scurt sondaj, primește 10% reducere!", @@ -2405,7 +2405,7 @@ "identify_upsell_opportunities_question_1_choice_4": "5+ ore", "identify_upsell_opportunities_question_1_headline": "Câte ore economisește echipa dumneavoastră pe săptămână folosind $[projectName]?", "improve_activation_rate_description": "Identifică punctele slabe în fluxul de onboarding pentru a crește activarea utilizatorilor.", - "improve_activation_rate_name": "Îmbunătățește Rata de Activare", + "improve_activation_rate_name": "Îmbunătățește rata de activare", "improve_activation_rate_question_1_choice_1": "Nu părea util pentru mine", "improve_activation_rate_question_1_choice_2": "Dificil de configurat sau utilizat", "improve_activation_rate_question_1_choice_3": "Lipsit de funcții/funcționalități", @@ -2427,12 +2427,12 @@ "improve_newsletter_content_name": "Îmbunătățește Conținutul Newsletterului", "improve_newsletter_content_question_1_headline": "Cum ați evalua newsletterul din această săptămână?", "improve_newsletter_content_question_1_lower_label": "Însă", - "improve_newsletter_content_question_1_upper_label": "Groza", + "improve_newsletter_content_question_1_upper_label": "Grozav", "improve_newsletter_content_question_2_headline": "Ce ar fi făcut ca newsletter-ul din această săptămână să fie mai util?", "improve_newsletter_content_question_2_placeholder": "Tastează răspunsul aici...", "improve_newsletter_content_question_3_button_label": "Bucuros să ajut!", "improve_newsletter_content_question_3_dismiss_button_label": "Găsește-ți proprii prieteni", - "improve_newsletter_content_question_3_headline": "Mulțumim! ❤️ Răspândește iubirea către UN prieten.", + "improve_newsletter_content_question_3_headline": "Mulțumim! ❤️ Răspândește iubirea către un prieten.", "improve_newsletter_content_question_3_html": "

Cine gândește ca tine? Ne-ai face o mare favoare dacă ai împărtăși episodul acestei săptămâni cu prietenul tău de creier!

", "improve_trial_conversion_description": "Află de ce oamenii au încetat perioada de încercare. Aceste informații te ajută să îți îmbunătățești procesul de achiziție.", "improve_trial_conversion_name": "Îmbunătățește Conversia În Proba", @@ -2464,7 +2464,7 @@ "integration_setup_survey_question_3_headline": "Ce alte instrumente ați dori să utilizați cu $[projectName]?", "integration_setup_survey_question_3_subheader": "Continuăm să dezvoltăm integrări, a ta poate fi următoarea:", "interview_prompt_description": "Invită un subset specific de utilizatori să programeze un interviu cu echipa ta de produs.", - "interview_prompt_name": "Întrebare Interviu", + "interview_prompt_name": "Întrebare interviu", "interview_prompt_question_1_button_label": "Rezervă intervalul", "interview_prompt_question_1_headline": "Ai 15 minute să discuți cu noi? \uD83D\uDE4F", "interview_prompt_question_1_html": "Ești unul dintre utilizatorii noștri frecvenți. Ne-ar plăcea să te intervievăm pe scurt!", @@ -2503,16 +2503,16 @@ "long_term_retention_check_in_question_9_lower_label": "Nemulțumit", "long_term_retention_check_in_question_9_upper_label": "Foarte mulțumit", "market_attribution_description": "Aflați cum au auzit utilizatorii pentru prima dată despre produsul dumneavoastră.", - "market_attribution_name": "Atribuirea Marketingului", + "market_attribution_name": "Atribuirea marketingului", "market_attribution_question_1_choice_1": "Recomandare", - "market_attribution_question_1_choice_2": "Rețele Sociale", + "market_attribution_question_1_choice_2": "Rețele sociale", "market_attribution_question_1_choice_3": "Reclame", "market_attribution_question_1_choice_4": "Căutare Google", - "market_attribution_question_1_choice_5": "Într-un Podcast", + "market_attribution_question_1_choice_5": "Într-un podcast", "market_attribution_question_1_headline": "Cum ați aflat pentru prima dată despre noi?", "market_attribution_question_1_subheader": "Vă rugăm să selectați una dintre următoarele opțiuni:", "market_site_clarity_description": "Identificați utilizatorii care părăsesc site-ul dvs. de marketing. Îmbunătățiți mesajele dvs.", - "market_site_clarity_name": "Claritate Site de Marketing", + "market_site_clarity_name": "Claritate site de marketing", "market_site_clarity_question_1_choice_1": "Da, complet", "market_site_clarity_question_1_choice_2": "Un fel de...", "market_site_clarity_question_1_choice_3": "Nu, deloc", @@ -2590,17 +2590,17 @@ "onboarding_segmentation_question_2_headline": "Care este dimensiunea companiei dumneavoastră?", "onboarding_segmentation_question_2_subheader": "Vă rugăm să selectați una dintre următoarele opțiuni:", "onboarding_segmentation_question_3_choice_1": "Recomandare", - "onboarding_segmentation_question_3_choice_2": "Rețele Sociale", + "onboarding_segmentation_question_3_choice_2": "Rețele sociale", "onboarding_segmentation_question_3_choice_3": "Reclame", "onboarding_segmentation_question_3_choice_4": "Căutare Google", "onboarding_segmentation_question_3_choice_5": "Într-un Podcast", "onboarding_segmentation_question_3_headline": "Cum ai aflat pentru prima dată despre noi?", "onboarding_segmentation_question_3_subheader": "Vă rugăm să selectați una dintre următoarele opțiuni:", - "picture_selection": "Selecție Poze", + "picture_selection": "Selecție poze", "picture_selection_description": "Cereți respondenților să aleagă una sau mai multe imagini", "preview_survey_ending_card_description": "Vă rugăm să continuați onboarding-ul.", "preview_survey_ending_card_headline": "Ai reușit!", - "preview_survey_name": "Previzualizare Chestionar", + "preview_survey_name": "Previzualizare chestionar", "preview_survey_question_1_headline": "Cum ai evalua {projectName}?", "preview_survey_question_1_lower_label": "Nu este bine", "preview_survey_question_1_subheader": "Aceasta este o previzualizare a chestionarului.", @@ -2612,7 +2612,7 @@ "preview_survey_welcome_card_headline": "Bun venit!", "preview_survey_welcome_card_html": "Mulțumesc pentru feedback-ul dvs - să începem!", "prioritize_features_description": "Identificați caracteristicile de care utilizatorii dumneavoastră au cel mai mult și cel mai puțin nevoie.", - "prioritize_features_name": "Prioritizați Caracteristicile", + "prioritize_features_name": "Prioritizați caracteristicile", "prioritize_features_question_1_choice_1": "Caracteristica 1", "prioritize_features_question_1_choice_2": "Caracteristica 2", "prioritize_features_question_1_choice_3": "Caracteristica 3", @@ -2656,7 +2656,7 @@ "product_market_fit_superhuman_question_6_headline": "Cum putem îmbunătăți $[projectName] pentru dumneavoastră?", "product_market_fit_superhuman_question_6_subheader": "Vă rugăm să fiți cât mai specific posibil.", "professional_development_growth_survey_description": "Evaluează satisfacția angajaților cu privire la oportunitățile de dezvoltare și creștere profesională.", - "professional_development_growth_survey_name": "Sondaj de Creștere și Dezvoltare Profesională", + "professional_development_growth_survey_name": "Sondaj de creștere și dezvoltare profesională", "professional_development_growth_survey_question_1_headline": "Simt că am oportunități să cresc și să-mi dezvolt abilitățile la muncă.", "professional_development_growth_survey_question_1_lower_label": "Nicio oportunitate de creștere", "professional_development_growth_survey_question_1_upper_label": "Multe oportunități de creștere", diff --git a/apps/web/modules/projects/settings/lib/project.test.ts b/apps/web/modules/projects/settings/lib/project.test.ts index 7f0b10ef99..dd0ec7b482 100644 --- a/apps/web/modules/projects/settings/lib/project.test.ts +++ b/apps/web/modules/projects/settings/lib/project.test.ts @@ -4,6 +4,7 @@ import { Prisma } from "@prisma/client"; import { beforeEach, describe, expect, test, vi } from "vitest"; import { prisma } from "@formbricks/database"; import { logger } from "@formbricks/logger"; +import { StorageErrorCode } from "@formbricks/storage"; import { TEnvironment } from "@formbricks/types/environment"; import { DatabaseError, InvalidInputError, ValidationError } from "@formbricks/types/errors"; import { ZProject } from "@formbricks/types/project"; @@ -159,7 +160,10 @@ describe("project lib", () => { test("logs error if file deletion fails", async () => { vi.mocked(prisma.project.delete).mockResolvedValueOnce(baseProject as any); - vi.mocked(deleteFilesByEnvironmentId).mockRejectedValueOnce(new Error("fail")); + vi.mocked(deleteFilesByEnvironmentId).mockResolvedValue({ + ok: false, + error: { code: StorageErrorCode.Unknown }, + } as any); vi.mocked(logger.error).mockImplementation(() => {}); await deleteProject("p1"); expect(logger.error).toHaveBeenCalled();